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); }); });