feat: add state management with full serialization and rebirth reset
This commit is contained in:
@@ -0,0 +1,125 @@
|
|||||||
|
// ==================== 游戏状态管理 ====================
|
||||||
|
|
||||||
|
export function createInitialState() {
|
||||||
|
return {
|
||||||
|
// 时间
|
||||||
|
day: 0,
|
||||||
|
year: 0,
|
||||||
|
age: 0,
|
||||||
|
reincarnation: 0,
|
||||||
|
|
||||||
|
// 玩家状态
|
||||||
|
alive: true,
|
||||||
|
identity: null,
|
||||||
|
stats: { body: 0, wisdom: 0, charm: 0, destiny: 0, business: 0, intelligence: 0 },
|
||||||
|
talents: [],
|
||||||
|
careers: {},
|
||||||
|
money: 0,
|
||||||
|
|
||||||
|
// 商店与增益
|
||||||
|
shopItems: {},
|
||||||
|
activeBuffs: {},
|
||||||
|
|
||||||
|
// 神器与世界
|
||||||
|
artifacts: {},
|
||||||
|
worldFlags: {},
|
||||||
|
npcs: {},
|
||||||
|
|
||||||
|
// 入侵事件
|
||||||
|
invasionsTriggered: { military_40: false, spiritual_45: false, political_50: false, final_60: false },
|
||||||
|
|
||||||
|
// 突破路线
|
||||||
|
breakthroughRoute: null,
|
||||||
|
|
||||||
|
// 运行状态
|
||||||
|
speed: 0,
|
||||||
|
paused: false,
|
||||||
|
logs: [],
|
||||||
|
triggeredEvents: new Set(),
|
||||||
|
|
||||||
|
// 跨轮传承
|
||||||
|
metaExp: {},
|
||||||
|
memories: [],
|
||||||
|
unlockedEntries: new Set(),
|
||||||
|
history: [],
|
||||||
|
unlockedShopPool: new Set(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetForRebirth(state) {
|
||||||
|
// 保存本轮记录
|
||||||
|
const record = {
|
||||||
|
reincarnation: state.reincarnation,
|
||||||
|
age: state.age,
|
||||||
|
day: state.day,
|
||||||
|
careers: { ...state.careers },
|
||||||
|
identity: state.identity,
|
||||||
|
stats: { ...state.stats },
|
||||||
|
money: state.money,
|
||||||
|
};
|
||||||
|
state.history.push(record);
|
||||||
|
if (state.history.length > 50) {
|
||||||
|
state.history.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保留跨轮数据
|
||||||
|
const preserved = {
|
||||||
|
reincarnation: state.reincarnation + 1,
|
||||||
|
metaExp: state.metaExp,
|
||||||
|
memories: state.memories,
|
||||||
|
unlockedEntries: state.unlockedEntries,
|
||||||
|
history: state.history,
|
||||||
|
artifacts: state.artifacts,
|
||||||
|
unlockedShopPool: state.unlockedShopPool,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 重置本轮数据
|
||||||
|
state.day = 0;
|
||||||
|
state.year = 0;
|
||||||
|
state.age = 0;
|
||||||
|
state.alive = true;
|
||||||
|
state.careers = {};
|
||||||
|
state.money = 0;
|
||||||
|
state.shopItems = {};
|
||||||
|
state.activeBuffs = {};
|
||||||
|
state.worldFlags = {};
|
||||||
|
state.npcs = {};
|
||||||
|
state.logs = [];
|
||||||
|
state.triggeredEvents = new Set();
|
||||||
|
state.identity = null;
|
||||||
|
state.stats = { body: 0, wisdom: 0, charm: 0, destiny: 0, business: 0, intelligence: 0 };
|
||||||
|
state.talents = [];
|
||||||
|
state.invasionsTriggered = { military_40: false, spiritual_45: false, political_50: false, final_60: false };
|
||||||
|
state.breakthroughRoute = null;
|
||||||
|
state.speed = 0;
|
||||||
|
state.paused = false;
|
||||||
|
|
||||||
|
// 恢复跨轮数据
|
||||||
|
state.reincarnation = preserved.reincarnation;
|
||||||
|
state.metaExp = preserved.metaExp;
|
||||||
|
state.memories = preserved.memories;
|
||||||
|
state.unlockedEntries = preserved.unlockedEntries;
|
||||||
|
state.history = preserved.history;
|
||||||
|
state.artifacts = preserved.artifacts;
|
||||||
|
state.unlockedShopPool = preserved.unlockedShopPool;
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeState(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
triggeredEvents: Array.from(state.triggeredEvents),
|
||||||
|
unlockedEntries: Array.from(state.unlockedEntries),
|
||||||
|
unlockedShopPool: Array.from(state.unlockedShopPool),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deserializeState(data, target) {
|
||||||
|
Object.assign(target, data, {
|
||||||
|
triggeredEvents: new Set(data.triggeredEvents || []),
|
||||||
|
unlockedEntries: new Set(data.unlockedEntries || []),
|
||||||
|
unlockedShopPool: new Set(data.unlockedShopPool || []),
|
||||||
|
});
|
||||||
|
return target;
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
// ==================== 状态管理测试 ====================
|
||||||
|
|
||||||
|
import { createInitialState, resetForRebirth, serializeState, deserializeState } from '../src/engine/state.js';
|
||||||
|
|
||||||
|
let passCount = 0;
|
||||||
|
let failCount = 0;
|
||||||
|
|
||||||
|
function assert(condition, message) {
|
||||||
|
if (condition) {
|
||||||
|
passCount++;
|
||||||
|
} else {
|
||||||
|
failCount++;
|
||||||
|
console.error('FAIL:', message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function testCreateInitialState() {
|
||||||
|
const state = createInitialState();
|
||||||
|
|
||||||
|
assert(state.day === 0, 'day should be 0');
|
||||||
|
assert(state.year === 0, 'year should be 0');
|
||||||
|
assert(state.age === 0, 'age should be 0');
|
||||||
|
assert(state.reincarnation === 0, 'reincarnation should be 0');
|
||||||
|
assert(state.alive === true, 'alive should be true');
|
||||||
|
assert(state.identity === null, 'identity should be null');
|
||||||
|
|
||||||
|
assert(state.stats.body === 0, 'stats.body should be 0');
|
||||||
|
assert(state.stats.wisdom === 0, 'stats.wisdom should be 0');
|
||||||
|
assert(state.stats.charm === 0, 'stats.charm should be 0');
|
||||||
|
assert(state.stats.destiny === 0, 'stats.destiny should be 0');
|
||||||
|
assert(state.stats.business === 0, 'stats.business should be 0');
|
||||||
|
assert(state.stats.intelligence === 0, 'stats.intelligence should be 0');
|
||||||
|
|
||||||
|
assert(Array.isArray(state.talents) && state.talents.length === 0, 'talents should be empty array');
|
||||||
|
assert(Object.keys(state.careers).length === 0, 'careers should be empty object');
|
||||||
|
assert(state.money === 0, 'money should be 0');
|
||||||
|
assert(Object.keys(state.shopItems).length === 0, 'shopItems should be empty object');
|
||||||
|
assert(Object.keys(state.activeBuffs).length === 0, 'activeBuffs should be empty object');
|
||||||
|
assert(Object.keys(state.artifacts).length === 0, 'artifacts should be empty object');
|
||||||
|
assert(Object.keys(state.worldFlags).length === 0, 'worldFlags should be empty object');
|
||||||
|
assert(Object.keys(state.npcs).length === 0, 'npcs should be empty object');
|
||||||
|
|
||||||
|
assert(state.invasionsTriggered.military_40 === false, 'invasionsTriggered.military_40 should be false');
|
||||||
|
assert(state.invasionsTriggered.spiritual_45 === false, 'invasionsTriggered.spiritual_45 should be false');
|
||||||
|
assert(state.invasionsTriggered.political_50 === false, 'invasionsTriggered.political_50 should be false');
|
||||||
|
assert(state.invasionsTriggered.final_60 === false, 'invasionsTriggered.final_60 should be false');
|
||||||
|
|
||||||
|
assert(state.breakthroughRoute === null, 'breakthroughRoute should be null');
|
||||||
|
assert(state.speed === 0, 'speed should be 0');
|
||||||
|
assert(state.paused === false, 'paused should be false');
|
||||||
|
assert(Array.isArray(state.logs) && state.logs.length === 0, 'logs should be empty array');
|
||||||
|
assert(state.triggeredEvents instanceof Set, 'triggeredEvents should be a Set');
|
||||||
|
assert(Object.keys(state.metaExp).length === 0, 'metaExp should be empty object');
|
||||||
|
assert(Array.isArray(state.memories) && state.memories.length === 0, 'memories should be empty array');
|
||||||
|
assert(state.unlockedEntries instanceof Set, 'unlockedEntries should be a Set');
|
||||||
|
assert(Array.isArray(state.history) && state.history.length === 0, 'history should be empty array');
|
||||||
|
assert(state.unlockedShopPool instanceof Set, 'unlockedShopPool should be a Set');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testResetForRebirth() {
|
||||||
|
const state = createInitialState();
|
||||||
|
|
||||||
|
// 模拟一轮游戏数据
|
||||||
|
state.reincarnation = 2;
|
||||||
|
state.age = 30;
|
||||||
|
state.day = 100;
|
||||||
|
state.money = 5000;
|
||||||
|
state.careers = { merchant: { level: 3 } };
|
||||||
|
state.identity = 'merchant';
|
||||||
|
state.stats = { body: 10, wisdom: 15, charm: 8, destiny: 5, business: 20, intelligence: 12 };
|
||||||
|
state.artifacts = { ringOfLuck: true };
|
||||||
|
state.metaExp = { merchant: 100 };
|
||||||
|
state.memories = ['memory1'];
|
||||||
|
state.unlockedEntries.add('entry1');
|
||||||
|
state.unlockedShopPool.add('item1');
|
||||||
|
state.history = [{ reincarnation: 0, age: 20 }];
|
||||||
|
|
||||||
|
resetForRebirth(state);
|
||||||
|
|
||||||
|
// 验证重置
|
||||||
|
assert(state.age === 0, 'age should reset to 0');
|
||||||
|
assert(state.day === 0, 'day should reset to 0');
|
||||||
|
assert(state.year === 0, 'year should reset to 0');
|
||||||
|
assert(state.alive === true, 'alive should reset to true');
|
||||||
|
assert(state.money === 0, 'money should reset to 0');
|
||||||
|
assert(Object.keys(state.careers).length === 0, 'careers should reset to empty');
|
||||||
|
assert(state.identity === null, 'identity should reset to null');
|
||||||
|
assert(state.stats.body === 0, 'stats.body should reset to 0');
|
||||||
|
assert(state.reincarnation === 3, 'reincarnation should increment by 1');
|
||||||
|
|
||||||
|
// 验证保留
|
||||||
|
assert(state.artifacts.ringOfLuck === true, 'artifacts should be preserved');
|
||||||
|
assert(state.metaExp.merchant === 100, 'metaExp should be preserved');
|
||||||
|
assert(state.memories.length === 1, 'memories should be preserved');
|
||||||
|
assert(state.unlockedEntries.has('entry1'), 'unlockedEntries should be preserved');
|
||||||
|
assert(state.unlockedShopPool.has('item1'), 'unlockedShopPool should be preserved');
|
||||||
|
assert(state.history.length === 2, 'history should have new record appended');
|
||||||
|
assert(state.history[state.history.length - 1].reincarnation === 2, 'new history record should have correct reincarnation');
|
||||||
|
assert(state.history[state.history.length - 1].age === 30, 'new history record should have correct age');
|
||||||
|
assert(state.history[state.history.length - 1].money === 5000, 'new history record should have correct money');
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSerializeDeserialize() {
|
||||||
|
const state = createInitialState();
|
||||||
|
state.triggeredEvents.add('event1');
|
||||||
|
state.triggeredEvents.add('event2');
|
||||||
|
state.unlockedEntries.add('entry1');
|
||||||
|
state.unlockedShopPool.add('item1');
|
||||||
|
state.unlockedShopPool.add('item2');
|
||||||
|
state.age = 25;
|
||||||
|
state.money = 1000;
|
||||||
|
|
||||||
|
const serialized = serializeState(state);
|
||||||
|
|
||||||
|
// 验证序列化后的 Set 变为 Array
|
||||||
|
assert(Array.isArray(serialized.triggeredEvents), 'serialized triggeredEvents should be Array');
|
||||||
|
assert(serialized.triggeredEvents.includes('event1'), 'serialized triggeredEvents should contain event1');
|
||||||
|
assert(Array.isArray(serialized.unlockedEntries), 'serialized unlockedEntries should be Array');
|
||||||
|
assert(Array.isArray(serialized.unlockedShopPool), 'serialized unlockedShopPool should be Array');
|
||||||
|
assert(serialized.unlockedShopPool.includes('item2'), 'serialized unlockedShopPool should contain item2');
|
||||||
|
|
||||||
|
// 验证 JSON 可序列化
|
||||||
|
const jsonStr = JSON.stringify(serialized);
|
||||||
|
const parsed = JSON.parse(jsonStr);
|
||||||
|
|
||||||
|
// 反序列化到新对象
|
||||||
|
const target = createInitialState();
|
||||||
|
deserializeState(parsed, target);
|
||||||
|
|
||||||
|
assert(target.triggeredEvents instanceof Set, 'deserialized triggeredEvents should be Set');
|
||||||
|
assert(target.triggeredEvents.has('event1'), 'deserialized triggeredEvents should have event1');
|
||||||
|
assert(target.triggeredEvents.has('event2'), 'deserialized triggeredEvents should have event2');
|
||||||
|
assert(target.unlockedEntries instanceof Set, 'deserialized unlockedEntries should be Set');
|
||||||
|
assert(target.unlockedEntries.has('entry1'), 'deserialized unlockedEntries should have entry1');
|
||||||
|
assert(target.unlockedShopPool instanceof Set, 'deserialized unlockedShopPool should be Set');
|
||||||
|
assert(target.unlockedShopPool.has('item1'), 'deserialized unlockedShopPool should have item1');
|
||||||
|
assert(target.age === 25, 'deserialized age should be 25');
|
||||||
|
assert(target.money === 1000, 'deserialized money should be 1000');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行测试
|
||||||
|
testCreateInitialState();
|
||||||
|
testResetForRebirth();
|
||||||
|
testSerializeDeserialize();
|
||||||
|
|
||||||
|
if (failCount === 0) {
|
||||||
|
console.log('All state tests PASS');
|
||||||
|
} else {
|
||||||
|
console.log(`${passCount} passed, ${failCount} failed`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user