// Who the tool asks to turn over, and in what order. // // The board half of the card feature always worked: every street, a sheet asks which // cards came off the deck. The hands half never did. A dealer entered five community // cards, pressed the showdown button, and the pot wizard told them "Chris, Mike and // Alex haven't turned over, so who won is still yours to say" — with nothing in the // sheet to turn anybody over with. The only way in was to close the wizard, tap a seat // puck, find the card slots in the seat editor, and repeat it for each player, with the // pot sitting on the table. // // So the showdown button now asks, the same way the street button does. This file is // the list it asks from. The sheet itself is DOM and is checked in the browser; the // question of who is on the list, and in which order, is state and lives here. // // Three rules hold it together: // // Only when asked for. The setting is off by default and the list is empty when it // is off, so a table that never wanted this never sees a sheet. // // Only what isn't known. A hand already entered is an answer already given, and // asking again offers to overwrite it. // // In the order the cards actually come over — the showdown order the action bar // already announces, then round the table from there. A dealer looking at the seat // the bar just named should not be asked about the one behind them. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Render, Betting, HoldemState, makeTable } = require("./load"); // A hand played to the river with everyone still in, which is the only shape that // has a showdown to ask about. const atShowdown = ({ seats = 4, tracking = true } = {}) => { const state = makeTable({ seatCount: seats, stack: 500, smallBlind: 5, bigBlind: 10 }); Betting.beginHand(state); state.street = "river"; state.board = ["2C", "7D", "9S", "JH", "3S"]; state.trackBoard = tracking; state.seats.forEach(player => { player.hasActed = true; }); return state; }; const queue = state => Render.showdownCardQueue(state); describe("only when the dealer asked to be asked", () => { test("the setting off means nobody is asked anything", () => { assert.deepStrictEqual(queue(atShowdown({ tracking: false })), []); }); test("the setting on asks everyone still in", () => { assert.strictEqual(queue(atShowdown()).length, 4); }); test("a table nobody is sitting at is not a showdown", () => { const state = atShowdown(); state.seats.forEach(player => { player.inHand = false; }); assert.deepStrictEqual(queue(state), []); }); }); describe("a hand won without a showdown", () => { // Everyone else folded, so there is one hand left and it never comes over. The // bar says "Award pot to Chris" here, not "Showdown", and asking Chris to turn // over a hand nobody paid to see is asking for a card the tool will never use. test("one player left is asked for nothing", () => { const state = atShowdown(); [1, 2, 3].forEach(i => { state.seats[i].folded = true; }); assert.deepStrictEqual(queue(state), []); }); test("two left is a showdown, however it was arrived at", () => { const state = atShowdown(); [2, 3].forEach(i => { state.seats[i].folded = true; }); assert.strictEqual(queue(state).length, 2); }); // Everybody all-in on the flop is still a showdown: the cards go on their backs, // the board runs out, and the hands are read exactly as they would have been. test("all-in with the betting finished is still asked about", () => { const state = atShowdown(); state.seats.forEach(player => { player.stack = 0; }); assert.ok(!Betting.canStillBet(state)); assert.strictEqual(queue(state).length, 4); }); }); describe("only the hands it doesn't already have", () => { test("a hand already entered is not asked for again", () => { const state = atShowdown(); state.seats[1].cards = ["AS", "AH"]; assert.ok(!queue(state).includes(1)); }); test("half a hand entered is still asked for", () => { const state = atShowdown(); state.seats[1].cards = ["AS", ""]; assert.ok(queue(state).includes(1)); }); test("every hand entered means the sheet never opens", () => { const state = atShowdown(); const hands = [["AS", "AH"], ["KS", "KH"], ["QS", "QH"], ["9H", "9D"]]; state.seats.forEach((player, i) => { player.cards = hands[i]; }); assert.deepStrictEqual(queue(state), []); }); }); describe("in the order the cards come over", () => { // The bar already says "Mike shows first — last to bet or raise" out loud. The // sheet asking about somebody else first would be the tool contradicting itself // in the same breath. test("it starts with whoever the bar says shows first", () => { const state = atShowdown(); state.streetAggressorIndex = 2; assert.strictEqual(Betting.showdownOrder(state).index, 2); assert.strictEqual(queue(state)[0], 2); }); test("and then goes round the table from there", () => { const state = atShowdown(); state.streetAggressorIndex = 2; assert.deepStrictEqual(queue(state), [2, 3, 0, 1]); }); test("nobody checked the river, so it starts left of the button", () => { const state = atShowdown(); state.streetAggressorIndex = null; state.handAggressorIndex = null; const first = Betting.showdownOrder(state); assert.strictEqual(first.reason, "leftOfButton"); assert.strictEqual(queue(state)[0], first.index); }); test("a player already entered drops out of the order without moving the rest", () => { const state = atShowdown(); state.streetAggressorIndex = 2; state.seats[3].cards = ["KS", "KH"]; assert.deepStrictEqual(queue(state), [2, 0, 1]); }); test("everyone still in is asked exactly once", () => { const state = atShowdown({ seats: 9 }); const asked = queue(state); assert.strictEqual(new Set(asked).size, asked.length); assert.strictEqual(asked.length, Betting.liveSeats(state).length); }); });