// The long-press menu on a seat. Everything a dealer does to one player mid-hand, // one press and one tap away, without opening the full editor and without hunting // for whose turn it is in the action bar. const SeatMenu = { state: null, hooks: null, seatIndex: null, mode: null, open(state, hooks, seatIndex) { this.state = state; this.hooks = hooks; this.seatIndex = seatIndex; // Always the action list, never whatever the sheet was showing when it last // closed — a seat menu that opens on a list of chairs is a menu that lost the // dealer's place. this.mode = null; this.render(); document.getElementById("seatMenuOverlay").hidden = false; }, close() { document.getElementById("seatMenuOverlay").hidden = true; }, // Only the actions that make sense for this seat right now — a folded player has // nothing to go all-in with, and an empty chair has nothing to fold. actionsFor(player, index) { const actions = []; const dealtIn = player.inHand; const live = dealtIn && !player.folded; if (live) { actions.push({ label: "Fold", className: "menuDanger", run: () => this.hooks.fold(index) }); actions.push({ label: "All-in", className: "menuAccent", run: () => this.hooks.allIn(index) }); } else if (dealtIn && player.folded) { actions.push({ label: "Un-fold", run: () => this.hooks.unfold(index) }); } // The most common interruption in a cash game, and until now it meant opening // the editor and retyping a stack from memory. actions.push({ label: "Add chips…", run: () => this.hooks.addChips(index) }); actions.push({ label: dealtIn ? "Sit out" : "Deal in", run: () => this.hooks.toggleDealtIn(index), }); // 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) { actions.push({ label: "Cash out…", run: () => this.hooks.cashOut(index) }); } if (!this.state.dedicatedDealer && index !== this.state.dealerSeatIndex) { actions.push({ label: "Give the button", run: () => this.hooks.makeDealer(index) }); } // Naming a blind by hand. Only offered while nothing but the blinds are in — // once somebody has called, moving a blind would mean unpicking a bet that // has already been answered, and that's a decision for the dealer's mouth, // not a menu item. Which blind a seat already has is shown rather than // offered again. if (dealtIn && Betting.onlyBlindsIn(this.state)) { const blinds = HoldemState.getBlindSeats(this.state); if (blinds.sbIndex !== index) { actions.push({ label: "Make small blind", run: () => this.hooks.assignBlind(index, "sb") }); } if (blinds.bbIndex !== index) { actions.push({ label: "Make big blind", run: () => this.hooks.assignBlind(index, "bb") }); } if (this.state.blindOverride) { actions.push({ label: "Blinds follow the button again", run: () => this.hooks.clearBlindOverride() }); } } // Changing chairs was drag-and-drop only, and HTML5 drag events are never // generated from a touch screen — so on the device this tool is mostly used on, // the "drag to swap seats" the hint advertised did nothing at all. This is the // same move, by the route a thumb can actually take. if (this.state.seats.length > 1) { actions.push({ label: "Move to…", run: () => this.openMovePicker() }); } actions.push({ label: "Edit seat…", run: () => this.hooks.editSeat(index) }); return actions; }, // The chairs this player could move to. Every other chair: swapping into an empty // one is how a player moves, and swapping with an occupied one is two players // changing places, which is the other half of the same job. destinationsFor(index) { return this.state.seats .map((player, seat) => ({ player, seat })) .filter(entry => entry.seat !== index); }, openMovePicker() { this.mode = "move"; this.render(); }, render() { const player = this.state.seats[this.seatIndex]; const title = document.getElementById("seatMenuTitle"); const subtitle = document.getElementById("seatMenuSubtitle"); const list = document.getElementById("seatMenuActions"); list.innerHTML = ""; if (this.mode === "move") { title.textContent = `Move ${HoldemState.displayName(player, this.seatIndex)} to…`; subtitle.textContent = "Their chips, cards and bet go with them. The button and the blinds stay on the chair."; this.destinationsFor(this.seatIndex).forEach(({ player: other, seat }) => { const btn = document.createElement("button"); btn.type = "button"; btn.className = "seatMenuBtn"; // displayName falls back to "Seat 5" for a chair with nobody in it, // which reads as "Seat 5 — Seat 5". An empty chair says it's empty. const named = (other.name || "").trim(); btn.textContent = named ? `Seat ${seat + 1} — ${named}` : `Seat ${seat + 1} — empty`; btn.addEventListener("click", () => { const from = this.seatIndex; this.close(); this.hooks.swapSeats(from, seat); }); list.appendChild(btn); }); return; } title.textContent = HoldemState.displayName(player, this.seatIndex); const stack = Betting.stackOf(this.state, player); const owed = Betting.toCall(this.state, player); const bits = []; if (stack > 0) bits.push(`${Money.format(stack)} behind`); if (owed > 0) bits.push(`${Money.format(owed)} to call`); subtitle.textContent = bits.join(" · "); this.actionsFor(player, this.seatIndex).forEach(action => { const btn = document.createElement("button"); btn.type = "button"; btn.className = `seatMenuBtn ${action.className || ""}`; btn.textContent = action.label; btn.addEventListener("click", () => { // "Move to…" stays on the sheet and swaps it for the seat list; every // other action is the end of the interaction. if (action.label === "Move to…") { action.run(); return; } this.close(); action.run(); }); list.appendChild(btn); }); }, }; if (typeof module !== "undefined" && module.exports) { module.exports = SeatMenu; }