import { describe, it, expect } from "vitest"; import { createGameContext } from "@/core/game"; import { registry } from "@/samples/regicide/commands"; import { createInitialState } from "@/samples/regicide/state"; import { buildEnemyDeck, buildTavernDeck, createAllCards, createCard, createEnemy, getCardValue, isEnemyDefeated, } from "@/samples/regicide/utils"; import { Mulberry32RNG } from "@/utils/rng"; import { CARD_VALUES, ENEMY_COUNT, FACE_CARDS, INITIAL_HAND_SIZE, } from "@/samples/regicide/constants"; import { PlayerType } from "@/samples/regicide/types"; describe("Regicide - Utils", () => { describe("getCardValue", () => { it("should return correct value for number cards", () => { expect(getCardValue("A")).toBe(1); expect(getCardValue("5")).toBe(5); expect(getCardValue("10")).toBe(10); }); it("should return correct value for face cards", () => { expect(getCardValue("J")).toBe(10); expect(getCardValue("Q")).toBe(15); expect(getCardValue("K")).toBe(20); }); }); describe("createCard", () => { it("should create a card with correct properties", () => { const card = createCard("spades_A", "spades", "A"); expect(card.id).toBe("spades_A"); expect(card.suit).toBe("spades"); expect(card.rank).toBe("A"); expect(card.value).toBe(1); }); }); describe("createEnemy", () => { it("should create an enemy with correct HP", () => { const enemy = createEnemy("enemy_0", "J", "spades"); expect(enemy.rank).toBe("J"); expect(enemy.value).toBe(10); expect(enemy.hp).toBe(20); expect(enemy.maxHp).toBe(20); }); it("should create enemy with different values for different ranks", () => { const jEnemy = createEnemy("enemy_0", "J", "spades"); const qEnemy = createEnemy("enemy_1", "Q", "hearts"); const kEnemy = createEnemy("enemy_2", "K", "diamonds"); expect(jEnemy.value).toBe(10); expect(qEnemy.value).toBe(15); expect(kEnemy.value).toBe(20); expect(jEnemy.hp).toBe(20); expect(qEnemy.hp).toBe(30); expect(kEnemy.hp).toBe(40); }); }); describe("createAllCards", () => { it("should create 52 cards", () => { const cards = createAllCards(); expect(Object.keys(cards).length).toBe(52); }); it("should have all suits and ranks", () => { const cards = createAllCards(); const suits = ["spades", "hearts", "diamonds", "clubs"]; const ranks = [ "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", ]; for (const suit of suits) { for (const rank of ranks) { const id = `${suit}_${rank}`; expect(cards[id]).toBeDefined(); expect(cards[id].suit).toBe(suit); expect(cards[id].rank).toBe(rank); } } }); }); describe("buildEnemyDeck", () => { it("should create 12 enemies (J/Q/K)", () => { const rng = new Mulberry32RNG(12345); const deck = buildEnemyDeck(rng); expect(deck.length).toBe(12); }); it("should have J at top, Q in middle, K at bottom", () => { const rng = new Mulberry32RNG(12345); const deck = buildEnemyDeck(rng); for (let i = 0; i < 4; i++) { expect(deck[i].rank).toBe("J"); } for (let i = 4; i < 8; i++) { expect(deck[i].rank).toBe("Q"); } for (let i = 8; i < 12; i++) { expect(deck[i].rank).toBe("K"); } }); }); describe("buildTavernDeck", () => { it("should create 40 cards (A-10)", () => { const rng = new Mulberry32RNG(12345); const deck = buildTavernDeck(rng); expect(deck.length).toBe(40); }); it("should not contain face cards", () => { const rng = new Mulberry32RNG(12345); const deck = buildTavernDeck(rng); for (const card of deck) { expect(FACE_CARDS.includes(card.rank)).toBe(false); } }); }); describe("isEnemyDefeated", () => { it("should return true when enemy HP <= 0", () => { const enemy = createEnemy("enemy_0", "J", "spades"); expect(isEnemyDefeated(enemy)).toBe(false); enemy.hp = 0; expect(isEnemyDefeated(enemy)).toBe(true); enemy.hp = -5; expect(isEnemyDefeated(enemy)).toBe(true); }); it("should return false for null enemy", () => { expect(isEnemyDefeated(null)).toBe(false); }); }); }); describe("Regicide - Commands", () => { function createTestContext() { const initialState = createInitialState(); return createGameContext(registry, initialState); } function setupTestGame(game: ReturnType) { const cards = createAllCards(); const rng = new Mulberry32RNG(12345); const enemyDeck = buildEnemyDeck(rng); const tavernDeck = buildTavernDeck(rng); game.produce((state) => { state.cards = cards; state.playerCount = 2; state.currentPlayerIndex = 0; state.enemyDeck = [...enemyDeck]; state.currentEnemy = { ...enemyDeck[0] }; for (const card of tavernDeck) { state.regions.tavernDeck.childIds.push(card.id); } for (let i = 0; i < 6; i++) { const card1 = tavernDeck[i]; const card2 = tavernDeck[i + 6]; card1.regionId = "hand_player1"; card2.regionId = "hand_player2"; state.playerHands.player1.push(card1.id); state.playerHands.player2.push(card2.id); state.regions.hand_player1.childIds.push(card1.id); state.regions.hand_player2.childIds.push(card2.id); } }); } describe("play command", () => { it("should deal damage to current enemy", async () => { const game = createTestContext(); setupTestGame(game); const enemyHpBefore = game.value.currentEnemy!.hp; const cardId = game.value.playerHands.player1[0]; const card = game.value.cards[cardId]; const result = await game.run(`play player1 ${cardId}`); expect(game.value.currentEnemy!.hp).toBe(enemyHpBefore - card.value); }); it("should double damage for clubs suit", async () => { const game = createTestContext(); setupTestGame(game); game.produce((state) => { state.cards["clubs_5"] = createCard("clubs_5", "clubs", "5"); state.playerHands.player1.push("clubs_5"); state.regions.hand_player1.childIds.push("clubs_5"); }); const clubsCardId = "clubs_5"; const enemyHpBefore = game.value.currentEnemy!.hp; const card = game.value.cards[clubsCardId]; await game.run(`play player1 ${clubsCardId}`); expect(game.value.currentEnemy!.hp).toBe(enemyHpBefore - card.value * 2); }); }); describe("pass command", () => { it("should allow player to pass", async () => { const game = createTestContext(); setupTestGame(game); const result = await game.run("pass player1"); expect(result.success).toBe(true); }); }); describe("check-enemy command", () => { it("should detect defeated enemy and reveal next", async () => { const game = createTestContext(); setupTestGame(game); const firstEnemy = game.value.currentEnemy!; game.produce((state) => { state.currentEnemy!.hp = 0; }); await game.run("check-enemy"); expect(game.value.regions.discardPile.childIds).toContain(firstEnemy.id); expect(game.value.currentEnemy).not.toBe(firstEnemy); }); it("should not defeat enemy if HP > 0", async () => { const game = createTestContext(); setupTestGame(game); const currentEnemyId = game.value.currentEnemy!.id; await game.run("check-enemy"); expect(game.value.currentEnemy!.id).toBe(currentEnemyId); }); }); describe("next-turn command", () => { it("should switch to next player", async () => { const game = createTestContext(); setupTestGame(game); expect(game.value.currentPlayerIndex).toBe(0); await game.run("next-turn"); expect(game.value.currentPlayerIndex).toBe(1); }); it("should wrap around to first player", async () => { const game = createTestContext(); setupTestGame(game); game.produce((state) => { state.currentPlayerIndex = 1; }); await game.run("next-turn"); expect(game.value.currentPlayerIndex).toBe(0); }); }); }); describe("Regicide - Game Flow", () => { function createTestContext() { const initialState = createInitialState(); return createGameContext(registry, initialState); } it("should complete a full turn cycle", async () => { const game = createTestContext(); const cards = createAllCards(); const rng = new Mulberry32RNG(12345); const enemyDeck = buildEnemyDeck(rng); const tavernDeck = buildTavernDeck(rng); game.produce((state) => { state.cards = cards; state.playerCount = 1; state.currentPlayerIndex = 0; state.enemyDeck = [...enemyDeck.slice(1)]; state.currentEnemy = { ...enemyDeck[0] }; for (const card of tavernDeck) { state.regions.tavernDeck.childIds.push(card.id); } for (let i = 0; i < 6; i++) { const card = tavernDeck[i]; card.regionId = "hand_player1"; state.playerHands.player1.push(card.id); state.regions.hand_player1.childIds.push(card.id); } }); const cardId = game.value.playerHands.player1[0]; const card = game.value.cards[cardId]; const enemyHpBefore = game.value.currentEnemy!.hp; await game.run(`play player1 ${cardId}`); expect(game.value.currentEnemy!.hp).toBeLessThan(enemyHpBefore); }); it("should win game when all enemies defeated", async () => { const game = createTestContext(); const cards = createAllCards(); const rng = new Mulberry32RNG(12345); const tavernDeck = buildTavernDeck(rng); game.produce((state) => { state.cards = cards; state.playerCount = 1; state.currentPlayerIndex = 0; state.enemyDeck = []; state.currentEnemy = null; for (const card of tavernDeck) { state.regions.tavernDeck.childIds.push(card.id); } }); game.produce((state) => { state.phase = "victory"; state.winner = true; }); expect(game.value.phase).toBe("victory"); expect(game.value.winner).toBe(true); }); });