Skip to content

GameState ​

GameState 是引擎的不可变对局状态。任何修改都会产生新的 GameState 并将 version +1。通过 stateVersion 关联到 ActionHistory 与发出的 GameEvent,从而支持回放。

概述 ​

GameState 通过 players / cards / zones 使用 Record<id, T> 而非数组,让查找/删除/移动操作均为 O(1) 且避免线性扫描。

字段类型索引键备注
playersstring (玩家 id)—
cardsstring (卡牌 id)卡牌与 zone 是两个独立的索引
zonesstring (zone id)每个 zone 持有卡牌 id 的有序数组

GameStatus 类型 ​

ts
export type GameStatus = 'waiting' | 'playing' | 'paused' | 'finished'

对局生命周期状态机:

状态含义
waiting已创建未开始,玩家可加入
playing进行中,可正常 dispatch
paused暂停,外部业务可在此状态做快照或干预
finished已结束,winnerIds 已确定

GameState 接口 ​

ts
export interface GameState {
  gameId: string
  status: GameStatus
  turn: number
  currentPlayerId?: string
  players: Record<string, Player>
  cards: Record<string, Card>
  zones: Record<string, CardZone>
  variables: Record<string, unknown>
  winnerIds: string[]
  version: number
  seed: number
}
属性类型说明
gameIdstring对局唯一标识
statusGameStatus当前对局生命周期状态
turnnumber当前回合序号,从 0 起递增
currentPlayerId?string当前轮到行动的玩家 id;未设置时表示无当前玩家(如 finished 状态)
playersRecord<string, Player>玩家表,按 id 索引
cardsRecord<string, Card>卡牌表,按 id 索引。卡牌与 zone 是两个独立的索引,zone 中只存 id 列表
zonesRecord<string, CardZone>Zone 表,按 id 索引。每个 zone 持有卡牌 id 的有序数组
variablesRecord<string, unknown>Plugin 自定义数据存储。引擎不解释其内容,仅作为通用键值存储。适合保存游戏专属阶段标记、明牌信息、剩余轮数等
winnerIdsstring[]已获胜玩家 id 列表,按产生顺序保留
versionnumber单调递增的版本号。引擎每次成功执行一个 Action 时 +1,GameContext 的 mutation primitive 每次调用也各 +1。GameEvent 与 ActionRecord 会记录当时的 version 以便溯源
seednumber引擎 RandomProvider 所使用的种子。存入 state 是为了让对局可从 (initialState, actionHistory) 回放,而无需任何带外配置

GameStateInit 接口 ​

ts
export interface GameStateInit {
  gameId: string
  players?: Player[]
  cards?: Card[]
  zones?: CardZone[]
  variables?: Record<string, unknown>
  status?: GameStatus
  turn?: number
  currentPlayerId?: string
  winnerIds?: string[]
  version?: number
  seed?: number
}

构造 GameState 的入参。所有集合字段均可省略,由 createGameState 给出合理默认值。

与 GameState 的区别:players / cards / zones 以数组形式传入更直观,由 createGameState 转换为以 id 为键的 Record。

createGameState() ​

ts
export function createGameState(init: GameStateInit): GameState

参数 ​

参数类型必填说明
init.gameIdstring是对局 id
init.playersPlayer[]否玩家列表
init.cardsCard[]否卡牌列表
init.zonesCardZone[]否zone 列表
init.variablesRecord<string, unknown>否自定义变量
init.statusGameStatus否状态,缺省 waiting
init.turnnumber否回合序号,缺省 0
init.currentPlayerIdstring否当前玩家 id
init.winnerIdsstring[]否获胜者列表
init.versionnumber否版本号,缺省 0
init.seednumber否种子,缺省 0

返回值 ​

GameState —— 全新的 state(与入参无引用共享)。

行为 ​

  1. 数组 → Record 转换

    • 把 players / cards / zones 数组按 id 字段转为以 id 为键的 Record
    • 对 variables / winnerIds 做浅拷贝,避免外部修改影响初始状态
  2. 填充缺省值

    • status = 'waiting'
    • turn = 0
    • version = 0
    • seed = 0
  3. currentPlayerId 仅在显式传入时设置

    • 避免初始状态误判已有当前玩家
ts
function createGameState(init: GameStateInit): GameState {
  const players: Record<string, Player> = {}
  for (const p of init.players ?? []) players[p.id] = p
  // ... cards / zones 同理
  const state: GameState = {
    gameId: init.gameId,
    status: init.status ?? 'waiting',
    turn: init.turn ?? 0,
    players, cards, zones,
    variables: init.variables ? { ...init.variables } : {},
    winnerIds: init.winnerIds ? [...init.winnerIds] : [],
    version: init.version ?? 0,
    seed: init.seed ?? 0
  }
  if (init.currentPlayerId !== undefined) {
    state.currentPlayerId = init.currentPlayerId
  }
  return state
}

示例 ​

ts
import { createGameState, createPlayer, createCard, createZone } from 'decklet'

const state = createGameState({
  gameId: 'game-1',
  players: [createPlayer({ id: 'p1', name: 'Alice', seat: 1 })],
  cards: [createCard({ id: 'c1', type: 'number' })],
  zones: [createZone({ id: 'deck', type: 'deck', cards: ['c1'] })],
  seed: 123456
})

console.log(state.status)        // 'waiting'
console.log(state.turn)          // 0
console.log(state.version)      // 0
console.log(state.players['p1']) // Player 对象
console.log(state.zones['deck']) // CardZone 对象

注意事项 ​

  • GameState 是不可变的,任何修改都应通过 stateUtils.* 产生新 state
  • 引擎统一对每个 Action 仅 bump 一次 version;GameContext 的 mutation primitive 每次调用各 +1
  • cards 与 zones 是两个独立的索引:zone.cards 只保存 cardId 列表,实际 Card 实体由 state.cards 统一管理
  • currentPlayerId 字段是 optional 的 —— 仅在 createGameState 显式传入时才挂载
  • 同一张牌可在多个 zone 间迁移而无需复制 Card 对象(仅修改两个 zone 的 cards 数组)

关联类型 ​