import { setEventConfig, getEventConfig, checkAgeEvents, pickRandomEvent, getAvailableChoices, executeEvent, } from '../src/engine/eventEngine.js'; import { evaluateCondition } from '../src/engine/conditionEvaluator.js'; import { applyEffect } from '../src/engine/effectApplier.js'; // ==================== 测试数据 ==================== const testConfig = { events: [ { id: 'daily_training', name: '日常修炼', trigger: { type: 'random', pool: 'normal' }, weight: 50, effects: [{ type: 'addStat', stat: '体质', value: 1 }], }, { id: 'study_hard', name: '刻苦学习', trigger: { type: 'random', pool: 'normal' }, weight: 50, effects: [{ type: 'addStat', stat: '悟性', value: 1 }], }, { id: 'invasion_military_40', name: '蛮族入侵', trigger: { type: 'age', value: 40 }, isChoice: true, choices: [ { id: 'resist', text: '率军抵抗', requirements: { type: 'careerLevel', careerId: 'soldier', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你率领大军击退了蛮族入侵!' }], }, { id: 'flee', text: '弃城逃跑', effects: [{ type: 'addLog', text: '你选择了弃城逃跑。' }], }, ], }, { id: 'invasion_spiritual_45', name: '天魔降世', trigger: { type: 'age', value: 45 }, isChoice: true, choices: [ { id: 'suppress', text: '出手镇压', requirements: { type: 'careerLevel', careerId: 'taoist', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你以无上道法镇压天魔!' }], }, { id: 'hide', text: '躲藏起来', effects: [{ type: 'addLog', text: '你选择了躲藏起来。' }], }, ], }, { id: 'invasion_political_50', name: '王朝崩坏', trigger: { type: 'age', value: 50 }, isChoice: true, choices: [ { id: 'assist', text: '辅佐明君', requirements: { type: 'stat', stat: '智力', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你以智谋辅佐明君!' }], }, { id: 'selfish', text: '独善其身', effects: [{ type: 'addLog', text: '你选择了独善其身。' }], }, ], }, { id: 'final_crisis_60', name: '最终危机', trigger: { type: 'age', value: 60 }, isChoice: true, choices: [ { id: 'general', text: '以将军之力平定天下', requirements: { type: 'careerLevel', careerId: 'soldier', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你以将军之力平定天下!' }], }, { id: 'chancellor', text: '以宰相之智治理国家', requirements: { type: 'stat', stat: '智力', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你以宰相之智治理国家!' }], }, { id: 'cultivator', text: '以修仙之道超脱轮回', requirements: { type: 'careerLevel', careerId: 'taoist', op: '>=', value: 50, }, effects: [{ type: 'addLog', text: '你以修仙之道超脱轮回!' }], }, { id: 'powerless', text: '无力回天', effects: [{ type: 'addLog', text: '你感到无力回天。' }], }, ], }, ], }; // ==================== 测试辅助函数 ==================== function assertEqual(actual, expected, message) { const actualStr = JSON.stringify(actual); const expectedStr = JSON.stringify(expected); if (actualStr !== expectedStr) { throw new Error( `${message}\n 期望: ${expectedStr}\n 实际: ${actualStr}` ); } } function assertTrue(value, message) { if (!value) { throw new Error(message || `期望为 true,实际为 ${value}`); } } function assertFalse(value, message) { if (value) { throw new Error(message || `期望为 false,实际为 ${value}`); } } // ==================== 测试用例 ==================== function testSetAndGetEventConfig() { setEventConfig(testConfig); const config = getEventConfig(); assertEqual(config.events.length, 6, '配置应有6个事件'); } function testCheckAgeEvents() { // 测试40岁触发入侵事件 const state = { age: 40, triggeredEvents: new Set(), }; const triggered = checkAgeEvents(testConfig, state); assertEqual(triggered.length, 1, '40岁应触发1个事件'); assertEqual(triggered[0].id, 'invasion_military_40', '40岁应触发蛮族入侵事件'); assertTrue( state.triggeredEvents.has('invasion_military_40'), '事件ID应加入triggeredEvents' ); // 再次检查,不应重复触发 const triggeredAgain = checkAgeEvents(testConfig, state); assertEqual(triggeredAgain.length, 0, '已触发的事件不应重复触发'); // 测试45岁触发 const state45 = { age: 45, triggeredEvents: new Set(), }; const triggered45 = checkAgeEvents(testConfig, state45); assertEqual(triggered45.length, 1, '45岁应触发1个事件'); assertEqual(triggered45[0].id, 'invasion_spiritual_45', '45岁应触发天魔降世事件'); // 测试50岁触发 const state50 = { age: 50, triggeredEvents: new Set(), }; const triggered50 = checkAgeEvents(testConfig, state50); assertEqual(triggered50.length, 1, '50岁应触发1个事件'); assertEqual(triggered50[0].id, 'invasion_political_50', '50岁应触发王朝崩坏事件'); // 测试60岁触发 const state60 = { age: 60, triggeredEvents: new Set(), }; const triggered60 = checkAgeEvents(testConfig, state60); assertEqual(triggered60.length, 1, '60岁应触发1个事件'); assertEqual(triggered60[0].id, 'final_crisis_60', '60岁应触发最终危机事件'); // 测试非触发年龄 const state30 = { age: 30, triggeredEvents: new Set(), }; const triggered30 = checkAgeEvents(testConfig, state30); assertEqual(triggered30.length, 0, '30岁不应触发任何事件'); } function testPickRandomEvent() { // 从normal池抽取 const state = { triggeredEvents: new Set(), }; const event = pickRandomEvent(testConfig, state, 'normal'); assertTrue(event !== null, '应从normal池抽到一个事件'); assertTrue( event.id === 'daily_training' || event.id === 'study_hard', '抽到的事件应在normal池中' ); // 测试空池 const emptyEvent = pickRandomEvent(testConfig, state, 'nonexistent'); assertEqual(emptyEvent, null, '不存在的池应返回null'); // 测试所有事件都被触发后 state.triggeredEvents.add('daily_training'); state.triggeredEvents.add('study_hard'); const noEvent = pickRandomEvent(testConfig, state, 'normal'); assertEqual(noEvent, null, '所有事件触发后应返回null'); } function testGetAvailableChoices() { // 测试无requirement的选项始终可用 const event = testConfig.events.find(e => e.id === 'invasion_military_40'); const stateNoSoldier = { careers: {}, }; const choicesNoSoldier = getAvailableChoices(event, stateNoSoldier); assertEqual(choicesNoSoldier.length, 1, '无士兵职业时应有1个可用选项'); assertEqual(choicesNoSoldier[0].id, 'flee', '可用选项应为逃跑'); // 测试满足requirement const stateWithSoldier = { careers: { soldier: { level: 50 }, }, }; const choicesWithSoldier = getAvailableChoices(event, stateWithSoldier); assertEqual(choicesWithSoldier.length, 2, '有士兵50级时应有2个可用选项'); // 测试最终危机事件 const finalEvent = testConfig.events.find(e => e.id === 'final_crisis_60'); const stateAll = { careers: { soldier: { level: 50 }, taoist: { level: 50 }, }, stats: { 智力: 50, }, }; const allChoices = getAvailableChoices(finalEvent, stateAll); assertEqual(allChoices.length, 4, '满足所有条件时应有4个可用选项'); const stateNone = { careers: {}, stats: {}, }; const noChoices = getAvailableChoices(finalEvent, stateNone); assertEqual(noChoices.length, 1, '不满足任何条件时应有1个可用选项(无力回天)'); assertEqual(noChoices[0].id, 'powerless', '唯一可用选项应为无力回天'); } function testExecuteEvent() { const event = testConfig.events.find(e => e.id === 'daily_training'); const state = { stats: {}, triggeredEvents: new Set(), }; executeEvent(event, state, applyEffect); assertEqual(state.stats['体质'], 1, '执行事件后体质应增加1'); assertTrue( state.triggeredEvents.has('daily_training'), '事件ID应加入triggeredEvents' ); } // ==================== 运行测试 ==================== function runTests() { const tests = [ { name: 'testSetAndGetEventConfig', fn: testSetAndGetEventConfig }, { name: 'testCheckAgeEvents', fn: testCheckAgeEvents }, { name: 'testPickRandomEvent', fn: testPickRandomEvent }, { name: 'testGetAvailableChoices', fn: testGetAvailableChoices }, { name: 'testExecuteEvent', fn: testExecuteEvent }, ]; let passed = 0; let failed = 0; for (const test of tests) { try { test.fn(); console.log(` PASS: ${test.name}`); passed++; } catch (err) { console.log(` FAIL: ${test.name}`); console.log(` ${err.message}`); failed++; } } console.log(`\n${passed} passed, ${failed} failed`); if (failed === 0) { console.log('All eventEngine tests PASS'); process.exit(0); } else { process.exit(1); } } runTests();