GameState
GameState 是引擎的不可变对局状态。任何修改都会产生新的 GameState 并将 version +1。通过 stateVersion 关联到 ActionHistory 与发出的 GameEvent,从而支持回放。
概述
GameState 通过 players / cards / zones 使用 Record<id, T> 而非数组,让查找/删除/移动操作均为 O(1) 且避免线性扫描。
| 字段类型 | 索引键 | 备注 |
|---|---|---|
players | string (玩家 id) | — |
cards | string (卡牌 id) | 卡牌与 zone 是两个独立的索引 |
zones | string (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
}| 属性 | 类型 | 说明 |
|---|---|---|
gameId | string | 对局唯一标识 |
status | GameStatus | 当前对局生命周期状态 |
turn | number | 当前回合序号,从 0 起递增 |
currentPlayerId? | string | 当前轮到行动的玩家 id;未设置时表示无当前玩家(如 finished 状态) |
players | Record<string, Player> | 玩家表,按 id 索引 |
cards | Record<string, Card> | 卡牌表,按 id 索引。卡牌与 zone 是两个独立的索引,zone 中只存 id 列表 |
zones | Record<string, CardZone> | Zone 表,按 id 索引。每个 zone 持有卡牌 id 的有序数组 |
variables | Record<string, unknown> | Plugin 自定义数据存储。引擎不解释其内容,仅作为通用键值存储。适合保存游戏专属阶段标记、明牌信息、剩余轮数等 |
winnerIds | string[] | 已获胜玩家 id 列表,按产生顺序保留 |
version | number | 单调递增的版本号。引擎每次成功执行一个 Action 时 +1,GameContext 的 mutation primitive 每次调用也各 +1。GameEvent 与 ActionRecord 会记录当时的 version 以便溯源 |
seed | number | 引擎 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.gameId | string | 是 | 对局 id |
init.players | Player[] | 否 | 玩家列表 |
init.cards | Card[] | 否 | 卡牌列表 |
init.zones | CardZone[] | 否 | zone 列表 |
init.variables | Record<string, unknown> | 否 | 自定义变量 |
init.status | GameStatus | 否 | 状态,缺省 waiting |
init.turn | number | 否 | 回合序号,缺省 0 |
init.currentPlayerId | string | 否 | 当前玩家 id |
init.winnerIds | string[] | 否 | 获胜者列表 |
init.version | number | 否 | 版本号,缺省 0 |
init.seed | number | 否 | 种子,缺省 0 |
返回值
GameState —— 全新的 state(与入参无引用共享)。
行为
数组 → Record 转换
- 把
players/cards/zones数组按id字段转为以 id 为键的Record - 对
variables/winnerIds做浅拷贝,避免外部修改影响初始状态
- 把
填充缺省值
status = 'waiting'turn = 0version = 0seed = 0
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 数组)
关联类型
Player— 玩家实体Card— 卡牌实体CardZone— zone 实体stateUtils— 不可变状态更新函数