I wanted the dice game you play at a kitchen table with a pencil and a printed sheet. Not the one with an app store category. Two people, one phone, fifteen boxes each, and the slow problem of running out of good places to put a bad roll.
The Problem
The game is old and the rules are not owned by anybody. The name is a different matter. The Scandinavian version is generically called Yatzy, and in Scandinavia that is fine. In English it is one phoneme from a live trademark, and this hub ships an Android build, where an automated complaint costs the complainant nothing and costs me weeks.
So the name changed and nothing else did. It is called Lemon Dice. The five-of-a-kind box is called Five of a Kind, because renaming the game and leaving the old word printed on the score sheet would have been theatre.
I ran that rename as a script and it broke three things: the five-of-a-kind category quietly became the game’s own slug, a test started comparing the new colour against itself, and three assertions still named the old label. All three were caught by the typechecker and the suite. A rename is only mechanical when the word means one thing, and this one meant three.
What It Is
Roll five dice. Reroll up to twice, keeping whichever you like. Then commit them to exactly one of fifteen boxes. When the box you want is already used, you take whatever the rules give you, and sometimes that is zero.
Two or up to four people passing one phone, or one person against bots at Easy or Normal. Nothing here needs a second device or an account.
The Scandinavian scoring is not the American scoring, and the differences are the point. Three of a Kind pays the three matching dice, not all five. Five of a kind is not a full house, and a full house pays what the dice show rather than a flat twenty five.
One rule crossed back over. Small Straight was an exact 1-2-3-4-5 across all five dice, and it is now any four in a row with the fifth die free, which is the American version and the one people actually expect. Large Straight stays an exact 2-3-4-5-6. That single change took the box from one qualifying hand in two hundred and fifty two to sixteen, and the bot’s pricing table had to move with it: a box you now make almost every game is worth its payout, not a windfall over it.
The Dice Are a Tape
Every die is a pure function of the seed, the player, the round, which roll it is, and which of the five slots it sits in. Nothing is consumed. Rerolling three dice does not shift what any other slot would have shown.
That sounds like an implementation detail and it is the whole reason there is a daily. With a stream of random numbers, two people playing the same day diverge the second they keep different dice, and “today’s game” is a shared number with no shared experience under it. With a tape, everyone gets the same dice and the only difference between two players is how they spend them.
The cost, stated plainly: you cannot change what a reroll gives you by changing which dice you keep, only how many you expose to it. No exploit follows, because you commit before you see. It is still an unusual property and it is a trade I made on purpose.
The Bot Was Wrong and the Table Was Why
The good bot works out every reroll exactly. All thirty two ways to hold dice, every outcome of what you throw back, with its true probability. No sampling.
It still played a move that made me stop. Handed a made large straight worth twenty points with one reroll left, it broke the straight to chase a slightly larger Chance.
The search was fine. The table underneath it was not. I had priced each box at what it pays when you make it, so Large Straight was worth twenty. Priced that way, a made large straight is a box performing exactly to expectation, and there is nothing there to protect. Priced at what it is actually worth across a whole game, which is closer to nine because most games never make it, that same straight is a windfall and the bot keeps it.
I rebuilt the objective around surplus: what a box pays now, minus what it was going to be worth anyway. That is the right target, because a final score is the sum of those expectations plus the sum of the surpluses, and the expectations do not move.
The first version of that formula priced the shortfall of the box you take and said nothing about the boxes you leave behind. It read plausibly and it was wrong.
Three Hundred Tests Did Not See the Screen
Two defects survived the entire suite, and both are the same kind.
The bar that confirms your choice sat underneath the score sheet’s pinned column, so it was asking me to commit a score without showing which box it was going into. And on a four player game the fourth player’s column was off the right edge of the sheet, which is a problem when comparing scores is the entire activity.
Neither was reachable from a test, because the test environment has no layout and no paint order. It cannot tell you one thing is on top of another, or that a column fell off a phone. I found both by building the site and opening it at the width of a phone.
The second one I fixed by measuring rather than guessing twice. The category column was a hundred and twenty four pixels against a container of three hundred and twenty four, and the label reading “Upper, 63 for bonus” was what pushed it there. The turn header already reports the same number. The label is now “Upper”.
What I Learned
I have written this lesson down before and I keep needing it. A green suite tells you the things you thought to check are still true. It does not tell you the screen is right.
The counter-practice that did work: every invariant I cared about, I broke on purpose and confirmed the suite caught it. Eight mutations, eight failures. The two that mattered most were a falsy zero, where a scratched box scoring nought would have read as still open and been scored twice, and collapsing the bot’s turn into one instant number instead of a sequence you can watch.
I Gave Up a Colour on Purpose
Every game in the hub gets its own accent, and they are spaced around the colour wheel so two cards never read as the same game. Lemon Dice originally took an indigo nobody else was using, picked as the widest remaining gap, twenty six degrees clear of its nearest neighbour on either side.
Then I put it next to Low Roller, which is the other dice game you play by passing one phone around, and the pair looked like two unrelated products. So Lemon Dice now renders the exact amber Low Roller does.
The cost is real and I would rather write it down than let it sit there unmentioned. That amber is a crowded part of the wheel: four accents now sit within twenty degrees of each other. The old test asserting Lemon Dice kept its distance had to go, and the test that replaced it pins the exact set of neighbours instead, so the next game that lands in that band fails the suite and somebody has to decide whether the cards are still telling themselves apart.
Two smaller things came out of the same pass. The setup screen lost the card it was sitting in, because that card wrapped the entire screen and so drew a border around everything and separated nothing from anything. And the accent turned out to be defined in only two of the five theme blocks, so in kids, ocean and sunset the game had been quietly falling back to the light value while Low Roller stayed on palette. That was a bug the re-skin found rather than caused.
Your name in the first seat now comes from the hub profile instead of reading “Player 1”. Only the first seat. The other people at the table are other people.
Not Included
No online multiplayer. One device or nothing.
No ads, no notifications, no streak, no reason manufactured to bring you back tomorrow. Lifetime stats are three numbers you can look at if you want them.
A win in pass and play is credited to nobody, and earns no coins. Two people sharing a phone means there is no “the player” to hand it to, and the person holding the device is not automatically the one who won.