An unofficial fan-made browser version of the board game Monza for 2–6 players, played hot-seat on one screen.
Play it here: https://rvenning.github.io/monza/
- Game design: Jürgen P. K. Grunau — all game rules and mechanics implemented here are his design.
- Original board game: Monza (Jubilee edition), published by HABA (HABA Sales GmbH & Co. KG, Bad Rodach, Germany), illustrated by Maša Zeidan.
- This project is a non-commercial fan tribute and is not affiliated with, endorsed by, or sponsored by HABA. All code, artwork, and text in this repository are original; no material from the published game is included (the official rulebook is not part of this repository, as it is HABA's copyrighted work).
- If you enjoy this game, please support the designer and publisher by buying the real thing — the physical game is wonderful with kids.
Just open index.html in any modern browser — no install, no server needed (double-click it).
Optionally you can serve it locally with any static server:
npx http-server . -p 8321 -c-1
# then open http://localhost:8321/Monza is a progressive web app, so it can live on a phone or tablet home screen with its own icon and run fullscreen, offline included:
- Android / Chrome / Edge: tap the 📲 Install button in the header (or the browser's own "Install app" menu item).
- iPhone / iPad: in Safari, tap Share → Add to Home Screen. The 📲 button in the header shows these steps.
The service worker caches the game the first time it loads, so a race started in the car keeps working with no signal. It is network-first, so a new deploy still arrives as soon as there is a connection.
The icons are generated, not hand-drawn — rerun node tools/make-icons.js
after editing the art in that file.
Basic game
- 2–6 players, each with a colored racing car (the six game colors: red, yellow, green, blue, purple, white)
- Short / medium / long oval tracks, randomly generated each race, with gray tire obstacle spaces
- Roll all six colored dice, then chain moves forward one space at a time — each space entered must match an unused die's color (tap a white-outlined space on the board, or a move button under the dice; Undo supported)
- Forward-only movement with lane changes; driving across occupied spaces is allowed
- Overtaking: end your turn on an occupied space and that car is moved back to the next free space in its lane (or to the start)
- A white die is required to cross the finish line
- When the first car finishes, the round is played out so everyone gets the same number of turns; among the finishers, whoever used the fewest dice in the final round wins — ties share the trophy
Monza Challenge (pro variation)
- One re-roll tile per player: once per game, re-roll any of your dice before placing
- Six acceleration tiles per player (one per color): each works once as a die of its color (spaces marked with a ⚡ bolt)
- Turbo roll: place all six dice in one turn and you move up, then roll two more dice and keep going; occupied-space overtaking is checked only after the turbo roll
Bonus step variation (toggle at setup, works with either mode)
- When a turn ends, the car matching the color of the last die/tile used may move one space forward to any free adjacent space
Family-night conveniences
- The setup is remembered: names, player count, car colors, turn order, track, mode and bonus toggle all come back next time
- Tap a car's color chip to change it; ▲▼ reorder the players (turn order)
- "First to roll" picks who starts (or 🎲 random), and moves on to the next player after every race
- Rematch on the trophy screen starts a new track straight away with the same players; Change setup goes back to the pre-filled setup
- Leaderboard: all-time wins, races, win % and overtakes by player name (placeholder "Player N" names aren't recorded), shared across the family's devices through the same Firestore sync the other family games use; reset button on the setup screen (resets it everywhere)
- Tonight's tally: trophies won in the current run of races with the same line-up (starts over when the line-up changes or after 12 hours)
- An unfinished race is saved at the start of every turn and offered back ("Resume race") after a refresh or closed tab
- 🏳️ Quit race in the header; tap the start lights to skip them
- When nothing on the board matches the roll, the turn passes by itself (unless an unused re-roll tile could still help)
- On a phone held upright the oval stands on its end to fill the screen, and the dice and buttons stick to the bottom; on a phone on its side the controls move to a column beside the board
- A tap anywhere near a legal space counts (the nearest one wins), so small boards stay easy to play
- Keyboard: Space/Enter = roll or end turn, 1 / 2 = drive to the first / second space offered (also picks a bonus-step space), Backspace/Z = undo, Esc = cancel re-roll, R = rematch; Tab reaches every button, and a focused button does what it says
The setup, tonight's tally and an unfinished race are per device, in localStorage (monzaSetup, monzaTonight, monzaSave).
How the leaderboard syncs. Monza vendors gamekit's gk-storage.js and gk-util.js (in lib/, via node ../gamekit/tools/sync-to-game.js .), with prefix and collection monza in the wordvoyage-e5a5c Firebase project. Each driver name is a gamekit profile with a deterministic id (d + hex of the lower-cased name), so every device writes "Rosalie" to the same record. That record's progress is {since, races:{<raceId>:{at,w,f,o}}}: merging is a union of race ids, so two devices recording at once lose nothing, and a reset stamps since so a stale device can't bring old results back. Offline, races are kept locally and sent up on the next online load.
The look (design pass, 2026-09)
Everything is drawn in code — procedural canvas art and CSS, no image files. The circuit sits in a royal park: tiled colour spaces with a quiet shape on each (▲ red, ● yellow, ■ green, ◆ blue, ★ purple, ✚ white — the same shapes are on the dice, for anyone who finds red and green hard to tell apart), grey tyre-stack obstacles, cream-and-slate kerbs, a chequered finish and a start grid, trees, gravel traps and a pit building. The cars are 1950s-style Grand Prix cars with the driver's initial in a roundel on the nose.
Legibility rules the art follows (see the header of js/art.js): cars share the six space colours, so a car is told from its space by its ink outline, roundel and shadow, never by hue; nothing that marks a move uses a space colour — a legal space is a white outline with an arrow, a ⚡ bolt means it spends an acceleration tile, a dashed outline with a ★ is a bonus step; spent dice are numbered in the order they were used, on the board and on the dice; a ↺ badge warns which car you will overtake if you end the turn where you are; and when nothing matches, the spaces in reach are crossed out so a pass reads as the rule working, not a stall. Announcements are drawn inside the infield, where nothing can be.
Effects and sound
- F1-style start light gantry with beeps before every race
- Dice tumble and settle; cars drive along the curve of the track with exhaust puffs; an overtaken car spins back and leaves skid marks
- Banners in the infield (GO! / TURBO! / OVERTAKE! / FINISH! / FINAL ROUND / NO MATCH / BONUS STEP), board shake on turbo, confetti and a fanfare for the winner, whose car is drawn on the podium
- Reduced motion: the system "reduce motion" setting turns off everything that pulses, drifts, spins or sparkles (
Art.setMotion()is the one switch for the canvas and the CSS) - All sounds are synthesized live with the Web Audio API (no audio files); use the 🔊 button in the header to mute (remembered between sessions)
| File | Purpose |
|---|---|
index.html |
Markup only: the setup screen, the race screen, the overlays |
css/style.css |
Every style, built from one token block (colour, space, radius, depth, motion, type) |
js/rules.js |
The colours, track lengths and random track generator (DOM-free) |
js/game.js |
The turn engine: every rule, reaching the screen only through Game.hooks (DOM-free) |
js/audio.js |
The synthesized sounds |
js/art.js |
What things look like: park, track, cars, move marks, banners (procedural canvas) |
js/board.js |
The board: fitting it to the screen, the frame loop, taps, car moves, effects |
js/storage.js |
The synced leaderboard, the saved setup, tonight's tally, the unfinished race |
js/main.js |
The app shell: setup, the turn dock, standings, results, keyboard |
js/pwa.js |
Service-worker registration and the Install button |
manifest.json |
PWA manifest — name, colors and home-screen icons |
lib/ |
Vendored gamekit (gk-util.js and gk-storage.js are loaded; tools/stamp-version.js stamps releases) |
sw.js |
Service worker — caches the game so it plays offline; node lib/tools/stamp-version.js . --bump bumps CACHE and stamps index.html + version.json |
tests/ |
npm test (node --test): 60 whole races replayed against a trace captured from the original single-file game, plus focused rule tests; tests/browser-drive.js is a console helper for checking the board in a browser |
icons/ |
Generated home-screen icons (192, 512, maskable) |
tools/make-icons.js |
Draws those icons; run node tools/make-icons.js |
tools/png.js |
Dependency-free PNG writer used by the icon script (vendored from gamekit) |