Skip to content

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[]
}

字段 ​

字段类型必填说明
idstring是插件唯一标识,用于去重注册与依赖识别
namestring是人类可读的插件名
versionstring是语义化版本号,便于兼容性判断
setup(context: GameContext) => void否引擎初始化阶段执行的一次性钩子,用于注册额外资源或初始化状态
rulesRule[]否该插件提供的规则列表,用于 Action 合法性校验
actionsActionHandler[]否该插件提供的 ActionHandler 列表
eventsEventHandler[]否该插件提供的 EventHandler 列表,由引擎订阅到 EventBus

setup() ​

ts
setup?(context: GameContext): void

引擎初始化阶段执行的一次性钩子,用于注册额外资源或初始化状态(如创建卡牌 zone、初始化 variables)。

参数 ​

参数类型必填默认值说明
contextGameContext是—引擎提供的上下文,可访问 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
}

字段 ​

字段类型说明
typeTType(默认 string)订阅的事件类型字符串;设为 '*' 表示订阅所有事件
handle(event, context) => void事件处理函数

handle() ​

ts
handle(event: GameEvent<TType, TPayload>, context: GameContext): void

参数 ​

参数类型必填默认值说明
eventGameEvent<TType, TPayload>是—触发的事件对象(含 id / type / payload / playerId / timestamp / stateVersion 等元数据)
contextGameContext是—引擎提供的共享上下文

行为 ​

  • 在 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 = '*' 是订阅所有事件的特殊值,常用于调试或日志埋点。