Grandes Colonies
A board game for eight to twelve players, in the browser, on a 3D board. Everyone joins from their own phone, and nobody loses their seat when the wifi drops.
8 to 12 players in a single game613 tests, including full games played end to endReconnects on its own, seat keptRuns on my own hardware
The problem
Board games that take twelve players exist, but they take an evening to set up and a referee to keep straight. Somebody always ends up counting cards for everyone else instead of playing.
The digital versions solve the counting and lose the table. They are built for four players, matched with strangers, and they assume everyone stays connected for two hours. On a home network with twelve phones, that assumption breaks in the first ten minutes.
What it does
One machine hosts. It shows a screen with a QR code and a game code that can be read out loud, and everyone else opens the link on their phone. No account, no install, no lobby full of strangers.
The board is rendered in 3D and resizes itself to the number of players, so twelve people are not fighting over a board built for four. Missing players can be handed to a bot, and the host can pause the game when somebody goes to get a drink.
- Joining
- A QR code and a code you can say out loud
- Board
- Archipelago or disc, sized to the player count
- Opponents
- Bots with levels and characters
- Recovery
- Every game is journalled and can be resumed
Architecture
- Host screenlocal network only
Game server
- Players' phonesone per guest
Game server
- Botsown processes
Game server
- Game serverraw WebSocket
Rules engine
- Rules engineno network, no clock
Game journal
- Game journalone line per command
Rules engine
The whole game, as pure functions with no network and no clock. It is the part that is tested, and it can play thousands of games against itself in a few seconds.
Game server
A raw WebSocket transport and nothing else. It routes commands into the engine and sends back a view per player, redacted so that nobody ever receives another player's hand.
Host screen
The referee: settings, seats, pause, extra time, and the panel that can adjust a game in progress. It answers on the local network only, and refuses anything arriving from the internet.
Bots
Separate processes that connect through the same WebSocket as everyone else. They see the same views and get the same refusals, so no shortcut into the game state can exist.
Game journal
Every command, written to disk as it happens. A game that crashes is exactly the moment you wish you had it, so it is written whether or not anyone asked.
Built with
Client
- React
- Screens, hands, and the trading interface
- three.js
- The 3D board, dice, and pieces
- Vite
- Build, and the dev server during a session
Server
- Node
- One process serving the page and the game
- ws
- Raw WebSocket transport, no framework above it
- TypeScript
- Shared protocol types, so client and server cannot drift
Testing
- Vitest
- 613 tests over the engine, client and server
- Simulation harness
- Plays whole games, bots against bots, to check balance
Delivery
- GitHub Actions
- Tests, then builds the image
- Container registry
- Where the built image waits to be collected
- Cloudflare Tunnel
- Public access with no inbound port at home
Under the hood
01The framework that was the plan turned out not to exist
The design called for Colyseus. Its 0.16 release does not install: a workspace dependency was published by mistake. Its 0.18 has no JavaScript client. No single version offered both ends. The loss was smaller than it looked, because only the rooms and the transport were ever going to be used: the state synchronisation broadcasts to everyone, which would have meant filtering hands and secret objectives field by field, where one mistake reveals a hand. The transport was replaced by raw WebSocket in an afternoon.
02A reconnection is the normal case, not an exception
Twelve phones on a home network means a one second dropout is routine, and a locked screen is not a departure. The seat token is written to persistent storage rather than held in memory, and the client reconnects by itself. Nobody should have to reload a page to survive a lift, and nobody should lose a game because their phone went to sleep.
03The clock had to learn to wait
Resuming a saved game restarted the turn timer immediately, and every absent player had their turn played for them, cycle after cycle. Six hundred and sixty-nine cycles were eaten before the first human managed to reopen their link. The game now resumes paused. It is also the right human behaviour: after a power cut, the host waits for the room to reconnect, then starts.
04Bots come in through the front door
The automatic opponents are separate processes that connect over the same WebSocket as the guests. They receive the same redacted views and the same refusals, and declaring yourself a bot grants no privilege at all. It serves exactly one purpose: when the host shrinks the table, the bots are the ones who give up their seats. Keeping them outside the server also means no code path exists that could hand them the full game state.
Gallery
Try it
Yes, but it is better with a room full of people.
The link opens the player view, and you can take a seat. What you cannot do from outside is host: the referee screen answers on the local network only, so a game has to be started from the house. The architecture below is what the host side looks like.
What broke
Publishing the game removed its only access control
The referee panel had no authentication and never needed any: it lived on a port that only the guests in the room knew. Putting the game on the internet deleted that protection without replacing it, and for a short window anyone could have granted themselves resources. The rule taken from it: a protection that lives in a dashboard is not a protection. The refusal now sits in the code, with a test holding it in place.
Journalling every game before anyone asked for it
Every command is written to disk as it happens, without being switched on. It was not on any list of features. It is what makes a crashed evening recoverable, and it is also what the balance measurements are computed from. Two uses for something that was written on a hunch.
A twelve player game is still long
The simulation suite plays whole games and measures how many cycles they take. At twelve players it stays well above the target length, and the smaller disc board only shortens it so far. Making it faster is a rules problem, not a code problem, and it has not been solved.
Links
Source
Not public
Not public.