架构文档
本文档说明 GameEngine 与各子系统之间的关系。
总体架构
┌──────────────────────────┐
│ GameEngine │
│ (orchestrates everything)│
└──────────┬───────────────┘
│
┌──────────────┬─────────────────┼─────────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌──────────┐ ┌───────────┐
│ GameState│ │ PluginMgr │ │ ActionQueue │ │ EventBus │ │StateValidator│
│ (immutable)│ │ + Plugins │ │ (maxActions/ │ │ + Listeners │ │ + StateCheck│
└────┬─────┘ └─────┬──────┘ │ tick cap) │ └────┬─────┘ └─────┬─────┘
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ rules ┌─────▼──────┐ │ │ │
│ ──────▶│ RuleEngine │ │ │ │
│ └─────┬──────┘ │ │ │
│ │ validate ok │ │ │
│ ▼ ▼ │ │
│ ┌──────────────┐ ┌─────────────┐ │ │
│ │ActionExecutor│◀─│ process │ │ │
│ │ + handlers │ │ Action │ │ │
│ └──────┬───────┘ └─────┬───────┘ │ │
│ │ { state, events } │ │ │
│ ▼ ▼ │ │
│ bumpVersion ┌────────────────────────┐ emit ────┘ │
└─────────────│ setState(updater) │ │
│ state.version + 1 │ │
└───────────┬───────────┘ │
│ │
│ ┌───────────────┐ │
└──────────▶│ ActionHistory │ │
record │ + sequence │ │
└──────┬───────┘ │
│ │
▼ ▼
┌──────────────────────────────────┐
│ RandomProvider (Math / Seeded) │
│ GameSnapshot / ReplayEngine │
│ compareStates │
└──────────────────────────────────┘各子系统的职责
GameEngine
GameEngine 是引擎门面,组装并驱动所有子系统:
GameState(不可变对局状态)PluginManager(插件注册表)RuleEngine(按注册顺序短路校验)ActionExecutor(type → handler 派发)EventBus(同步派发事件)ActionHistory(只追加日志)StateValidator(状态不变量)RandomProvider(数学/带种子随机)GameContextImpl(Plugin 与引擎之间的桥接)
GameEngine 同时实现 EngineInternal 接口,提供 random / setState / emitEvent / addStateCheck / removeStateCheck 给 GameContextImpl 使用。
GameContext
GameContext 是 Plugin 与 Engine 交互的唯一许可入口。Plugin 在 setup、EventHandler 与(经由 RuleContext)Rule 中都会收到一个 GameContext。
Mutation primitive(moveCard / setVariable / addPlayer ...)执行不可变更新,且每次调用各 state.version + 1。Action handler 直接接收原始 GameState 并使用 stateUtils.*(引擎对每个 action 仅 bump 一次版本)。
更上层的辅助方法(drawCards / dispatch)通过引擎的 ActionQueue 入队新 action,借助 maxActionsPerTick 限制避免无限递归。
GameState
GameState 是不可变对局状态。任何修改都会产生新的 GameState 并把 version 递增 1。
players / cards / zones 使用 Record<id, T> 而非数组,让查找/删除/移动操作均为 O(1) 且避免线性扫描。
Action / ActionExecutor
- Action — 玩家或插件发起的意图(
DRAW_CARD/PLAY_CARD/MOVE_CARD/END_TURN/PASS与插件自定义类型) - ActionExecutor — 把 Action 翻译为
{ state, events }的纯函数派发器
ActionExecutor 不会 bump state.version,也不会补全事件元信息 —— 这些由上层 GameEngine 统一处理。
Rule / RuleEngine
- Rule — 决定一个 Action 是否可以应用到 GameState
- RuleEngine — 按注册顺序依次执行所有已注册 Rule,遇到首个
deny即短路
Rule 实现只能读取上下文(RuleContext.state / RuleContext.action / RuleContext.game),不能修改 state。
Event / EventBus
emit 按订阅顺序同步调用匹配的监听器;监听器抛错会中断后续派发(fail-fast)。
Plugin / PluginManager
- GamePlugin — 把一组 Rule、ActionHandler、EventHandler 与可选的
setup打包成可注册到引擎的单元 PluginManager— 按插入顺序持有已注册插件;本身不调用setup,那是GameEngine的职责
History / Snapshot
ActionHistory— 已成功执行 Action 的只追加日志,是回放的基础GameSnapshot— 在特定 version 处捕获 GameState 的一份深拷贝
Replay
ReplayEngine 基于 (initialState, actionHistory) 确定性回放:
- 显式传入 seed,构造新的
SeededRandomProvider保证随机序列与原始一致 - 安装初始状态后跳过插件 setup,避免 setup 内的洗牌等随机操作重新执行
- 若初始状态处于
waiting,会调用start()重放GAME_STARTED事件链
Validation
StateValidator— 对 GameState 执行结构完整性检查(8 项内置不变量 + 插件可注册addCheck)compareStates— 比较两个 GameState 是否逻辑等价,用于回放后的等价校验
Random
RandomProvider 对随机性做了一层抽象,使游戏具备可确定性:
MathRandomProvider— 包装Math.random(),不可确定性SeededRandomProvider— 基于 Mulberry32 PRNG,相同种子 → 相同序列
SeededRandomProvider 是回放能力的基础。
dispatch 完整流程
dispatch(Action)
│
▼
ActionQueue(非递归;一次处理一个 action)
│
▼
RuleEngine.validate(state, action, ctx)
│
├─ 拒绝 → state 不变,返回 denied
│
└─ 通过 → ActionExecutor.execute(state, action, random) → { state, events }
│
▼
state.version + 1(引擎统一 bump 一次)
│
▼
emit 每个事件(同步;handler 可入队新 action)
│
├─ 成功时:将 ActionRecord 追加到 ActionHistory
└─ 校验 state(除非 validationMode 为 'none')由 event handler 触发的 dispatch 进入队尾,在同一个 tick 内处理,受 maxActionsPerTick 约束以打断意外的无限循环。
设计要点
- Engine 不知游戏规则 — 任何游戏专属逻辑都不在 Engine 出现,全部交给 Plugin
- 不可变 State — Plugin 无法直接修改
engine.state,必须通过stateUtils.*或 GameContext mutation primitive - Action ≠ Event — Action 是"我要做什么",Event 是"已经发生了什么"
- ActionQueue 防递归 — EventHandler 内
ctx.dispatch的新动作进队尾而非递归栈 - Plugin 隔离 — Plugin 通过
GameContext与 Engine 交互 - 确定性可回放 — 所有随机决策必须经
ctx.random