Add the one-stack-rule round to the release
This commit is contained in:
@@ -69,7 +69,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
hooks.rerender();
|
||||
return;
|
||||
}
|
||||
const occupied = (seat.name || "").trim() || Number(seat.buyIns) > 0 || Number(seat.stack) > 0;
|
||||
const occupied = HoldemState.seatInPlay(seat);
|
||||
if (occupied) {
|
||||
Toast.show(`${HoldemState.displayName(seat, index)} is already in that seat.`, { duration: 3500 });
|
||||
hooks.rerender();
|
||||
@@ -366,12 +366,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// in progress. Shrinking the table throws these away permanently, and it also
|
||||
// orphans them in the session standings, where they keep their winnings but lose
|
||||
// the stack and buy-in the settle-up is worked out from.
|
||||
const seatHasSomeoneIn = player =>
|
||||
!!(player.name && player.name.trim()) ||
|
||||
player.inHand ||
|
||||
(Number(player.stack) || 0) > 0 ||
|
||||
(Number(player.buyIns) || 0) > 0 ||
|
||||
(Number(player.amountOverride) || 0) > 0;
|
||||
// One question, asked in one place. This predicate existed here, in the seat menu,
|
||||
// in the action bar and twice in the state module, in four slightly different
|
||||
// spellings — and a chair that counts as occupied for the waiting list but empty
|
||||
// for the seat count is how somebody's chips get thrown away.
|
||||
const seatHasSomeoneIn = player => HoldemState.seatInPlay(player);
|
||||
|
||||
document.getElementById("seatCountInput").addEventListener("change", async e => {
|
||||
const requested = Math.max(2, Math.min(24, Math.floor(Number(e.target.value)) || state.seatCount));
|
||||
|
||||
@@ -936,14 +936,20 @@ const Render = {
|
||||
const list = document.getElementById("colourUpList");
|
||||
if (!host || !list) return;
|
||||
|
||||
// What each player is holding, asked the same way the felt asks it — which
|
||||
// includes the table's starting stack standing in for a seat that has never
|
||||
// been given one. Reading `player.stack` here meant that a table opened the
|
||||
// ordinary way, with "everyone starts with 200", showed an empty colour-up
|
||||
// panel: nobody to count out, at the table where every single stack is a
|
||||
// round number made of the largest chips there are.
|
||||
const legend = this.state.chipLegend || [];
|
||||
const rows = this.state.seats
|
||||
.map((player, index) => ({ player, index }))
|
||||
.filter(({ player }) => Betting.round(Number(player.stack) || 0) > 0)
|
||||
.map(({ player, index }) => ({
|
||||
.map((player, index) => ({ player, index, held: HoldemState.chipsHeldBy(this.state, player) }))
|
||||
.filter(({ held }) => held > 0)
|
||||
.map(({ player, index, held }) => ({
|
||||
name: HoldemState.displayName(player, index),
|
||||
stack: Betting.round(Number(player.stack) || 0),
|
||||
chips: Money.formatChipBreakdown(Number(player.stack) || 0, legend),
|
||||
stack: held,
|
||||
chips: Money.formatChipBreakdown(held, legend),
|
||||
}))
|
||||
.filter(row => row.chips);
|
||||
|
||||
|
||||
@@ -52,8 +52,7 @@ const SeatMenu = {
|
||||
// the queue is offered, for the same reason the panel only offers them — a list
|
||||
// that isn't kept in order is just a pile of names.
|
||||
const waiting = (this.state.waitingList || [])[0];
|
||||
const emptyChair = !(player.name || "").trim() &&
|
||||
!(Number(player.buyIns) > 0) && !(Number(player.stack) > 0);
|
||||
const emptyChair = !HoldemState.seatInPlay(player);
|
||||
if (waiting && emptyChair) {
|
||||
actions.push({
|
||||
label: `Seat ${waiting.name}`,
|
||||
@@ -65,7 +64,7 @@ const SeatMenu = {
|
||||
// Leaving for the night, which is a different thing from sitting out a hand:
|
||||
// the chips go back over the rail and the chair opens. Offered only for a seat
|
||||
// that has somebody in it, since an empty chair has nobody to pay.
|
||||
if ((player.name || "").trim() || Number(player.buyIns) > 0 || Number(player.stack) > 0) {
|
||||
if (HoldemState.seatInPlay(player)) {
|
||||
actions.push({ label: "Cash out…", run: () => this.hooks.cashOut(index) });
|
||||
}
|
||||
|
||||
|
||||
@@ -917,8 +917,7 @@ const HoldemState = {
|
||||
openSeats(state) {
|
||||
return state.seats
|
||||
.map((player, index) => ({ player, index }))
|
||||
.filter(({ player }) => !(player.name || "").trim() &&
|
||||
!(Number(player.buyIns) > 0) && !(Number(player.stack) > 0))
|
||||
.filter(({ player }) => !this.seatInPlay(player))
|
||||
.map(({ index }) => index);
|
||||
},
|
||||
|
||||
@@ -929,7 +928,7 @@ const HoldemState = {
|
||||
seatFromWaitingList(state, id, index) {
|
||||
const seat = state.seats[index];
|
||||
if (!seat) return { ok: false, reason: "There's no such seat." };
|
||||
if ((seat.name || "").trim() || Number(seat.buyIns) > 0 || Number(seat.stack) > 0) {
|
||||
if (this.seatInPlay(seat)) {
|
||||
return { ok: false, reason: `${this.displayName(seat, index)} is already in that seat.` };
|
||||
}
|
||||
const entry = this.removeFromWaitingList(state, id);
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// One question, one answer.
|
||||
//
|
||||
// The round before this found the bank check and the standings reading `seat.stack`
|
||||
// where the felt reads the table's starting stack — two parts of the tool disagreeing
|
||||
// about how much money is on the table. This is the sweep that followed: everywhere
|
||||
// else the same two questions get asked, asked the same way.
|
||||
//
|
||||
// "What is this seat holding?" -> HoldemState.chipsHeldBy
|
||||
// "Is anybody in this chair?" -> HoldemState.seatInPlay
|
||||
const test = require("node:test");
|
||||
const { describe } = require("node:test");
|
||||
const assert = require("node:assert");
|
||||
const { HoldemState, Betting, SettleUp } = require("./load");
|
||||
|
||||
const openedOnTheDefault = (stack = 200) => {
|
||||
const state = HoldemState.createDefault();
|
||||
HoldemState.resizeSeats(state, 6);
|
||||
state.defaultStack = stack;
|
||||
["Ann", "Ben", "Cal"].forEach((name, i) => {
|
||||
state.seats[i].name = name;
|
||||
state.seats[i].inHand = true;
|
||||
state.seats[i].buyIns = stack;
|
||||
});
|
||||
return state;
|
||||
};
|
||||
|
||||
describe("what a seat is holding", () => {
|
||||
test("is the same number the felt plays with", () => {
|
||||
const state = openedOnTheDefault();
|
||||
state.seats.forEach((player, i) => {
|
||||
if (i > 2) return;
|
||||
assert.equal(HoldemState.chipsHeldBy(state, player), Betting.stackOf(state, player));
|
||||
});
|
||||
});
|
||||
|
||||
test("and is nothing at all for an empty chair", () => {
|
||||
const state = openedOnTheDefault();
|
||||
// Betting's own answer for an empty chair is the table default, because it is
|
||||
// only ever asked about seats that are in a hand. The money side has to ask
|
||||
// about all ten chairs, so it checks whether anybody is in them first.
|
||||
assert.equal(Betting.stackOf(state, state.seats[5]), 200);
|
||||
assert.equal(HoldemState.chipsHeldBy(state, state.seats[5]), 0);
|
||||
});
|
||||
|
||||
test("counts a stack the dealer typed over the table's default", () => {
|
||||
const state = openedOnTheDefault();
|
||||
state.seats[0].stack = 75;
|
||||
assert.equal(HoldemState.chipsHeldBy(state, state.seats[0]), 75);
|
||||
});
|
||||
});
|
||||
|
||||
describe("is anybody in this chair", () => {
|
||||
const empty = () => HoldemState.createDefault().seats[0];
|
||||
|
||||
test("an untouched chair is empty", () => {
|
||||
assert.equal(HoldemState.seatInPlay(empty()), false);
|
||||
assert.equal(HoldemState.seatInPlay(null), false);
|
||||
});
|
||||
|
||||
test("a name, chips, cash, a bet or a dealt hand all count", () => {
|
||||
[
|
||||
["a name", p => { p.name = "Ann"; }],
|
||||
["chips", p => { p.stack = 50; }],
|
||||
["cash taken", p => { p.buyIns = 100; }],
|
||||
["a bet in front of them", p => { p.committed = 5; }],
|
||||
["money already swept in", p => { p.amountOverride = 20; }],
|
||||
["a hand dealt to them", p => { p.inHand = true; }],
|
||||
].forEach(([what, mark]) => {
|
||||
const seat = empty();
|
||||
mark(seat);
|
||||
assert.equal(HoldemState.seatInPlay(seat), true, what);
|
||||
});
|
||||
});
|
||||
|
||||
test("whitespace is not a name", () => {
|
||||
const seat = empty();
|
||||
seat.name = " ";
|
||||
assert.equal(HoldemState.seatInPlay(seat), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the parts that used to disagree", () => {
|
||||
test("the open seats and the seat count see the same empty chairs", () => {
|
||||
const state = openedOnTheDefault();
|
||||
// Three players, six chairs.
|
||||
assert.deepEqual(HoldemState.openSeats(state), [3, 4, 5]);
|
||||
const occupied = state.seats.filter(p => HoldemState.seatInPlay(p)).length;
|
||||
assert.equal(occupied + HoldemState.openSeats(state).length, state.seats.length);
|
||||
});
|
||||
|
||||
test("a chair holding only a dealt hand is not offered to the waiting list", () => {
|
||||
// The old test looked at name, buy-ins and stack. A seat marked in-hand with
|
||||
// none of those would have been handed to the next person in the queue, on top
|
||||
// of whoever was already playing it.
|
||||
const state = openedOnTheDefault();
|
||||
state.seats[4].inHand = true;
|
||||
assert.deepEqual(HoldemState.openSeats(state), [3, 5]);
|
||||
|
||||
HoldemState.addToWaitingList(state, "Priya");
|
||||
const result = HoldemState.seatFromWaitingList(state, state.waitingList[0].id, 4);
|
||||
assert.equal(result.ok, false);
|
||||
});
|
||||
|
||||
test("the bank, the standings and the settle-up all price the night the same way", () => {
|
||||
const state = openedOnTheDefault();
|
||||
state.seats[0].stack = 320; // Ann is up
|
||||
state.seats[1].stack = 80; // Ben is down
|
||||
|
||||
const bank = HoldemState.bankReconciliation(state);
|
||||
const standings = HoldemState.sessionStandings(state);
|
||||
const chipsFromStandings = standings
|
||||
.filter(row => !row.cashedOut)
|
||||
.reduce((sum, row) => sum + row.chipsInFront, 0);
|
||||
|
||||
assert.equal(bank.chipsInPlay, chipsFromStandings, "same chips on the table");
|
||||
assert.equal(bank.balanced, true);
|
||||
|
||||
// And the payments the dealer is told to make add up to the same money.
|
||||
const plan = SettleUp.plan(standings);
|
||||
const owed = standings.reduce((sum, row) => sum + Math.max(0, row.cashPosition), 0);
|
||||
const paid = plan.payments.reduce((sum, row) => sum + row.amount, 0);
|
||||
assert.equal(paid + (plan.shortBy || 0) - (plan.overBy || 0), owed);
|
||||
});
|
||||
|
||||
test("a player still holding the table's starting stack is in all three", () => {
|
||||
const state = openedOnTheDefault();
|
||||
const standings = HoldemState.sessionStandings(state);
|
||||
assert.equal(standings.length, 3);
|
||||
assert.deepEqual(standings.map(row => row.chipsInFront), [200, 200, 200]);
|
||||
assert.equal(HoldemState.bankReconciliation(state).chipsInPlay, 600);
|
||||
assert.deepEqual(SettleUp.plan(standings).payments, [], "everyone is even, so nobody pays anybody");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user