// Saying who the blind is, before saying "cards". // // A dealer sets the table up out loud — button here, she's the small blind, he's the // big blind — and only then deals. The seat menu offers "Make small blind" for exactly // that, and it was answering by dealing the hand: betting opened, two stacks docked, // the action bar up and waiting on the seat after the big blind. The dealer had said // where the blinds go, and the tool heard "deal". // // The condition it was asking is `onlyBlindsIn`, which means "nothing has happened in // this hand except the blinds" — true of a hand one second old, and equally true of a // table in setup with no cards out and nothing committed. Those two need telling apart, // which is what `blindsArePosted` is for. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Betting, HoldemState, makeTable, chipsOnTable } = require("./load"); describe("before the deal", () => { test("the blinds are not down, however much the table looks ready", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] }); assert.equal(state.bettingOpen, false); // The old condition, on its own, says yes here — this is the whole bug. assert.equal(Betting.onlyBlindsIn(state), true); assert.equal(Betting.blindsArePosted(state), false); }); test("a table straight out of setup, with nobody seated, is the same answer", () => { assert.equal(Betting.blindsArePosted(HoldemState.createDefault()), false); }); test("re-posting would have dealt the hand, which is why it is guarded", () => { // Not a rule, a demonstration: repostBlinds ends in beginHand. Calling it in // setup is the deal, and nothing about naming a blind should reach it. const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] }); Betting.repostBlinds(state); assert.equal(state.bettingOpen, true, "chips off two stacks and the action bar up"); assert.notEqual(state.actingSeatIndex, null); }); test("naming a blind moves a badge and no chips", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] }); const before = chipsOnTable(state); // What the seat menu does: pin the override, repost only if the blinds are down. state.blindOverride = { sbIndex: 3, bbIndex: 0 }; if (Betting.blindsArePosted(state)) Betting.repostBlinds(state); assert.equal(state.bettingOpen, false, "still setup — the dealer hasn't dealt"); assert.equal(state.actingSeatIndex, null, "nobody is being asked to act"); assert.deepEqual(state.seats.map(p => Betting.committed(p)), [0, 0, 0, 0, 0, 0]); assert.equal(chipsOnTable(state), before); // And the badges say what the dealer just said. const blinds = HoldemState.getBlindSeats(state); assert.equal(blinds.sbIndex, 3); assert.equal(blinds.bbIndex, 0); }); test("and the hand that follows posts from where they were aimed", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], smallBlind: 1, bigBlind: 2 }); state.blindOverride = { sbIndex: 3, bbIndex: 0 }; if (Betting.blindsArePosted(state)) Betting.repostBlinds(state); Betting.beginHand(state); assert.equal(Betting.committed(state.seats[3]), 1); assert.equal(Betting.committed(state.seats[0]), 2); }); test("giving the button away is a marker moving too", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3] }); const before = chipsOnTable(state); const willRepost = Betting.blindsArePosted(state); state.dealerSeatIndex = 2; if (willRepost) Betting.repostBlinds(state); assert.equal(willRepost, false); assert.equal(state.bettingOpen, false); assert.equal(chipsOnTable(state), before); }); }); describe("once the hand is dealt", () => { test("the blinds are down, and re-aiming them moves real chips", () => { const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); const before = chipsOnTable(state); Betting.beginHand(state); assert.equal(Betting.blindsArePosted(state), true); assert.equal(Betting.committed(state.seats[1]), 1); state.blindOverride = { sbIndex: 3, bbIndex: 0 }; if (Betting.blindsArePosted(state)) Betting.repostBlinds(state); assert.equal(Betting.committed(state.seats[1]), 0, "the old small blind got their chip back"); assert.equal(Betting.committed(state.seats[3]), 1); assert.equal(Betting.committed(state.seats[0]), 2); assert.equal(chipsOnTable(state), before, "no chips created or lost in the shuffle"); }); test("but not once somebody has answered them", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.call(state, state.actingSeatIndex); assert.equal(Betting.blindsArePosted(state), false, "there is a call to unpick now"); }); test("nor on a later street", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); state.street = "flop"; assert.equal(Betting.blindsArePosted(state), false); }); test("and a misdealt hand goes back to being a table in setup", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.takeHandBack(state); assert.equal(Betting.blindsArePosted(state), false, "nothing is posted until the dealer deals again"); }); test("as does a hand that has been paid out", () => { const state = makeTable({ seatCount: 4, seated: [0, 1, 2, 3], dealerSeatIndex: 0 }); Betting.beginHand(state); Betting.sweepToHandTotals(state); HoldemState.startNextHand(state); assert.equal(Betting.blindsArePosted(state), false); }); });