项目介绍
Card Game Engine 是一个可扩展、插件化、事件驱动的卡牌游戏引擎核心,使用 Node.js + TypeScript 实现。
设计哲学
Engine 不知游戏规则
引擎核心本身不包含任何具体游戏规则。所有规则、动作、事件处理都通过 Plugin 注入。Engine 只提供通用能力:
- 状态管理(
GameState+stateUtils) - 卡牌、牌堆、玩家、区域的不可变数据结构
- Action 派发与 Rule 校验
- 事件总线
- 插件生命周期(
setup) - 快照、回放、状态校验、随机源
引擎核心文件不应出现任何游戏专属字符串(uno / skip-card / wild_draw4 / doudizhu 等)。
不可变 State
GameState 是不可变的。任何修改都会产生新的 GameState 并把 version 递增 1:
- Action 处理路径上,引擎统一对每个 Action 仅
bump一次版本 GameContext的 mutation primitive(moveCard/setVariable/addPlayer...)每次调用各+1
通过 stateVersion 关联到 ActionHistory 与发出的 GameEvent,从而支持回放。
Action ≠ Event
- Action — "我要做什么",由玩家或插件发起的意图
- Event — "已经发生了什么",由 ActionHandler 在执行成功后产出
Action 触发 Event,Event 处理器可再触发新的 Action(链式 dispatch)。引擎通过 ActionQueue 把"递归 dispatch"转化为"迭代处理",避免调用栈无限增长。
Plugin 隔离
Plugin 通过 GameContext 与 Engine 交互,不直接拿到 engine 引用。Plugin 在 setup、事件处理器中,以及(经由 RuleContext)规则中都会收到一个 GameContext。
确定性可回放
所有随机决策必须经 ctx.random:
- 严禁使用
Math.random() - 使用
SeededRandomProvider+ 相同动作序列即可复现整局游戏 ReplayEngine基于(initialState, actionHistory)确定性回放
状态校验可扩展
StateValidator 内置 8 项通用不变量检查;通过 addStateCheck(name, check) 是通用能力,任意游戏可注入自己的不变量(如"牌堆总数恒为 54")。
模块划分
| 模块 | 路径 | 说明 |
|---|---|---|
| core | src/core/ | GameEngine / GameState / GameContext / GameConfig / stateUtils |
| card | src/card/ | Card / CardZone / Deck |
| player | src/player/ | Player |
| action | src/action/ | Action / ActionExecutor |
| event | src/event/ | GameEvent / EventBus |
| rule | src/rule/ | Rule / RuleEngine |
| plugin | src/plugin/ | GamePlugin / PluginManager |
| combination | src/combination/ | 通用牌型系统(Detector / Comparator / CardQuery) |
| history | src/history/ | ActionHistory / GameSnapshot |
| replay | src/replay/ | ReplayEngine |
| validation | src/validation/ | StateValidator / compareStates |
| random | src/random/ | RandomProvider(Math / Seeded) |
| plugins | src/plugins/ | 内置插件(StandardDeck / Turn / Draw / SimpleGame / Poker / Uno / Doudizhu) |
| server | src/server/ | Phase 6 Game Server Runtime(HTTP + WebSocket) |
| utils | src/utils/ | id 生成器 |
错误类型一览
引擎与子系统定义了如下错误类,便于上层按错误类型分支处理:
| 错误类 | 来源 | 触发场景 |
|---|---|---|
ActionQueueOverflowError | GameEngine | 单 tick 内 Action 数超过 maxActionsPerTick |
EngineDestroyedError | GameEngine | 引擎销毁后再调用公共 API |
StateValidationError | GameEngine | StateValidator.validate 检测到违规 |
UnknownActionError | ActionExecutor | 未注册对应 type 的 handler |
DuplicateActionHandlerError | ActionExecutor | 同一 type 被重复注册 |
DuplicateRuleError | RuleEngine | Rule id 已注册 |
DuplicatePluginError | PluginManager | 插件 id 已注册 |
PluginNotFoundError | PluginManager | 按 id 取插件未找到 |
安装与运行
要求 Node.js >= 18。
bash
# 安装依赖
npm install
# 构建(产物输出到 dist/)
npm run build
# 运行测试
npm test
# 类型检查
npm run typecheck运行示例:
bash
npm run example # 2 人 SimpleGame
npm run example:uno # 3 人 UNO
npm run example:doudizhu # 3 人斗地主
npm run example:replay # 演示 ReplayEngine 确定性回放
npm run example:snapshot # 演示 createSnapshot / restoreSnapshot