import { describe, it, expect } from 'vitest'; import { createGameHost, GameHost } from '@/core/game-host'; import { createGameContext, createGameCommandRegistry } from '@/core/game'; import type { CombatState, CombatGameContext } from '@/samples/slay-the-spire-like/combat/types'; import { createCombatState } from '@/samples/slay-the-spire-like/combat/state'; import { runCombat } from '@/samples/slay-the-spire-like/combat/procedure'; import { prompts } from '@/samples/slay-the-spire-like/combat/prompts'; import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory'; import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory'; import type { GameItemMeta, PlayerState } from '@/samples/slay-the-spire-like/progress/types'; import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision'; import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape'; import { encounterDesertData, enemyDesertData } from '@/samples/slay-the-spire-like/data'; function createTestMeta(name: string, shapeStr: string): GameItemMeta { const shape = parseShapeString(shapeStr); return { itemData: { type: 'weapon', name, shape: shapeStr, costType: 'energy', costCount: 1, targetType: 'single', price: 10, desc: '测试', }, shape, }; } function createTestInventory(): GridInventory { const inv = createGridInventory(6, 4); const meta1 = createTestMeta('短刀', 'oe'); const item1: InventoryItem = { id: 'item-1', shape: meta1.shape, transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } }, meta: meta1, }; placeItem(inv, item1); return inv; } function createTestCombatState(): CombatState { const inv = createTestInventory(); const playerState: PlayerState = { maxHp: 50, currentHp: 50, gold: 0 }; const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!; return createCombatState(playerState, inv, encounter); } function waitForPrompt(host: GameHost): Promise { return new Promise((resolve) => { const check = () => { if (host.activePromptSchema.value !== null) { resolve(); } else { setTimeout(check, 10); } }; check(); }); } describe('combat/procedure', () => { describe('runCombat with GameHost', () => { it('should start combat and prompt for player action', async () => { const registry = createGameCommandRegistry(); const initialState = createTestCombatState(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); expect(host.activePromptSchema.value).not.toBeNull(); expect(host.activePromptSchema.value?.name).toBe('play-card'); host._context._commands._cancel(); try { await combatPromise; } catch {} }); it('should accept play-card input', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); const state = host.state.value; const cardId = state.player.deck.hand[0]; const error = host.tryAnswerPrompt(prompts.playCard, cardId, state.enemyOrder[0]); expect(error).toBeNull(); host._context._commands._cancel(); try { await combatPromise; } catch {} }); it('should reject invalid card play', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); const error = host.tryAnswerPrompt(prompts.playCard, 'nonexistent-card'); expect(error).not.toBeNull(); host._context._commands._cancel(); try { await combatPromise; } catch {} }); it('should transition to end-turn after playing cards', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); const state = host.state.value; const cardId = state.player.deck.hand[0]; host.tryAnswerPrompt(prompts.playCard, cardId, state.enemyOrder[0]); await waitForPrompt(host); expect(host.activePromptSchema.value).not.toBeNull(); host._context._commands._cancel(); try { await combatPromise; } catch {} }); it('should accept end-turn', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); const error = host.tryAnswerPrompt(prompts.endTurn); expect(error).toBeNull(); host._context._commands._cancel(); try { await combatPromise; } catch {} }); }); describe('combat outcome', () => { it('should return victory when all enemies are dead', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => { const state = createTestCombatState(); for (const enemyId of state.enemyOrder) { state.enemies[enemyId].hp = 1; } return state; }, async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); let iterations = 0; while (host.status.value === 'running' && iterations < 100) { const state = host.state.value; if (host.activePromptSchema.value?.name === 'play-card') { const cardId = state.player.deck.hand[0]; if (cardId) { const targetId = state.enemyOrder.find(id => state.enemies[id].isAlive); host.tryAnswerPrompt(prompts.playCard, cardId, targetId); } } else if (host.activePromptSchema.value?.name === 'end-turn') { host.tryAnswerPrompt(prompts.endTurn); } await new Promise(r => setTimeout(r, 10)); iterations++; } if (host.status.value === 'running') { host._context._commands._cancel(); try { await combatPromise; } catch {} } }); }); describe('combat state transitions', () => { it('should track turn number across turns', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); host.tryAnswerPrompt(prompts.endTurn); await waitForPrompt(host); host._context._commands._cancel(); try { await combatPromise; } catch {} }); it('should reset energy at start of player turn', async () => { const registry = createGameCommandRegistry(); const host = new GameHost( registry, () => createTestCombatState(), async (ctx) => { return await runCombat(ctx); }, ); const combatPromise = host.start(42); await waitForPrompt(host); const state = host.state.value; expect(state.player.energy).toBe(state.player.maxEnergy); host._context._commands._cancel(); try { await combatPromise; } catch {} }); }); });