// Somebody leaving with their chips. // // The most ordinary thing that happens at a home game after midnight, and the tool had // nothing for it. Freeing the chair meant wiping the seat by hand, and then the bank // check — cash taken against chips on the table — declared the departing player's whole // stack missing for the rest of the night. Wiping their buy-in too silenced it, at the // price of losing their night from the standings and the file the dealer hands over. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Money, HoldemState, Betting, makeTable } = require("./load"); const table = () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], stack: 200 }); ["Ann", "Ben", "Cal", "Dee"].forEach((name, i) => { state.seats[i].name = name; state.seats[i].buyIns = 200; }); return state; }; describe("counting them out", () => { test("the preview is what the dealer's hands follow", () => { const state = table(); state.seats[0].stack = 350; const preview = HoldemState.cashOutPreview(state, 0); assert.equal(preview.name, "Ann"); assert.equal(preview.chips, 350, "what they are holding"); assert.equal(preview.boughtIn, 200); assert.equal(preview.net, 150, "up for the night"); assert.match(preview.breakdown, /\d+×\$\d+/, "and the chips that make it"); }); test("a player who is down reads as down, not as a smaller stack", () => { const state = table(); state.seats[1].stack = 60; assert.equal(HoldemState.cashOutPreview(state, 1).net, -140); }); test("a seat that never set a stack is holding the table's starting stack", () => { const state = table(); state.defaultStack = 200; state.seats[2].stack = null; assert.equal(HoldemState.cashOutPreview(state, 2).chips, 200); }); }); describe("what it refuses", () => { test("paying somebody with money still in the middle", () => { const state = table(); Betting.beginHand(state); const bb = state.seats.findIndex(p => (Number(p.committed) || 0) > 0); const refusal = HoldemState.canCashOut(state, bb); assert.equal(refusal.ok, false); assert.match(refusal.reason, /in the pot/); // And the refusal is the whole story: nothing moved. assert.equal(HoldemState.cashOut(state, bb).ok, false); assert.equal((state.cashedOut || []).length, 0); assert.ok(state.seats[bb].name, "the seat is untouched"); }); test("an empty chair, which has nobody to pay", () => { const state = table(); const refusal = HoldemState.canCashOut(state, 99); assert.equal(refusal.ok, false); }); }); describe("the chair afterwards", () => { test("is empty and ready for the next player", () => { const state = table(); state.seats[0].stack = 350; HoldemState.cashOut(state, 0); const seat = state.seats[0]; assert.equal(seat.name, ""); assert.equal(seat.stack, null); assert.equal(seat.buyIns, 0); assert.equal(seat.inHand, false); }); test("belongs to somebody new, so two people's nights can't merge", () => { const state = table(); const wasId = state.seats[0].id; HoldemState.cashOut(state, 0); assert.notEqual(state.seats[0].id, wasId); }); test("and isn't owed the blind the person before them was posting", () => { const state = table(); state.postingIn = [state.seats[0].id, state.seats[1].id]; HoldemState.cashOut(state, 0); assert.deepEqual(state.postingIn, [state.seats[1].id]); }); }); describe("the bank check", () => { test("balanced before, balanced after — the chips went somewhere known", () => { const state = table(); state.seats[0].stack = 350; state.seats[1].stack = 50; assert.equal(HoldemState.bankReconciliation(state).balanced, true); HoldemState.cashOut(state, 0); const bank = HoldemState.bankReconciliation(state); assert.equal(bank.paidOut, 350); assert.equal(bank.cashIn, 800, "their buy-in is still cash the box took"); assert.equal(bank.chipsInPlay, 450); assert.equal(bank.balanced, true); }); test("this is the fault it fixes: wiping the seat by hand loses $350", () => { const state = table(); state.seats[0].stack = 350; state.seats[1].stack = 50; // so the table starts out balanced // What a dealer had to do before: empty the chair themselves. state.seats[0].name = ""; state.seats[0].stack = null; state.seats[0].inHand = false; const bank = HoldemState.bankReconciliation(state); assert.equal(bank.balanced, false); assert.equal(bank.difference, -350, "the tool called it missing money"); }); test("chips that really are missing still show as missing", () => { // Counting a cash-out must not become a way of swallowing a genuine shortfall. const state = table(); state.seats[0].stack = 350; state.seats[1].stack = 50; // balanced to start with HoldemState.cashOut(state, 0); state.seats[2].stack = 150; // and now fifty chips have gone astray const bank = HoldemState.bankReconciliation(state); assert.equal(bank.balanced, false); assert.equal(bank.difference, -50); }); test("a rake and a cash-out are counted apart", () => { const state = table(); state.seats[0].stack = 350; state.rakeTaken = 12; state.seats[1].stack = 38; // the rake came out of Ben's stack HoldemState.cashOut(state, 0); const bank = HoldemState.bankReconciliation(state); assert.equal(bank.raked, 12); assert.equal(bank.paidOut, 350); assert.equal(bank.balanced, true); }); }); describe("the record of their night", () => { test("they stay in the standings with what they bought and what they left with", () => { const state = table(); state.seats[0].stack = 350; HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 150 }]); HoldemState.cashOut(state, 0); const ann = HoldemState.sessionStandings(state).find(row => row.name === "Ann"); assert.ok(ann, "still on the list"); assert.equal(ann.boughtIn, 200); assert.equal(ann.chipsInFront, 350, "what the box handed over"); assert.equal(ann.cashPosition, 150); assert.equal(ann.cashedOut, true, "and marked, so nobody pays them twice"); }); test("somebody still sitting there is not marked", () => { const state = table(); HoldemState.cashOut(state, 0); const ben = HoldemState.sessionStandings(state).find(row => row.name === "Ben"); assert.equal(ben.cashedOut, false); }); test("two people leaving are both kept", () => { const state = table(); state.seats[0].stack = 350; state.seats[1].stack = 50; HoldemState.cashOut(state, 0); HoldemState.cashOut(state, 1); assert.equal(state.cashedOut.length, 2); assert.equal(HoldemState.bankReconciliation(state).paidOut, 400); assert.equal(HoldemState.bankReconciliation(state).balanced, true); }); test("a table saved before any of this existed has nobody cashed out", () => { const parsed = JSON.parse(JSON.stringify(table())); delete parsed.cashedOut; const migrated = HoldemState.migrate(parsed); assert.deepEqual(migrated.cashedOut, []); assert.equal(HoldemState.bankReconciliation(migrated).paidOut, 0); }); test("nonsense in a saved record is cleaned up rather than trusted", () => { const parsed = JSON.parse(JSON.stringify(table())); parsed.cashedOut = [ { id: "a", name: "Ann", buyIns: -5, chips: "350", at: "nope" }, { name: "no id" }, null, ]; const migrated = HoldemState.migrate(parsed); assert.equal(migrated.cashedOut.length, 1); assert.equal(migrated.cashedOut[0].buyIns, 0); assert.equal(migrated.cashedOut[0].chips, 350); assert.equal(migrated.cashedOut[0].at, 0); }); }); describe("the table carries on", () => { test("the next hand deals round the empty chair", () => { const state = table(); state.seats[0].stack = 350; HoldemState.cashOut(state, 0); HoldemState.startNextHand(state); Betting.beginHand(state); assert.equal(state.seats[0].inHand, false, "nobody is in the empty chair"); assert.equal(Betting.liveSeats(state).length, 3); assert.ok(state.actingSeatIndex !== null, "somebody is on the clock"); }); test("and the money still adds up once it has", () => { const state = table(); state.seats[0].stack = 350; state.seats[1].stack = 50; HoldemState.cashOut(state, 0); HoldemState.startNextHand(state); Betting.beginHand(state); const bank = HoldemState.bankReconciliation(state); assert.equal(Money.round(bank.chipsInPlay + bank.paidOut), bank.cashIn); assert.equal(bank.balanced, true); }); });