GamePlugin
插件接口:把一组 Rule、ActionHandler、EventHandler 与可选的 setup 打包成可注册到引擎的单元。
概述
GamePlugin 位于 src/plugin/GamePlugin.ts,是 CardGameEngine 插件体系的根接口。插件通过 GameContext 与 Engine 交互:
setup在引擎初始化阶段执行一次性准备;rules校验 Action 是否合法;actions处理 Action 并产出新状态与事件;events订阅并响应已发生的事件。
GamePlugin 接口
ts
interface GamePlugin {
id: string
name: string
version: string
setup?(context: GameContext): void
rules?: Rule[]
actions?: ActionHandler[]
events?: EventHandler[]
}字段
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | 插件唯一标识,用于去重注册与依赖识别 |
name | string | 是 | 人类可读的插件名 |
version | string | 是 | 语义化版本号,便于兼容性判断 |
setup | (context: GameContext) => void | 否 | 引擎初始化阶段执行的一次性钩子,用于注册额外资源或初始化状态 |
rules | Rule[] | 否 | 该插件提供的规则列表,用于 Action 合法性校验 |
actions | ActionHandler[] | 否 | 该插件提供的 ActionHandler 列表 |
events | EventHandler[] | 否 | 该插件提供的 EventHandler 列表,由引擎订阅到 EventBus |
setup()
ts
setup?(context: GameContext): void引擎初始化阶段执行的一次性钩子,用于注册额外资源或初始化状态(如创建卡牌 zone、初始化 variables)。
参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
context | GameContext | 是 | — | 引擎提供的上下文,可访问 state、random、mutation primitive 等 |
注意事项
setup在引擎初始化阶段(createGame之后、start之前)由GameEngine调用。- 在回放场景下,
setup会被再次执行;插件应通过 idempotent 检查(如if (!ctx.state.zones[id]))保证幂等性。 setup不应直接 dispatch Action,否则会与回放引擎跳过 setup 的策略不一致。
EventHandler 类型
插件的事件处理器。引擎会将其订阅到 EventBus,并在匹配的 GameEvent 触发时调用 handle,同时传入完整事件元数据与共享的 GameContext。
ts
interface EventHandler<TType extends string = string, TPayload = unknown> {
type: TType
handle(event: GameEvent<TType, TPayload>, context: GameContext): void
}字段
| 字段 | 类型 | 说明 |
|---|---|---|
type | TType(默认 string) | 订阅的事件类型字符串;设为 '*' 表示订阅所有事件 |
handle | (event, context) => void | 事件处理函数 |
handle()
ts
handle(event: GameEvent<TType, TPayload>, context: GameContext): void参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
event | GameEvent<TType, TPayload> | 是 | — | 触发的事件对象(含 id / type / payload / playerId / timestamp / stateVersion 等元数据) |
context | GameContext | 是 | — | 引擎提供的共享上下文 |
行为
- 在
EventBus派发匹配type的事件时被调用;type: '*'的处理器会被所有事件触发。 - 通过
context.dispatch派发的后续 Action 会进入引擎 ActionQueue 队尾而非递归展开,受maxActionsPerTick约束。
示例
ts
import type { GamePlugin } from '../plugin/GamePlugin.js'
import type { ActionHandler } from '../action/ActionExecutor.js'
import type { Rule } from '../rule/Rule.js'
import { ok, deny } from '../rule/Rule.js'
const myRule: Rule = {
id: 'my-rule',
name: 'My Rule',
appliesTo: ['PLAY_CARD'],
validate(c) {
return c.state.status === 'playing'
? ok()
: deny('game not playing', 'NOT_PLAYING')
}
}
const myHandler: ActionHandler = {
type: 'PLAY_CARD',
execute(action, ctx) {
// 处理出牌逻辑,返回新 state 与事件
return { state: ctx.state, events: [] }
}
}
const MyPlugin: GamePlugin = {
id: 'my-plugin',
name: 'My Plugin',
version: '1.0.0',
setup(ctx) {
// 初始化:创建一个 zone
// ctx.addZone(...)
},
rules: [myRule],
actions: [myHandler],
events: [
{
type: 'GAME_STARTED',
handle(_event, ctx) {
// 响应开局事件
}
}
]
}注意事项
id在同一PluginManager中必须唯一,重复注册会抛出DuplicatePluginError。name与version也是必填;通过PluginManager.use注册时若缺失会抛错。- 插件的所有 EventHandler 都会被引擎订阅到 EventBus;同一事件可被多个插件的处理器响应,按订阅顺序执行。
EventHandler.type = '*'是订阅所有事件的特殊值,常用于调试或日志埋点。