import { defineComponent } from "../../src/component"; import type { World, Entity } from "../../src/index"; import { query } from "../../src/query"; import type { CommandQueue } from "../../src/commands/index"; import { Card, InDeck, InPlayerHand, InDealerHand, HoleHidden, Score, Bet, GamePhase, } from "./components"; import { handValue, isBust, isBlackjack, determineOutcome, payout, } from "./game"; // ── Command definitions ────────────────────────────── /** Player takes another card. */ export const Hit = defineComponent("hit", {}); /** Player stands (ends their turn). */ export const Stand = defineComponent("stand", {}); /** Start a new round. */ export const NewRound = defineComponent("newRound", {}); /** Increase the bet. */ export const BetMore = defineComponent("betMore", {}); /** Decrease the bet. */ export const BetLess = defineComponent("betLess", {}); // ── Command handlers ───────────────────────────────── export function registerCommands( world: World, commands: CommandQueue, helpers: ReturnType, ): void { commands.handle(Hit, () => { const phase = world.getSingleton(GamePhase); if (phase.phase !== "playerTurn") return; const cardEntity = helpers.drawCard(); if (cardEntity) { helpers.dealTo(cardEntity, InPlayerHand); } if (isBust(helpers.getHand(InPlayerHand))) { world.removeSingleton(HoleHidden); resolveRound(world, helpers); } }); commands.handle(Stand, () => { const phase = world.getSingleton(GamePhase); if (phase.phase !== "playerTurn") return; world.removeSingleton(HoleHidden); world.setSingleton(GamePhase, { phase: "dealerTurn", message: "Dealer's turn...", }); }); commands.handle(NewRound, () => { const phase = world.getSingleton(GamePhase); if (phase.phase !== "roundOver" && phase.phase !== "betting") return; const score = world.getSingleton(Score); if (score.chips <= 0) { world.setSingleton(GamePhase, { phase: "roundOver", message: "You're out of chips! Restart the program to play again.", }); return; } helpers.startRound(); }); commands.handle(BetMore, () => { const phase = world.getSingleton(GamePhase); if (phase.phase !== "betting" && phase.phase !== "roundOver") return; const bet = world.getSingleton(Bet); const score = world.getSingleton(Score); bet.amount = Math.min(bet.amount + 10, score.chips); }); commands.handle(BetLess, () => { const phase = world.getSingleton(GamePhase); if (phase.phase !== "betting" && phase.phase !== "roundOver") return; const bet = world.getSingleton(Bet); bet.amount = Math.max(bet.amount - 10, 10); }); } // ── Internal ───────────────────────────────────────── export function resolveRound( world: World, helpers: ReturnType, ): void { const playerCards = helpers.getHand(InPlayerHand); const dealerCards = helpers.getHand(InDealerHand); const score = world.getSingleton(Score); const bet = world.getSingleton(Bet); const outcome = determineOutcome(playerCards, dealerCards); const winnings = payout(outcome, bet.amount); score.chips += bet.amount + winnings; switch (outcome) { case "blackjack": case "win": score.wins++; break; case "lose": score.losses++; break; case "push": score.pushes++; break; } const messages: Record = { win: "You win!", lose: "Dealer wins.", push: "Push — tie!", blackjack: "Blackjack! You win 3:2!", }; world.setSingleton(GamePhase, { phase: "roundOver", message: `${messages[outcome]} Press N for new round.`, }); }