Compare commits

..
Author SHA1 Message Date
jheapsandClaude Sonnet 5 4f72dda6fa Ask before a smaller table deletes the players sitting in it
Typing a smaller number into "Seats at table" silently threw away every seat past
the new count. Nine players down to six removed three of them — names, stacks and
buy-ins gone, no confirmation, no way back.

It also quietly falsified the settle-up. Those players keep their rows in the
session standings, because the hand log remembers who won what, but the seat they
were read from no longer exists — so a player who had just won a pot showed as
having bought in for nothing and holding nothing.

Shrinking now says who it is about to remove and asks, and either way the change
is undoable like the rest of the table's actions. Growing the table, and
shrinking onto chairs nobody is sitting in, still happen without a word.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-01 02:33:03 -06:00
4 changed files with 37 additions and 60 deletions
@@ -1480,12 +1480,6 @@ 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;
@@ -74,16 +74,6 @@ 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,19 +70,7 @@ 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) return index; if (player.inHand && !player.folded && this.stackOf(state, player) >= 0) 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;
}, },
@@ -168,7 +156,7 @@ const Betting = {
}, },
passAction(state, index) { passAction(state, index) {
state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextToAct(state, index); state.actingSeatIndex = this.isStreetComplete(state) ? null : this.nextLiveIndex(state, index);
}, },
// ---- Street and hand boundaries ---- // ---- Street and hand boundaries ----
@@ -196,13 +184,8 @@ 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
// It takes two players with chips to have a bet. Once everyone left is all-in ? this.nextLiveIndex(state, state.dealerSeatIndex)
// — 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;
}, },
@@ -210,24 +193,6 @@ 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) {
@@ -250,7 +215,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.nextToAct(state, bbIndex); state.actingSeatIndex = this.nextLiveIndex(state, bbIndex);
}, },
// Winners get their share put back in front of them. // Winners get their share put back in front of them.
+32 -4
View File
@@ -183,10 +183,38 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
}; };
document.getElementById("seatCountInput").addEventListener("change", e => { // A seat that has anything of a player in it — a name, chips, cash in, or a hand
HoldemState.resizeSeats(state, Number(e.target.value)); // in progress. Shrinking the table throws these away permanently, and it also
hooks.save(state); // orphans them in the session standings, where they keep their winnings but lose
Render.renderAll(); // the stack and buy-in the settle-up is worked out from.
const seatHasSomeoneIn = player =>
!!(player.name && player.name.trim()) ||
player.inHand ||
(Number(player.stack) || 0) > 0 ||
(Number(player.buyIns) || 0) > 0 ||
(Number(player.amountOverride) || 0) > 0;
document.getElementById("seatCountInput").addEventListener("change", async e => {
const requested = Math.max(2, Math.min(24, Math.floor(Number(e.target.value)) || state.seatCount));
const losing = state.seats.slice(requested).filter(seatHasSomeoneIn);
if (losing.length > 0) {
const who = losing
.map((p, i) => HoldemState.displayName(p, requested + state.seats.slice(requested).indexOf(p)))
.join(", ");
const ok = await Dialog.confirm({
title: `Remove ${losing.length} ${losing.length === 1 ? "player" : "players"}?`,
message: `Going down to ${requested} seats takes ${who} off the table along with their chips and buy-ins, and their session totals stop adding up.`,
confirmLabel: "Remove them",
danger: true,
});
if (!ok) {
e.target.value = state.seatCount;
return;
}
}
runUndoable(`Table set to ${requested} seats.`, () => HoldemState.resizeSeats(state, requested));
}); });
document.getElementById("directionSelect").addEventListener("change", e => { document.getElementById("directionSelect").addEventListener("change", e => {