Until now a dealer kept the money in their head and typed each player's total into a form. This adds the running hand: blinds post themselves, every seat has a stack, and one tap per decision moves the action on. The bar sits at the bottom of the screen where a thumb already is, and shows only what's true right now — Fold, then Check or Call with the amount owed, then Bet or Raise. Calling more than a player has left says "Call all-in" rather than quoting a number they can't cover. Raising offers min, half pot, pot and all-in before it offers a keypad, because a dealer calling "raise to sixty" wants a button. When everyone has answered, the bar becomes Deal Flop, then Turn, River, and finally Showdown, which hands off to the pot wizard that already existed. Winnings go straight back in front of the winner, so the next hand's stacks are already right. The money hooks into what was already there rather than running beside it: a street's chips sweep into each seat's amountOverride, the same field the pot badge, the pot wizard, the session standings and the CSV export all read. So nothing downstream changed, and a dealer who prefers the old typed workflow still has it. Tables saved before this exist without any of these fields, so load() fills them in rather than making someone reset a table mid-session. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
172 lines
6.4 KiB
JavaScript
172 lines
6.4 KiB
JavaScript
document.addEventListener("DOMContentLoaded", () => {
|
|
const state = HoldemState.load();
|
|
|
|
const hooks = {
|
|
save: s => HoldemState.save(s),
|
|
rerender: () => Render.renderAll(),
|
|
};
|
|
|
|
Render.mount(state, hooks);
|
|
|
|
const openPotWizard = () => {
|
|
// Chips still in front of players belong in the pot being priced, so bank them
|
|
// before the wizard reads anyone's contribution.
|
|
Betting.sweepToHandTotals(state);
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
|
|
PotWizard.open(state, payouts => {
|
|
const paid = payouts.map(p => `${p.label} ${Money.format(p.amount)}`).join(", ");
|
|
runUndoable(paid ? `Paid ${paid}.` : "Hand closed with nothing to pay out.", () => {
|
|
if (payouts.length > 0) {
|
|
HoldemState.recordHand(state, payouts);
|
|
// Winnings go straight back in front of the player, so the next
|
|
// hand's stacks are already right.
|
|
Betting.creditWinnings(state, payouts);
|
|
}
|
|
HoldemState.startNextHand(state);
|
|
Betting.beginHand(state);
|
|
});
|
|
});
|
|
};
|
|
|
|
ActionBar.mount(state, {
|
|
save: hooks.save,
|
|
rerender: hooks.rerender,
|
|
resolvePot: openPotWizard,
|
|
});
|
|
|
|
// Moving to the next hand used to ask "are you sure?" every single time —
|
|
// the most-pressed button in a session, gated behind a system dialog. It now
|
|
// just happens, and the toast offers the way back for the few seconds anyone
|
|
// would notice a misclick. The snapshot is deliberately in-memory only: an
|
|
// undo is for the mistake you just made, not one from last night's session.
|
|
const runUndoable = (message, mutate) => {
|
|
const snapshot = JSON.parse(JSON.stringify(state));
|
|
mutate();
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
|
|
Toast.show(message, {
|
|
actionLabel: "Undo",
|
|
onAction: () => {
|
|
Object.keys(state).forEach(key => delete state[key]);
|
|
Object.assign(state, snapshot);
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
Toast.show("Undone.", { duration: 2500 });
|
|
},
|
|
});
|
|
};
|
|
|
|
document.getElementById("seatCountInput").addEventListener("change", e => {
|
|
HoldemState.resizeSeats(state, Number(e.target.value));
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
});
|
|
|
|
document.getElementById("directionSelect").addEventListener("change", e => {
|
|
state.direction = e.target.value;
|
|
hooks.save(state);
|
|
Render.renderSeats();
|
|
});
|
|
|
|
document.getElementById("basePotInput").addEventListener("input", e => {
|
|
state.basePotAmount = e.target.value === "" ? null : Number(e.target.value);
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
});
|
|
|
|
document.getElementById("dedicatedDealerToggle").addEventListener("change", e => {
|
|
state.dedicatedDealer = e.target.checked;
|
|
hooks.save(state);
|
|
Render.renderSeats();
|
|
Render.renderTableHint();
|
|
});
|
|
|
|
document.getElementById("tableNotes").addEventListener("input", e => {
|
|
state.notes = e.target.value;
|
|
hooks.save(state);
|
|
});
|
|
|
|
document.getElementById("addChipColorBtn").addEventListener("click", () => {
|
|
state.chipLegend.push({
|
|
id: HoldemState.makeId("chip"),
|
|
color: "#888888",
|
|
value: 1,
|
|
});
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
});
|
|
|
|
document.getElementById("nextHandBtn").addEventListener("click", () => {
|
|
// startNextHand moves the button on by one; say where it landed so the
|
|
// dealer can confirm the rotation without hunting for the badge.
|
|
const nextDealer = HoldemState.nextSeatIndex(state, state.dealerSeatIndex, 1);
|
|
const where = state.dedicatedDealer
|
|
? `small blind on seat ${HoldemState.nextSeatIndex(state, nextDealer, 1) + 1}`
|
|
: `button on seat ${nextDealer + 1}`;
|
|
|
|
runUndoable(`Next hand — ${where}. Blinds posted.`, () => {
|
|
HoldemState.startNextHand(state);
|
|
Betting.beginHand(state);
|
|
});
|
|
});
|
|
|
|
document.getElementById("resetTableBtn").addEventListener("click", async () => {
|
|
const ok = await Dialog.confirm({
|
|
title: "Reset the whole table?",
|
|
message: "Every seat, chip colour and note goes back to defaults. The hand history and session standings are cleared too.",
|
|
confirmLabel: "Reset table",
|
|
danger: true,
|
|
});
|
|
if (!ok) return;
|
|
|
|
HoldemState.clear();
|
|
Object.assign(state, HoldemState.createDefault());
|
|
Render.mount(state, hooks);
|
|
Toast.show("Table reset.", { duration: 2500 });
|
|
});
|
|
|
|
document.getElementById("resolvePotBtn").addEventListener("click", openPotWizard);
|
|
|
|
[
|
|
["smallBlindInput", "smallBlind"],
|
|
["bigBlindInput", "bigBlind"],
|
|
["defaultStackInput", "defaultStack"],
|
|
].forEach(([id, key]) => {
|
|
document.getElementById(id).addEventListener("input", e => {
|
|
state[key] = e.target.value === "" ? null : Number(e.target.value);
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
});
|
|
});
|
|
|
|
document.getElementById("closeSeatEditorBtn").addEventListener("click", () => Render.closeSeatEditor());
|
|
|
|
document.getElementById("closePotWizardBtn").addEventListener("click", () => PotWizard.close());
|
|
document.getElementById("potWizardOverlay").addEventListener("click", e => {
|
|
if (e.target.id === "potWizardOverlay") PotWizard.close();
|
|
});
|
|
|
|
document.getElementById("clearHistoryBtn").addEventListener("click", async () => {
|
|
const ok = await Dialog.confirm({
|
|
title: "Clear the hand history?",
|
|
message: "This wipes every logged hand and the session standings, and puts the hand counter back to #1. Export first if anyone might want the numbers.",
|
|
confirmLabel: "Clear history",
|
|
danger: true,
|
|
});
|
|
if (!ok) return;
|
|
|
|
HoldemState.clearHistory(state);
|
|
hooks.save(state);
|
|
Render.renderAll();
|
|
Toast.show("Hand history cleared.", { duration: 2500 });
|
|
});
|
|
|
|
document.getElementById("exportHistoryBtn").addEventListener("click", () => {
|
|
if ((state.handHistory || []).length === 0) return;
|
|
HandHistoryExport.download(state.handHistory, HoldemState.sessionStandings(state));
|
|
});
|
|
});
|