The model had to ask
A web game of Caribbean dominoes, built in about two weeks with Claude Code. The code came mostly from the model. The rules came from the people who actually play, and the model was told, in writing, not to make any up. It’s live, playable, and still a work in progress.
dominoes-zeta.vercel.app- Role
- All of it
- Built with
- Claude Code
- Stack
- TypeScript, Vite, Vitest, Colyseus
- Status
- Work in progress
Why dominoes
I lived in the Dominican Republic for two years, and in that time I played a lot of dominoes with my father-in-law. Dominoes there isn’t a board game you get out on holidays. It’s just what the table is for.
I also wanted a real project to push Claude Code on. Not a to-do app. Something with enough rules, edge cases, and arguments in it that a model left alone would confidently get things wrong, so I could find out what it takes to keep it from doing that.
Dominoes is perfect for that, because there is no single game. Dominican tables play to 200 in teams and pay bonuses for a capicúa. Puerto Rican tables call it capicú, and some play to 500. Cuban tables use the double-nine set and ten tiles each. Every family has a house rule or two that it will defend as the official one. A model trained on the internet has read all of those, mixed together, and will hand you an average of them with a straight face.
The instruction
If a rule is unclear, ask me. Do not invent rules.
The rules lived in a file, not in the model
The first commit, before any game code, had a document in it: docs/RULES.md, the full rules written out in plain language, one section per part of the game, with every variant named. The project guide the agent reads first says that file is the source of truth, and says what to do when it runs out.
AGENTS.mdthe guide the agent reads before it codes
- Source of truth
- docs/RULES.md
- Unclear rule
- Ask me. Do not invent rules.
- Every rule
- Needs a test
- Randomness
- Seeded. No Math.random() in the engine.
- Hidden tiles
- Bots and screens only see one player’s view
That one line did most of the work. When the agent hit something the spec didn’t cover, it stopped and asked, and the answer went into the file, marked user decision, usually with the month it was decided. RULES.md has seventeen of those marks now. They’re the most useful thing in it, because they’re exactly the places where the sources disagree.
Two of them came from me and my father-in-law.
- Doubles never count as a capicú. Going out with a tile that fits either open end earns the bonus. A 5-5 on ends of 5 and 5 technically fits both. At our table it’s not a capicú, in any style, and now it isn’t in the game either.
- A chuchazo never stacks with a capicú. Going out with the double blank is its own bonus. Going out with it on two blank ends could be read as both. It counts once, as a chuchazo. Some Puerto Rican tables don’t count that case at all, which is the whole point: somebody had to decide, and it shouldn’t have been the model.

The spec gets corrected too, in the open. The Puerto Rican preset first played clockwise. A rules check later found no source for that, every source said counterclockwise, so I changed it, and the file says what changed, when, and why. Clockwise is still there as a house rule for anyone whose family plays it that way.
The model was very good at writing the game. It was never the authority on how the game is played.
Where no rulebook covers a case at all, like a tied tranque when bonuses have pushed both teams past the target, the file says that too, and says what we picked. That’s the difference between a spec and a prompt. A prompt is forgotten at the end of the session. The spec is still there for the next one, and for the tests.
What’s under the table
The engine is pure: no screen, no network, no clock. You give it a game and a move, it gives you back the next game. Randomness comes from a seed, so the same seed deals the same tiles every time, which means any game, including the strange one a tester reports, can be replayed exactly.
Every style is just settings on that engine. Dominican, Dominican one-on-one, Puerto Rican 200, Puerto Rican 500, and Cuban double-nine are presets, and Mi mesa, “my table,” lets you start from any of them and change the rules your family argues about. Nothing about a style is written into the game itself.
The bots come in three levels. Fácil plays its heaviest tile. Normal keeps track of what has been played and who has passed on what, and wins about 85% of games against Fácil. Difícil guesses where the hidden tiles are and plays the hand out many times before it moves, and wins about 73% of team games against Normal. None of them can cheat, because none of them can see anything a person in that seat couldn’t: their own tiles, how many everyone else holds, the table, the score.
589
automated tests, across 61 test files
5
rule presets, plus Mi mesa for house rules
3
bot levels, all limited to one player’s view
186
commits, 177 of them co-written with Claude
The spec says every rule needs a test, and that’s why there are 589 of them. GitHub runs them on every change, and a change doesn’t go into the live game until they pass.
A table you can hold in one hand
I wanted it to look like a real table, not a casino: a wooden top with felt, tiles that sit on it like objects, the score on a strip of paper. You can look straight down at it or tilt it into 3D.

The line of tiles is the hard part. It has to snake around the corners of whatever screen it’s on without ever leaving the table, and it has to do that on a phone held upright, which is how most people will play it. Every word on screen comes in Spanish and English, switched from the start screen.

A game is saved as you play, so a refresh doesn’t lose it. It installs to the home screen and plays offline against the bots. Sounds are off until you turn them on.

Proving nobody cheated
When I read through what players complain about in other domino apps, one of the complaints was that the shuffle is rigged. You can’t argue someone out of that. You can let them check.
So before the first move, the game shows a short code, the fingerprint of the shuffle. It’s made from the seed with a one-way function, so it gives nothing away during the game. When the game ends, the seed is revealed and a Comprobar button checks two things: that the seed really produces the code you were shown at the start, and that it really deals the tiles you got. The screen explaining it, Juego limpio, says this in plain words, because the people most suspicious of the shuffle are not the people who want to read about hashes.
Online play runs through a Colyseus server hosted on Render. The server holds the only real copy of the game, and each phone is sent only its own view of it, so a curious cousin with the browser’s developer tools open sees their own tiles and nothing else. You open a table, share the link, and friends join from their phones with no account. Empty seats get bots. If someone drops, a bot plays for them and the seat waits a minute for them to come back.
What I’d do next
The roadmap is written the same way as the rules, from what players of other domino apps actually complain about.
- Friend tables that finish, every time: your own seat back after a drop, and “play again with the same people” that remembers who they were.
- The fingerprint at online tables, so Juego limpio means the same thing everywhere.
- A partner bot that plays like a partner: doesn’t block you, and holds back a double so you can go out.
- Easy for abuela: arrive from a WhatsApp link, in Spanish, with big tiles, no account, and no timers.
- Android and iOS apps, wrapped from the same web game.
The roadmap also has a short list of things it will never do: real-money prizes, betting, loot boxes, ads that grow over time, or notifications that guilt you into opening it. More games are on the list after that, on the same engine, and each one starts the same way: its rules written into the spec before any code.