Raspberry Pi Pico Retro Gaming Console with OLED Display – 4 Classic Arcade Games

Raspberry Pi Pico Retro Gaming Console | Snake, Pong, Shooter, Breakout – Wokwi
🎮 Expert Level 🍓 Raspberry Pi Pico 🕹️ 4 Games 🟢 Wokwi

Retro Arcade Gaming Console
on Raspberry Pi Pico

Turn a Raspberry Pi Pico into a pocket arcade machine running Snake, Pong, Space Shooter, and Breakout — all on a 128×64 OLED display with real button controls, 8-bit sound effects, and MicroPython. Simulate everything in Wokwi first.

~30 min read 8 Components MicroPython 20 FPS game loop
🐍
Snake
D-Pad
🏓
Pong
UP / DOWN
🚀
Shooter
L/R + A
🧱
Breakout
LEFT / RIGHT
0Requirements

Hardware Components

🍓
Raspberry Pi Pico
RP2040, MicroPython
📺
SSD1306 OLED
0.96" 128×64, I2C 0x3C
🎮
6× Push Buttons
D-Pad + A + B
🔊
Active Buzzer
8-bit sound effects
〰️
6× 10kΩ Resistors
Button pull-ups (optional)
🔌
Breadboard + Wires
Prototyping setup
🐍
MicroPython Firmware
Flashed on Pico

All components are available in Wokwi — no physical hardware needed to get started. Internal pull-ups are used via MicroPython configuration.

1Wiring

Circuit Connections

📺 OLED SSD1306 (I2C)
SDAGP0
SCLGP1
VCC3.3V(OUT)
GNDGND.8
🎮 D-Pad Buttons (Active Low)
UPGP10 / GND.1
DOWNGP11 / GND.2
LEFTGP12 / GND.3
RIGHTGP13 / GND.4
🔴 Action Buttons
A (Action)GP14 / GND.5
B (Back/Menu)GP15 / GND.6
🔊 Buzzer
Positive (+)GP16
Negative (−)GND.7

All buttons use Pin.PULL_UP internally — no external resistors required.

🎮 Games

4 Classic Arcade Games

128×64 OLED
RETRO ARCADE ───────────── ▶Snake Pong Shooter Breakout
Game Menu
Game 01
🐍 Snake
Classic grid-based movement. Eat food to grow, avoid walls and your own tail.
D-Pad • +10pts/food
  • Grid logic
  • Self-collision detection
  • Random food generation
  • Direction control
Game 02
🏓 Pong
Arcade paddle ball. Keep the ball from passing your paddle to score points.
UP / DOWN • +1pt/rally
  • Ball physics
  • Paddle collision
  • Score increment
  • Velocity inversion
Game 03
🚀 Space Shooter
Destroy incoming enemies. 3 lives — survive as long as possible.
L/R + A (Shoot) • 3 lives
  • Projectile management
  • Enemy spawning
  • AABB hit detection
  • Life system
Game 04
🧱 Breakout
32 bricks, one ball. Clear all bricks without letting the ball fall.
LEFT / RIGHT • +5pts/brick
  • Brick grid management
  • Ball reflection physics
  • Win-state detection
  • Score scaling
⚙️ Design

Code Architecture

The project uses a clean state machine design with object-oriented game classes, running at 20 FPS (50ms sleep per frame).

Boot Splash
GameMenu
State: menu
Game Selected
A button
Gameplay
State: playing
B = Back
to menu
Classes
SSD1306_I2C
SnakeGame
PongGame
SpaceShooter
BreakoutGame
GameMenu
Key Functions
read_buttons()
button_pressed()
beep()
game.update()
game.draw()
oled.show()
Sound Patterns
"single"
"double"
"game_over"
2Wokwi Config

diagram.json

Paste this into Wokwi's diagram.json tab to auto-place and wire all components.

diagram.json
{
  "version": 1,
  "author": "Retro Gaming Console",
  "editor": "wokwi",
  "parts": [
    { "type": "wokwi-pi-pico", "id": "pico", "top": 0, "left": 0, "attrs": {} },
    {
      "type": "wokwi-ssd1306", "id": "oled1",
      "top": -137.3, "left": 213.1,
      "attrs": { "i2cAddress": "0x3C" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_up",
      "top": -120, "left": -115.2,
      "attrs": { "color": "blue", "label": "UP" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_down",
      "top": -60, "left": -115.2,
      "attrs": { "color": "blue", "label": "DOWN" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_left",
      "top": -90, "left": -153.6,
      "attrs": { "color": "blue", "label": "LEFT" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_right",
      "top": -90, "left": -76.8,
      "attrs": { "color": "blue", "label": "RIGHT" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_a",
      "top": -90, "left": 57.6,
      "attrs": { "color": "red", "label": "A" }
    },
    {
      "type": "wokwi-pushbutton", "id": "btn_b",
      "top": -90, "left": 124.8,
      "attrs": { "color": "green", "label": "B" }
    },
    {
      "type": "wokwi-buzzer", "id": "buzzer1",
      "top": 120, "left": 100,
      "attrs": { "volume": "0.6" }
    }
  ],
  "connections": [
    [ "pico:GP0",       "oled1:SDA",      "green",   [ "v0" ] ],
    [ "pico:GP1",       "oled1:SCL",      "blue",    [ "v0" ] ],
    [ "pico:3V3(OUT)", "oled1:VCC",      "red",     [ "v0" ] ],
    [ "pico:GND.8",    "oled1:GND",      "black",   [ "v0" ] ],
    [ "pico:GP10",     "btn_up:1.l",    "blue",    [ "v0" ] ],
    [ "btn_up:2.l",   "pico:GND.1",    "black",   [ "v0" ] ],
    [ "pico:GP11",     "btn_down:1.l",  "blue",    [ "v0" ] ],
    [ "btn_down:2.l", "pico:GND.2",    "black",   [ "v0" ] ],
    [ "pico:GP12",     "btn_left:1.l",  "blue",    [ "v0" ] ],
    [ "btn_left:2.l", "pico:GND.3",    "black",   [ "v0" ] ],
    [ "pico:GP13",     "btn_right:1.l", "blue",    [ "v0" ] ],
    [ "btn_right:2.l","pico:GND.4",    "black",   [ "v0" ] ],
    [ "pico:GP14",     "btn_a:1.l",     "red",     [ "v0" ] ],
    [ "btn_a:2.l",    "pico:GND.5",    "black",   [ "v0" ] ],
    [ "pico:GP15",     "btn_b:1.l",     "green",   [ "v0" ] ],
    [ "btn_b:2.l",    "pico:GND.6",    "black",   [ "v0" ] ],
    [ "pico:GP16",     "buzzer1:1",     "magenta", [ "v0" ] ],
    [ "buzzer1:2",    "pico:GND.7",    "black",   [ "v0" ] ]
  ],
  "dependencies": {}
}
3Code

MicroPython Code (main.py)

Save this as main.py on your Pico, or paste into Wokwi's code editor.

main.py — MicroPython
from machine import Pin, I2C
import time
import framebuf
import random

# ── SSD1306 OLED Driver (128×64) ──────────────────────────────
class SSD1306_I2C:
    def __init__(self, width, height, i2c, addr=0x3C):
        self.i2c = i2c;  self.addr = addr
        self.width = width;  self.height = height
        self.pages = height // 8
        self.buffer = bytearray(self.pages * width)
        self.framebuf = framebuf.FrameBuffer(
            self.buffer, width, height, framebuf.MONO_VLSB)
        self.init_display()

    def init_display(self):
        for cmd in (
            0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40,
            0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12,
            0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0xAF
        ):
            self.write_cmd(cmd)
        self.fill(0);  self.show()

    def write_cmd(self, cmd):
        self.i2c.writeto(self.addr, bytearray([0x00, cmd]))
    def write_data(self, buf):
        self.i2c.writeto(self.addr, b'\x40' + buf)
    def show(self):
        for page in range(self.pages):
            self.write_cmd(0xB0 + page)
            self.write_cmd(0x00);  self.write_cmd(0x10)
            self.write_data(self.buffer[page*self.width:(page+1)*self.width])
    def fill(self, c):       self.framebuf.fill(c)
    def pixel(self,x,y,c):   self.framebuf.pixel(x,y,c)
    def text(self,s,x,y,c=1):self.framebuf.text(s,x,y,c)
    def rect(self,x,y,w,h,c): self.framebuf.rect(x,y,w,h,c)
    def fill_rect(self,x,y,w,h,c): self.framebuf.fill_rect(x,y,w,h,c)
    def line(self,x1,y1,x2,y2,c): self.framebuf.line(x1,y1,x2,y2,c)

# ── Hardware Init ─────────────────────────────────────────────
i2c  = I2C(0, scl=Pin(1), sda=Pin(0), freq=400000)
oled = SSD1306_I2C(128, 64, i2c)

btn_up    = Pin(10, Pin.IN, Pin.PULL_UP)
btn_down  = Pin(11, Pin.IN, Pin.PULL_UP)
btn_left  = Pin(12, Pin.IN, Pin.PULL_UP)
btn_right = Pin(13, Pin.IN, Pin.PULL_UP)
btn_a     = Pin(14, Pin.IN, Pin.PULL_UP)
btn_b     = Pin(15, Pin.IN, Pin.PULL_UP)
buzzer    = Pin(16, Pin.OUT)

# ── Sound Effects ─────────────────────────────────────────────
def beep(duration=0.05, pattern="single"):
    if pattern == "single":
        buzzer.on();  time.sleep(duration);  buzzer.off()
    elif pattern == "double":
        for _ in range(2):
            buzzer.on();  time.sleep(0.05)
            buzzer.off(); time.sleep(0.05)
    elif pattern == "game_over":
        for f in [0.1, 0.1, 0.15]:
            buzzer.on();  time.sleep(f)
            buzzer.off(); time.sleep(0.05)

# ── Button Debouncing ─────────────────────────────────────────
last_press    = {"up":0, "down":0, "left":0, "right":0, "a":0, "b":0}
debounce_time = 150  # ms

def button_pressed(name):
    now = time.ticks_ms()
    if time.ticks_diff(now, last_press[name]) > debounce_time:
        last_press[name] = now;  return True
    return False

def read_buttons():
    return {
        "up":    btn_up.value()    == 0,
        "down":  btn_down.value()  == 0,
        "left":  btn_left.value()  == 0,
        "right": btn_right.value() == 0,
        "a":     btn_a.value()     == 0,
        "b":     btn_b.value()     == 0,
    }

# ── SNAKE ─────────────────────────────────────────────────────
class SnakeGame:
    def __init__(self): self.reset()
    def reset(self):
        self.snake = [[64,32],[60,32],[56,32]]
        self.direction = [4,0]
        self.food = [random.randint(0,30)*4, random.randint(0,14)*4]
        self.score = 0;  self.game_over = False

    def update(self, buttons):
        if self.game_over: return
        dx, dy = self.direction
        if buttons["up"]    and dy == 0: self.direction = [0,-4]
        elif buttons["down"]  and dy == 0: self.direction = [0,4]
        elif buttons["left"]  and dx == 0: self.direction = [-4,0]
        elif buttons["right"] and dx == 0: self.direction = [4,0]
        head = [self.snake[0][0]+self.direction[0], self.snake[0][1]+self.direction[1]]
        if head[0] < 0 or head[0] >= 128 or head[1] < 0 or head[1] >= 64:
            self.game_over = True;  beep(0.1, "game_over");  return
        if head in self.snake:
            self.game_over = True;  beep(0.1, "game_over");  return
        self.snake.insert(0, head)
        if head == self.food:
            self.score += 10;  beep(0.05, "double")
            self.food = [random.randint(0,30)*4, random.randint(0,14)*4]
        else: self.snake.pop()

    def draw(self):
        oled.fill(0)
        for s in self.snake: oled.fill_rect(s[0],s[1],4,4,1)
        oled.fill_rect(self.food[0],self.food[1],4,4,1)
        oled.text(f"Score:{self.score}",0,0)
        if self.game_over:
            oled.fill_rect(20,25,88,20,0);  oled.rect(20,25,88,20,1)
            oled.text("GAME OVER",30,30)
        oled.show()

# ── PONG ──────────────────────────────────────────────────────
class PongGame:
    def __init__(self): self.reset()
    def reset(self):
        self.paddle_y = 24;  self.ball_x = 64;  self.ball_y = 32
        self.ball_dx = 2;  self.ball_dy = 1
        self.score = 0;  self.game_over = False

    def update(self, buttons):
        if self.game_over: return
        if buttons["up"]   and self.paddle_y > 0:  self.paddle_y -= 3
        if buttons["down"] and self.paddle_y < 48: self.paddle_y += 3
        self.ball_x += self.ball_dx;  self.ball_y += self.ball_dy
        if self.ball_y <= 0 or self.ball_y >= 62:
            self.ball_dy *= -1;  beep(0.03)
        if self.ball_x <= 8 and self.paddle_y <= self.ball_y <= self.paddle_y+16:
            self.ball_dx *= -1;  self.score += 1;  beep(0.05)
        if self.ball_x < 0:
            self.game_over = True;  beep(0.1, "game_over")
        if self.ball_x >= 126: self.ball_dx *= -1

    def draw(self):
        oled.fill(0)
        oled.fill_rect(2,self.paddle_y,4,16,1)
        oled.fill_rect(self.ball_x,self.ball_y,2,2,1)
        oled.text(f"Score:{self.score}",40,0)
        if self.game_over:
            oled.fill_rect(20,25,88,20,0);  oled.rect(20,25,88,20,1)
            oled.text("GAME OVER",30,30)
        oled.show()

# ── SPACE SHOOTER ─────────────────────────────────────────────
class SpaceShooter:
    def __init__(self): self.reset()
    def reset(self):
        self.player_x = 60;  self.bullets = [];  self.score = 0
        self.enemies  = [[random.randint(0,120), random.randint(-30,-10)] for _ in range(3)]
        self.lives = 3;  self.game_over = False

    def update(self, buttons):
        if self.game_over: return
        if buttons["left"]  and self.player_x > 0:   self.player_x -= 3
        if buttons["right"] and self.player_x < 120: self.player_x += 3
        if buttons["a"] and button_pressed("a"):
            self.bullets.append([self.player_x+3, 50]);  beep(0.03)
        for b in self.bullets[:]:
            b[1] -= 4
            if b[1] < 0: self.bullets.remove(b)
        for e in self.enemies:
            e[1] += 1
            if e[1] > 64:
                e[0] = random.randint(0,120);  e[1] = random.randint(-30,-10)
                self.lives -= 1;  beep(0.1)
                if self.lives <= 0:
                    self.game_over = True;  beep(0.1, "game_over")
        for b in self.bullets[:]:
            for e in self.enemies[:]:
                if abs(b[0]-e[0]) < 6 and abs(b[1]-e[1]) < 6:
                    self.bullets.remove(b)
                    e[0] = random.randint(0,120);  e[1] = random.randint(-30,-10)
                    self.score += 10;  beep(0.05, "double");  break

    def draw(self):
        oled.fill(0)
        oled.fill_rect(self.player_x,54,8,6,1);  oled.pixel(self.player_x+4,52,1)
        for b in self.bullets: oled.fill_rect(b[0],b[1],2,4,1)
        for e in self.enemies:  oled.fill_rect(e[0],e[1],6,6,1)
        oled.text(f"S:{self.score}",0,0);  oled.text(f"L:{self.lives}",100,0)
        if self.game_over:
            oled.fill_rect(20,25,88,20,0);  oled.rect(20,25,88,20,1)
            oled.text("GAME OVER",30,30)
        oled.show()

# ── BREAKOUT ──────────────────────────────────────────────────
class BreakoutGame:
    def __init__(self): self.reset()
    def reset(self):
        self.paddle_x = 52;  self.ball_x = 64;  self.ball_y = 50
        self.ball_dx = 2;  self.ball_dy = -2
        self.bricks = [[x*16, y*6+10] for x in range(8) for y in range(4)]
        self.score = 0;  self.game_over = False;  self.won = False

    def update(self, buttons):
        if self.game_over or self.won: return
        if buttons["left"]  and self.paddle_x > 0:   self.paddle_x -= 4
        if buttons["right"] and self.paddle_x < 104: self.paddle_x += 4
        self.ball_x += self.ball_dx;  self.ball_y += self.ball_dy
        if self.ball_x <= 0 or self.ball_x >= 126:
            self.ball_dx *= -1;  beep(0.03)
        if self.ball_y <= 0:
            self.ball_dy *= -1;  beep(0.03)
        if (self.paddle_x <= self.ball_x <= self.paddle_x+24 and
                58 <= self.ball_y <= 60):
            self.ball_dy *= -1;  beep(0.05)
        if self.ball_y > 64:
            self.game_over = True;  beep(0.1, "game_over")
        for brick in self.bricks[:]:
            if (brick[0] <= self.ball_x <= brick[0]+14 and
                    brick[1] <= self.ball_y <= brick[1]+4):
                self.bricks.remove(brick);  self.ball_dy *= -1
                self.score += 5;  beep(0.04)
                if not self.bricks:
                    self.won = True;  beep(0.1, "double")
                break

    def draw(self):
        oled.fill(0)
        oled.fill_rect(self.paddle_x,58,24,4,1)
        oled.fill_rect(self.ball_x,self.ball_y,2,2,1)
        for b in self.bricks: oled.rect(b[0],b[1],14,4,1)
        oled.text(f"Score:{self.score}",0,0)
        if self.game_over:
            oled.fill_rect(20,25,88,20,0);  oled.rect(20,25,88,20,1)
            oled.text("GAME OVER",30,30)
        elif self.won:
            oled.fill_rect(20,25,88,20,0);  oled.rect(20,25,88,20,1)
            oled.text("YOU WIN!",35,30)
        oled.show()

# ── GAME MENU ─────────────────────────────────────────────────
class GameMenu:
    def __init__(self):
        self.games = ["Snake", "Pong", "Shooter", "Breakout"]
        self.selected = 0

    def update(self, buttons):
        if buttons["up"]   and button_pressed("up"):
            self.selected = (self.selected - 1) % len(self.games);  beep(0.03)
        if buttons["down"] and button_pressed("down"):
            self.selected = (self.selected + 1) % len(self.games);  beep(0.03)
        if buttons["a"] and button_pressed("a"):
            beep(0.05, "double");  return self.selected
        return None

    def draw(self):
        oled.fill(0);  oled.rect(0,0,128,64,1)
        oled.text("RETRO ARCADE",20,5);  oled.line(0,15,128,15,1)
        for i, game in enumerate(self.games):
            y = 22 + i * 10
            oled.text(f"{'>' if i==self.selected else ' '}{game}", 15, y)
        oled.show()

# ── MAIN LOOP ─────────────────────────────────────────────────
print("=" * 40);  print("🎮 RETRO GAMING CONSOLE");  print("=" * 40)
print("\nD-Pad: UP/DOWN/LEFT/RIGHT | A=Select | B=Back\n")

oled.fill(0)
oled.text("RETRO ARCADE",25,20);  oled.text("Loading...",30,35)
oled.show();  time.sleep(1)

menu = GameMenu();  current_game = None;  game_mode = "menu"

try:
    while True:
        buttons = read_buttons()
        if game_mode == "menu":
            selected = menu.update(buttons);  menu.draw()
            if selected is not None:
                current_game = [SnakeGame, PongGame, SpaceShooter, BreakoutGame][selected]()
                game_mode = "playing"
                print(f"🎮 Starting {menu.games[selected]}...")
        elif game_mode == "playing":
            current_game.update(buttons);  current_game.draw()
            if buttons["b"] and button_pressed("b"):
                game_mode = "menu";  beep(0.05)
                print("📋 Back to menu...")
        time.sleep(0.05)  # 20 FPS

except KeyboardInterrupt:
    print("\n🛑 Shutting down...")
    oled.fill(0);  oled.text("GAME OVER",35,28);  oled.show()
    buzzer.off()
4Run

Run the Simulation

Paste diagram.json

Copy into Wokwi's diagram tab to auto-wire all components.

Paste main.py

Copy the full code into Wokwi's code editor.

Click ▶ Play

Press the green start button — the OLED boot animation appears.

Navigate Menu

UP/DOWN to select a game, A to launch it.

Play Games

Use the 6 virtual buttons. B returns to the main menu.

More Arduino & Pico Projects

🟢 Beginner — Foundation & Basics
🔵 Intermediate — Displays & Sensors
🔴 Advanced — Motors & Actuators
🟣 Expert — Security, IoT & Gaming

© 2026 MakeMindz · Raspberry Pi Pico & Arduino Projects for STEM Learners & Makers

Comments

Product Cards
Buddy Bot eBook
⭐ New 2026 Release
Build Your
Own Robot!
3D design, wiring &
Arduino coding.
Young inventors love it!
🖨️
3D Print
All parts
Wire it
Circuit guide
💻
Code it
Arduino IDE
🤖
Watch it
Walk & react
📋 Your Details
Enter your name
Valid 10-digit no.
Enter a valid email
Special Website Offer
₹499 300
🌍 International: $5 USD
One-time · Instant digital delivery
🔒 Secured by Razorpay · Your data is safe
📄 Download Free Sample Copy
🔒 Secured by Razorpay · Your data is safe
🍓
Raspberry Pi Pico Mastery
21 Projects
⚡ Launch Price — 80% OFF
Learn Pico
Build 21 Projects!
MicroPython · Wokwi
IoT · Certificate
Perfect for beginners!
🖥️
Wokwi
No hardware
🐍
MicroPy
From zero
🔨
21 Projects
IoT + sensors
📄
Certificate
Verified cert
📋 Your Details
Enter your name
Valid 10-digit no.
Enter a valid email
Special Launch Offer
₹999 200 80% OFF
🌍 International: $5 USD
One-time · Lifetime access · No subscription
🔒 Secured by Razorpay · UPI · Cards · NetBanking
🎉

You're in!

Payment successful! Your Buddy Bot eBook is ready. Time to build!

📖 Access Your eBook Now
🎉

Enrolled!

Payment successful! Lifetime access to all 21 Pico Projects is yours!

🍓 Go to My Course