From c70cb150dbeb4255dc87230b548161b366661303 Mon Sep 17 00:00:00 2001 From: JoshHeaps Date: Tue, 1 Sep 2026 23:20:13 -0600 Subject: [PATCH] Stop telling a brand-new table its players are broke, and give it a way out MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following on from seating the table: a dealer who names six people and hasn't yet said what a buy-in is gets told Nobody at the table has chips left. Add a buy-in to deal. with no buttons on the bar at all. Both halves are wrong for that table. "Left" says the chips were had and lost, which sends the dealer looking for a rebuy — and a blocked prompt with nothing to press is a dead end, when the fix is one field in the sidebar the message never pointed at. A table where nobody has ever bought in is now told what is actually true, and given the way out: Nobody has bought in yet. Set what everyone starts with, or give each seat their chips. [ Set starting stack ] The button opens Table Setup, scrolls the starting stack into view and selects it, so the next thing typed is the answer. A seat with no stack of its own falls back to the table default, which is why one number is enough to seat everybody's chips at once. A table that genuinely busted out is a different situation and still says so, with no starting-stack button — there the fix is a rebuy, not a default. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb --- .../wwwroot/js/casino/holdem/actionBar.js | 31 +++++++++++- tests/holdem/seatingTheTable.test.js | 47 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/JoshHeaps.Net/wwwroot/js/casino/holdem/actionBar.js b/JoshHeaps.Net/wwwroot/js/casino/holdem/actionBar.js index 17b18a8..caf7f93 100644 --- a/JoshHeaps.Net/wwwroot/js/casino/holdem/actionBar.js +++ b/JoshHeaps.Net/wwwroot/js/casino/holdem/actionBar.js @@ -593,10 +593,19 @@ const ActionBar = { // thing: "only one player is dealt in" is wrong when four are sitting there // and three of them are just out of chips. const withChips = seated - broke.length; + // "Nobody has chips left" is the wrong story for a table that never had + // any. A dealer who has just named six people and not yet said what a + // buy-in is gets told their players are busted, which sends them looking + // for a rebuy button instead of the one field that fixes it. + const nobodyHasEverBoughtIn = this.state.seats.every(p => + !(Number(p.buyIns) > 0) && !(Number(p.stack) > 0)); + if (broke.length > 0 && withChips < 2) { line.textContent = withChips === 1 ? `Only ${listNames(Betting.liveSeats(this.state).map(s => s.index).filter(i => !broke.includes(i)))} has chips — ${listNames(broke)} ${broke.length === 1 ? "needs" : "need"} to buy in.` - : "Nobody at the table has chips left. Add a buy-in to deal."; + : nobodyHasEverBoughtIn + ? "Nobody has bought in yet. Set what everyone starts with, or give each seat their chips." + : "Nobody at the table has chips left. Add a buy-in to deal."; } else { line.textContent = seated === 0 ? "Nobody is dealt in yet. Tap a seat to add a player." @@ -605,6 +614,26 @@ const ActionBar = { line.classList.add("dealPromptBlocked"); wrap.appendChild(line); + // A blocked prompt with nothing to press is a dead end, and this was the + // one a first-time dealer hits: six players named, a sentence saying they + // have no chips, and no button anywhere on the bar. The starting stack is a + // single field in the sidebar and the message never pointed at it. + if (nobodyHasEverBoughtIn && seated > 0) { + const row = document.createElement("div"); + row.className = "actionButtons"; + row.appendChild(this.button("Set starting stack", "actionDeal", () => { + const field = document.getElementById("defaultStackInput"); + if (!field) return; + const panel = field.closest("details"); + if (panel) panel.open = true; + field.scrollIntoView({ block: "center", behavior: "smooth" }); + field.focus(); + field.select(); + })); + wrap.appendChild(row); + return wrap; + } + // Named seats that aren't in the hand. Naming a chair now seats whoever is // in it, so reaching this takes something else — a table where everyone was // sat out, or a save from before that was true — but the way out of it diff --git a/tests/holdem/seatingTheTable.test.js b/tests/holdem/seatingTheTable.test.js index 87ae837..67580d0 100644 --- a/tests/holdem/seatingTheTable.test.js +++ b/tests/holdem/seatingTheTable.test.js @@ -132,3 +132,50 @@ describe("the way back from a table where everybody is sat out", () => { assert.equal(state.seats.filter(p => p.inHand).length, 1, "empty chairs stay empty"); }); }); + +describe("a table where nobody has bought in", () => { + // Different from a table that has busted out, and the difference is the whole + // point: one needs a starting stack named, the other needs a rebuy. Telling a + // dealer their brand-new players have "no chips left" sends them hunting for the + // wrong thing. + const neverBoughtIn = state => state.seats.every(p => + !(Number(p.buyIns) > 0) && !(Number(p.stack) > 0)); + + test("six freshly named players have never bought in", () => { + const state = emptyTable(); + ["Ann", "Ben", "Cal", "Dee", "Eve", "Fay"].forEach((name, i) => { + state.seats[i].name = name; + state.seats[i].inHand = true; + }); + + assert.equal(neverBoughtIn(state), true); + assert.equal(Betting.blindPreview(state), null, "and there is no hand to deal"); + }); + + test("a table that has busted out is not the same thing", () => { + const state = makeTable({ seatCount: 6, seated: [0, 1], stack: 200 }); + state.seats.forEach(p => { p.buyIns = 200; p.stack = 0; }); + + assert.equal(neverBoughtIn(state), false, "they had chips — they lost them"); + }); + + test("one player still holding chips is not a never-bought-in table either", () => { + const state = emptyTable(); + ["Ann", "Ben"].forEach((name, i) => { state.seats[i].name = name; state.seats[i].inHand = true; }); + state.seats[0].stack = 50; + + assert.equal(neverBoughtIn(state), false); + }); + + test("naming a starting stack is enough to deal", () => { + // The one field the message now points at. A seat with no stack of its own + // falls back to the table default, so setting it seats everybody's chips at + // once — which is why it's the right thing to send the dealer to. + const state = emptyTable(); + ["Ann", "Ben", "Cal"].forEach((name, i) => { state.seats[i].name = name; state.seats[i].inHand = true; }); + assert.equal(Betting.blindPreview(state), null); + + state.defaultStack = 200; + assert.ok(Betting.blindPreview(state), "one number and the table can play"); + }); +});