148 lines
3.9 KiB
TypeScript
148 lines
3.9 KiB
TypeScript
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<typeof import("./components").createCardHelpers>,
|
|
): 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<typeof import("./components").createCardHelpers>,
|
|
): 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<string, string> = {
|
|
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.`,
|
|
});
|
|
}
|