Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc6a1902bc | ||
|
|
a488bbf832 |
@@ -318,15 +318,34 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
Render.renderAll();
|
Render.renderAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("nextHandBtn").addEventListener("click", () => {
|
document.getElementById("nextHandBtn").addEventListener("click", async () => {
|
||||||
|
// Moving on wipes every seat's contribution, so an unresolved pot is simply
|
||||||
|
// destroyed — measured at a four-handed table, pressing this with $8 in the
|
||||||
|
// middle took the table from 800 chips to 792, and the toast said nothing
|
||||||
|
// about it. Chips only ever leave this tool because a dealer meant them to.
|
||||||
|
const unresolved = HoldemState.unresolvedContributions(state);
|
||||||
|
|
||||||
|
if (unresolved > Betting.EPSILON_CHIPS) {
|
||||||
|
const ok = await Dialog.confirm({
|
||||||
|
title: `Throw away ${Money.format(unresolved)}?`,
|
||||||
|
message: "There's still money in the middle from this hand. Starting the next one takes it off the table without paying anybody — use Resolve Pot if somebody won it.",
|
||||||
|
confirmLabel: "Throw it away",
|
||||||
|
danger: true,
|
||||||
|
});
|
||||||
|
if (!ok) return;
|
||||||
|
}
|
||||||
|
|
||||||
// startNextHand moves the button on by one; say where it landed so the
|
// startNextHand moves the button on by one; say where it landed so the
|
||||||
// dealer can confirm the rotation without hunting for the badge.
|
// dealer can confirm the rotation without hunting for the badge.
|
||||||
const nextDealer = HoldemState.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
const nextDealer = HoldemState.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
||||||
const where = state.dedicatedDealer
|
const where = state.dedicatedDealer
|
||||||
? `small blind on seat ${HoldemState.nextSeatIndex(state, nextDealer, 1) + 1}`
|
? `small blind on seat ${HoldemState.nextSeatIndex(state, nextDealer, 1) + 1}`
|
||||||
: `button on seat ${nextDealer + 1}`;
|
: `button on seat ${nextDealer + 1}`;
|
||||||
|
const discarded = unresolved > Betting.EPSILON_CHIPS
|
||||||
|
? ` ${Money.format(unresolved)} taken off the table.`
|
||||||
|
: "";
|
||||||
|
|
||||||
runUndoable(`Next hand — ${where}. Blinds posted.`, () => {
|
runUndoable(`Next hand — ${where}. Blinds posted.${discarded}`, () => {
|
||||||
HoldemState.startNextHand(state);
|
HoldemState.startNextHand(state);
|
||||||
Betting.beginHand(state);
|
Betting.beginHand(state);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -272,11 +272,25 @@ const PotWizard = {
|
|||||||
});
|
});
|
||||||
actions.appendChild(applyBtn);
|
actions.appendChild(applyBtn);
|
||||||
|
|
||||||
const closeBtn = document.createElement("button");
|
// This button cancels — it does not pay anybody. Under a heading reading
|
||||||
closeBtn.className = "holdemBtn holdemBtnGhost";
|
// "Final Payouts" with an amount beside a name, "Close" reads like dismissing
|
||||||
closeBtn.textContent = "Close";
|
// something already done, and a dealer who taps it walks away believing the
|
||||||
closeBtn.addEventListener("click", () => this.close());
|
// pot has been pushed. It hasn't: the chips are still in the middle and the
|
||||||
actions.appendChild(closeBtn);
|
// hand isn't logged. So it says what it does.
|
||||||
|
const cancelBtn = document.createElement("button");
|
||||||
|
cancelBtn.className = "holdemBtn holdemBtnGhost";
|
||||||
|
cancelBtn.textContent = "Cancel — don't pay yet";
|
||||||
|
cancelBtn.addEventListener("click", () => this.close());
|
||||||
|
actions.appendChild(cancelBtn);
|
||||||
|
|
||||||
|
if (this.totals.size > 0) {
|
||||||
|
const note = document.createElement("p");
|
||||||
|
note.className = "fieldHint potWizardPendingNote";
|
||||||
|
note.textContent = "Nothing has been paid out yet. Start Next Hand pushes the pot and logs the hand.";
|
||||||
|
wrap.appendChild(actions);
|
||||||
|
wrap.appendChild(note);
|
||||||
|
return wrap;
|
||||||
|
}
|
||||||
|
|
||||||
wrap.appendChild(actions);
|
wrap.appendChild(actions);
|
||||||
return wrap;
|
return wrap;
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ const Render = {
|
|||||||
this.state = state;
|
this.state = state;
|
||||||
this.hooks = hooks;
|
this.hooks = hooks;
|
||||||
this.selectedSeatIndex = null;
|
this.selectedSeatIndex = null;
|
||||||
|
// A reset hands over a different table entirely, so nothing cached about the
|
||||||
|
// last one still applies.
|
||||||
|
this.lastHistorySignature = null;
|
||||||
|
this.handTimeCache.clear();
|
||||||
this.renderAll();
|
this.renderAll();
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -703,24 +707,57 @@ const Render = {
|
|||||||
|
|
||||||
// ---- Hand counter + history ----
|
// ---- Hand counter + history ----
|
||||||
|
|
||||||
|
// toLocaleTimeString is the single most expensive call in the history panel, and a
|
||||||
|
// logged hand's timestamp never changes, so the answer is worth keeping.
|
||||||
|
handTimeCache: new Map(),
|
||||||
|
|
||||||
formatHandTime(timestamp) {
|
formatHandTime(timestamp) {
|
||||||
|
if (this.handTimeCache.has(timestamp)) return this.handTimeCache.get(timestamp);
|
||||||
|
let formatted = "";
|
||||||
try {
|
try {
|
||||||
return new Date(timestamp).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
|
formatted = new Date(timestamp).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
|
||||||
} catch {
|
} catch {
|
||||||
return "";
|
formatted = "";
|
||||||
}
|
}
|
||||||
|
this.handTimeCache.set(timestamp, formatted);
|
||||||
|
return formatted;
|
||||||
},
|
},
|
||||||
|
|
||||||
renderHandHistory() {
|
// Cheap stand-in for "has the log changed". Entries are immutable once written and
|
||||||
|
// only ever added at the front, so the length, the counter and the newest row
|
||||||
|
// between them catch a recorded hand, a cleared history and an undo.
|
||||||
|
historySignature() {
|
||||||
|
const history = this.state.handHistory || [];
|
||||||
|
const newest = history[0];
|
||||||
|
return [
|
||||||
|
history.length,
|
||||||
|
this.state.handNumber || 1,
|
||||||
|
newest ? newest.handNumber : "",
|
||||||
|
newest ? newest.timestamp : "",
|
||||||
|
newest ? newest.pot : "",
|
||||||
|
].join("|");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Rebuilding this panel measured 25.7ms of the 30ms a full re-render costs — and a
|
||||||
|
// full re-render runs after every fold, call and raise. The log only changes when a
|
||||||
|
// hand is settled or the history is cleared, perhaps once every thirty taps, so it
|
||||||
|
// is skipped unless it actually did change. Everything else on the table reflects
|
||||||
|
// the current hand and still redraws every time.
|
||||||
|
renderHandHistory(force = false) {
|
||||||
|
const signature = this.historySignature();
|
||||||
|
if (!force && signature === this.lastHistorySignature) return;
|
||||||
|
this.lastHistorySignature = signature;
|
||||||
|
|
||||||
document.getElementById("handCounterBadge").textContent = `Hand #${this.state.handNumber || 1}`;
|
document.getElementById("handCounterBadge").textContent = `Hand #${this.state.handNumber || 1}`;
|
||||||
|
|
||||||
const list = document.getElementById("handHistoryList");
|
const list = document.getElementById("handHistoryList");
|
||||||
const emptyHint = document.getElementById("handHistoryEmptyHint");
|
const emptyHint = document.getElementById("handHistoryEmptyHint");
|
||||||
const history = this.state.handHistory || [];
|
const history = this.state.handHistory || [];
|
||||||
list.innerHTML = "";
|
|
||||||
emptyHint.hidden = history.length > 0;
|
emptyHint.hidden = history.length > 0;
|
||||||
document.getElementById("exportHistoryBtn").disabled = history.length === 0;
|
document.getElementById("exportHistoryBtn").disabled = history.length === 0;
|
||||||
|
|
||||||
|
// Built off-document so the panel is laid out once instead of once per row.
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
const template = document.getElementById("handHistoryRowTemplate");
|
const template = document.getElementById("handHistoryRowTemplate");
|
||||||
history.forEach(entry => {
|
history.forEach(entry => {
|
||||||
const row = template.content.firstElementChild.cloneNode(true);
|
const row = template.content.firstElementChild.cloneNode(true);
|
||||||
@@ -731,8 +768,10 @@ const Render = {
|
|||||||
const payoutsEl = row.querySelector(".handHistoryPayouts");
|
const payoutsEl = row.querySelector(".handHistoryPayouts");
|
||||||
payoutsEl.textContent = entry.payouts.map(p => `${p.label} ${Money.format(p.amount)}`).join(", ");
|
payoutsEl.textContent = entry.payouts.map(p => `${p.label} ${Money.format(p.amount)}`).join(", ");
|
||||||
|
|
||||||
list.appendChild(row);
|
fragment.appendChild(row);
|
||||||
});
|
});
|
||||||
|
list.innerHTML = "";
|
||||||
|
list.appendChild(fragment);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -167,6 +167,38 @@ const HoldemState = {
|
|||||||
return !player.inHand;
|
return !player.inHand;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// What starting the next hand would take off the table for good: exactly the
|
||||||
|
// fields startNextHand clears.
|
||||||
|
//
|
||||||
|
// Deliberately not totalPot(). The table-wide base pot is a standing ante — a
|
||||||
|
// stand-in for "everyone puts the same in every hand", used by a dealer who
|
||||||
|
// isn't tracking stacks — so it re-applies to the next hand rather than being
|
||||||
|
// lost with this one. Counting it would mean warning about money that isn't
|
||||||
|
// going anywhere, on every single hand, for exactly the dealers who chose the
|
||||||
|
// simplest way to run the table.
|
||||||
|
unresolvedContributions(state) {
|
||||||
|
const total = state.seats.reduce((sum, player) => {
|
||||||
|
const override = player.amountOverride;
|
||||||
|
let banked = 0;
|
||||||
|
|
||||||
|
if (override !== null && override !== undefined && String(override).trim() !== "") {
|
||||||
|
const parsed = Number(override);
|
||||||
|
if (!Number.isNaN(parsed)) banked = Math.max(0, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (banked === 0) {
|
||||||
|
banked = (state.chipLegend || []).reduce((chips, chip) => {
|
||||||
|
const count = Number(player.chipCounts[chip.id]) || 0;
|
||||||
|
return chips + count * Number(chip.value || 0);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum + banked + Math.max(0, Number(player.committed) || 0);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return Money.round(total);
|
||||||
|
},
|
||||||
|
|
||||||
totalPot(state) {
|
totalPot(state) {
|
||||||
return state.seats.reduce(
|
return state.seats.reduce(
|
||||||
(sum, p) => sum + this.computeContribution(p, state.chipLegend, state.basePotAmount),
|
(sum, p) => sum + this.computeContribution(p, state.chipLegend, state.basePotAmount),
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
// The hand-history panel is only rebuilt when its signature changes, which takes a
|
||||||
|
// full re-render from ~30ms to ~3ms. The whole saving depends on that signature
|
||||||
|
// noticing every way the log can actually change — a stale panel would have the
|
||||||
|
// dealer reading last hand's payouts while settling this one.
|
||||||
|
const { test, describe } = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const { Render, HoldemState, makeTable } = require("./load");
|
||||||
|
|
||||||
|
// historySignature() reads Render.state, so point it at a table for each case.
|
||||||
|
const signatureOf = state => {
|
||||||
|
Render.state = state;
|
||||||
|
return Render.historySignature();
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableWithHands = (count) => {
|
||||||
|
const state = makeTable({ seatCount: 4, seated: [0, 1, 2] });
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
state.seats[0].amountOverride = 2;
|
||||||
|
state.seats[1].amountOverride = 2;
|
||||||
|
HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 4 }]);
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("the history panel knows when to redraw", () => {
|
||||||
|
test("nothing changes while a hand is being played", () => {
|
||||||
|
const state = tableWithHands(3);
|
||||||
|
const before = signatureOf(state);
|
||||||
|
|
||||||
|
// A whole street of betting touches no part of the log.
|
||||||
|
state.seats[0].committed = 5;
|
||||||
|
state.seats[1].folded = true;
|
||||||
|
state.actingSeatIndex = 2;
|
||||||
|
state.street = "flop";
|
||||||
|
|
||||||
|
assert.equal(signatureOf(state), before, "betting must not force a rebuild");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("settling a hand changes it", () => {
|
||||||
|
const state = tableWithHands(3);
|
||||||
|
const before = signatureOf(state);
|
||||||
|
HoldemState.recordHand(state, [{ id: state.seats[1].id, label: "Ben", amount: 12 }]);
|
||||||
|
assert.notEqual(signatureOf(state), before);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("clearing the history changes it", () => {
|
||||||
|
const state = tableWithHands(5);
|
||||||
|
const before = signatureOf(state);
|
||||||
|
HoldemState.clearHistory(state);
|
||||||
|
assert.notEqual(signatureOf(state), before);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("undoing back to a shorter log changes it", () => {
|
||||||
|
const state = tableWithHands(2);
|
||||||
|
const snapshot = JSON.parse(JSON.stringify(state));
|
||||||
|
HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 30 }]);
|
||||||
|
const afterRecord = signatureOf(state);
|
||||||
|
|
||||||
|
// The undo path swaps the state's contents in place.
|
||||||
|
Object.keys(state).forEach(k => delete state[k]);
|
||||||
|
Object.assign(state, snapshot);
|
||||||
|
|
||||||
|
assert.notEqual(signatureOf(state), afterRecord, "an undone hand must disappear from the panel");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("two hands that differ only in payout are told apart", () => {
|
||||||
|
const a = tableWithHands(1);
|
||||||
|
const b = tableWithHands(1);
|
||||||
|
// Same count and hand number; different pot.
|
||||||
|
b.handHistory[0].pot = a.handHistory[0].pot + 25;
|
||||||
|
assert.notEqual(signatureOf(a), signatureOf(b));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the hand counter moving on changes it", () => {
|
||||||
|
// The counter badge is drawn by the same function, so a new hand number has
|
||||||
|
// to invalidate even when the log itself is untouched.
|
||||||
|
const state = tableWithHands(2);
|
||||||
|
const before = signatureOf(state);
|
||||||
|
state.handNumber += 1;
|
||||||
|
assert.notEqual(signatureOf(state), before);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an empty log has a stable signature of its own", () => {
|
||||||
|
const state = makeTable({ seatCount: 4, seated: [0, 1] });
|
||||||
|
const first = signatureOf(state);
|
||||||
|
assert.equal(signatureOf(state), first);
|
||||||
|
|
||||||
|
HoldemState.recordHand(state, [{ id: state.seats[0].id, label: "Ann", amount: 4 }]);
|
||||||
|
assert.notEqual(signatureOf(state), first, "the first hand ever must draw the panel");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("the hand-time cache", () => {
|
||||||
|
test("the same timestamp is only formatted once, and formats the same", () => {
|
||||||
|
Render.handTimeCache.clear();
|
||||||
|
const stamp = Date.UTC(2026, 8, 1, 15, 4);
|
||||||
|
const first = Render.formatHandTime(stamp);
|
||||||
|
assert.equal(Render.handTimeCache.size, 1);
|
||||||
|
|
||||||
|
const second = Render.formatHandTime(stamp);
|
||||||
|
assert.equal(second, first);
|
||||||
|
assert.equal(Render.handTimeCache.size, 1, "not re-formatted");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("different timestamps still format differently", () => {
|
||||||
|
Render.handTimeCache.clear();
|
||||||
|
const a = Render.formatHandTime(Date.UTC(2026, 8, 1, 15, 4));
|
||||||
|
const b = Render.formatHandTime(Date.UTC(2026, 8, 1, 18, 47));
|
||||||
|
assert.notEqual(a, b);
|
||||||
|
assert.equal(Render.handTimeCache.size, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a nonsense timestamp doesn't throw", () => {
|
||||||
|
Render.handTimeCache.clear();
|
||||||
|
assert.doesNotThrow(() => Render.formatHandTime(undefined));
|
||||||
|
assert.doesNotThrow(() => Render.formatHandTime("not a date"));
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -20,6 +20,10 @@ const SplitCalculator = load("splitCalculator");
|
|||||||
const Betting = load("betting");
|
const Betting = load("betting");
|
||||||
const HandHistoryExport = load("handHistoryExport");
|
const HandHistoryExport = load("handHistoryExport");
|
||||||
|
|
||||||
|
// render.js exports { Render } rather than the namespace directly.
|
||||||
|
const Render = require(path.join(SRC, "render.js")).Render;
|
||||||
|
globalThis.Render = Render;
|
||||||
|
|
||||||
// state.js exports `HoldemState`, which doesn't match its filename.
|
// state.js exports `HoldemState`, which doesn't match its filename.
|
||||||
const HoldemState = require(path.join(SRC, "state.js"));
|
const HoldemState = require(path.join(SRC, "state.js"));
|
||||||
globalThis.HoldemState = HoldemState;
|
globalThis.HoldemState = HoldemState;
|
||||||
@@ -69,6 +73,7 @@ function contributions(state) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Money,
|
Money,
|
||||||
|
Render,
|
||||||
HandHistoryExport,
|
HandHistoryExport,
|
||||||
PotCalculator,
|
PotCalculator,
|
||||||
SplitCalculator,
|
SplitCalculator,
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
// Chips only ever leave this tool because a dealer meant them to. Starting the next
|
||||||
|
// hand clears every seat's contribution, so an unresolved pot is simply destroyed —
|
||||||
|
// the UI now asks first, and the number it quotes has to be exactly what goes.
|
||||||
|
const { test, describe } = require("node:test");
|
||||||
|
const assert = require("node:assert/strict");
|
||||||
|
const { Betting, HoldemState, Money, makeTable, chipsOnTable } = require("./load");
|
||||||
|
|
||||||
|
// The same measure the confirmation uses.
|
||||||
|
const unresolvedPot = state => HoldemState.unresolvedContributions(state);
|
||||||
|
|
||||||
|
describe("what starting the next hand destroys", () => {
|
||||||
|
test("the quoted amount is exactly what leaves the table", () => {
|
||||||
|
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||||
|
Betting.beginHand(state);
|
||||||
|
Betting.call(state, 3); Betting.call(state, 0); Betting.call(state, 1); Betting.check(state, 2);
|
||||||
|
Betting.sweepToHandTotals(state);
|
||||||
|
|
||||||
|
const before = chipsOnTable(state);
|
||||||
|
const quoted = unresolvedPot(state);
|
||||||
|
assert.ok(quoted > 0, "there is a pot to warn about");
|
||||||
|
|
||||||
|
HoldemState.startNextHand(state);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
Money.toCents(chipsOnTable(state)),
|
||||||
|
Money.toCents(before - quoted),
|
||||||
|
"the dealer must be told the real number, not an approximation"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("chips still in front of players on the current street are counted too", () => {
|
||||||
|
const state = makeTable({ seatCount: 4, seated: [0, 1, 2], dealerSeatIndex: 0 });
|
||||||
|
Betting.beginHand(state); // blinds are `committed`, not yet banked
|
||||||
|
Betting.raiseTo(state, 0, 25); // and so is a raise mid-street
|
||||||
|
|
||||||
|
const before = chipsOnTable(state);
|
||||||
|
const quoted = unresolvedPot(state);
|
||||||
|
HoldemState.startNextHand(state);
|
||||||
|
|
||||||
|
assert.equal(Money.toCents(chipsOnTable(state)), Money.toCents(before - quoted));
|
||||||
|
assert.ok(quoted >= 25, `a live raise has to be included; quoted ${quoted}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a hand that was paid out properly leaves nothing to warn about", () => {
|
||||||
|
const state = makeTable({ seatCount: 4, seated: [0, 1, 2], dealerSeatIndex: 0 });
|
||||||
|
Betting.beginHand(state);
|
||||||
|
Betting.call(state, 0); Betting.call(state, 1); Betting.check(state, 2);
|
||||||
|
Betting.sweepToHandTotals(state);
|
||||||
|
|
||||||
|
const pot = HoldemState.totalPot(state);
|
||||||
|
const payouts = [{ id: state.seats[0].id, label: "Ann", amount: pot }];
|
||||||
|
HoldemState.recordHand(state, payouts);
|
||||||
|
Betting.creditWinnings(state, payouts);
|
||||||
|
HoldemState.startNextHand(state);
|
||||||
|
|
||||||
|
assert.equal(unresolvedPot(state), 0, "nothing left over after a settled hand");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("settling then dealing on conserves every chip", () => {
|
||||||
|
const state = makeTable({ seatCount: 6, seated: [0, 1, 2, 3], dealerSeatIndex: 0 });
|
||||||
|
const start = chipsOnTable(state);
|
||||||
|
|
||||||
|
for (let hand = 0; hand < 5; hand++) {
|
||||||
|
Betting.beginHand(state);
|
||||||
|
const live = Betting.liveSeats(state);
|
||||||
|
live.forEach(s => { if (Betting.toCall(state, s.player) > 0) Betting.call(state, s.index); });
|
||||||
|
Betting.sweepToHandTotals(state);
|
||||||
|
|
||||||
|
const pot = HoldemState.totalPot(state);
|
||||||
|
const winner = Betting.liveSeats(state)[0];
|
||||||
|
const payouts = [{ id: winner.player.id, label: "w", amount: pot }];
|
||||||
|
HoldemState.recordHand(state, payouts);
|
||||||
|
Betting.creditWinnings(state, payouts);
|
||||||
|
HoldemState.startNextHand(state);
|
||||||
|
|
||||||
|
assert.equal(chipsOnTable(state), start, `chips drifted on hand ${hand + 1}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("an empty table has no phantom pot to warn about", () => {
|
||||||
|
const state = makeTable({ seatCount: 6, seated: [] });
|
||||||
|
assert.equal(unresolvedPot(state), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("a standing ante is never warned about, because it isn't lost", () => {
|
||||||
|
// The table default is a stand-in for "everyone puts the same in every hand",
|
||||||
|
// used by a dealer not tracking stacks at all. It re-applies to the next hand
|
||||||
|
// rather than going anywhere, so counting it would nag this dealer before
|
||||||
|
// every single hand — which is why the warning measures the fields
|
||||||
|
// startNextHand actually clears rather than the pot badge.
|
||||||
|
const state = makeTable({ seatCount: 9, seated: [0, 1] });
|
||||||
|
state.basePotAmount = 25;
|
||||||
|
state.seats.forEach(p => { p.amountOverride = null; });
|
||||||
|
|
||||||
|
// The base pot carries into the next hand, so nothing is being thrown away
|
||||||
|
// and the dealer must not be asked to confirm anything.
|
||||||
|
assert.equal(HoldemState.totalPot(state), 50, "the badge still shows a pot");
|
||||||
|
assert.equal(unresolvedPot(state), 0, "but none of it is at risk, so no warning");
|
||||||
|
|
||||||
|
HoldemState.startNextHand(state);
|
||||||
|
assert.equal(HoldemState.totalPot(state), 50, "the standing ante applies to the new hand too");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user