227 lines
6.7 KiB
JavaScript
227 lines
6.7 KiB
JavaScript
|
|
import { checkDeath, processDeath } from '../src/engine/deathEngine.js';
|
||
|
|
|
||
|
|
function assertEqual(actual, expected, message) {
|
||
|
|
if (actual !== expected) {
|
||
|
|
throw new Error(`${message}: expected ${expected}, got ${actual}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function assertTrue(value, message) {
|
||
|
|
if (!value) {
|
||
|
|
throw new Error(`${message}: expected true, got ${value}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function assertFalse(value, message) {
|
||
|
|
if (value) {
|
||
|
|
throw new Error(`${message}: expected false, got ${value}`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function createState(overrides = {}) {
|
||
|
|
return {
|
||
|
|
age: 0,
|
||
|
|
alive: true,
|
||
|
|
stats: { body: 10 },
|
||
|
|
worldFlags: {},
|
||
|
|
careers: {},
|
||
|
|
metaExp: {},
|
||
|
|
...overrides,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const careersConfig = {
|
||
|
|
careers: [
|
||
|
|
{
|
||
|
|
id: 'student',
|
||
|
|
expCurve: { base: 10, factor: 1.15 },
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'soldier',
|
||
|
|
expCurve: { base: 20, factor: 1.18 },
|
||
|
|
},
|
||
|
|
],
|
||
|
|
};
|
||
|
|
|
||
|
|
function testCheckDeathOldAge() {
|
||
|
|
const state = createState({ age: 100 });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertTrue(result, 'should die at age 100');
|
||
|
|
assertFalse(state.alive, 'alive should be false');
|
||
|
|
assertEqual(state.deathReason, '寿终正寝', 'death reason should be 寿终正寝');
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathBodyZero() {
|
||
|
|
const state = createState({ age: 30, stats: { body: 0 } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertTrue(result, 'should die when body <= 0');
|
||
|
|
assertFalse(state.alive, 'alive should be false');
|
||
|
|
assertEqual(state.deathReason, '体弱身亡', 'death reason should be 体弱身亡');
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathNotDead() {
|
||
|
|
const state = createState({ age: 30, stats: { body: 5 } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertFalse(result, 'should not die when young and body > 0');
|
||
|
|
assertTrue(state.alive, 'should remain alive');
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathMilitaryInvasion() {
|
||
|
|
// Mock Math.random to always return 0.01 (below 0.05 threshold)
|
||
|
|
const originalRandom = Math.random;
|
||
|
|
Math.random = () => 0.01;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const state = createState({ age: 45, stats: { body: 5 } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertTrue(result, 'should die from military invasion');
|
||
|
|
assertFalse(state.alive, 'alive should be false');
|
||
|
|
assertEqual(state.deathReason, '死于蛮族入侵', 'death reason should be 死于蛮族入侵');
|
||
|
|
} finally {
|
||
|
|
Math.random = originalRandom;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathMilitaryInvasionResisted() {
|
||
|
|
// Mock Math.random to always return 0.01 (below 0.05 threshold)
|
||
|
|
const originalRandom = Math.random;
|
||
|
|
Math.random = () => 0.01;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const state = createState({ age: 45, stats: { body: 5 }, worldFlags: { invasion_military_resisted: true } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
// Since military is resisted, should fall through to next condition (age >= 60 random check)
|
||
|
|
// But age is 45, so not dead
|
||
|
|
assertFalse(result, 'should not die when military invasion resisted');
|
||
|
|
assertTrue(state.alive, 'should remain alive when resisted');
|
||
|
|
} finally {
|
||
|
|
Math.random = originalRandom;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathSpiritualInvasion() {
|
||
|
|
const originalRandom = Math.random;
|
||
|
|
let callCount = 0;
|
||
|
|
Math.random = () => {
|
||
|
|
callCount++;
|
||
|
|
return callCount === 1 ? 0.1 : 0.01;
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const state = createState({ age: 50, stats: { body: 5 } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertTrue(result, 'should die from spiritual invasion');
|
||
|
|
assertFalse(state.alive, 'alive should be false');
|
||
|
|
assertEqual(state.deathReason, '被天魔吞噬', 'death reason should be 被天魔吞噬');
|
||
|
|
} finally {
|
||
|
|
Math.random = originalRandom;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function testCheckDeathPoliticalInvasion() {
|
||
|
|
const originalRandom = Math.random;
|
||
|
|
let callCount = 0;
|
||
|
|
Math.random = () => {
|
||
|
|
callCount++;
|
||
|
|
return callCount <= 2 ? 0.1 : 0.01;
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const state = createState({ age: 55, stats: { body: 5 } });
|
||
|
|
const result = checkDeath(state);
|
||
|
|
assertTrue(result, 'should die from political invasion');
|
||
|
|
assertFalse(state.alive, 'alive should be false');
|
||
|
|
assertEqual(state.deathReason, '死于战乱', 'death reason should be 死于战乱');
|
||
|
|
} finally {
|
||
|
|
Math.random = originalRandom;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function testProcessDeath() {
|
||
|
|
const state = createState({
|
||
|
|
careers: {
|
||
|
|
student: { level: 2, exp: 5 },
|
||
|
|
soldier: { level: 1, exp: 10 },
|
||
|
|
},
|
||
|
|
metaExp: {},
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = processDeath(state, careersConfig);
|
||
|
|
|
||
|
|
// student: totalExp = 5 + floor(10 * 1.15^0) + floor(10 * 1.15^1) = 5 + 10 + 11 = 26
|
||
|
|
// gain = floor(26 * 0.1) = 2
|
||
|
|
assertEqual(result.metaGains.student, 2, 'student meta gain should be 2');
|
||
|
|
assertEqual(state.metaExp.student, 2, 'student metaExp should be 2');
|
||
|
|
|
||
|
|
// soldier: totalExp = 10 + floor(20 * 1.18^0) = 10 + 20 = 30
|
||
|
|
// gain = floor(30 * 0.1) = 3
|
||
|
|
assertEqual(result.metaGains.soldier, 3, 'soldier meta gain should be 3');
|
||
|
|
assertEqual(state.metaExp.soldier, 3, 'soldier metaExp should be 3');
|
||
|
|
}
|
||
|
|
|
||
|
|
function testProcessDeathAccumulates() {
|
||
|
|
const state = createState({
|
||
|
|
careers: {
|
||
|
|
student: { level: 0, exp: 10 },
|
||
|
|
},
|
||
|
|
metaExp: { student: 5 },
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = processDeath(state, careersConfig);
|
||
|
|
|
||
|
|
// student: totalExp = 10, gain = floor(10 * 0.1) = 1
|
||
|
|
assertEqual(result.metaGains.student, 1, 'student meta gain should be 1');
|
||
|
|
assertEqual(state.metaExp.student, 6, 'student metaExp should accumulate from 5 to 6');
|
||
|
|
}
|
||
|
|
|
||
|
|
function testProcessDeathNoCareerConfig() {
|
||
|
|
const state = createState({
|
||
|
|
careers: {
|
||
|
|
unknown: { level: 1, exp: 10 },
|
||
|
|
},
|
||
|
|
metaExp: {},
|
||
|
|
});
|
||
|
|
|
||
|
|
const result = processDeath(state, careersConfig);
|
||
|
|
|
||
|
|
assertEqual(Object.keys(result.metaGains).length, 0, 'no gains for unknown career');
|
||
|
|
}
|
||
|
|
|
||
|
|
function runTests() {
|
||
|
|
let passed = 0;
|
||
|
|
let failed = 0;
|
||
|
|
|
||
|
|
const tests = [
|
||
|
|
{ name: 'testCheckDeathOldAge', fn: testCheckDeathOldAge },
|
||
|
|
{ name: 'testCheckDeathBodyZero', fn: testCheckDeathBodyZero },
|
||
|
|
{ name: 'testCheckDeathNotDead', fn: testCheckDeathNotDead },
|
||
|
|
{ name: 'testCheckDeathMilitaryInvasion', fn: testCheckDeathMilitaryInvasion },
|
||
|
|
{ name: 'testCheckDeathMilitaryInvasionResisted', fn: testCheckDeathMilitaryInvasionResisted },
|
||
|
|
{ name: 'testCheckDeathSpiritualInvasion', fn: testCheckDeathSpiritualInvasion },
|
||
|
|
{ name: 'testCheckDeathPoliticalInvasion', fn: testCheckDeathPoliticalInvasion },
|
||
|
|
{ name: 'testProcessDeath', fn: testProcessDeath },
|
||
|
|
{ name: 'testProcessDeathAccumulates', fn: testProcessDeathAccumulates },
|
||
|
|
{ name: 'testProcessDeathNoCareerConfig', fn: testProcessDeathNoCareerConfig },
|
||
|
|
];
|
||
|
|
|
||
|
|
for (const test of tests) {
|
||
|
|
try {
|
||
|
|
test.fn();
|
||
|
|
console.log(` PASS: ${test.name}`);
|
||
|
|
passed++;
|
||
|
|
} catch (err) {
|
||
|
|
console.log(` FAIL: ${test.name} - ${err.message}`);
|
||
|
|
failed++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`\n${passed} passed, ${failed} failed`);
|
||
|
|
if (failed === 0) {
|
||
|
|
console.log('All deathEngine tests PASS');
|
||
|
|
} else {
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
runTests();
|