TurnPlugin
管理回合顺序与方向的插件。
概述
TurnPlugin 位于 src/plugins/TurnPlugin.ts。它负责:
- 在 setup 时初始化
variables.direction(默认1,即顺时针)。 - 在
GAME_STARTED事件触发时,选择seat最小的玩家作为起始玩家,并发出TURN_STARTED。 - 处理
END_TURNaction:沿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[]参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
players | Record<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参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
currentId | string | 是 | — | 当前玩家 id |
players | Player[] | 是 | — | 按回合顺序(通常 seat 升序)排好的玩家数组 |
direction | TurnDirection | 是 | — | 回合方向(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
}行为
- 读
state.variables[TURN_VARIABLE_DIRECTION],缺省视为1。 - 调用
playersInSeatOrder(state.players)获取有序玩家列表。 - 校验
state.currentPlayerId:未设置则抛Error('Cannot end turn: no current player')。 - 调用
nextPlayerId(currentPlayerId, ordered, direction)计算下一位玩家。 - 计算
newTurn = state.turn + 1。 - 构造事件列表(按顺序):
TURN_ENDED:{ playerId: currentPlayerId, turn: state.turn },playerId 同步设置。TURN_STARTED:{ playerId: nextId, turn: newTurn },playerId 设为nextId。
- 通过
setCurrentPlayer(state, nextId)与setTurn(next, newTurn)产生新 state。 - 返回
{ 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
}行为
- 若
state.currentPlayerId !== undefined,直接返回(已有当前玩家时不重复设置)。 - 调用
playersInSeatOrder(state.players)获取有序玩家列表;为空则直接返回。 - 取
ordered[0](seat 最小的玩家)作为首玩家。 - 通过
ctx.setCurrentPlayer(first.id)与ctx.setTurn(1)设置。 - 通过
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_STARTEDhandler 仅在state.currentPlayerId === undefined时才设置起始玩家;若游戏在 createGame 阶段已显式设置currentPlayerId,则不会被覆盖。playersInSeatOrder假设seat字段唯一且非负;如存在重复或负数 seat,结果未定义。nextPlayerId假设players已按seat升序排好;调用方应使用playersInSeatOrder的输出而非未排序的玩家数组。END_TURNhandler 不校验当前是否轮到该玩家发起 action —— 该约束应由游戏插件(如SimpleGamePlugin的player-turnrule)实现。