Files
JoshHeaps.Net/tests/holdem/startingStackMoney.test.js
T
jheapsandClaude Opus 5 1689ecb4f0 Count the chips the table is actually playing with
"Starting stack" is how a night opens. The setup panel offers it and says what it
means — "seats without their own stack start here" — and everything a dealer
looks at reads it: the puck, the action bar, what a player is allowed to bet.

The money side didn't. The bank check and the standings read `seat.stack`
directly, which is null for exactly those seats. Measured on a three-handed table
with a $200 starting stack and nobody's own stack typed in: the felt played for
$600, the bank check reported no chips in play and pronounced itself balanced,
and the standings — the list a dealer settles the night from — listed nobody at
all. Three players, six hundred dollars, and the tool's own accounting said the
table was empty.

Both now ask the same question the felt asks. The check reports $600 in play
against nothing taken, which is the truth: those chips have not been paid for.
Empty chairs are excluded, or the starting stack would make every one of them
look like somebody holding two hundred dollars — a ten-seat table would have
opened with $2,000 of imaginary chips.

Being told is only half of it. The verdict names the seats rather than saying
"somebody's rebuy probably wasn't recorded", because the dealer's next move is to
go to a particular chair and the tool knows which. And the seats holding the
table's starting stack — the dealer's own instruction, said once in the setup
panel — can be written down in one tap instead of six trips through Add chips.
Only those: what somebody's forgotten rebuy was is a question only the dealer can
answer, and guessing it would be the tool inventing money, which is the fault
this round is about.

14 new tests, with the stack source and the empty-chair test broken on purpose to
confirm they fail. Walked through in the browser from a cleared table: set the
starting stack, name three seats, and the panel goes from "balanced, no chips" to
"$600 more in chips than cash taken. Ann, Ben and Cal are holding chips with no
buy-in written down" — then to balanced, with the buy-ins recorded and undoable.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
2026-09-02 08:52:56 -06:00

167 lines
7.6 KiB
JavaScript

// The chips the felt plays with are the chips the box is checked against.
//
// "Starting stack" is offered in the setup panel as the ordinary way to open a table —
// "seats without their own stack start here" — and everything a dealer looks at reads
// it: the puck, the action bar, what a player is allowed to bet. The money side did
// not. It read `seat.stack` directly, which is null for exactly those seats, so a
// three-handed table playing for $600 reported no chips in play, a balanced bank and a
// standings list with nobody on it.
const test = require("node:test");
const { describe } = require("node:test");
const assert = require("node:assert");
const { Money, HoldemState, Betting } = require("./load");
const openedWithStartingStack = (stack = 200, names = ["Ann", "Ben", "Cal"]) => {
const state = HoldemState.createDefault();
HoldemState.resizeSeats(state, 6);
state.defaultStack = stack;
names.forEach((name, i) => {
state.seats[i].name = name;
state.seats[i].inHand = true;
});
return state;
};
describe("what the table is playing for", () => {
test("is what the bank counts", () => {
const state = openedWithStartingStack();
// What the felt says each of them has.
assert.deepEqual(state.seats.slice(0, 3).map(p => Betting.stackOf(state, p)), [200, 200, 200]);
assert.equal(HoldemState.bankReconciliation(state).chipsInPlay, 600);
});
test("and the box being empty is a problem it now reports", () => {
const bank = HoldemState.bankReconciliation(openedWithStartingStack());
assert.equal(bank.cashIn, 0);
assert.equal(bank.difference, 600);
assert.equal(bank.balanced, false, "six hundred dollars of chips nobody paid for");
});
test("empty chairs are not people holding two hundred dollars", () => {
// The starting stack stands in for a seat that hasn't been given one, and every
// empty chair is such a seat. Counting them would invent $600 of chips at an
// empty table.
const state = openedWithStartingStack();
assert.equal(HoldemState.bankReconciliation(state).chipsInPlay, 600, "three players, not six chairs");
const empty = HoldemState.createDefault();
empty.defaultStack = 200;
assert.equal(HoldemState.bankReconciliation(empty).chipsInPlay, 0);
assert.equal(HoldemState.bankReconciliation(empty).balanced, true);
});
test("once the cash is written down it balances", () => {
const state = openedWithStartingStack();
state.seats.slice(0, 3).forEach(p => { p.buyIns = 200; });
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
});
test("a table where everyone has their own stack is unchanged", () => {
const state = HoldemState.createDefault();
HoldemState.resizeSeats(state, 4);
["Ann", "Ben"].forEach((name, i) => {
state.seats[i].name = name;
state.seats[i].stack = 150;
state.seats[i].buyIns = 150;
});
const bank = HoldemState.bankReconciliation(state);
assert.equal(bank.chipsInPlay, 300);
assert.equal(bank.balanced, true);
});
});
describe("who is on the settle-up list", () => {
test("everyone holding chips, including the ones holding the table's own stack", () => {
const rows = HoldemState.sessionStandings(openedWithStartingStack());
assert.deepEqual(rows.map(r => r.name), ["Ann", "Ben", "Cal"]);
assert.deepEqual(rows.map(r => r.chipsInFront), [200, 200, 200]);
});
test("with nothing bought in yet, which is the thing to notice", () => {
const rows = HoldemState.sessionStandings(openedWithStartingStack());
assert.deepEqual(rows.map(r => r.boughtIn), [0, 0, 0]);
assert.deepEqual(rows.map(r => r.cashPosition), [200, 200, 200]);
});
});
describe("naming the seats the cash is missing for", () => {
test("says who, and which kind of missing it is", () => {
const state = openedWithStartingStack();
state.seats[2].stack = 320; // Cal typed his own: an unrecorded rebuy
const owing = HoldemState.seatsOwingCash(state);
assert.deepEqual(owing.map(row => row.name), ["Ann", "Ben", "Cal"]);
assert.deepEqual(owing.map(row => row.fromStartingStack), [true, true, false]);
assert.deepEqual(owing.map(row => row.chips), [200, 200, 320]);
});
test("a seat whose buy-in is written down isn't on the list", () => {
const state = openedWithStartingStack();
state.seats[0].buyIns = 200;
assert.deepEqual(HoldemState.seatsOwingCash(state).map(row => row.name), ["Ben", "Cal"]);
});
test("and a table where nothing is owed has nobody on it", () => {
const state = openedWithStartingStack();
state.seats.slice(0, 3).forEach(p => { p.buyIns = 200; });
assert.deepEqual(HoldemState.seatsOwingCash(state), []);
});
});
describe("writing the starting stacks down", () => {
test("records exactly what those seats are holding", () => {
const state = openedWithStartingStack();
const done = HoldemState.recordStartingBuyIns(state);
assert.equal(done.seats, 3);
assert.equal(done.total, 600);
assert.deepEqual(state.seats.slice(0, 3).map(p => p.buyIns), [200, 200, 200]);
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
});
test("and leaves somebody else's forgotten rebuy alone", () => {
// Only the dealer knows what that was. Guessing would be the tool inventing
// money, which is the fault this whole round is about.
const state = openedWithStartingStack();
state.seats[2].stack = 320;
HoldemState.recordStartingBuyIns(state);
assert.deepEqual(state.seats.slice(0, 3).map(p => p.buyIns), [200, 200, 0]);
const bank = HoldemState.bankReconciliation(state);
assert.equal(bank.balanced, false);
assert.equal(bank.difference, 320, "still exactly Cal's chips, and still reported");
});
test("does nothing on a table that doesn't need it", () => {
const state = openedWithStartingStack();
state.seats.slice(0, 3).forEach(p => { p.buyIns = 200; });
const done = HoldemState.recordStartingBuyIns(state);
assert.equal(done.seats, 0);
assert.equal(done.total, 0);
assert.deepEqual(state.seats.slice(0, 3).map(p => p.buyIns), [200, 200, 200]);
});
});
describe("through a hand", () => {
test("the money adds up from the deal to the payout", () => {
const state = openedWithStartingStack(200, ["Ann", "Ben", "Cal", "Dee"]);
HoldemState.recordStartingBuyIns(state);
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
Betting.beginHand(state);
assert.equal(HoldemState.bankReconciliation(state).balanced, true, "blinds are still on the table");
[3, 0, 1].forEach(() => { if (state.actingSeatIndex !== null) Betting.call(state, state.actingSeatIndex); });
assert.equal(HoldemState.bankReconciliation(state).balanced, true);
const pot = Betting.potSoFar(state);
Betting.sweepToHandTotals(state);
Betting.creditWinnings(state, [{ id: state.seats[0].id, label: "Ann", amount: pot }]);
HoldemState.startNextHand(state);
const bank = HoldemState.bankReconciliation(state);
assert.equal(bank.balanced, true);
assert.equal(bank.chipsInPlay, 800, "four buy-ins of two hundred, wherever they have moved to");
assert.ok(state.seats[0].stack > 200, "and the winner is holding more than they sat down with");
});
});