import type { View } from "./types"; import { cardFace, clear, money, node } from "./dom"; let lastAction = 0; function actionAllowed(): boolean { if (Date.now() - lastAction < 400) return false; lastAction = Date.now(); return true; } function cardElement(card: string): HTMLElement { if (card === "?") { const back = node("div", "card back"); back.append(node("span", "rank", "?"), node("span", "suit", "")); return back; } const face = cardFace(card); const element = node("div", "card"); if (face.red) element.classList.add("red"); element.append(node("span", "rank", face.rank), node("span", "suit", face.suit)); return element; } function handBlock(label: string, cards: string[], total: number): HTMLElement { const block = node("div", "hand"); block.append(node("div", "label", label)); const row = node("div", "cards"); for (const card of cards) row.append(cardElement(card)); const sum = node("div", "total"); sum.append(document.createTextNode("Total "), node("strong", undefined, String(total))); block.append(row, sum); return block; } function outcome(view: View, hand: any): string { const result = String(hand?.result ?? ""); const winnings = Number(hand?.winnings ?? 0); switch (result) { case "blackjack": return `Blackjack — won ${money(winnings)}`; case "win": return `You win — ${money(winnings)}`; case "push": return `Push — ${money(winnings)} returned`; case "bust": return "Bust — the bet is lost"; case "lose": return "Dealer wins — the bet is lost"; case "forfeit": return "Hand forfeited — the bet is lost"; case "playing": return view.status === "player" ? "Your move." : "Dealing…"; default: return "Ready to deal."; } } export function renderBlackjack( board: HTMLElement, side: HTMLElement, view: View, send: (command: unknown) => void, ): void { clear(board); clear(side); const hand = view.hand; const playerPhase = hand?.phase === "player" || view.status === "player"; const done = hand?.phase === "done" || view.status === "done"; const canDeal = !hand || done; board.append(node("h2", undefined, "Blackjack")); board.append( node("p", "note", "Six decks. Dealer stands on all 17. Blackjack pays 3:2."), ); if (hand) { board.append(handBlock("Dealer", hand.dealer ?? [], Number(hand.dealer_total ?? 0))); board.append(handBlock("You", hand.player ?? [], Number(hand.player_total ?? 0))); } else { const empty = node("div", "outcome"); empty.textContent = "Press DEAL to start a hand."; board.append(empty); } if (done || (hand && !playerPhase)) { const result = node("div", "outcome"); result.textContent = outcome(view, hand); if (String(hand?.result) === "win" || String(hand?.result) === "blackjack" || String(hand?.result) === "push") { result.classList.add("win-text"); } board.append(result); } if (view.error) board.append(node("p", "error-text", String(view.error))); // Sidebar side.append(node("div", "label", "Blackjack")); const bet = Number(hand?.bet ?? view.bet ?? 0); const balance = view.balance ?? view.hand?.balance; side.append(node("p", "note", "Hit, stand or double. Forfeiting loses the reserved bet.")); if (canDeal) { const betRow = node("div", "row"); const minus = node("button", "btn small", "−10"); const amount = node("strong", "mono", money(bet)); const plus = node("button", "btn small", "+10"); minus.addEventListener("click", () => send({ AdjustBet: -10 })); plus.addEventListener("click", () => send({ AdjustBet: 10 })); betRow.append(minus, amount, plus); side.append(node("div", "label", "Bet"), betRow); } else { const stat = node("div", "stat"); stat.append(node("span", "muted", "Bet"), node("strong", undefined, money(bet))); side.append(stat); } if (balance !== undefined) { const stat = node("div", "stat"); stat.append(node("span", "muted", "Balance"), node("strong", undefined, money(Number(balance)))); side.append(stat); } const actions = node("div", "actions"); if (playerPhase) { const actionsRow = node("div", "row wrap"); for (const action of hand?.actions ?? []) { const label = action === "hit" ? "HIT" : action === "stand" ? "STAND" : "DOUBLE"; const button = action === "stand" ? node("button", "btn primary", label) : node("button", "btn", label); button.addEventListener("click", () => { if (!actionAllowed()) return; if (action === "hit") send("Hit"); else if (action === "stand") send("Stand"); else send("Double"); }); actionsRow.append(button); } actions.append(actionsRow); const forfeit = node("button", "btn danger wide", "Forfeit hand"); forfeit.addEventListener("click", () => { if (!actionAllowed()) return; if (window.confirm("Forfeit this hand? The reserved bet is lost.")) send("Forfeit"); }); actions.append(forfeit); } else if (canDeal) { const deal = node("button", "btn primary wide big", "DEAL"); deal.addEventListener("click", () => { if (actionAllowed()) send("Deal"); }); actions.append(deal); } const leave = node("button", "btn ghost wide", playerPhase ? "Leave (forfeits)" : "Leave table"); leave.addEventListener("click", () => { if (playerPhase && !window.confirm("Leaving forfeits this hand. Continue?")) return; send("Leave"); }); actions.append(leave); side.append(actions); }