Skip to content

TurnPlugin ​

管理回合顺序与方向的插件。

概述 ​

TurnPlugin 位于 src/plugins/TurnPlugin.ts。它负责:

  • 在 setup 时初始化 variables.direction(默认 1,即顺时针)。
  • 在 GAME_STARTED 事件触发时,选择 seat 最小的玩家作为起始玩家,并发出 TURN_STARTED。
  • 处理 END_TURN action:沿 direction 推进到下一位玩家,递增回合计数器,先发出 TURN_ENDED 再发出 TURN_STARTED。

游戏可通过 initialVariables.direction 覆盖默认方向。

导出 ​

TURN_VARIABLE_DIRECTION ​

存储回合方向的 variable key,值为 TurnDirection。

ts
export const TURN_VARIABLE_DIRECTION = 'direction'
常量值说明
TURN_VARIABLE_DIRECTION'direction'state.variables 中的方向键

TurnDirection ​

回合方向的联合类型。

ts
export type TurnDirection = 1 | -1
值含义
1顺时针(CW),按 seat 升序
-1逆时针(CCW),按 seat 降序

playersInSeatOrder() ​

按回合顺序返回玩家(按 seat 升序排序)。

ts
function playersInSeatOrder(players: Record<string, Player>): Player[]

参数 ​

参数类型必填默认值说明
playersRecord<string, Player>是—玩家表

返回值 ​

Player[]:按 seat 升序排序的玩家数组。

示例 ​

ts
import { playersInSeatOrder } from 'decklet/plugins'

const players = {
  p1: { id: 'p1', name: 'Alice', seat: 2, status: 'active', data: {} },
  p2: { id: 'p2', name: 'Bob', seat: 0, status: 'active', data: {} },
  p3: { id: 'p3', name: 'Carol', seat: 1, status: 'active', data: {} }
}
playersInSeatOrder(players)
// -> [p2(seat=0), p3(seat=1), p1(seat=2)]

nextPlayerId() ​

根据当前 id、方向以及按 seat 排序的玩家列表,计算下一位玩家的 id。在两端会循环回绕。

ts
function nextPlayerId(
  currentId: string,
  players: Player[],
  direction: TurnDirection
): string

参数 ​

参数类型必填默认值说明
currentIdstring是—当前玩家 id
playersPlayer[]是—按回合顺序(通常 seat 升序)排好的玩家数组
directionTurnDirection是—回合方向(1 或 -1)

返回值 ​

string:下一位玩家的 id。

抛错 ​

  • Error('No players'):当 players.length === 0。
  • Error('Current player ${currentId} not in players list'):当 currentId 不在 players 列表中。

行为 ​

  • 计算 idx = players.findIndex(p => p.id === currentId)。
  • raw = idx + direction,通过 ((raw % n) + n) % n 归一化为合法下标(处理负数回绕)。
  • 返回 players[nextIdx].id。

示例 ​

ts
import { playersInSeatOrder, nextPlayerId } from 'decklet/plugins'

const ordered = playersInSeatOrder(players) // [p2, p3, p1]
nextPlayerId('p2', ordered, 1)  // -> 'p3'(顺时针)
nextPlayerId('p2', ordered, -1) // -> 'p1'(逆时针,回绕到最后一位)

TurnPlugin 对象 ​

ts
export const TurnPlugin: GamePlugin = {
  id: 'turn',
  name: 'Turn Manager',
  version: '1.0.0',
  setup, actions, events
}

字段 ​

字段值说明
id'turn'插件唯一标识
name'Turn Manager'插件名
version'1.0.0'插件版本
setup见下初始化方向变量
actions[END_TURN handler]处理 END_TURN action
events[GAME_STARTED handler]在开局时设置起始玩家

setup() ​

ts
setup(ctx: GameContext): void

行为 ​

读取 ctx.getVariable<TurnDirection>(TURN_VARIABLE_DIRECTION);若为 undefined 则通过 ctx.setVariable(TURN_VARIABLE_DIRECTION, 1) 设置为顺时针(1)。

游戏可以通过 initialVariables.direction = -1 覆盖默认方向;此时 setup 不会覆盖它。

actions — END_TURN handler ​

ts
{
  type: END_TURN_ACTION,
  execute(_action: EndTurnAction, ctx): ActionHandlerResult
}

行为 ​

  1. 读 state.variables[TURN_VARIABLE_DIRECTION],缺省视为 1。
  2. 调用 playersInSeatOrder(state.players) 获取有序玩家列表。
  3. 校验 state.currentPlayerId:未设置则抛 Error('Cannot end turn: no current player')。
  4. 调用 nextPlayerId(currentPlayerId, ordered, direction) 计算下一位玩家。
  5. 计算 newTurn = state.turn + 1。
  6. 构造事件列表(按顺序):
    • TURN_ENDED:{ playerId: currentPlayerId, turn: state.turn },playerId 同步设置。
    • TURN_STARTED:{ playerId: nextId, turn: newTurn },playerId 设为 nextId。
  7. 通过 setCurrentPlayer(state, nextId) 与 setTurn(next, newTurn) 产生新 state。
  8. 返回 { state: next, events }。

示例 ​

ts
import { createEndTurnAction } from 'decklet'

const action = createEndTurnAction('p1')
// engine.dispatch(action) -> TURN_ENDED { playerId: 'p1', turn: 0 }
//                       -> TURN_STARTED { playerId: 'p2', turn: 1 }(假设顺时针且 p1 -> p2)

events — GAME_STARTED handler ​

ts
{
  type: 'GAME_STARTED',
  handle(_event, ctx): void
}

行为 ​

  1. 若 state.currentPlayerId !== undefined,直接返回(已有当前玩家时不重复设置)。
  2. 调用 playersInSeatOrder(state.players) 获取有序玩家列表;为空则直接返回。
  3. 取 ordered[0](seat 最小的玩家)作为首玩家。
  4. 通过 ctx.setCurrentPlayer(first.id) 与 ctx.setTurn(1) 设置。
  5. 通过 ctx.emit({ type: 'TURN_STARTED', payload: { playerId: first.id, turn: 1 }, playerId: first.id }) 发出事件。

示例 ​

ts
import { GameEngine } from 'decklet'
import {
  TurnPlugin,
  TURN_VARIABLE_DIRECTION
} from 'decklet/plugins'
import type { Player } from 'decklet'

const engine = new GameEngine({ seed: 42, gameId: 'g1' })
engine.use(TurnPlugin)

const players: Player[] = [
  { id: 'p1', name: 'Alice', seat: 0, status: 'active', data: {} },
  { id: 'p2', name: 'Bob', seat: 1, status: 'active', data: {} }
]
engine.createGame({ players })
// setup 阶段:variables.direction = 1(顺时针)
engine.start()
// GAME_STARTED -> currentPlayerId = 'p1'(seat 最小), turn = 1
//              -> TURN_STARTED 事件触发

逆时针开局 ​

ts
engine.createGame({
  players,
  initialVariables: { [TURN_VARIABLE_DIRECTION]: -1 }
})
// setup 阶段不覆盖,variables.direction 保持 -1
// 后续 END_TURN 会沿逆时针推进

注意事项 ​

  • setup 仅在 variables.direction 未设置时才写入默认值 1;游戏可通过 initialVariables.direction = -1 在创建游戏时覆盖默认方向。
  • GAME_STARTED handler 仅在 state.currentPlayerId === undefined 时才设置起始玩家;若游戏在 createGame 阶段已显式设置 currentPlayerId,则不会被覆盖。
  • playersInSeatOrder 假设 seat 字段唯一且非负;如存在重复或负数 seat,结果未定义。
  • nextPlayerId 假设 players 已按 seat 升序排好;调用方应使用 playersInSeatOrder 的输出而非未排序的玩家数组。
  • END_TURN handler 不校验当前是否轮到该玩家发起 action —— 该约束应由游戏插件(如 SimpleGamePlugin 的 player-turn rule)实现。