Skip to content

Indspl0it/Pocket-RPG

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pocket RPG Banner

Pocket RPG

An AI-powered text RPG on a $10 microcontroller.

Version 1.0.0


Overview

Pocket RPG is a dark fantasy text RPG that runs entirely from flash on the M5Stack Basic v2.7 (ESP32). An AI narrator drives every turn -- generating narrative text, selecting pixel-art scenes, spawning enemies, and setting the mood -- all rendered on a 320x240 TFT display. The result looks and feels like a Game Boy Color RPG.

The game world is Vaeloria. You create a character, choose a class, name your hero, and venture into dungeons, forests, ruins, and taverns. Every decision you make is sent to an AI backend (OpenRouter API) which returns structured JSON that controls what you see, hear, and experience.


Screenshots

Main Menu Forest Exploration
Menu Explore
Dungeon Combat Tavern Rest
Combat Tavern
Death Screen
Death

The Challenge

Building this game required solving hard problems at every layer of the stack on a device with no operating system, no GPU, and less RAM than a 1989 Game Boy:

Constraint What it means
123KB usable heap MicroPython VM + WiFi + SSL + game engine + renderer + assets must all coexist in 123KB. Every byte is accounted for.
No PSRAM Unlike ESP32-S3 or M5Stack Fire, the Basic v2.7 has zero external RAM. What you see is what you get.
HTTPS on 20KB free heap After loading game modules, only ~20KB remains. SSL/TLS handshakes need ~40-50KB. Required pre-compiled bytecode (.mpy), aggressive gc.collect(), deferred imports, and memory-aware boot sequencing.
Shared SPI bus The display and SD card share a single SPI bus with a mutex lock. Every draw call and every file read must coordinate.
No filesystem at boot UIFlow2 doesn't expose /flash in sys.path. Game modules needed manual path injection before any imports could work.
AI integration in 5KB Full OpenRouter API integration -- system prompt construction, HTTP POST, 3-stage JSON response parsing, conversation history -- in a module that fits in 5KB of compiled bytecode.
Cooperative async on single core The 50Hz game loop runs input polling, engine state machine, animation ticks, and audio ticks in a single-threaded uasyncio loop. Blocking calls freeze everything.
RGB565 assets, RGB888 API BMP files are stored as RGB565 (16-bit) for the ILI9342C display, but the UIFlow2 drawing API accepts RGB888 (24-bit). Every color constant lives in two worlds.

The Characters persist across sessions. The AI generates unique adventures every turn. Sprites animate, scenes transition, dice roll on shake, and mood-driven ambient tones play through the speaker -- all on a $10 chip with 123KB of breathing room.


Features

  • AI-driven narration -- Every turn generates unique narrative text, scene changes, enemy encounters, and game state deltas via the OpenRouter API
  • 4 character classes -- Warrior, Mage, Rogue, and Ranger with distinct stats and playstyles
  • Pixel-art scene system -- 13 background BMPs driven by AI scene selection
  • Sprite compositing -- 48x48 player and enemy sprites with frame animation and transparency keying
  • Animated d20 dice rolls -- Shake the device to roll; result is injected into the AI prompt
  • Typewriter text reveal -- Narrative text scrolls in character-by-character in the dialog panel
  • Screen transition animations -- Scene wipes, screen flashes, HP drain, level-up bursts, death sequences
  • Ambient mood audio -- PWM tone system with mood-driven ambient soundscapes (calm, tense, combat, eerie, triumph, silent)
  • SFX library -- Button clicks, combat hits, level-up fanfares, death knells, dice rolls, item pickups
  • IMU gesture input -- Shake detection for dice rolls, tilt detection for scrolling
  • Offline fallback mode -- Branching adventure tree from offline.json when WiFi is unavailable
  • 3-stage JSON parsing -- Robust AI response parser with direct parse, whitespace strip, and brace extraction fallback
  • Save/load system -- Up to 3 save slots with conversation history preservation
  • Custom bitmap font engine -- Large readable font rendered from a font atlas BMP
  • Settings menu -- In-game configuration for text speed, volume, tilt sensitivity, and save slot
  • Character overlays -- Full-screen stats and inventory screens accessible via long-press
  • Flash-only deployment -- All code and assets are flashed to the device; no SD card required

Hardware Requirements

Component Spec Role
Device M5Stack Basic v2.7 Game platform
MCU ESP32-D0WDQ6, 240MHz dual-core, 520KB SRAM Game logic, WiFi, rendering
Flash 16MB 3MB app + 12.9MB VFS for assets
Display ILI9342C, 320x240, RGB565, SPI @ 40MHz All visuals
Buttons 3x tactile (A/B/C) on GPIO 39/38/37 All player input
IMU MPU6886 6-axis accelerometer + gyroscope Shake = dice roll, tilt = scroll
Speaker DAC via GPIO25, mono SFX and ambient tones
WiFi 802.11 b/g/n 2.4GHz OpenRouter API calls
MicroSD SPI slot (OPTIONAL) Override assets or config if present

Software Requirements

  • Firmware: UIFlow2 v2.4.2 for M5Stack Basic v2.7 (16MB flash)
  • Runtime: MicroPython v1.25.0 (bundled with UIFlow2)
  • Deploy tools: Python 3.8+ with mpremote and esptool (pip install mpremote esptool)
  • API Key: OpenRouter account with an API key (optional -- offline mode works without it)
  • AI Model: arcee-ai/trinity-large-preview:free (free tier, no cost)

Quick Start

Deploy prebuilt firmware (recommended)

The deploy tool flashes the firmware and uploads your configuration in one step.

python tools/deploy.py

It will interactively prompt for WiFi credentials, OpenRouter API key, and game settings. The tool detects the serial port automatically on both Windows (COM ports) and Linux (/dev/ttyUSB*).

Options:

python tools/deploy.py --port COM3       # Specify serial port
python tools/deploy.py --config-only     # Skip firmware flash
python tools/deploy.py --flash-only      # Skip config upload

Requires: pip install esptool mpremote

Build from source

If you want to build the firmware yourself (requires WSL or Linux with ESP-IDF):

./firmware/build.sh setup    # First time: install toolchain
./firmware/build.sh build    # Build firmware
./firmware/build.sh flash    # Flash to device via USB
./firmware/build.sh all      # setup + build + flash

The build script freezes all Python source into ROM and writes all assets into a 12.9MB VFS partition.

Recovery

Hold BtnA (leftmost) during power-on to return to the UIFlow2 startup menu.


Configuration

Settings are loaded from config.json on the flash VFS. Missing or invalid values fall back to defaults. Numeric values are clamped to valid ranges.

Key Type Default Range Description
wifi_ssid string "" -- WiFi network name
wifi_password string "" -- WiFi password
openrouter_api_key string "" -- OpenRouter API key
text_speed_ms int 40 10-200 Typewriter delay per character (ms)
volume int 3 0-10 Master volume level
tilt_sensitivity_deg int 25 5-90 IMU tilt threshold in degrees
shake_threshold_g float 2.5 0.5-10.0 Shake detection threshold in g-force
active_slot int 0 0-2 Active save slot
offline_mode bool false -- Force offline fallback mode
world_file string "world.txt" -- World lore file name
ble_enabled bool false -- Enable BLE (experimental)

Controls

Gameplay

Button Short Press Long Press (hold 1.2s)
A (left) Select choice 1 / Navigate left Show character stats overlay
B (center) Select choice 2 / Confirm Manual save to active slot
C (right) Select choice 3 / Navigate right Show inventory overlay

Character Name Entry

Button Action
A Previous character
B Accept current character
C Next character
Hold B Finish name entry

Class Selection

Button Action
A Previous class
B Select class
C Next class

Gestures

Gesture Action
Shake Roll a d20 dice (sustained >2.5g for 150ms). Result is injected into the next AI prompt. Triggers a d20 animation overlay.
Tilt Tilt detection for UI scrolling (configurable sensitivity).

Game Classes

Class HP MP STR DEX INT HP/Level Weapon
Warrior 25 5 4 2 1 +4 Sword
Mage 15 20 1 1 5 +2 Staff
Rogue 18 8 2 5 2 +3 Dagger
Ranger 20 10 2 4 2 +3 Bow

XP progression follows the formula: XP_next = 100 * 1.5^(level - 1)

On death, characters respawn at 30% of max HP (minimum 1).


Architecture

The game runs as a single-threaded async loop at 50Hz using uasyncio. See docs/architecture.md for the full technical deep-dive.

Flash Layout (16MB)

+------------------+  0x000000
| Bootloader       |
+------------------+  0x009000
| NVS (24KB)       |
+------------------+  0x00F000
| PHY init (4KB)   |
+------------------+  0x010000
|                  |
|  App / factory   |  3 MB  (MicroPython + frozen modules)
|                  |
+------------------+  0x310000
|                  |
|  VFS (FAT)       |  12.9 MB  (scenes, sprites, ui, fonts,
|                  |            config, saves, world data)
|                  |
+------------------+  0x1000000 (16 MB)

Screen Layout (320x240)

+-------------------------------------+  y=0
|                                     |
|       SCENE ZONE  (320x168)         |  Pixel-art background + sprites
|                                     |
+-------------------------------------+  y=168
|  HP bar | MP bar | LVL | WiFi      |  STATUS STRIP (12px)
+-------------------------------------+  y=180 (gold divider)
|                                     |
|  DIALOG PANEL  (320x48)            |  Narrative text, 3 lines max
|                                     |
+----------+----------+---------------+  y=229 (gold divider)
| [A] Verb | [B] Verb | [C] Verb     |  CHOICE BAR (10px)
+----------+----------+---------------+  y=239

State Machine

  • menu -- Main menu (New Game / Continue / Settings)
  • char_create_class -- Class selection carousel
  • char_create_name -- Character name entry
  • char_create_roll -- Bonus stat roll
  • idle -- Awaiting player input
  • ai_pending -- Waiting for AI API response
  • animating -- Playing turn animations
  • overlay -- Displaying stats or inventory
  • settings -- Settings menu
  • death -- Death sequence with respawn option

Module Dependency Flow

main.py (boot + 50Hz loop)
  +-- engine.py (state machine, turn orchestration)
  |     +-- ai_client.py (OpenRouter API, JSON parsing)
  |     +-- world.py (RPG classes, stats, deltas)
  |     +-- fallback.py (offline adventure tree)
  |     +-- storage.py (flash VFS I/O, save/load)
  +-- renderer.py (display controller, all screen zones)
  |     +-- font_engine.py (bitmap font rendering)
  |     +-- sprite.py (sprite sheet loading, transparency)
  +-- animator.py (typewriter, wipes, flashes, HP drain)
  +-- input_handler.py (buttons + IMU polling)
  +-- audio.py (SFX + ambient mood system)
  +-- config.py (config.json loader with validation)
  +-- paths.py (flash/SD path abstraction)
  +-- wifi.py (WiFi connection manager)

Project Structure

Pocket-RPG/
  README.md
  src/
    main.py                  # Boot entry point, 50Hz game loop
    engine.py                # Game engine state machine
    renderer.py              # Display controller (all screen zones)
    ai_client.py             # OpenRouter API client + JSON parser
    world.py                 # RPG class definitions, stat logic
    config.py                # Configuration loader with validation
    paths.py                 # Flash/SD path abstraction layer
    audio.py                 # SFX and ambient mood audio engine
    input_handler.py         # Button polling + IMU shake/tilt
    fallback.py              # Offline branching adventure navigator
    animator.py              # Animation system (typewriter, wipes, etc.)
    sprite.py                # Sprite sheet loader with transparency
    font_engine.py           # Bitmap font atlas renderer
    storage.py               # Flash VFS I/O, save/load
    wifi.py                  # WiFi connection manager
    world.txt                # World lore text for AI context
    offline.json             # Offline adventure tree data
  assets/
    scenes/                  # Background BMPs (320x168)
    sprites/                 # Character and enemy sprite sheets
    ui/                      # UI element BMPs (splash, panels, font, d20)
  firmware/
    build.sh                 # Custom firmware build script
    partitions_16mb_game.csv # Flash partition table
    create_game_main.py      # Frozen main.py generator
  tools/
    deploy.py                # Interactive deploy tool (flash + config)
    generate_builtin_assets.py # Regenerate bundled asset pack
    convert_assets.py        # PNG to RGB565 BMP converter
  docs/
    architecture.md          # System design deep-dive
    configuration.md         # Config reference and setup
    game-mechanics.md        # Classes, combat, XP, inventory
    development.md           # Contributing guide and coding patterns

Documentation

Detailed documentation is available in the docs/ directory:

Document Description
Architecture System design, state machine, async patterns, flash layout, display zones
Configuration Config reference, API setup, deployment, WiFi, audio, offline mode
Game Mechanics Character classes, combat, XP/leveling, death, inventory, dice rolls
Development Guide Contributing guide, MicroPython constraints, coding patterns, asset specs

License

MIT License. See LICENSE file for details.

About

Pocket RPG is a dark fantasy text RPG that runs entirely from flash on the M5Stack Basic v2.7 (ESP32). An AI narrator drives every turn -- generating narrative text, selecting pixel-art scenes, spawning enemies, and setting the mood -- all rendered on a 320x240 TFT display.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors