// Taking a hand back. // // The rulings sheet in this tool has told dealers to redeal since the day it was // written — first card to the wrong seat, a seat entitled to a hand dealt out, one of // the first two cards exposed — and the tool had no way to do it. The only control that // ended a live hand was Next Hand, which says out loud that it takes the money off the // table without paying anybody. That is right for an abandoned pot and wrong for a // misdeal, where the blinds and the calls belong to the people who put them in. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Money, Betting, HoldemState, makeTable, chipsOnTable } = require("./load"); const dealt = (extra = {}) => { const state = makeTable({ seatCount: 6, stack: 200, smallBlind: 5, bigBlind: 10, dealerSeatIndex: 0, ...extra }); Betting.beginHand(state); return state; }; describe("the money", () => { test("goes back to the people who put it in", () => { const state = dealt(); const before = state.seats.map(p => Betting.stackOf(state, p) + Betting.committed(p)); Betting.takeHandBack(state); assert.deepEqual(state.seats.map(p => Betting.stackOf(state, p)), before); }); test("including calls and raises, not just the blinds", () => { const state = dealt(); Betting.raiseTo(state, state.actingSeatIndex, 40); Betting.call(state, state.actingSeatIndex); assert.ok(Betting.potSoFar(state) > 50, "there is real money in the middle"); const taken = Betting.takeHandBack(state); assert.equal(taken.total, 95, "blinds and both wagers"); assert.deepEqual(state.seats.map(p => Betting.stackOf(state, p)), [200, 200, 200, 200, 200, 200]); }); test("and across streets, once chips have been swept into hand totals", () => { const state = dealt(); [3, 4, 5, 0, 1, 2].forEach(seat => { if (state.actingSeatIndex !== null) Betting.call(state, state.actingSeatIndex); }); Betting.closeStreet(state); assert.ok(state.seats.some(p => (Number(p.amountOverride) || 0) > 0), "the flop's money is banked"); Betting.takeHandBack(state); assert.deepEqual(state.seats.map(p => Betting.stackOf(state, p)), [200, 200, 200, 200, 200, 200]); assert.deepEqual(state.seats.map(p => p.amountOverride), [null, null, null, null, null, null]); }); test("the table has exactly the chips it started with", () => { const fresh = makeTable({ seatCount: 6, stack: 200, smallBlind: 5, bigBlind: 10 }); const total = chipsOnTable(fresh); const state = dealt(); Betting.raiseTo(state, state.actingSeatIndex, 40); Betting.takeHandBack(state); assert.equal(chipsOnTable(state), total); }); test("nothing is thrown away — the discard pile doesn't move", () => { const state = dealt(); state.discarded = 8; Betting.takeHandBack(state); assert.equal(state.discarded, 8, "a misdeal is not an abandoned pot"); }); test("it says what went back and to whom", () => { const state = dealt(); const taken = Betting.takeHandBack(state); assert.equal(taken.returned.length, 2, "the two blinds"); assert.deepEqual(taken.returned.map(r => r.amount).sort((a, b) => a - b), [5, 10]); assert.ok(taken.returned.every(r => r.name), "named, because the dealer says it out loud"); }); }); describe("the table afterwards", () => { test("is waiting to deal the same hand again", () => { const state = dealt(); Betting.takeHandBack(state); assert.equal(state.bettingOpen, false, "the dealer says when"); assert.equal(state.street, "preflop"); assert.equal(state.actingSeatIndex, null); assert.equal(state.currentBet, 0); }); test("keeps the button where it was — a misdeal is not the next hand", () => { const state = dealt({ dealerSeatIndex: 3 }); const handNumber = state.handNumber; Betting.takeHandBack(state); assert.equal(state.dealerSeatIndex, 3); assert.equal(state.handNumber, handNumber, "no hand has been played"); assert.equal((state.handHistory || []).length, 0, "and none is logged"); }); test("keeps the straddle, which was announced and still stands", () => { const state = makeTable({ seatCount: 6, stack: 200, smallBlind: 5, bigBlind: 10 }); state.straddleCount = 1; Betting.beginHand(state); assert.deepEqual(state.postedStraddles, [3]); Betting.takeHandBack(state); assert.equal(state.straddleCount, 1, "still on for the redeal"); assert.deepEqual(state.postedStraddles, [], "but nothing is out there yet"); Betting.beginHand(state); assert.deepEqual(state.postedStraddles, [3], "and it goes out again"); }); test("clears the cards and the folds, because none of it happened", () => { const state = dealt(); state.seats[3].cards = ["As", "Kd"]; Betting.fold(state, 3); Betting.takeHandBack(state); assert.deepEqual(state.seats[3].cards, ["", ""]); assert.equal(state.seats[3].folded, false); assert.ok(state.seats.every(p => !p.hasActed)); }); test("deals again cleanly, with the same blinds on the same players", () => { const state = dealt(); const first = state.seats.map(p => Betting.committed(p)); Betting.takeHandBack(state); Betting.beginHand(state); assert.deepEqual(state.seats.map(p => Betting.committed(p)), first); }); }); describe("the bank still balances", () => { test("through a misdeal and the redeal after it", () => { const state = dealt(); state.seats.forEach(p => { p.buyIns = 200; }); assert.equal(HoldemState.bankReconciliation(state).balanced, true); Betting.raiseTo(state, state.actingSeatIndex, 60); Betting.takeHandBack(state); assert.equal(HoldemState.bankReconciliation(state).balanced, true); Betting.beginHand(state); const bank = HoldemState.bankReconciliation(state); assert.equal(bank.balanced, true); assert.equal(bank.cashIn, 1200); }); }); describe("taking back a hand nobody had money in", () => { test("is harmless: no blinds, nothing moves", () => { const state = makeTable({ seatCount: 6, stack: 200, smallBlind: 0, bigBlind: 0 }); Betting.beginHand(state); const taken = Betting.takeHandBack(state); assert.equal(taken.total, 0); assert.deepEqual(taken.returned, []); assert.equal(state.bettingOpen, false); }); });