Compare commits

..
Author SHA1 Message Date
jheapsandClaude Sonnet 5 ae591e4212 Stop asking all-in players to act
A player who is all-in is still in the hand and still wins pots, but has nothing
left to decide. The engine didn't know that: it picked the next player to act by
walking to whoever was still in the hand, chips or no chips.

So a dealer with three players all-in pre-flop was asked to check for each of
them on the flop, the turn and the river — nine taps to say nothing — and the
action could land mid-street on someone who'd already pushed their last chip in.

Now the action passes over players who are all-in, and once fewer than two
people have chips left the betting is simply over: the bar runs Deal Flop, Turn,
River, Showdown straight through, and says "All-in — running out the board" so
the absence of a question reads as deliberate rather than as a stuck screen.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-01 01:37:19 -06:00
3 changed files with 70 additions and 19 deletions
+20 -14
View File
@@ -146,7 +146,7 @@ body {
} }
.tableHint { .tableHint {
color: #949494; color: #888;
font-size: 0.8rem; font-size: 0.8rem;
text-align: center; text-align: center;
margin-top: 1rem; margin-top: 1rem;
@@ -357,7 +357,7 @@ body {
} }
.seatEmptyLabel { .seatEmptyLabel {
color: #8a8a8a; color: #6d6d6d;
font-size: 0.6rem; font-size: 0.6rem;
letter-spacing: 0.08em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
@@ -373,7 +373,7 @@ body {
top: 2px; top: 2px;
left: 6px; left: 6px;
font-size: 0.58rem; font-size: 0.58rem;
color: #8a8a8a; color: #777;
} }
.seatPuckName { .seatPuckName {
@@ -578,7 +578,7 @@ body {
.cardSlot.cardSlotEmpty { .cardSlot.cardSlotEmpty {
background: #191919; background: #191919;
color: #8a8a8a; color: #5c5c5c;
border-style: dashed; border-style: dashed;
border-color: #3f3f3f; border-color: #3f3f3f;
box-shadow: none; box-shadow: none;
@@ -694,7 +694,7 @@ body {
} }
.addChipsCurrency { .addChipsCurrency {
color: #8a8a8a; color: #777;
font-size: 1.3rem; font-size: 1.3rem;
} }
@@ -735,7 +735,7 @@ body {
background: none; background: none;
border: none; border: none;
padding: 0; padding: 0;
color: #d8b46a; color: #a9782f;
font-family: inherit; font-family: inherit;
font-size: 0.78rem; font-size: 0.78rem;
text-decoration: underline; text-decoration: underline;
@@ -834,7 +834,7 @@ body {
margin: 0; margin: 0;
padding-top: 0.6rem; padding-top: 0.6rem;
border-top: 1px solid #2c2c2c; border-top: 1px solid #2c2c2c;
color: #8a8a8a; color: #7a7a7a;
font-size: 0.72rem; font-size: 0.72rem;
line-height: 1.45; line-height: 1.45;
} }
@@ -1213,7 +1213,7 @@ body {
.subPanel summary { .subPanel summary {
cursor: pointer; cursor: pointer;
color: #9a9a9a; color: #8f8f8f;
font-size: 0.78rem; font-size: 0.78rem;
} }
@@ -1249,7 +1249,7 @@ body {
.fieldHint { .fieldHint {
font-size: 0.75rem; font-size: 0.75rem;
color: #949494; color: #888;
line-height: 1.4; line-height: 1.4;
margin: 0; margin: 0;
} }
@@ -1306,7 +1306,7 @@ body {
} }
.standingsHeader { .standingsHeader {
color: #8a8a8a; color: #777;
font-size: 0.7rem; font-size: 0.7rem;
letter-spacing: 0.04em; letter-spacing: 0.04em;
text-transform: uppercase; text-transform: uppercase;
@@ -1374,7 +1374,7 @@ body {
.handHistoryTime { .handHistoryTime {
margin-left: auto; margin-left: auto;
color: #8a8a8a; color: #777;
font-size: 0.72rem; font-size: 0.72rem;
} }
@@ -1480,6 +1480,12 @@ body.hasActionBar #holdemSidebar {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.actionRunout {
margin-left: auto;
color: #d8b46a;
font-size: 0.74rem;
}
.actionWho { .actionWho {
margin-left: auto; margin-left: auto;
color: #9c9c9c; color: #9c9c9c;
@@ -1494,7 +1500,7 @@ body.hasActionBar #holdemSidebar {
flex: none; flex: none;
min-width: 3.1rem; min-width: 3.1rem;
text-align: right; text-align: right;
color: #949494; color: #7d7d7d;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
transition: color 0.3s ease; transition: color 0.3s ease;
} }
@@ -1732,7 +1738,7 @@ body.hasActionBar #holdemSidebar {
.seatPuckStack { .seatPuckStack {
font-size: 0.58rem; font-size: 0.58rem;
color: #9a9a9a; color: #8f8f8f;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
margin-top: 0.1rem; margin-top: 0.1rem;
} }
@@ -1836,7 +1842,7 @@ body.hasActionBar #toastHost {
border-radius: 50%; border-radius: 50%;
border: none; border: none;
background: transparent; background: transparent;
color: #949494; color: #888;
font-size: 0.8rem; font-size: 0.8rem;
font-family: inherit; font-family: inherit;
cursor: pointer; cursor: pointer;
@@ -74,6 +74,16 @@ const ActionBar = {
players.textContent = `${remaining} ${remaining === 1 ? "player" : "players"}`; players.textContent = `${remaining} ${remaining === 1 ? "player" : "players"}`;
row.appendChild(players); row.appendChild(players);
// Nobody is being asked to act because nobody can — say so, or the bar just
// looks like it's waiting for something.
if (Betting.isRunningOut(this.state) && !Betting.isHandOver(this.state)) {
const runout = document.createElement("span");
runout.className = "actionRunout";
const stillIn = Betting.liveSeats(this.state).length;
runout.textContent = `All-in — running out the board (${stillIn} players)`;
row.appendChild(runout);
}
const index = this.state.actingSeatIndex; const index = this.state.actingSeatIndex;
if (index !== null && index !== undefined && !Betting.isHandOver(this.state)) { if (index !== null && index !== undefined && !Betting.isHandOver(this.state)) {
const player = this.state.seats[index]; const player = this.state.seats[index];
@@ -70,7 +70,19 @@ const Betting = {
for (let step = 1; step <= count; step++) { for (let step = 1; step <= count; step++) {
const index = this.seatAt(state, fromIndex, step); const index = this.seatAt(state, fromIndex, step);
const player = state.seats[index]; const player = state.seats[index];
if (player.inHand && !player.folded && this.stackOf(state, player) >= 0) return index; if (player.inHand && !player.folded) return index;
}
return null;
},
// Who the dealer asks next. A player who is all-in is still in the hand and still
// wins pots, but has no decision left, so the action passes over them.
nextToAct(state, fromIndex) {
const count = state.seatCount;
for (let step = 1; step <= count; step++) {
const index = this.seatAt(state, fromIndex, step);
const player = state.seats[index];
if (player.inHand && !player.folded && this.stackOf(state, player) > this.EPSILON_CHIPS) return index;
} }
return null; return null;
}, },
@@ -156,7 +168,7 @@ const Betting = {
}, },
passAction(state, index) { passAction(state, index) {
state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextLiveIndex(state, index); state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextToAct(state, index);
}, },
// ---- Street and hand boundaries ---- // ---- Street and hand boundaries ----
@@ -184,8 +196,13 @@ const Betting = {
const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1; const nextStreetIndex = this.STREETS.indexOf(state.street || "preflop") + 1;
state.street = this.STREETS[nextStreetIndex] || "river"; state.street = this.STREETS[nextStreetIndex] || "river";
state.actingSeatIndex = this.liveSeats(state).length > 1
? this.nextLiveIndex(state, state.dealerSeatIndex) // It takes two players with chips to have a bet. Once everyone left is all-in
// — or only one player still has anything behind — the rest of the board is
// just dealt out, and asking someone to check when they have nothing to check
// with only slows the dealer down.
state.actingSeatIndex = this.canStillBet(state)
? this.nextToAct(state, state.dealerSeatIndex)
: null; : null;
}, },
@@ -193,6 +210,24 @@ const Betting = {
return this.liveSeats(state).length <= 1; return this.liveSeats(state).length <= 1;
}, },
// Players still in the hand who have chips left to put in. Fewer than two of them
// means the betting is finished even though the hand isn't.
seatsWithChips(state) {
return this.liveSeats(state).filter(s => this.stackOf(state, s.player) > this.EPSILON_CHIPS);
},
EPSILON_CHIPS: 0.004,
canStillBet(state) {
return this.liveSeats(state).length > 1 && this.seatsWithChips(state).length > 1;
},
// True when the hand is still live but nobody can bet again — the dealer is just
// running the board out.
isRunningOut(state) {
return this.liveSeats(state).length > 1 && !this.canStillBet(state);
},
// Deals a fresh betting round: blinds posted, action to the seat after the big // Deals a fresh betting round: blinds posted, action to the seat after the big
// blind, which is where a dealer expects to be looking when the cards go out. // blind, which is where a dealer expects to be looking when the cards go out.
beginHand(state) { beginHand(state) {
@@ -215,7 +250,7 @@ const Betting = {
state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex]))); state.currentBet = this.round(Math.max(this.committed(state.seats[sbIndex]), this.committed(state.seats[bbIndex])));
state.lastRaiseSize = bb || 0; state.lastRaiseSize = bb || 0;
state.actingSeatIndex = this.nextLiveIndex(state, bbIndex); state.actingSeatIndex = this.nextToAct(state, bbIndex);
}, },
// Winners get their share put back in front of them. // Winners get their share put back in front of them.