// Card definitions loaded from data/cards.json (verified against the owner's // physical 6th edition + Expansion Set #1), and physical-deck construction: // each printed copy of a card becomes one CardInstance with a stable id. import cardsData from "../data/cards.json"; export type CardSet = "basic" | "expansion1" | "expansion2"; export type CardType = | "attack" | "neutral" | "counteraction" | "neutral/counteraction" | "number" | "object" | "trap" | "artifact" | "special"; export interface CardDef { id: string; name: string; set: CardSet; cardType: CardType | null; subtypes?: string[]; los: boolean | null; /** Printed ADJACENT corner marking (Pick Lock, Master Key, Wizardblade). */ adjacent?: boolean; text: string | null; quantity: number | null; value?: number; // number cards alsoIn?: { set: CardSet; quantity: number }[]; faqRulings: string[]; } export interface CardInstance { /** e.g. "fireball#2" — stable across the whole game. */ instanceId: string; cardId: string; } const defs: CardDef[] = (cardsData as { cards: CardDef[] }).cards; const byId = new Map(defs.map((d) => [d.id, d])); export function cardDef(cardId: string): CardDef { const def = byId.get(cardId); if (!def) throw new Error(`unknown card: ${cardId}`); return def; } export function allCardDefs(): readonly CardDef[] { return defs; } /** * Build the physical deck for the chosen sets. The 6e basic deck is exactly * 125 cards; adding Expansion Set #1 adds exactly 75 more (including its own * number cards) — both counts verified against the owner's rulebook lists. */ export function buildDeck(sets: CardSet[]): CardInstance[] { const instances: CardInstance[] = []; for (const def of defs) { let copies = 0; if (sets.includes(def.set) && def.quantity != null) copies += def.quantity; for (const extra of def.alsoIn ?? []) { if (sets.includes(extra.set)) copies += extra.quantity; } for (let i = 1; i <= copies; i++) { instances.push({ instanceId: `${def.id}#${i}`, cardId: def.id }); } } return instances; } export function isNumberCard(cardId: string): boolean { return cardDef(cardId).cardType === "number"; } export function numberValue(cardId: string): number { const def = cardDef(cardId); if (def.cardType !== "number" || def.value == null) { throw new Error(`${cardId} is not a number card`); } return def.value; } /** TRAP! is discarded and redrawn if it comes up during the initial deal. */ export function isTrap(cardId: string): boolean { return cardId === "trap"; } /** Magic stones (Powerstone, Shieldstone, ...) — destroyed by Fireball. */ export function isMagicStone(cardId: string): boolean { return cardDef(cardId).subtypes?.includes("stone") ?? false; }