Skip to content

快速开始 ​

本文档演示一个最小可运行的示例:2 人 SimpleGame,使用引擎内置插件完成"创建 Engine → 配置游戏 → 注册 Plugin → createGame → start → dispatch → subscribe"完整链路。

安装 ​

要求 Node.js >= 18。

bash
# 克隆仓库后安装依赖
npm install

最小示例 ​

下面示例使用 StandardDeckPlugin + TurnPlugin + DrawPlugin + SimpleGamePlugin 4 个内置插件,完成一局完整的"出完手牌即胜"对局:

ts
import { GameEngine } from 'decklet'
import {
  StandardDeckPlugin,
  TurnPlugin,
  DrawPlugin,
  SimpleGamePlugin,
  createSimplePlayCardAction,
  playerHandZoneId
} from 'decklet/plugins'

// 1. 创建 Engine(可选传入 seed 让对局可回放)
const engine = new GameEngine({ seed: 123456 })

// 2. 订阅事件(可选)—— 通配监听所有事件
engine.subscribeAll((event) => {
  console.log(`[event] ${event.type}`, event.payload)
})

// 3. 注册插件 → createGame → start
engine
  .use(StandardDeckPlugin) // 创建 52 张标准牌堆,GAME_STARTED 时自动洗牌
  .use(TurnPlugin)          // 管理回合顺序与方向
  .use(DrawPlugin)          // 处理 DRAW_CARD action
  .use(SimpleGamePlugin)    // PLAY_CARD action handler + 胜负判定
  .createGame({
    players: [
      { id: 'p1', name: 'Alice', seat: 1 },
      { id: 'p2', name: 'Bob', seat: 2 }
    ]
  })

engine.start()  // 触发 GAME_STARTED → 初始洗牌 + 每人发 5 张牌

// 4. 查询状态
const state = engine.getState()
console.log(`当前玩家: ${state.currentPlayerId}`)
console.log(`Alice 的手牌: ${state.zones[playerHandZoneId('p1')]?.cards.length} 张`)
console.log(`Bob 的手牌: ${state.zones[playerHandZoneId('p2')]?.cards.length} 张`)

// 5. dispatch Action —— 当前玩家出第一张牌
let turn = 0
while (engine.getState().status === 'playing' && turn < 50) {
  const currentId = engine.getState().currentPlayerId!
  const hand = engine.getState().zones[playerHandZoneId(currentId)]!
  const cardId = hand.cards[0]!

  const result = engine.dispatch(createSimplePlayCardAction(currentId, [cardId]))
  if (!result.ok) {
    console.error('action rejected:', result.denyCode, result.denyReason)
    break
  }
  turn++
}

console.log(`status: ${engine.getState().status}`)
console.log(`winners: ${engine.getState().winnerIds.join(', ')}`)

运行:

bash
npm run example   # 等价于 tsx examples/simple-game.ts

关键 API ​

创建 Engine ​

ts
new GameEngine(config?: GameConfigInit & { validation?: ValidationMode })

config 可省略,缺省值由 DEFAULT_GAME_CONFIG 提供。传入 seed 后引擎会使用 SeededRandomProvider,使对局可回放。

注册 Plugin ​

ts
engine.use(plugin: GamePlugin): this

链式注册。必须在 createGame 之前调用 —— 插件的 rules / actions / events 会在 createGame 内通过 wirePlugins 统一注册到对应子系统。

创建对局 ​

ts
engine.createGame(options: CreateGameOptions): this

CreateGameOptions 字段:

字段类型必填说明
playersCreateGamePlayerInit[]是玩家列表
gameIdstring否对局 id 覆盖;缺省取 config.gameId
initialVariablesRecord<string, unknown>否初始 variables
seednumber否单局种子覆盖

CreateGamePlayerInit 字段:

字段类型必填默认值说明
idstring是—玩家 id
namestring是—显示名
seatnumber否下标 +1座位号;同局不可重复
statusPlayerStatus否'active'玩家状态
dataRecord<string, unknown>否{}游戏专属数据

启动对局 ​

ts
engine.start(): this

把对局状态从 waiting 切换到 playing,并发出 GAME_STARTED 事件。

派发 Action ​

ts
engine.dispatch(action: Action): DispatchResult

DispatchResult 字段:

字段类型说明
okboolean是否通过 Rule 校验且无异常
actionAction派发的 Action
denyReasonstring?Rule 拒绝原因
denyCodestring?稳定错误码
errorstring?执行阶段抛错的 message
eventsGameEvent[]本 tick 内发出的事件
processedActionsProcessedAction[]本 tick 内处理的所有 Action

订阅事件 ​

ts
// 订阅指定类型
const off = engine.subscribe('CARD_PLAYED', (event) => {
  console.log(event.payload)
})
off()  // 取消订阅

// 订阅所有事件(等价于 subscribe(WILDCARD, listener))
engine.subscribeAll((event) => {
  console.log(event.type, event.payload)
})

使用内置 Action 工厂 ​

decklet 入口已导出 5 个基础 Action 工厂:

ts
import {
  createDrawCardAction,
  createPlayCardAction,
  createMoveCardAction,
  createEndTurnAction,
  createPassAction
} from 'decklet'

// 抽 3 张牌
const draw = createDrawCardAction('p1', 3)

// 出一张牌到默认 discard 区
const play = createPlayCardAction('p1', ['card-001'])

// 在两个明确 zone 间迁移单张牌
const move = createMoveCardAction('card-001', 'deck', 'hand:p1', { playerId: 'p1' })

// 结束当前回合
const end = createEndTurnAction('p1')

// 过牌
const pass = createPassAction('p1')

下一步 ​