feat: add game tick loop integrating all engines
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
import {
|
||||
tick,
|
||||
startGameLoop,
|
||||
stopGameLoop,
|
||||
setSpeed,
|
||||
} from '../src/engine/tickLoop.js';
|
||||
|
||||
// ==================== 测试配置 ====================
|
||||
|
||||
const careerConfig = {
|
||||
careers: [
|
||||
{
|
||||
id: 'student',
|
||||
name: '学童',
|
||||
type: 'scholar',
|
||||
unlockConditions: { type: 'age', op: '>=', value: 6 },
|
||||
dailyIncome: 1,
|
||||
expCurve: { base: 10, factor: 1.15 },
|
||||
},
|
||||
{
|
||||
id: 'soldier',
|
||||
name: '士兵',
|
||||
type: 'military',
|
||||
unlockConditions: {
|
||||
type: 'and',
|
||||
conditions: [
|
||||
{ type: 'age', op: '>=', value: 14 },
|
||||
{ type: 'stat', op: '>=', stat: 'body', value: 10 },
|
||||
],
|
||||
},
|
||||
dailyIncome: 2,
|
||||
expCurve: { base: 20, factor: 1.18 },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const eventConfig = {
|
||||
events: [
|
||||
{
|
||||
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: 'daily_training',
|
||||
name: '日常修炼',
|
||||
trigger: { type: 'random', pool: 'normal' },
|
||||
weight: 50,
|
||||
effects: [{ type: 'addStat', stat: 'body', value: 1 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const shopConfig = {
|
||||
artifacts: [],
|
||||
};
|
||||
|
||||
// ==================== 测试辅助函数 ====================
|
||||
|
||||
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 testTickAdvancesDay() {
|
||||
const state = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 5,
|
||||
stats: {},
|
||||
careers: {},
|
||||
money: 0,
|
||||
worldFlags: {},
|
||||
};
|
||||
|
||||
tick(state, 1, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state.day, 1, 'day should advance by 1');
|
||||
}
|
||||
|
||||
function testTickUnlocksCareer() {
|
||||
const state = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 5,
|
||||
stats: {},
|
||||
careers: {},
|
||||
money: 0,
|
||||
worldFlags: {},
|
||||
};
|
||||
|
||||
// Age 5 -> 6 after 365 days
|
||||
tick(state, 365, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state.age, 6, 'age should be 6 after 365 days');
|
||||
assertTrue(state.careers.student !== undefined, 'student should unlock at age 6');
|
||||
}
|
||||
|
||||
function testTickIncome() {
|
||||
const state = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 10,
|
||||
stats: {},
|
||||
careers: { student: { level: 0, exp: 0 } },
|
||||
money: 0,
|
||||
worldFlags: {},
|
||||
};
|
||||
|
||||
tick(state, 10, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state.money, 10, 'money should increase by 10 (1 per day from student)');
|
||||
}
|
||||
|
||||
function testTickExp() {
|
||||
const state = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 10,
|
||||
stats: { wisdom: 0 },
|
||||
careers: { student: { level: 0, exp: 0 } },
|
||||
money: 0,
|
||||
metaExp: {},
|
||||
worldFlags: {},
|
||||
};
|
||||
|
||||
tick(state, 1, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state.careers.student.level, 1, 'student should level up to 1 after 1 day');
|
||||
assertEqual(state.careers.student.exp, 0, 'remaining exp should be 0 after level up');
|
||||
|
||||
// Test level up: need 10 exp for level 1, 11.5 for level 2
|
||||
const state2 = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 10,
|
||||
stats: { wisdom: 0 },
|
||||
careers: { student: { level: 0, exp: 0 } },
|
||||
money: 0,
|
||||
metaExp: {},
|
||||
worldFlags: {},
|
||||
};
|
||||
|
||||
tick(state2, 2, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state2.careers.student.level, 1, 'student should level up to 1');
|
||||
assertEqual(state2.careers.student.exp, 10, 'remaining exp should be 10 (20 - 10)');
|
||||
}
|
||||
|
||||
function testAgeEventTriggers() {
|
||||
const state = {
|
||||
alive: true,
|
||||
paused: false,
|
||||
day: 0,
|
||||
year: 0,
|
||||
age: 39,
|
||||
stats: {},
|
||||
careers: {},
|
||||
money: 0,
|
||||
worldFlags: {},
|
||||
triggeredEvents: new Set(),
|
||||
};
|
||||
|
||||
// 365 ticks to go from age 39 to 40
|
||||
tick(state, 365, careerConfig, eventConfig, shopConfig);
|
||||
assertEqual(state.age, 40, 'age should be 40');
|
||||
assertEqual(state.paused, true, 'game should pause on choice event');
|
||||
assertTrue(state.currentEvent !== undefined, 'currentEvent should be set');
|
||||
assertEqual(state.currentEvent.id, 'invasion_military_40', 'should trigger invasion event at 40');
|
||||
}
|
||||
|
||||
// ==================== 运行测试 ====================
|
||||
|
||||
function runTests() {
|
||||
const tests = [
|
||||
{ name: 'testTickAdvancesDay', fn: testTickAdvancesDay },
|
||||
{ name: 'testTickUnlocksCareer', fn: testTickUnlocksCareer },
|
||||
{ name: 'testTickIncome', fn: testTickIncome },
|
||||
{ name: 'testTickExp', fn: testTickExp },
|
||||
{ name: 'testAgeEventTriggers', fn: testAgeEventTriggers },
|
||||
];
|
||||
|
||||
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 tickLoop tests PASS');
|
||||
process.exit(0);
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
runTests();
|
||||
Reference in New Issue
Block a user