You Have Got Pizza is an original PRG32 cartridge game for the European RISC-V Summit 2026 demonstration track. It is a small, fully playable, Burger Time-inspired arcade game reimagined in an academic Piazza. The player is a professor preparing pizza components to feed starving students. The metaphor is simple: building cool teaching tools makes students hungry for knowledge and foolish enough to try hard things with joy.
The game is deliberately implemented twice:
assembly/game.S: RISC-V assembly using the PRG32 cartridge ABI directly. Draws with plain rectangles and single-tone beeps, kept deliberately simple as a first low-level lab.c/game.c: a pedagogically commented C version with the same gameplay structure. It additionally renders small painted, palette-indexed sprites/tiles and plays an 8-voice SID-like soundtrack through PRG32's real synth mixer -- see "Indexed-color art and SID-like audio (C version)" below.
The repository also includes original PNG graphics and WAV sound masters. No copyrighted sprites, music, names, layouts, or sounds from BurgerTime or any other commercial game are included.
This is a standalone third-party PRG32 game repository. It is not expected to live inside the PRG32 source tree.
you_have_got_pizza/
|-- README.md
|-- LICENSE
|-- assembly/
| `-- game.S
|-- c/
| |-- game.c
| `-- assets_indexed.inc (generated; see pack_indexed_assets.py)
|-- assets/
| |-- icon.png
| |-- screenshot.png
| |-- generate_assets.py (background/splash/UI reference art + WAV masters)
| |-- generate_indexed_art.py (small palette-limited ROM sprite/tile art)
| |-- pack_indexed_assets.py (PNG -> c/assets_indexed.inc, via PRG32's own tools)
| |-- generate_audio.py (writes audio.json, the SID-like score)
| |-- audio.json
| |-- manifest.json
| |-- original_art.md
| |-- png/
| | |-- background_piazza_320x200.png
| | |-- splash_you_have_got_pizza_320x200.png
| | |-- ui_lives_score_icons.png
| | `-- rom/ (small indexed PNGs actually compiled into the cartridge)
| | |-- rom_professor_4frames_12x16.png
| | |-- rom_student_blue_4frames_12x16.png
| | |-- rom_student_magenta_4frames_12x16.png
| | |-- rom_ingredients_4x4_28x12.png
| | |-- rom_tile_arch_20x26.png
| | |-- rom_tile_stone_16x10.png
| | |-- rom_tile_ladder_14x12.png
| | |-- rom_tile_plate_80x24.png
| | |-- rom_title_emblem_56x48.png
| | `-- rom_life_icon_8x8.png
| `-- wav/
| |-- sfx_start_jingle.wav
| |-- sfx_climb_up.wav
| |-- sfx_climb_down.wav
| |-- sfx_collect_dough.wav
| |-- sfx_collect_sauce.wav
| |-- sfx_collect_cheese.wav
| |-- sfx_collect_basil.wav
| |-- sfx_pizza_ready.wav
| |-- sfx_student_collision.wav
| `-- sfx_game_over.wav
|-- docs/
| `-- build-and-publish.md
|-- metadata/
| |-- colophon.json
| |-- manifest.json
| `-- metadata.json
|-- scripts/
| |-- build.sh
| `-- pack-store-bundle.sh
`-- tools/
`-- build_cartridges.sh
You move through a Piazza made of stone platforms and ladders. Collect the pizza ingredients in the order suggested by the level art: dough, sauce, cheese, and basil. Starving students wander across the platforms. If a student reaches you before the pizza is ready, you lose a life. When all ingredients are collected, a pizza is ready, the students are fed, and the board resets at a slightly more urgent pace.
The assembly version draws the professor and students as plain rectangles with single-tone beeps, keeping every pixel operation and every sound trivially traceable in RISC-V. The C version instead draws small, hand-painted, palette-indexed sprites and plays them over an 8-voice synth soundtrack -- see the next section for why the two versions now differ, and how to regenerate or restyle that art and music.
Use Joystick 1 only.
| Input | Action |
|---|---|
| LEFT / RIGHT | Walk on the current platform |
| UP / DOWN | Climb when aligned with a ladder |
| START | Restart after game over |
PRG32 is an educational RISC-V gaming runtime. It turns low-level programming from an abstract lecture into a visible loop: change a register, rebuild a cartridge, move a sprite, and see the result. That loop is powerful for youngsters learning computer architecture because the screen makes the consequences of state, branches, memory layout, and calling conventions tangible.
A PRG32 cartridge exports three functions:
<prefix>_init
<prefix>_update
<prefix>_draw
init sets the initial state, update reads input and advances the model, and draw renders the state. This separation is a gentle bridge from C to assembly: students first see the same game logic in C, then follow the exact same responsibilities in RISC-V assembly.
For this game, the prefixes are:
| Version | Entry prefix | Exported symbols |
|---|---|---|
| Assembly | you_have_got_pizza |
you_have_got_pizza_init, you_have_got_pizza_update, you_have_got_pizza_draw |
| C | you_have_got_pizza_c |
you_have_got_pizza_c_init, you_have_got_pizza_c_update, you_have_got_pizza_c_draw |
For a complete first-year laboratory sequence, see
docs/tutorial-create-you-have-got-pizza.md.
It guides students from design through the code-deploy-debug cycle and final
publication on the Cartridge Store.
You need two separate repositories/directories:
- this game repository;
- a cloned PRG32 repository, used as the resident runtime and cartridge tool provider.
Example layout:
mkdir -p $HOME/src
cd $HOME/src
git clone https://github.com/riscv-prg32/PRG32.git
git clone https://github.com/riscv-prg32/YouHaveGotPizza.gitInstall and export ESP-IDF as required by PRG32. The PRG32 documentation currently uses ESP-IDF with ESP32-C3 support for QEMU and ESP32-C6 support for the physical board.
On each new shell, source ESP-IDF before using idf.py:
. $HOME/esp-idf/export.shThen tell the portable helper script where PRG32 is:
export PRG32_REPO=$HOME/src/PRG32
cd $HOME/src/you_have_got_pizzaThe default build matches the portable cartridge workflow used by DeviceDemo on
PRG32 main: it builds the C cartridge as a portable ABI-table
cartridge, attaches Store metadata, and writes a publishable .prg32 file.
Build for ESP32-C6 hardware:
export PRG32_REPO=$HOME/src/PRG32
export PRG32_ARCHITECTURE=esp32c6
scripts/build.shThe output is:
dist/you-have-got-pizza-esp32c6.prg32
Build for QEMU:
export PRG32_REPO=$HOME/src/PRG32
export PRG32_ARCHITECTURE=qemu
scripts/build.shThe output is:
dist/you-have-got-pizza-qemu.prg32
Current PRG32 main builds portable cartridges only. Both C and assembly
versions use its public timed-note audio API.
Upload to a PRG32 ESP32-C6 board:
PYTHONPATH="$PRG32_REPO" python3 -m prg32 esp32c6 upload \
dist/you-have-got-pizza-esp32c6.prg32 \
--url http://192.168.4.1Stage the QEMU cartridge into a PRG32 QEMU flash image:
PYTHONPATH="$PRG32_REPO" python3 -m prg32 qemu upload \
dist/you-have-got-pizza-qemu.prg32 \
--flash "$PRG32_REPO/build-qemu/qemu_flash.bin" \
--partitions "$PRG32_REPO/partitions_prg32.csv"Build both architecture variants, pack the flat Store bundle, then publish:
export PRG32_REPO=$HOME/src/PRG32
export PRG32_ARCHITECTURE=esp32c6
scripts/build.sh
export PRG32_ARCHITECTURE=qemu
scripts/build.sh
scripts/pack-store-bundle.sh
PYTHONPATH="$PRG32_REPO" python3 -m prg32 store publish-bundle \
dist/you-have-got-pizza-store-bundle.zip \
--store-url http://192.168.1.42:5080 \
--token "$PRG32_STORE_TOKEN"See docs/build-and-publish.md for the focused build, deploy, QEMU, and Store
workflow.
tools/build_cartridges.sh builds both C and assembly portable cartridges.
It can also build the resident firmware before staging or uploading them.
The helper can build the resident firmware automatically, but these are the explicit PRG32-side commands for clarity:
cd $PRG32_ROOT
idf.py -B build-qemu \
-D SDKCONFIG=build-qemu/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults.qemu \
set-target esp32c3
idf.py -B build-qemu \
-D SDKCONFIG=build-qemu/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults.qemu \
buildRun QEMU once to create the flash image:
idf.py -B build-qemu \
-D SDKCONFIG=build-qemu/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults.qemu \
qemu --graphics monitorStop QEMU after the first successful launch. This creates build-qemu/qemu_flash.bin.
From the game repository root:
export PRG32_ROOT=$HOME/src/PRG32
./tools/build_cartridges.sh qemuThe generated cartridges are written to:
dist/qemu/you-have-got-pizza-asm.prg32
dist/qemu/you-have-got-pizza-c.prg32
To build and stage the assembly cartridge into the QEMU flash image:
./tools/build_cartridges.sh qemu --upload-qemuThen run QEMU from the PRG32 repository:
cd $PRG32_ROOT
idf.py -B build-qemu \
-D SDKCONFIG=build-qemu/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults.qemu \
qemu --graphics monitorTo stage the C cartridge instead, build normally and call the PRG32 builder directly:
PYTHONPATH="$PRG32_ROOT" python3 -m prg32 qemu upload \
dist/qemu/you-have-got-pizza-c.prg32 \
--flash $PRG32_ROOT/build-qemu/qemu_flash.binAgain, the helper can do this automatically, but the explicit commands are:
cd $PRG32_ROOT
idf.py -B build-esp32c6 \
-D SDKCONFIG=build-esp32c6/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults \
set-target esp32c6
idf.py -B build-esp32c6 \
-D SDKCONFIG=build-esp32c6/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults \
build
idf.py -B build-esp32c6 \
-D SDKCONFIG=build-esp32c6/sdkconfig \
-D SDKCONFIG_DEFAULTS=sdkconfig.defaults \
flash monitorAfter this step the resident PRG32 firmware is on the board. Cartridge upload is normally performed over the PRG32 Wi-Fi access point.
From the game repository root:
export PRG32_ROOT=$HOME/src/PRG32
./tools/build_cartridges.sh esp32c6The generated cartridges are written to:
dist/esp32c6/you-have-got-pizza-asm.prg32
dist/esp32c6/you-have-got-pizza-c.prg32
To build and upload the assembly cartridge to a running ESP32-C6 PRG32 board:
./tools/build_cartridges.sh esp32c6 --upload-hardware --url http://192.168.4.1To upload the C cartridge instead:
PYTHONPATH="$PRG32_ROOT" python3 -m prg32 esp32c6 upload \
dist/esp32c6/you-have-got-pizza-c.prg32 \
--url http://192.168.4.1From the PRG32 checkout, build either source with the current CLI. Use
--architecture qemu for the emulator and --architecture esp32c6 for the
physical board.
cd "$PRG32_ROOT"
python3 -m prg32 cartridge build /path/to/YouHaveGotPizza/assembly/game.S \
--portable --architecture qemu --entry-prefix you_have_got_pizza \
--name pizza-asm --out /path/to/YouHaveGotPizza/dist/qemu/pizza-asm.prg32The C source uses c/game.c and entry prefix you_have_got_pizza_c. It
#includes the generated c/assets_indexed.inc, so run
assets/generate_indexed_art.py and assets/pack_indexed_assets.py first
(see the next section). To include the SID-like soundtrack, also pack
assets/audio.json with tools/prg32audio_pack.py and pass the result via
--audio-block.
PRG32 caps a cartridge at 64 KiB total -- code, sprite data, and audio
block together (PRG32_CART_MAX_KIB / PRG32_CART_RAM_KIB in PRG32's
prg32.h). A single full-screen 320x200 painted background at 4 or 8 bits
per pixel would be 16-64 KB by itself -- more than the entire budget. Real
'90s hardware never stored full-screen raster backgrounds for exactly this
reason: it built scenes from small, cheaply repeated tiles and sprites. The C
version follows the same approach, using PRG32's real indexed-sprite pipeline
(prg32_sprite_draw_indexed / prg32_sprite_draw_bitplanes,
prg32_indexed_sprite_t) instead of the flat rectangles used everywhere
else. This is why the C and assembly versions now render differently: porting
this to hand-written RV32 assembly would roughly double the size of a file
meant to stay small and traceable, so assembly/game.S intentionally stays
on the original rectangle/single-tone approach.
A full 320x200 build of the whole cartridge (game logic + all sprite/tile art
- the audio block) currently comes to about 15 KB -- comfortably inside the 64 KB budget, with room for further additions.
python3 assets/generate_assets.py # background/splash/UI reference PNGs + WAV masters
python3 assets/generate_indexed_art.py # small palette-limited ROM sprite/tile PNGs (assets/png/rom/)
PRG32_REPO=/path/to/PRG32 python3 assets/pack_indexed_assets.py # -> c/assets_indexed.inc
python3 assets/generate_audio.py # -> assets/audio.jsonscripts/build.sh and tools/build_cartridges.sh already run the indexed-art
and audio steps automatically before building the C cartridge, so this is
only needed when iterating on art/audio without a full rebuild.
assets/generate_assets.pydraws the big, full-resolution reference art (background_piazza_320x200.png, the splash screen, the UI icon sheet) and the WAV sound masters. None of this is compiled into the cartridge -- it exists for documentation, the Store icon, and the screenshot.assets/generate_indexed_art.pydraws the small, palette-limited ROM art underassets/png/rom/that is actually compiled in: the professor and student walk cycles (12x16, 4 frames), the ingredient sheet (28x12, 4 kinds x 4 frames), and small repeating environment tiles (skyline arch, stone platform brick, ladder rung, bottom plate) plus a painted title emblem and a HUD life icon. Every image is flattened onto a solid magenta(255, 0, 255)key color, painted first so it lands at palette index 0 for transparency.assets/pack_indexed_assets.pyconverts those PNGs intoc/assets_indexed.incusing PRG32's owntools/prg32_image_convert.pypacking functions -- the same code path PRG32'sdevicedemocartridge uses for its indexed/bitplane examples. PRG32 cartridges compile exactly one C source file, so every sprite asset has to live in this one generated,#included file; requiresPRG32_REPOto point at a PRG32 checkout.
To personalize a sprite or tile: edit the matching drawing function in
generate_indexed_art.py (e.g. professor_frame(), ingredient_frame(),
stone_tile(), title_emblem()), keep the frame's pixel dimensions and
frame-grid layout unchanged (or update the matching entry in
pack_indexed_assets.py's ASSETS list and the draw call in c/game.c if
you resize it), then rerun generate_indexed_art.py + pack_indexed_assets.py.
Each image currently stays within a 16-color budget except the title emblem,
which deliberately uses more colors to exercise PRG32's 256-color path.
The animation frame in both cartridge versions is derived from the frame counter:
uint8_t anim = (frame_no >> 3) & 3u;That expression is an excellent low-level programming lesson: shifting divides by a power of two, masking computes a modulo for a power-of-two frame count, and the resulting small integer selects a pose.
The C version plays PRG32's real synth mixer: four waveforms (triangle, saw,
pulse, noise) with a cutoff/resonance filter baked into the sample id,
per-instrument ADSR envelopes, up to 8 voices, and stereo panning.
assets/generate_audio.py writes assets/audio.json (instruments + one
looping tracker track); PRG32's tools/prg32audio_pack.py packs that into a
binary AUDIO block, which scripts/build.sh passes to the cartridge
builder via --audio-block. The block auto-loads when the cartridge
installs, so c/game.c only has to call prg32_audio_play_track() and
prg32_audio_note().
Channel layout (channel index == instrument index for tracker-driven notes, enforced by PRG32's tracker):
| Channels | Role |
|---|---|
| 0-4 | looping 5-voice background theme (bass, two chord pads, an arpeggio lead, a noise hi-hat) |
| 5-7 | one-shot sound effects (collect, climb, collision, pizza-ready, game-over), round-robined by sfx() in c/game.c so gameplay stingers never steal a voice from the music |
To personalize the music, edit the chord progression, bass/arpeggio patterns,
or instrument ADSR/waveform values in generate_audio.py, then regenerate
audio.json and rebuild. To personalize an effect, change the sfx(...)
call site in c/game.c (instrument, MIDI note, volume, duration, pan) --
see move_player(), collect_ingredients(), and check_collisions().
Note: mono-vs-stereo output is a resident-firmware build setting
(prg32_audio_set_mode is not exposed to portable cartridges), so a cartridge
cannot force stereo at runtime -- the panning here shapes the mix either way,
but a true stereo image needs the firmware itself built with stereo output
enabled.
The original WAV masters under assets/wav/ and the write_wav() calls in
generate_assets.py are unchanged and still documented as before, in case
you want a sample-based (rather than synth-based) starting point.
When publishing a personalized fork on GitHub:
- use original drawings, generated shapes, or assets you have permission to redistribute;
- do not copy sprites, sound effects, logos, level art, or music from commercial games;
- write a short provenance note in
assets/original_art.md; - regenerate
assets/manifest.jsonafter changes; - keep licenses clear for every contributed asset.
./tools/build_cartridges.sh [qemu|esp32c6] [options]Options:
| Option | Meaning |
|---|---|
--prg32-root DIR |
Path to a cloned PRG32 repository. Equivalent to setting PRG32_ROOT. |
--out-dir DIR |
Where to write .prg32 cartridges. Default: dist/<target>. |
--skip-firmware |
Do not rebuild the resident PRG32 firmware. Useful when the resident firmware is already built. |
--upload-qemu |
Stage the assembly cartridge into qemu_flash.bin. |
--upload-hardware |
Upload the assembly cartridge to ESP32-C6 over Wi-Fi. |
--url URL |
Hardware upload URL. Default: http://192.168.4.1. |
Examples:
./tools/build_cartridges.sh qemu --prg32-root ../PRG32
./tools/build_cartridges.sh qemu --skip-firmware --upload-qemu
./tools/build_cartridges.sh esp32c6 --upload-hardware --url http://192.168.4.1Cartridges are the preferred demo path, but a lab may also embed the game in firmware. Copy or reference exactly one source file from this repository in PRG32's main/CMakeLists.txt:
idf_component_register(
SRCS
"main.c"
"/absolute/path/to/you_have_got_pizza/c/game.c"
REQUIRES prg32
INCLUDE_DIRS "."
)The assembly version is better demonstrated as a cartridge because that keeps the ABI boundary explicit.
This project is intentionally compact. It is not a generic game engine. That is a feature for a summit demo and for a classroom: a student can read the C version, then open the assembly version and recognize the same ideas: tables, counters, loops, conditionals, calls, and state variables.
Suggested lab path:
- play the C cartridge;
- change a speed, a color, or a sound;
- inspect the same idea in assembly;
- change one rectangle in the animated sprite;
- rebuild and deploy to QEMU;
- deploy the final cartridge to the ESP32-C6 board.
The reward is immediate: a low-level register-level change feeds hungry students with pizza.