# DIY Arcade Machine ![Python](https://img.shields.io/badge/Python-3.x-blue?style=flat-square&logo=python) ![MicroPython](https://img.shields.io/badge/MicroPython-RP2040-green?style=flat-square) ![PyGame](https://img.shields.io/badge/PyGame-CE-yellow?style=flat-square) [![Deploy to GitHub Pages](https://github.com/SimonWaldherr/DIY-Arcade-Machine/actions/workflows/deploy-pages.yml/badge.svg)](https://github.com/SimonWaldherr/DIY-Arcade-Machine/actions/workflows/deploy-pages.yml) [![Short video of the DIY Arcade Console in action (on YouTube)](https://img.youtube.com/vi/3mumzf_0GiM/0.jpg)](https://www.youtube.com/watch?v=3mumzf_0GiM) A complete mini arcade system that runs on **hardware, desktop, and in the browser**: play a collection of classic-inspired games on a **64×64 RGB LED matrix** (HUB75 + MicroPython), on your computer with a **PyGame emulator**, or directly in the browser via **WebAssembly (pygbag)**. ## Features - **Triple Runtime Support** - **MicroPython + HUB75 LED Matrix**: Runs on RP2040-based boards (Interstate 75) - **Desktop (CPython) + PyGame**: Full emulator for development and testing - **Browser (WebAssembly) + pygbag**: Play directly in any modern browser, no install needed - **70+ Games Available by Default**: Classics, puzzle games, racers, shooters, reflex challenges, and compact original arcade games built for the 64×64 matrix - **Intro Screen**: Animated logo display on startup - **64×64 Display Layout** - 58-pixel playfield (rows 0-57) - 6-pixel HUD at bottom (score + clock) - **High Score System**: Persistent scores with 3-letter initials entry - **Memory-Optimized**: Buffered framebuffer, packed grid storage, lazy font loading - **Controller Support** - MicroPython: Wii Nunchuk-style I2C controller (with auto-detection for variants) - Desktop: Keyboard emulation (arrow keys + Z/X) --- ## Table of Contents - [Quick Start](#quick-start) - [Web build (Pygbag)](#web-build-pygbag) - [Hardware Requirements](#hardware-requirements) - [Software Requirements](#software-requirements) - [Installation](#installation) - [Desktop Setup](#desktop-setup) - [Browser Setup (pygbag)](#browser-setup-pygbag) - [MicroPython Setup](#micropython-setup) - [Make Commands](#make-commands) - [Game List](#game-list) - [Controls](#controls) - [Usage](#usage) - [Architecture](#architecture) - [Troubleshooting](#troubleshooting) --- ## Quick Start ### Run on desktop ```bash pip install pygame-ce # or: make install python main.py # or: make run ``` ### Test in browser locally ```bash pip install pygame-ce pygbag==0.9.2 # or: make web-install python -m pygbag . # build + serve at http://localhost:8000 # Safari: make web-safari (adds required COOP+COEP headers) ``` ### Deploy to GitHub Pages Push to `main` — the [GitHub Actions workflow](.github/workflows/deploy-pages.yml) builds the WebAssembly bundle with `python -m pygbag --build .` (Python 3.11) and deploys it to GitHub Pages automatically. --- ## Web build (Pygbag) **Local preview** (build + serve at `http://localhost:8000`): ```bash pip install pygame-ce pygbag==0.9.2 python -m pygbag . ``` **CI / offline bundle** (writes to `build/web/`): ```bash python -m pygbag --build . ``` **GitHub Pages**: in your repo go to *Settings → Pages → Source* and select **GitHub Actions**. Every push to `main` triggers the workflow which builds and deploys automatically. --- ## Hardware Requirements ### For Physical Arcade Machine - **Interstate 75 (W) by Pimoroni (PIM584)** - RP2040-based HUB75 driver board - [Pimoroni](https://shop.pimoroni.com/products/interstate-75?variant=39443584417875) | [DigiKey](https://www.digikey.de/de/products/detail/pimoroni-ltd/PIM584/15851385) | [Adafruit](https://www.adafruit.com/product/5342) | [BerryBase](https://www.berrybase.de/pimoroni-interstate-75-controller-fuer-led-matrizen) - **64×64 Pixel RGB LED Matrix** with HUB75 connector - [Example on Amazon](https://amzn.to/3Yadyhh) - **Controller** (one of): - [Wii Nunchuk](https://www.amazon.de/dp/B0D4V5JC71) + [Breakout Adapter](https://www.berrybase.de/adafruit-wii-nunchuck-breakout-adapter) - Compatible I2C joystick module - **Power Supply**: 5V adequate for LED matrix (typically 2-4A) - **Optional**: - [3D-Printed Enclosure](https://www.thingiverse.com/thing:6751325) or [Tilted Version](https://www.thingiverse.com/thing:6781604) - [Diffuser Mesh](https://www.thingiverse.com/thing:6751323) - [Satin Plexiglass Diffuser](https://acrylglas-shop.com/plexiglas-gs-led-9h04-sc-black-white-hinterleuchtung-3-mm-staerke) --- ## Software Requirements ### MicroPython Hardware - [MicroPython Firmware](https://github.com/pimoroni/pimoroni-pico/releases/download/v1.23.0-1/pico-v1.23.0-1-pimoroni-micropython.uf2) (Pimoroni build recommended) - Optional: [Thonny IDE](https://thonny.org/) for uploading files - Optional: `mpy-cross` for compiling to bytecode (reduces boot RAM usage) ### Desktop - Python 3.7+ - pygame-ce 2.x (Community Edition — drop-in replacement for pygame) --- ## Installation ### Desktop Setup 1. **Install dependencies**: ```bash pip install pygame-ce ``` 2. **Run the game**: ```bash make run # or manually: python main.py ``` A 640×640 window will appear showing the emulated LED matrix (10× scale). ### Browser Setup (pygbag) [pygbag](https://pygame-web.github.io/) packages the game as WebAssembly so it runs in any modern browser — no Python installation needed. 1. **Install dependencies**: ```bash pip install pygame-ce pygbag==0.9.2 # or via make: make web-install ``` 2. **Build and serve locally**: ```bash python -m pygbag . # build + serve at http://localhost:8000 make web # same, via Makefile (Chrome / Firefox) make web-safari # Safari (adds required COOP+COEP headers) make web-ios # iPhone/iPad fullscreen-oriented variant make web-ios-safari # iOS/Safari variant with COOP+COEP headers ``` 3. **Controls in browser**: same keyboard mapping as desktop — Arrow Keys, `Z`/`Space` to confirm, `X`/`Escape` to cancel. On touch devices the browser build exposes an on-screen D-pad plus `A`/`B` action buttons; tapping the game canvas triggers the primary action, and swiping the canvas sends directional taps. > **Browser support:** Chrome and Firefox work out of the box. Safari requires `Cross-Origin-Isolation` headers (`make web-safari` handles this automatically). > > **iOS fullscreen:** the iOS build keeps the game logic at the original 64×64 matrix resolution and scales the canvas fullscreen with safe-area-aware touch controls. This avoids rewriting every game for a new coordinate system while still filling the phone or tablet screen. On GitHub Pages it is published under `/ios/`; locally use `make web-ios-safari` for the closest Safari/iOS behavior. > > **Automated deployment:** every push to `main` triggers the GitHub Actions workflow which builds the regular WebAssembly bundle plus the `/ios/` variant and deploys both to GitHub Pages automatically. > > **Note:** High scores are stored in-memory while the page is open and reset on page reload. ### MicroPython Setup #### Quick Method (Recommended) The project now uses a **tiny bootstrap** approach to avoid on-device compilation memory errors: 1. **Install `mpy-cross`** (optional but highly recommended): ```bash brew install micropython # macOS # or: pip install mpy-cross ``` 2. **Connect your Interstate 75** via USB 3. **Upload with Make**: ```bash make upload # or manually: ./upload.sh ``` The script will: - Auto-detect connected devices - Compile `arcade_app.py` → `arcade_app.mpy` (if `mpy-cross` available) - Upload both `main.py` (tiny bootstrap) and the compiled module 4. **Reboot the device** - games start automatically #### Manual Method If you prefer manual upload via Thonny or `ampy`: 1. Flash MicroPython firmware to Interstate 75 2. Upload `main.py` (tiny bootstrap file) 3. Upload `arcade_app.py` or `arcade_app.mpy` (the main application) 4. Optional: Upload `highscores.json` if you want to preserve scores --- ## Make Commands For ease of use, a `Makefile` is provided with the following commands: - `make install`: Installs desktop dependencies (PyGame) - `make run`: Runs the PyGame emulator locally (`python main.py`) - `make arcade-bundle`: Rebuilds `arcade_app.py` from the modules in `arcade_src/` - `make test`: Rebuilds the bundle and runs the unit tests - `make check`: Verifies the committed bundle and runs the unit tests - `make targets-check`: Builds and smoke-tests desktop, MicroPython and both web targets - `make desktop-check`: Initializes PyGame and presents a headless frame - `make hardware-check`: Compiles `main.py` and `arcade_app.py` with `mpy-cross` - `make web-check`: Builds and validates the regular and iOS/touch web bundles - `make web-install`: Installs pygbag for browser/WebAssembly builds - `make web-build`: Builds the regular browser version into `build/web/` - `make web-ios-build`: Builds the fullscreen-oriented iOS version into `build/ios/` - `make web-pages-build`: Builds the Pages artifact with the regular version at `/` and the iOS version at `/ios/` - `make web`: Builds the WebAssembly version and serves it at `http://localhost:8000` - `make web-ios`: Builds and serves the iOS fullscreen-oriented version at `http://localhost:8000` - `make web-ios-safari`: Serves the iOS build with COOP+COEP headers for Safari - `make upload`: Compiles and uploads the code to the hardware (`./upload.sh`) - `make build`: Precompiles `arcade_app.py` into bytecode (`arcade_app.mpy`) - `make clean`: Cleans up previous build artifacts and pycache - `make clean-all`: Cleans up all files and the python virtual environment ### Modular source workflow The editable application source lives in [`arcade_src/`](./arcade_src/). Its ordered fragments share one namespace and are concatenated into `arcade_app.py`. This preserves the single-module deployment model and low runtime overhead required by MicroPython while keeping development work split into manageable files. After changing a fragment, run `make arcade-bundle` and commit both the source fragment and the regenerated `arcade_app.py`. `make check` detects a stale bundle. ### Target support and validation | Target | Runtime and input | Validation | |--------|-------------------|------------| | HUB75 console | MicroPython, Interstate 75, Nunchuk/I2C controller | `make hardware-check`, then a physical-device smoke test before release | | Desktop | CPython, pygame-ce, keyboard | `make desktop-check` | | Browser | pygbag/WebAssembly, keyboard and optional touch | `make web-check` plus a browser canvas smoke test | | iOS/touch browser | pygbag/WebAssembly, on-screen D-pad, action buttons and gestures | Included in `make web-check`; published below `/ios/` | `make targets-check` runs every automated check. The HUB75 check proves that both deployable modules compile to MicroPython bytecode; display timing, brightness, I2C wiring and controller behavior still require the physical console because those properties cannot be reproduced faithfully on a host. --- ## Content Selection The configuration block at the top of [`arcade_src/00_runtime.py`](./arcade_src/00_runtime.py) can hide content for a smaller or curated build. Add menu IDs to `CONFIG_DISABLED_GAMES` and effect IDs to `CONFIG_DISABLED_DEMOS`; leave the lists empty to show everything. CPU game previews in DEMOS use `G:NAME`, for example `"G:DOOMLT"`. ```python CONFIG_DISABLED_GAMES = ("DOOMLT", "RAYRCR") CONFIG_DISABLED_DEMOS = ("MANDEL", "G:DOOMLT") ``` --- ## Game List The default configuration offers more than 70 selectable games plus demo animations. The table below documents the complete built-in catalog; entries can be enabled or disabled through the content-selection settings above. | Game ID | Name | Description | |---------|------|-------------| | `DEMOS` | Demo Showcase | Zero-player demos: Snake, Life, Cube, Spark, Plasma, Orbit, Warp, Bounce, Tunnel, Matrix, Fire, Spring, Cradle | | `2048` | 2048 | Sliding tile puzzle with merge scoring | | `AIRHKY` | Air Hockey | Fast puck-and-mallet game with CPU or 2-player support | | `ARENA` | Arena | Top-down wave survival with movement and shooting | | `ARTILL` | Artillery Simulator | Turn-based angle-and-power shell duel with wind and deforming terrain | | `ASTRD` | Asteroids | Rotate, thrust, shoot asteroids in space | | `BEAT` | Beat Grid | Tap matching directions as notes cross the beat line and build a combo | | `BEJWL` | Bejeweled | Match-3 gem swapping puzzle | | `BILLI` | Billiards | Pool/Snooker-style table game with cue aim, pockets, rails, and ball collisions | | `BLOBBY` | Blobby Volley | One-button beach volleyball against a compact CPU blob | | `BOMBER` | Bomber | Timed bombs, block clearing, and maze enemies | | `BRKOUT` | Breakout | Brick breaker with rainbow bricks and optional powerups | | `BUBBLE` | Bubble Bobble | Trap enemies in bubbles and touch them to pop them | | `BTLZON` | Battlezone | Atari-style vector tank combat with radar, rocks, projectile shots, and waves | | `CAVEFL` | Cave Flyer | Tunnel navigation (starts wide, narrows progressively) | | `CENTI` | Centipede | Atari-style segmented shooter with mushrooms and waves | | `CGOLG` | Conway's Game of Life Game | Competitive Life battle with directed gliders and spaceships | | `CITY` | City Chase | Top-down city driving with jobs, traffic, police heat, and drop-offs | | `CLIMB` | Climber | Platform-jumping tower climb with scrolling height | | `COLMNS` | Columns | Match falling columns of colored gems in horizontal, vertical, or diagonal lines | | `CONNECT` | Connect Four | Drop counters against a compact CPU opponent and make four in a row | | `DEFUSE` | Defuse | Cut colored wires in sequence before the timer expires | | `DEFEND` | Defender | Patrol a wrapping planet and rescue settlers from raiders | | `DIGDUG` | Dig Dug | Dig tunnels, collect gems, and pump burrowing enemies | | `DODGE` | Dodge | Avoid falling blocks, dash to dodge | | `DONKEY` | Donkey Kong | Climb girders, use ladders, and jump rolling barrels | | `DOOMLT` | Doom Lite | Target-aware raycaster FPS with textured walls, enemy archetypes, and Quad burst fire; also powers the WINMAZE demo renderer | | `FLAPPY` | Flappy Bird | Navigate through moving pipe gaps | | `FLOOD` | Flood | Recolor the connected corner region to clear the seven-by-seven board | | `FIGHT` | Street Fighters | Footwork and timing duel against an aggressive CPU fighter | | `FROGGR` | Frogger | Hop across traffic lanes and advance through harder levels | | `GALAGA` | Galaga | Formation shooter with diving enemy attacks | | `GALAXY` | Galaxy | Capture planets by sending fleets across a compact star map | | `GOLF` | Golf | Tiny minigolf courses with aim, power, bounces, and obstacles | | `INVADR` | Invaders | Shoot marching alien waves, protect shields, hit saucers | | `JOUST` | Joust | Flap between platforms and defeat riders from above | | `KEEN` | Keen | Platformer with jumps, gems, keys, enemies, and exit doors | | `KERBAL` | Kerbal Arcade | Launch, circularize, and optionally return in a tiny orbital flight sim | | `LANDER` | Lunar Lander | Multi-level landing challenge plus optional scrolling v2 route mode with fuel powerups | | `LASER` | Laser | Mirror-rotation puzzle: guide the beam into the target | | `LIGHTS` | Lights Out | Toggle a light and its neighbors to clear the five-by-five grid | | `LOCO` | LocoMotion | Rotating railway puzzle with train routing | | `LOOP` | Time Loop | Record movement loops whose ghosts hold switches for the next run | | `MAZE` | Maze Explorer | Fog-of-war maze with gems, enemies, shooting | | `MARBLE` | Marble Madness | Accelerate a marble through walls, holes, and a finish gate | | `MINES` | Mines | Minesweeper-style reveal puzzle for the LED matrix | | `MOON` | Moon Patrol | Armed lunar rover with jumps, craters, and airborne hazards | | `ORBIT` | Orbit | Absorb smaller blobs, avoid larger ones, and navigate gravity wells | | `ORBTAL` | Orbital | Bounce shots through numbered circles until they burst | | `PACMAN` | Pac-Man | Collect pellets, avoid ghosts, power pellets | | `PAIRS` | Pairs | Memory card matching on a 4x4 board | | `PAPER` | Paperboy | Cycle through hazards and throw papers into marked mailboxes | | `PEGGLE` | Peggle | Aim a bouncing ball and clear orange pegs with limited shots | | `PICROS` | Picross | Solve five-by-five picture logic puzzles from row and column clues | | `PINBAL` | Pinball | Plunger launch, flippers, bumpers, targets, and multipliers | | `PITFAL` | Pitfall Runner | Endless runner with snakes, pits, treasures (safe start zone) | | `POLAR` | Magnetron | Switch polarity and steer charged particles into matching collectors | | `PONG` | Pong | Paddle vs. CPU or optional 2-player paddle duel | | `QIX` | Qix | Territory capture, avoid the enemy | | `RACING` | Top-Down Racing | Overhead circuit racer with curved road, boost, laps, and traffic | | `RAYRCR` | Ray Racer | Raytrace-style anti-grav racing with boost, energy gates, and rival hovercars | | `REACT` | Reaction Grid | Hit green pads before time expires and avoid red decoys | | `REVRS` | Othello/Reversi | Board game with simple CPU opponent | | `RTYPE` | R-Type Shooter | Side-scrolling endless shooter | | `SABOTR` | Saboteur Stealth | Sneak through multiple patrol maps and reach the objective | | `SIMON` | Simon Says | Memory sequence game with colored quadrants | | `SIGNAL` | Signal Control | Switch traffic lights and keep a busy intersection collision-free | | `SLALOM` | Slalom | Carve through downhill gates; tuck for more speed and double points | | `SNAKE` | Snake | Classic snake with red/green targets, wraparound | | `SOCCER` | Championship Soccer | Atari-style soccer with direction-based passes and shots | | `SONAR` | Sonar Ruins | Explore a dark maze with limited sonar pulses, recover beacons, and find the exit | | `SOKO` | Sokoban | Multi-level crate-pushing puzzle campaign | | `STACK` | Stacker | Timing game: trim and stack moving blocks | | `STKARC` | Stick Archer | Archery duel with charged shots, wind, and simple arrow physics | | `TAPPER` | Tapper | Serve four bar lanes and catch every returning mug | | `TEMPEST` | Tempest | Circle a wireframe tunnel and shoot enemies before the rim | | `TETRIS` | Tetris | Falling blocks with line clearing | | `TILT` | Tilt | Slide across ice, collect every crystal, and reset with Z if you get stuck | | `TRON` | Tron Lightcycle | Leave a trail, steer 90° turns, dodge CPU or optional second player | | `TWRDEF` | Tower Defense | Build towers across rotating road and open-field layouts | | `UFODEF` | UFO Defense | Missile Command-style defense with turret/base and wave/time settings | | `WIRES` | Wires | Rotate cable tiles to power one fully connected circuit | | `WORMS` | Worms Mini | Turn-based team artillery with destructible terrain and CPU/2-player settings | | `ZAXXON` | Zaxxon | Isometric fortress flight through moving energy gates | Each game tracks high scores with optional initials entry. --- ## Controls ### Common Controls **Menu Navigation**: - **Up/Down**: Navigate menu - **Z (or Space/Enter)**: Start/Confirm - **C (or X/Escape)**: Open game options when available; otherwise Back/Cancel **In-Game**: - **Directional movement**: Arrow keys / Joystick - **Primary action** (jump/shoot/rotate): Z button / Space / Enter - **Secondary/Back**: C button / X / Escape ### Desktop Keyboard Mapping | Action | Keys | |--------|------| | Move | Arrow Keys | | Confirm/Action | `Z`, `Space`, or `Enter` | | Back/Cancel | `X` or `Escape` | | Player 1 Move in 2P | `W`, `A`, `S`, `D` | | Player 1 Action in 2P | `Shift` where used | | Player 2 Move in 2P | Arrow Keys | | Player 2 Action in 2P | `Z`, `Space`, or `Enter` where used | ### Game Settings Highlights - `AIRHKY`: `PLAYR` switches between `1P` and `2P`; `GOALS` sets the match length. - `BILLI`: `RULE` switches between `POOL` and `SNOOK`; `AIM` changes the aim guide length. - `PONG`: `PLAYR` switches between `1P` and `2P`. - `TRON`: `PLAYR` switches between CPU opponent and `2P`. - `CITY`: `JOBS` sets mission count; `TRAF` toggles civilian traffic. - `KERBAL`: `MISN` switches between orbit-only and return mission; `ASST` toggles arcade flight assist. - `LANDER`: `MODE` switches between classic `V1` and scrolling `V2` with route pads and fuel pickups. - `RACING`: `LAPS` sets race length; `TRAF` toggles traffic cars. - `WORMS`: `PLAYR` switches between CPU opponent and `2P`; `TEAM` sets two or three worms per side. ### MicroPython Controller - **I2C Address**: 0x52 (standard Nunchuk) - **Pins**: SCL=21, SDA=20 (configurable in code) - **Buttons**: - **C button**: Back/Cancel - **Z button**: Confirm/Action - **Analog stick**: 8-directional movement (includes diagonals) The code auto-detects controller variants including the "new signature" controllers (`A0 20 10 00 FF FF`). --- ## Usage Start the app, select a game with Up/Down, and press `Z` to launch it. If a game has settings, press `C` on the game list to open the shared settings menu before starting the game. High scores are stored per game ID. The shared game-over menu offers retry, high-score view, and return-to-menu actions. --- ## Architecture The modular source workflow is documented in [`arcade_src/README.md`](./arcade_src/README.md). Per-game descriptions are listed in the game table above. The short version: - `GameSelect.GAME_REGISTRY` controls which games appear in the selector. - `GameSettings.DEFINITIONS` declares per-game settings for the shared settings menu. - Games that accept a constructor context read settings with `get_context_setting()`. - Callback-based games inherit `FrameLoopGame` for portable sync/async frame pacing. - Grid games can inherit `GridCursorGame` for shared, debounced cursor movement. - Games report final results through `set_game_over_score()` so the shared lost/won and high-score flow can handle them. --- ## Troubleshooting - If the browser build appears blank, rebuild with `python -m pygbag .` and check the console for import or asset errors. - If settings do not appear for a game, confirm the game ID in `GameSettings.DEFINITIONS` exactly matches the ID in `GameSelect.GAME_REGISTRY`. - If a game class exists but is missing from the menu, add it to `GameSelect.GAME_REGISTRY`. - If desktop input behaves differently from the cabinet, check the keyboard mapping in the Controls section and the joystick/button helper functions in `arcade_app.py`.