Files
rebirthlife/test/eventEngine.test.js
T

356 lines
9.9 KiB
JavaScript

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