Utility Bar

Space Dodger - Survival Game for RP2350 / Raspberry Pi Pico

Ethan Zaitchik |

At Zaitronics, we've recently built an upgraded version of a Raspberry Pi Pico 2, called Nexus RP2350 LiPo, with the intension of being a drop in replacement for any existing Pico projects. To demonstrate the board's capabilities, I've designed a simple Space Dodger game which requires very few parts most makers should have. For this version, we used a 1.3 inch OLED from Waveshare, but any similar small display should work with slight adjustments in the code.

Parts List:
Wiring (if you are not using the Nexus + OLED stack)
OLED Pin RP2350 / Pico Pin
VCC 3.3 V
GND GND
SCK GP10
MOSI GP11
DC GP8
RST GP12
CS GP9
Button RP2350 / Pico Pin
Button A (UP GP15 + GND (internal pull-up used)
Button B DOWN GP17 + GND (internal pull-up used)
How to Play
  • Button A (GP15) → move ship UP
  • Button B (GP17) → move ship DOWN
  • Avoid the white squares coming from the right
  • Your score is survival time + 10 points per dodged ball
  • The game gets faster the longer you survive
Tweaking Difficulty

Variable Effect Suggested range
ball_speed = 1.8 Starting speed 1.2 – 2.5
speed_increment How quickly it accelerates 0.0005 – 0.002
Spawn chance line How often new balls appear 0.02 – 0.06
Ball size in loop 6×6 is good visibility 4–8
Complete Ready-to-Flash Code (MicroPython)

Copy the entire code below into Thonny (or any MicroPython IDE) and save as main.py on your board.

from machine import Pin, SPI
import framebuf, time, random

# Display pins
DC = 8
RST = 12
MOSI = 11
SCK = 10
CS = 9

# Button pins
keyA = Pin(15, Pin.IN, Pin.PULL_UP)
keyB = Pin(17, Pin.IN, Pin.PULL_UP)

# OLED driver class (same as yours)
class OLED_1inch3(framebuf.FrameBuffer):
    def __init__(self):
        self.width = 128
        self.height = 64
        self.rotate = 0
        self.cs = Pin(CS, Pin.OUT)
        self.rst = Pin(RST, Pin.OUT)
        self.cs(1)
        self.spi = SPI(1, 20000000, polarity=0, phase=0, sck=Pin(SCK), mosi=Pin(MOSI), miso=None)
        self.dc = Pin(DC, Pin.OUT)
        self.dc(1)
        self.buffer = bytearray(self.height * self.width // 8)
        super().__init__(self.buffer, self.width, self.height, framebuf.MONO_HMSB)
        self.init_display()
        self.white = 0xffff
        self.balck = 0x0000

    def write_cmd(self, cmd):
        self.cs(1)
        self.dc(0)
        self.cs(0)
        self.spi.write(bytearray([cmd]))
        self.cs(1)

    def write_data(self, buf):
        self.cs(1)
        self.dc(1)
        self.cs(0)
        self.spi.write(bytearray([buf]))
        self.cs(1)

    def init_display(self):
        self.rst(1)
        time.sleep(0.001)
        self.rst(0)
        time.sleep(0.01)
        self.rst(1)
        for cmd in (
            0xAE, 0x00, 0x10, 0xB0, 0xDC, 0x00, 0x81, 0x6F, 0x21,
            0xA1 if self.rotate == 180 else 0xA0,
            0xC0, 0xA4, 0xA6, 0xA8, 0x3F, 0xD3, 0x60, 0xD5, 0x41,
            0xD9, 0x22, 0xDB, 0x35, 0xAD, 0x8A, 0xAF
        ):
            self.write_cmd(cmd)

    def show(self):
        self.write_cmd(0xB0)
        for page in range(0, 64):
            self.column = page if self.rotate == 180 else 63 - page
            self.write_cmd(0x00 + (self.column & 0x0F))
            self.write_cmd(0x10 + (self.column >> 4))
            for num in range(0, 16):
                self.write_data(self.buffer[page * 16 + num])

# Initialize display
OLED = OLED_1inch3()

# Ball list: each ball is [x, y]
balls = []

# Spaceship position
ship_x = 0
ship_y = 28
ship_w = 6
ship_h = 6

# Draw spaceship
def draw_ship(x, y, scale=2):
    """
    Draw the spaceship pattern at (x, y)
    scale: how many screen pixels per pattern pixel
    """
    pattern = [
        "00000000000",
        "000xxx00000",
        "0000xxxx000",
        "xxxxxxxxx00",
        "00xxx00xxx0",
        "0xxxx000xxx",
        "00xxx00xxx0",
        "xxxxxxxxx00",
        "0000xxxx000",
        "000xxx00000",
        "00000000000",
    ]

    for row_idx, row in enumerate(pattern):
        for col_idx, pixel in enumerate(row):
            if pixel == "x":
                OLED.fill_rect(x + col_idx*scale, y + row_idx*scale, scale, scale, OLED.white)

import time

# Game variables
score = 0
ball_speed = 2      # initial speed of balls
speed_increment = 0.05  # increase speed over time

start_time = time.ticks_ms()  # start timer

# Main loop
while True:
    OLED.fill(0)  # clear screen

    # Update score based on elapsed time
    elapsed_ms = time.ticks_diff(time.ticks_ms(), start_time)
    score = elapsed_ms // 100  # roughly increments every 0.1 second

    # Display score
    OLED.text("Score: {}".format(score), 0, 0)

    # Move spaceship up/down
    if keyA.value() == 0:  # up
        ship_y = max(0, ship_y - 2)
    if keyB.value() == 0:  # down
        ship_y = min(OLED.height - ship_h*2, ship_y + 2)  # scale=2

    # Randomly spawn a new ball on the right
    if random.random() < 0.05:  # reduce spawn rate for balance
        y = random.randint(0, OLED.height - 4)
        balls.append([127, y])

    # Draw and update balls
    new_balls = []
    for ball in balls:
        x, y = ball
        OLED.fill_rect(x, y, 4, 4, OLED.white)  # 4x4 ball
        x -= int(ball_speed)  # move left
        if x > 0:
            new_balls.append([x, y])
    balls = new_balls

    # Gradually increase speed
    ball_speed += speed_increment / 50  # slow acceleration

    # Draw spaceship
    draw_ship(ship_x, ship_y)

    OLED.show()
    time.sleep(0.05)
Additional Resources
  • Full Driver & Examples: Check the Waveshare wiki for the original MicroPython library: https://www.waveshare.com/wiki/Pico-OLED-1.3 (includes C/C++ too).
  • Customization Ideas: Modify the ship pattern, add a buzzer on GP16 for collision sounds, or implement high-score saving to onboard flash.

Leave a comment