import { setCareerConfig, getCareerConfig, checkUnlocks, calcDailyIncome, calcDailyExp, checkLevelUp, STAT_MAP, } from '../src/engine/careerEngine.js'; const careersConfig = { careers: [ { id: 'student', name: '学童', type: 'scholar', unlockConditions: { type: 'age', op: '>=', value: 6 }, dailyIncome: 1, expCurve: { base: 10, factor: 1.15 }, }, { id: 'book_boy', name: '书童', type: 'scholar', unlockConditions: { type: 'careerLevel', op: '>=', careerId: 'student', value: 100 }, dailyIncome: 2, expCurve: { base: 50, factor: 1.2 }, }, { id: 'scholar', name: '书生', type: 'scholar', unlockConditions: { type: 'and', conditions: [ { type: 'careerLevel', op: '>=', careerId: 'student', value: 250 }, { type: 'age', op: '>=', value: 14 }, ], }, dailyIncome: -3, expCurve: { base: 100, factor: 1.25 }, }, { 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 }, }, { id: 'taoist_priest', name: '道士', type: 'immortal', unlockConditions: { type: 'and', conditions: [ { type: 'age', op: '>=', value: 10 }, { type: 'hasItem', itemId: 'taoist_license' }, ], }, dailyIncome: 1, expCurve: { base: 30, factor: 1.22 }, }, ], }; 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 testCheckUnlocks() { // Test age-based unlock let state = { age: 6, stats: {}, careers: {} }; checkUnlocks(careersConfig, state); assertTrue(state.careers.student !== undefined, 'student should unlock at age 6'); assertEqual(state.careers.student.level, 0, 'student initial level'); assertEqual(state.careers.student.exp, 0, 'student initial exp'); // Test career level unlock state = { age: 20, stats: {}, careers: { student: { level: 100, exp: 0 } } }; checkUnlocks(careersConfig, state); assertTrue(state.careers.book_boy !== undefined, 'book_boy should unlock when student reaches 100'); // Test combined unlock (student 250 + age 14) state = { age: 14, stats: { body: 20 }, careers: { student: { level: 250, exp: 0 } } }; checkUnlocks(careersConfig, state); assertTrue(state.careers.scholar !== undefined, 'scholar should unlock at student 250 and age 14'); // Test not unlocking when conditions not met state = { age: 5, stats: {}, careers: {} }; checkUnlocks(careersConfig, state); assertTrue(state.careers.student === undefined, 'student should not unlock at age 5'); // Test soldier unlock (age 14 + body >= 10) state = { age: 14, stats: { body: 10 }, careers: {} }; checkUnlocks(careersConfig, state); assertTrue(state.careers.soldier !== undefined, 'soldier should unlock at age 14 with body 10'); // Test taoist_priest unlock (age 10 + hasItem) state = { age: 10, stats: {}, shopItems: { taoist_license: 1 }, careers: {} }; checkUnlocks(careersConfig, state); assertTrue(state.careers.taoist_priest !== undefined, 'taoist_priest should unlock at age 10 with taoist_license'); } function testCalcDailyIncome() { // Test basic income let state = { stats: { business: 0 }, careers: { student: { level: 0, exp: 0 } } }; let income = calcDailyIncome(careersConfig, state); assertEqual(income, 1, 'basic income with student only'); // Test multiple careers state = { stats: { business: 0 }, careers: { student: { level: 0, exp: 0 }, soldier: { level: 0, exp: 0 } } }; income = calcDailyIncome(careersConfig, state); assertEqual(income, 3, 'income with student + soldier'); // Test business bonus state = { stats: { business: 50 }, careers: { student: { level: 0, exp: 0 } } }; income = calcDailyIncome(careersConfig, state); assertEqual(income, 1.5, 'income with 50 business bonus'); // Test business cap at 100 state = { stats: { business: 150 }, careers: { student: { level: 0, exp: 0 } } }; income = calcDailyIncome(careersConfig, state); assertEqual(income, 2, 'income with business capped at 100'); } function testCalcDailyExp() { // Test basic exp let state = { stats: { wisdom: 0 }, metaExp: {} }; let exp = calcDailyExp(careersConfig, state, 'student'); assertEqual(exp, 10, 'basic student exp with 0 wisdom'); // Test stat bonus state = { stats: { wisdom: 50 }, metaExp: {} }; exp = calcDailyExp(careersConfig, state, 'student'); assertEqual(exp, 15, 'student exp with 50 wisdom bonus'); // Test meta exp bonus state = { stats: { wisdom: 0 }, metaExp: { student: 100 } }; exp = calcDailyExp(careersConfig, state, 'student'); const expectedMetaBonus = 1 + Math.log(2); assertEqual(exp, 10 * expectedMetaBonus, 'student exp with meta bonus'); // Test stat cap at 100 state = { stats: { wisdom: 150 }, metaExp: {} }; exp = calcDailyExp(careersConfig, state, 'student'); assertEqual(exp, 20, 'student exp with wisdom capped at 100'); // Test military type uses body stat state = { stats: { body: 50 }, metaExp: {} }; exp = calcDailyExp(careersConfig, state, 'soldier'); assertEqual(exp, 30, 'soldier exp with 50 body bonus'); } function testCheckLevelUp() { // Test single level up let state = { careers: { student: { level: 0, exp: 12 } } }; checkLevelUp(careersConfig, state); assertEqual(state.careers.student.level, 1, 'student should level up once'); assertEqual(state.careers.student.exp, 2, 'remaining exp after level up'); // Test multiple level ups state = { careers: { student: { level: 0, exp: 30 } } }; checkLevelUp(careersConfig, state); assertEqual(state.careers.student.level, 2, 'student should level up twice'); // need0=10, need1=11.5, total=21.5, remaining=8.5 assertTrue(Math.abs(state.careers.student.exp - 8.5) < 0.0001, 'remaining exp after 2 level ups'); // Test no level up when exp insufficient state = { careers: { student: { level: 0, exp: 5 } } }; checkLevelUp(careersConfig, state); assertEqual(state.careers.student.level, 0, 'student should not level up with 5 exp'); assertEqual(state.careers.student.exp, 5, 'exp unchanged'); } function runTests() { let passed = 0; let failed = 0; const tests = [ { name: 'testCheckUnlocks', fn: testCheckUnlocks }, { name: 'testCalcDailyIncome', fn: testCalcDailyIncome }, { name: 'testCalcDailyExp', fn: testCalcDailyExp }, { name: 'testCheckLevelUp', fn: testCheckLevelUp }, ]; 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 careerEngine tests PASS'); } else { process.exit(1); } } runTests();