2cc2c66976a2e60fbc7e28518259d42090693c7d
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c77161c3e9 |
Stop the last two things the table did behind the dealer's back
Your bug report was about a category, not one button, so I went looking for the
rest of the guessing rather than picking a new topic. Two left, both about the
button -- and one of them was in the fix I'd just shipped.
The deal preview lied
The prompt added last time promises who will post. With the button parked on an
empty chair it promised the wrong pair: it read the button where it currently sat,
while dealing relocates the button onto a player first and only then works out the
blinds.
three players in seats 2-4, button parked on empty seat 1
preview said: s2 posts $1, s3 posts $2
dealing gave: s3 posts $1, s4 posts $2
Exactly the fault you reported -- the display and the thing it describes computed
from different moments -- inside the change meant to remove it. buttonSeatForDeal
answers where the button will be without moving it, so the preview asks the same
question the deal will answer. And the move is now stated before it happens:
"Button moves to Ben - seat 1 is empty."
Moving the button after the deal moved the blinds silently
Once the blinds are posted, moving the button takes them back and re-posts them,
because otherwise the badges and the money disagree. That's the right behaviour
and it was completely silent. The toast says so now: "Button on Dee - blinds taken
back and re-posted."
It also only does it when there is actually something posted to move. Before the
deal there is nothing committed, so passing the button is just moving a marker --
which is the whole point of the setup step.
Checked and deliberately left alone
Streets still advance one press at a time during an all-in run-out, which is right
-- the dealer is physically dealing the board between them. A player who joins
mid-hand is still marked folded for that hand and plays the next one. The
base-pot-amount shorthand still stands in for "everyone antes the same". Nothing
mutates state during a render.
4 tests (152 total): the preview matches what dealing produces with the button on
an empty chair, asking where the button will be doesn't move it, a button already
on a player is left alone, and an empty table doesn't break the question.
Driven through the UI: the prompt warns about the move and then the deal matches
it exactly; moving the button after the deal announces the re-post; and the blinds
survive being passed around three seats with the chip count fixed at 400. Layout
and contrast audits clean.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
843ef9cd42 |
Wait to be told to deal, instead of guessing
Dealing and betting were one button. "Next Hand" moved the button and posted the
blinds in the same breath, so the moment between hands -- the one where people are
still sitting down, standing up and arguing about whose turn it is -- was spent
with money already committed.
That's where the guessing came from. The blind badges recompute from `inHand`
every render, but the posted chips are frozen from whenever beginHand ran, so the
two drift apart the instant the table changes:
seat a fourth player after Next Hand
badges said sb=s3 bb=s4, money on the table was s1=$2 s3=$1
s4 was shown as the big blind having posted nothing
take out the big blind after Next Hand
badges moved to bb=s1, s4's $2 stayed in the pot, s1 had posted nothing
press Next Hand twice
burned a hand, moved the button twice, chips went 300 -> 297
Now the dealer says when. Next Hand advances the button and clears the table, and
stops. The bar shows who will post -- "Ben posts $1, Cal posts $2" -- and waits on
"Post blinds & deal". Enter does it too, so a dealer can carry a hand from setup
through the streets to showdown without reaching for the trackpad.
Until that press nothing is committed, so setting the table is free: seat someone,
take someone out, pass the button around, name the blinds by hand. The preview
follows every change and the chip count never moves. Settling a pot ends the hand
rather than starting the next one, for the same reason -- people cash out between
hands.
The preview is honest about what it can't do: fewer than two players and there's
no button at all, just what's missing.
A table saved before this existed has no flag, and guessing "not dealt" would
offer to deal over a live hand. Anything already committed, or anyone already on
the clock, means the betting was open when the tab was closed.
11 tests (148 total): the table waits, no money moves until the deal, seating and
unseating and passing the button all cost nothing, what the preview promises is
what gets posted, and a mid-hand save comes back mid-hand.
Driven through the real UI: seat a player and move the button while waiting, watch
the preview follow with the chip count fixed at 400, deal, play it out, settle, and
land back in setup. Triple-tapping Next Hand now costs nothing but button position
-- it used to cost a hand and three chips. Layout and contrast audits clean in both
the new setup state and after dealing.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
60d192a9ec |
Let the banker check the cash box against the chips on the table
Ran out of things I could find by auditing the UI, so I went and read what people
who run home games actually complain about. The recurring one isn't a dealing
mistake, it's a banking one: "the last two guys go to cash out at the end of some
all-night game and there isn't enough cash to cover the chips they have."
Every chip on the table is a promise to pay cash for it later. Nothing in here
checked that the promise could be kept, and there's no way to notice by eye —
it's always a rebuy nobody wrote down, or chips taken without paying, and it
stays silent until the box is empty and two people are still holding chips.
The books now add up, or say why not
HoldemState.bankReconciliation compares the cash taken in against every chip that
could be handed back — stacks, chips in front of players, and the pot waiting to
be won. The sidebar shows both numbers and what they mean:
Balanced — the cash covers every chip on the table.
$160 more in chips than cash taken. Someone's rebuy probably wasn't
recorded — the box will be short at cash-out.
$150 more cash than chips in play. Someone has paid for chips they
haven't been given.
Money in the middle counts as chips in play, so an open pot isn't reported as a
shortfall — otherwise it would cry wolf during almost every hand.
Players who had bought in were missing from the settle-up
sessionStandings was built from the hand log, so anybody who hadn't yet been in a
recorded hand didn't appear at all. Somebody hands over $200, sits down, folds a
few, and the banker's own record doesn't mention them — then they turn up at
cash-out holding chips the list never accounted for. Anyone with money in the
game is in the list now, whether or not they've played a logged hand.
The settle-up number was already being computed and hidden
cashPosition — chips in front of them less the cash they handed over — was
calculated for every player and written to the CSV, but never shown on screen.
It's the number you read while counting out cash at the end of the night, so it's
a column now.
15 tests (127 total): a buy-in with no hands played still appears and reads as
level rather than a loss, empty chairs stay out, a player who cashed out shows
what they took, both directions of imbalance are caught, an open pot is not a
discrepancy, six played-and-settled hands never knock the books out, a proper
rebuy balances while a chip grab doesn't, and float dust isn't an alarm.
Validated with Playwright through real play — seated, blinds posted, pot open,
hand settled, rebuy — balanced at every step, plus the three seeded verdicts and
a fresh table where the panel stays hidden.
Sources: pokerlistings.com/blog/how-to-run-a-home-poker-game,
chipsoffury.com/learn/how-to-handle-poker-buy-ins-with-friends/
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
|
||
|
|
cc6a1902bc |
Stop the tool from throwing a pot away without saying so
Two ways to lose a hand's money by accident, both found by walking the settle flow and checking the chip count afterwards. Next Hand destroyed an unresolved pot silently Starting the next hand clears every seat's contribution, so money still in the middle simply vanished. Measured at a four-handed table with $8 in the pot: the table went from 800 chips to 792, and the toast said "Next hand — button on seat 3. Blinds posted." — not a word about the eight chips that had just left. It now asks, names the amount, and says what will happen to it. Confirming still works and the toast says what went. The undo was always there, but a dealer can't undo something they were never told about. The wizard's "Close" abandoned a payout that looked finished After picking winners and confirming, the wizard shows "Final Payouts — Ann $8" above two buttons: "Start Next Hand" and "Close". Close pays nobody. Under that heading, with a name and an amount beside it, a dealer taps Close believing the pot has been pushed — and walks away with the chips still in the middle and the hand unlogged. The button now says "Cancel — don't pay yet", with a line underneath spelling out that nothing has been paid. The warning measures the right thing The first version counted the pot badge, which was wrong in a way the tests caught before it shipped: the table-wide base pot is a standing ante for dealers who don't track stacks, and it re-applies to the next hand rather than being lost. Quoting it would have put a confirmation in front of those dealers before every single hand — a nag, on the simplest way to run a table, for money that wasn't going anywhere. HoldemState.unresolvedContributions() measures exactly the fields startNextHand clears: banked contributions, chip counts, and whatever is still in front of players on this street. Verified both ways in the browser — a base-pot table showing a $100 pot is not asked at all, a stack-tracking table with $8 live is. 6 tests (115 total): the quoted number equals what actually leaves the table, mid-street chips are included, a settled hand leaves nothing to warn about, five settled hands conserve every chip, an empty table has no phantom pot, and a standing ante is never warned about. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
3d63ba50ee |
Handle the messy table: people joining, leaving, and arguing about blinds
Home games don't run like a cardroom. People walk up mid-hand, storm off mid-hand, and pass the button round three times before anyone deals. Probing each of those found four problems, one of which loses chips. A player taken out mid-hand took their chips with them `computeContribution` opened with `if (!player.inHand) return 0`, so the moment a dealer switched a seat off, everything that seat had already put in vanished from the pot. Measured: four players in for $2 each, one taken out, pot drops from $8 to $6. Their $2 was still sitting on the seat record — it just stopped counting. A dealer following the tool would have paid out $6 of an $8 pot. Chips already pushed into the middle are dead money. They stay in the pot and go to whoever wins, whether or not the player is still in the hand, and it is not theirs to take back. The `inHand` check now guards only the table-wide base-pot default, which does have to stay tied to being dealt in or every empty chair would be treated as having anted. The pot wizard counts a departed seat as folded, so its chips are in the pot but it can never be picked as a winner. Passing the button before the cards come out Moving the button left the posted blinds where they were while everything positional started reading off the new spot, so the money and the blind positions silently disagreed. While nothing but the blinds are in, moving the button now refunds them and re-posts from where it lands — pass it round six times and the chip count is unchanged. Once anybody has acted the blinds are settled and it stops offering. Naming the blinds by hand Blinds were purely positional with no way to overrule them. The seat menu now offers "Make small blind" / "Make big blind" while only the blinds are in, and a badge with a ring marks a blind that was named rather than dealt. Naming one pins the other where it is. It's refused if it would put both blinds on one player or post to somebody who isn't dealt in, and it lasts one hand — carrying it forward would freeze the blinds on two people for the night. The first hand, and a table that empties out A fresh table puts the button on seat 1 whether or not anyone is sitting there, and a table can empty out around it. The button is moved onto a real player before a hand is dealt. Joining mid-hand A newcomer was added straight into the live seats and would be asked to act in a hand they hadn't been paying into. They now sit the hand out and are a full player from the next one. Nothing is covered on a phone any more Swept 13 viewport sizes with elementFromPoint, five points per control, on the table and every overlay. Before: the action bar covered the bottom seats on short screens (62px of felt at 360x640), the toast covered up to six seats at once — Undo button included, so a tap meant for a player hit "Undo" — and in landscape the table came out 765px tall in a 390px window, putting six of nine seats off-screen entirely. - the action bar's real height is published as `--actionBarHeight` and drives both the page's bottom padding and the toast, replacing a hardcoded 9rem that was wrong the moment the raise pad opened - landscape sizes the felt from the height that's left, not from width. The first attempt did nothing: `padding-bottom` is a percentage of the containing block's *width*, so narrowing the wrap left it full height, and a `max-width: 900px` rule later in the file was overriding it anyway. It's set outright now and lives at the end of the sheet. - on short screens the toast floats over the action bar's status readout rather than over the seats. Nowhere is clear of both; covering something you re-read beats covering something you tap. - headers go compact on short screens, which is where the vertical room came from 13/13 sizes clean, 360x640 through 844x390, portrait and landscape. Also fixes a marker that flickered: `classList.toggle(cls, undefined)` toggles rather than clearing, and `manual` is undefined for positional blinds, so the hand-picked ring appeared on alternate renders. 18 new tests (76 total, all passing). Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
c1a484b97a |
Pay down the Hold'em dealer's tech debt and give it a test suite
Every module in the dealer ends with a `module.exports` guard, so they were all written to be testable, and none of them were ever tested. The correctness fixes that went in recently were checked by hand in a browser console that has since been closed. This puts a real safety net under them and clears out the duplication that made those bugs possible in the first place. Tests (58, ~300ms, no dependencies) Node's built-in runner, no package.json and no install step: the modules are plain script-tag files, so `load.js` just requires them and puts them on globalThis under the names they use to find each other in the page. The randomised suite is the part that matters. Every bug found in this tool so far has been shape-dependent — blinds posted to empty chairs, the button skipping players, heads-up acting out of order — and none of them show up on the full six-handed table anyone would write a fixed example for. It deals 1000 hands per property across random seat counts, empty chairs, stack depths and dealer input, checking that chips are never created or destroyed, that the pot breakdown plus uncalled returns equals the money in, that paying out settles to the cent, and that the button and blinds always land on real players. The generator is seeded so a failure reproduces instead of flaking. One implementation each - `Math.round(x * 100) / 100` was written out 16 times across 6 files. It's `Money.round` / `toCents` / `fromCents` now, in the module both pages already load first. - The chip epsilon was declared twice with the same value under two names (`PotCalculator.EPSILON`, `Betting.EPSILON_CHIPS`). Both now point at `Money.EPSILON`. - `Betting.seatAt` and `HoldemState.nextSeatIndex` were the same modular arithmetic written twice. Walking the seats is exactly where the button, the blinds and the action drifted apart from each other before, so there is one of it now. - The undo snapshot/restore dance appeared three times in main.js, each one re-deleting every key on `state` in place. It's `snapshotState` / `restoreState` once, with the reason it can't just reassign written down. A bug the tests found `layers()` and `uncalledReturns()` both decide what money was never contested, separately, and disagreed. `uncalledReturns` measured each player against the biggest live contribution *including their own*, which makes the biggest bet its own yardstick — so the one case it exists for came back empty, and `layers()` announced the overbet as a side pot only the bettor could win. With Ann shoving 200 into Ben's 60, the dealer was told "main pot 4, side pot 3, side pot 116, side pot 140" — four pots, one of them fictional. It now reads "main pot 123, uncalled 140 back to Ann", which is what a dealer says out loud. Layers are derived from contributions with the uncalled money already removed, so the two can't disagree again. A band every payer folded out of is now merged into the pot below rather than dropped, which was quietly shrinking the announced pot in another edge case. Validated with Playwright against a real hand on both surfaces — the table's live pot readout and Quick Split's all-in mode — plus undo, with no console errors. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
e5b02b00e4 | Merge remote-tracking branch 'origin/feature/casino-holdem-button-moves' into feature/casino-holdem-dealer-batch | ||
|
|
18bb23ad6d |
Pass the button to the next player, not the next chair
The button moved one seat per hand regardless of whether anyone was sitting there. At a ten-seat table with four players — an ordinary home game — it spent five hands out of eight on an empty chair. That reads as untidy, but the real problem is fairness. Position and the blinds are meant to come round to each player at the same rate. Rotating past empty chairs breaks that: a player with three empty seats to their right takes the button far less often than one sitting in a cluster, while both pay blinds every time the rotation reaches them. The button now skips to the next seat that's actually dealt in, so with four players it visits those four in turn and each takes it once every four hands. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
89d79a0f7d |
Post the blinds to the players rather than to the chairs
Blinds were assigned by seat offset — always button+1 and button+2 — which is only right when everyone is dealt in and there are three or more of them. Heads-up it had the blinds backwards. The button posted the big blind and acted second before the flop; the rule is the opposite on both counts. That's the end of every home tournament, dealt wrong. Worse, the offsets don't care whether anyone is sitting in those chairs. With two players at a nine-handed table, or with the seats left of the button sitting out, the blinds landed on empty seats and simply weren't posted — no money in the middle, and SB/BB badges pointing at nobody. Blinds now walk the players who are actually dealt in, starting from the button, with heads-up as its own case: the button is the small blind, acts first pre-flop, and the big blind acts first afterwards. The badges come from the same function as the money, so what the table shows and what gets posted can't disagree. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
552ec1a542 |
Add chips to a seat without leaving the table
Somebody rebuying is the most common interruption in a cash game, and the only way to record it was to open the seat editor and retype a stack from memory. Press and hold a seat and there's now "Add chips…": quick amounts off the table's starting stack — half a buy-in, a buy-in, two — with a keypad for the times it isn't one of those. It's undoable like every other seat action, and it records cash handed over as well as chips added, because those answer different questions. The stack moves every hand; what a player has paid in only moves when they buy. The export gains bought in, chips now, and up/down, so settling up at the end of the night is reading a row rather than reconstructing it. Getting that last column right needed care. Chips in front of a player disagree with the hand log for as long as a hand is open — and since blinds post the instant the previous hand settles, that window is almost always. Counting what a player already has in the current pot as still theirs makes the cash position agree with the hand log at any moment, including across a rebuy. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
7bedee2934 |
Track the betting live so the dealer taps instead of types
Until now a dealer kept the money in their head and typed each player's total into a form. This adds the running hand: blinds post themselves, every seat has a stack, and one tap per decision moves the action on. The bar sits at the bottom of the screen where a thumb already is, and shows only what's true right now — Fold, then Check or Call with the amount owed, then Bet or Raise. Calling more than a player has left says "Call all-in" rather than quoting a number they can't cover. Raising offers min, half pot, pot and all-in before it offers a keypad, because a dealer calling "raise to sixty" wants a button. When everyone has answered, the bar becomes Deal Flop, then Turn, River, and finally Showdown, which hands off to the pot wizard that already existed. Winnings go straight back in front of the winner, so the next hand's stacks are already right. The money hooks into what was already there rather than running beside it: a street's chips sweep into each seat's amountOverride, the same field the pot badge, the pot wizard, the session standings and the CSV export all read. So nothing downstream changed, and a dealer who prefers the old typed workflow still has it. Tables saved before this exist without any of these fields, so load() fills them in rather than making someone reset a table mid-session. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
bcfc1f2f52 |
Track session standings so the dealer can settle up at the end of the night
The hand log answered "what happened in hand 12"; it couldn't answer "what does Alice owe". Each resolved pot now records a per-seat ledger (what that player put in, what they took down) alongside the payouts, and a Session Standings panel sums it into one row per player — paid in, won, net, sorted by who's up. The CSV export carries the same table, so one file covers both the hand-by-hand trail and the cash-out numbers. Keyed by seat id rather than name, so renaming or reseating a player doesn't split their totals. Hands recorded before this change have no ledger and are skipped rather than counted wrong. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb |
||
|
|
68e263f325 |
Add a persistent hand history log to the Texas Hold'em dealer tool
Give the dealer a running record of resolved pots — hand number, total pot, who got paid what, and the time — instead of losing that information the moment the pot wizard closes. It's the kind of audit trail a real dealer needs when a chip count gets disputed after the fact. - state.js tracks state.handNumber and state.handHistory (capped at 200 entries) and persists both through the existing localStorage save/load path, with backfill defaults for tables saved before this change. - potWizard.js now hands its final payouts back to the caller instead of discarding them once "Start Next Hand" is clicked. - main.js records a history entry (skipping hands where nobody actually won anything) before advancing to the next hand, and wires up a "Clear history" control that also resets the hand counter. - A new sidebar panel lists history entries newest-first; the header shows a running "Hand #N" badge next to the existing subtitle. Verified via Playwright MCP: seated two players, resolved a pot, and confirmed the header's hand counter incremented, the history panel listed the payout, the entry survived a full page reload (localStorage persistence), and Clear History reset both the log and the counter. No console errors during the flow. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3NiiUDimoKDJ8GjC3KL3x |
||
|
|
3851786b90 |
Add dedicated-dealer mode and redesign the table's visuals
Add a "Dedicated dealer (not a seat)" toggle for tables run by someone outside the game: it hides every seat's dealer button while blinds keep rotating automatically off the same internal position each hand. Also gave the table a real casino look, based on reference felt/rail and seat-pod patterns found via web search (huggingface.co/spaces/Estazz/ PokerGameDesigner, github.com/Tehes/poker): a richer radial felt gradient, a gold piping ring inside a wood-toned rail, a subtle center watermark, a pill-shaped chip-well pot badge, softer seat pucks with a pill-badge total and red/black suit coloring on cards, and casino-chip-styled dealer/blind badges (the dealer chip now dims to a faint disc on other seats and only lights up on hover, decluttering the ring of ten clickable buttons down to one visible button on the actual dealer). Verified via Playwright MCP: toggle hides/reveals dealer chips correctly, SB/BB still track it, no console errors. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_018CmAti1baCjidbJM2HeJfk |
||
|
|
98f95df8fb |
Add Casino demo section with a Texas Hold'em dealer's pot-splitting assistant
New /casino hub page (Texas Hold'em live, other table games marked coming soon) and /casino/texasholdem: a drag-and-drop seating table with auto-rotating blinds, per-seat chip/dollar contribution tracking, and a side-pot resolution wizard that asks who won and who's next until every dollar in the pot (including dead money from folds) is assigned. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_018CmAti1baCjidbJM2HeJfk |