// When does a bet reopen the action? The most misread rule in poker, and the one a // home game argues about longest: a player shoving in less than a full raise does not // give everybody behind them another go. // // TDA Rule 44 — an all-in wager of less than a full raise does not reopen the betting // to a player who has already acted and is not facing a full raise. const { test, describe } = require("node:test"); const assert = require("node:assert/strict"); const { Betting, makeTable, chipsOnTable } = require("./load"); const actedFlags = state => state.seats .map((p, i) => ({ seat: i, acted: p.hasActed, live: p.inHand && !p.folded })) .filter(s => s.live); describe("a short all-in does not reopen the betting", () => { // Blinds 1/2. Seat 4 raises to 10 (a full raise of 8). Seat 1 calls. // Seat 2 is all-in for 13 — three more, where a full raise needs eight. const shortAllInTable = () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], stack: 100, dealerSeatIndex: 0 }); state.seats[1].stack = 13; Betting.beginHand(state); Betting.raiseTo(state, 3, 10); Betting.call(state, 0); return state; }; test("players who already acted are not asked to act again", () => { const state = shortAllInTable(); assert.equal(state.seats[0].hasActed, true, "seat 1 has called"); assert.equal(state.seats[3].hasActed, true, "seat 4 has raised"); Betting.allIn(state, 1); assert.equal(state.seats[0].hasActed, true, "seat 1 must not be reopened"); assert.equal(state.seats[3].hasActed, true, "seat 4 must not be reopened"); }); test("but they do still owe the difference", () => { const state = shortAllInTable(); Betting.allIn(state, 1); assert.equal(Betting.toCall(state, state.seats[0]), 3, "seat 1 owes the extra 3"); assert.equal(Betting.isStreetComplete(state), false, "so the street isn't over"); }); test("a player who had not acted yet keeps full rights", () => { const state = shortAllInTable(); assert.equal(state.seats[2].hasActed, false, "seat 3 hasn't acted"); Betting.allIn(state, 1); assert.equal(state.seats[2].hasActed, false, "and still hasn't — they may still raise"); }); test("the minimum raise is not shrunk by the short all-in", () => { const state = shortAllInTable(); assert.equal(state.lastRaiseSize, 8); Betting.allIn(state, 1); assert.equal(state.lastRaiseSize, 8, "still the last *full* raise"); assert.equal(Betting.minRaiseTo(state), 21, "13 to call plus a full raise of 8"); }); test("calling the short all-in ends the street", () => { const state = shortAllInTable(); Betting.allIn(state, 1); Betting.call(state, 2); // seat 3, who had full rights, just calls Betting.call(state, 3); Betting.call(state, 0); assert.equal(Betting.isStreetComplete(state), true); }); test("no chips are created or lost by any of it", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], stack: 100, dealerSeatIndex: 0 }); state.seats[1].stack = 13; const start = chipsOnTable(state); Betting.beginHand(state); Betting.raiseTo(state, 3, 10); Betting.call(state, 0); Betting.allIn(state, 1); Betting.call(state, 2); Betting.call(state, 3); Betting.call(state, 0); assert.equal(chipsOnTable(state), start); }); }); describe("a full raise still reopens the betting", () => { test("an all-in that is a full raise or more reopens everyone", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], stack: 100, dealerSeatIndex: 0 }); state.seats[1].stack = 40; Betting.beginHand(state); Betting.raiseTo(state, 3, 10); Betting.call(state, 0); Betting.allIn(state, 1); // to 40: an increase of 30, well over the 8 needed assert.equal(state.seats[0].hasActed, false, "seat 1 gets another go"); assert.equal(state.seats[3].hasActed, false, "and so does seat 4"); assert.equal(state.lastRaiseSize, 30); assert.equal(Betting.minRaiseTo(state), 70); }); test("an ordinary raise reopens everyone", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, 3); Betting.call(state, 0); assert.equal(state.seats[3].hasActed, true); Betting.raiseTo(state, 1, 10); assert.equal(state.seats[3].hasActed, false); assert.equal(state.seats[0].hasActed, false); }); test("a raise exactly the size of the last one counts as full", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], stack: 100, dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.raiseTo(state, 3, 10); // raise of 8 Betting.call(state, 0); Betting.raiseTo(state, 1, 18); // exactly 8 more — a legal minimum raise assert.equal(state.seats[0].hasActed, false, "a minimum raise still reopens"); assert.equal(state.lastRaiseSize, 8); }); test("the first raise of a street is measured against the big blind", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], stack: 100, dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, 3); Betting.call(state, 0); Betting.call(state, 1); Betting.check(state, 2); Betting.closeStreet(state); // to the flop, no bet yet const first = Betting.nextToAct(state, state.dealerSeatIndex); Betting.raiseTo(state, first, 2); // a bet of exactly one big blind const others = state.seats.filter((p, i) => p.inHand && !p.folded && i !== first); assert.ok(others.every(p => p.hasActed === false), "a full-sized opening bet reopens everyone"); }); });