games: a table of bots you have to beat to the last card
UNO, played for chips. You stake once, sit down against one to three bots, and going out first pays the table: 2.2x heads up, 3.6x against a full house. Anybody else going out first takes the stake. The table size is the tier, because it is the only dial UNO has. The bots move inside ApplyMove. A game with opponents is normally where you reach for a socket, and the plan says solo UNO must not — so one request plays your move and every bot turn behind it, and hands back the whole lap as a script the felt plays in order. The RNG is in the state rather than an argument to it: the bots choose and a spent deck reshuffles, so the engine needs randomness mid-game, and there is no generator alive across requests to pass in. The seed rides in the state and each step derives its own. The game still replays exactly as it fell. The zero value of Color is Wild, and that is the whole point of it: a wild played with the colour field missing from the JSON must be refused, not quietly played as a red one. It was red for an hour. The browser never sees a bot's card — not the deck, not a hand, not the face of a card a bot drew, which is most of the deck. Seats cross the wire as a name and a count. The multiples are measured, not guessed: playing the first legal card you hold wins 43/32/27% of the time against these bots, so the tiers price that to lose about 8% a game and leave good play worth roughly the house's edge. PeteFX.flyNode is the throw with the chip taken out of it, so a card can be thrown across the felt the same way. fly() is now that with a chip in it. Not yet driven in a browser, which in this room means not yet finished. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -322,11 +322,65 @@ A multi-session build. This section is the handover; read it before anything els
|
||||
live games on the felt: no overlap with text, no overlap with the shoe, no
|
||||
horizontal overflow, desktop geometry unchanged.
|
||||
|
||||
- **UNO, and it plays for chips.** *(2026-07-14. Built and tested, **not yet driven
|
||||
in a browser** — see "Next" below, because in this room that means it is not
|
||||
finished.)*
|
||||
- **You beat the table, or you don't.** The user's call between three money
|
||||
models: stake once, go out first and take the tier's multiple; anybody else
|
||||
going out first takes the stake. **The table size is the tier**, which is the
|
||||
one dial UNO actually has: Duel (1 bot, 2.2×), Table (2 bots, 2.9×), Full
|
||||
House (3 bots, 3.6×). Rake on winnings only, as everywhere.
|
||||
- **The multiples are measured, not guessed.** A player who just plays the first
|
||||
legal card they hold goes out first 43% / 32% / 27% of the time against the
|
||||
bots, so the tiers are priced to make that lose about 8% a game — which leaves
|
||||
good play (holding the wilds back, dumping the colour you're long in) worth
|
||||
roughly the house's edge. The measurement is a throwaway test, not in the tree;
|
||||
re-run it if the bots or the tiers change, because the two are a pair.
|
||||
- **The bots move inside `ApplyMove`, and that is what keeps solo UNO off a
|
||||
socket.** One request plays your move *and every bot turn it hands off to*,
|
||||
and returns the lot as a script of events the felt plays back in order. §7 said
|
||||
solo first, no sockets; this is what that costs.
|
||||
- **The RNG is in the state, not an argument.** The bots choose and a spent deck
|
||||
reshuffles, so the engine needs randomness *mid-game* — but there is no rng
|
||||
alive across requests to hand it. So the seed rides in the state (which never
|
||||
leaves the server; the deck is in there too) and each step derives its own
|
||||
generator from the seed and the step count. Value in, value out, and a game
|
||||
still replays exactly as it fell.
|
||||
- **The zero value of `Color` is Wild, deliberately.** It was Red for an hour, and
|
||||
a wild played with the `color` field simply missing from the JSON went down as
|
||||
a red one. The zero has to be "no colour named", so the omission is refused
|
||||
instead of quietly meaning something. This is the kind of bug a rules test
|
||||
finds and a browser never would.
|
||||
- **The browser never sees a bot's card.** Not the deck, not a hand, not even the
|
||||
face of a card a bot drew — that last one is most of the deck, and sending it
|
||||
would turn counting cards into reading the network tab. Seats cross the wire as
|
||||
a name and a *count*. There are two walls: the engine only attaches a face to
|
||||
an event the seat may see it in, and `viewUnoEvents` drops it again anyway.
|
||||
- `internal/games/uno` — engine, 22 tests. The census one is the load-bearing one:
|
||||
108 cards, each in exactly one place, asserted after every move of 100 games
|
||||
played out end to end. It is what would catch a reshuffle that leaks cards (the
|
||||
wilds go back into the deck as *wilds*, not as the colour they were played as)
|
||||
or a turn the bots never hand back.
|
||||
- `PeteFX.flyNode` — the throw, with the chip taken out of it. `fly()` is now that
|
||||
with a chip in it, because UNO wanted the same arc with a card in it. Extracted
|
||||
rather than copied, same as `casino-cards.js` and `PeteFX.spot()` before it.
|
||||
- The felt has no corner free for the house rack (bots along the top, piles in the
|
||||
middle, your hand at the bottom), so it takes solitaire's **rail** instead:
|
||||
`data-at="rail"`, off the felt, no collision to check for.
|
||||
|
||||
### Next, in order
|
||||
|
||||
1. Phase 3 (UNO), Phase 4 (hold'em) as below.
|
||||
2. Trivia is played but not deployed. Hangman, solitaire and trivia are all still
|
||||
sitting on main un-deployed — the server runs `StartTriviaBank`, so its bank
|
||||
1. **Drive UNO in a browser.** It is built, the engine is tested and the handlers are
|
||||
tested, but nothing on this table has been *looked at* — and every game before it
|
||||
found bugs in that step that no Go test could see (a white-on-white pill, a rack on
|
||||
top of a multiplier, a spot printing double the stake). Specifically worth watching:
|
||||
the bots' turns play back as a readable script rather than a blur; the wild colour
|
||||
picker; a hand of ten cards at 390px with no sideways overflow; and a reload
|
||||
mid-game bringing the board back. `PETE_DEV_CASINO=:7788 go test ./internal/web
|
||||
-run TestDevCasino -timeout 0`, then Playwright.
|
||||
2. Phase 4 (hold'em) as below.
|
||||
3. Trivia is played but not deployed. Hangman, solitaire, trivia and now UNO are all
|
||||
still sitting on main un-deployed — the server runs `StartTriviaBank`, so its bank
|
||||
fills itself once the binary is out there, but the first player to try a ladder
|
||||
in the first minute after a deploy gets the 503.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user