Skip to content

CardZone ​

CardZone 表示一个卡牌容器,管理其中卡牌的 ID 顺序。zone 只保存 cardId(不直接持有 Card 对象),实际 Card 实体由 GameState 统一管理。

概述 ​

设计要点:

  • zone 只保存 cardId 列表,Card 实体由 GameState.cards 统一管理
  • 同一张牌可在多个 zone 间迁移而无需复制对象,也便于回放与快照
  • 通过 createZone 工厂函数构造,对 cards 做浅拷贝以隔离外部数组
  • 提供 zoneContainsCard / addCardToZone / removeCardFromZone 等纯函数操作

CardZoneType ​

ts
export type CardZoneType =
  | 'deck'
  | 'hand'
  | 'discard'
  | 'board'
  | 'removed'
  | 'custom'
类型说明
deck牌堆(通常不可见,供抽牌)
hand玩家手牌
discard弃牌堆
board桌面/出牌区
removed已移出游戏的牌(不参与后续流程)
custom插件自定义区域

CardZone 接口 ​

ts
export interface CardZone {
  id: string
  type: CardZoneType
  ownerId?: string
  cards: string[]
}
属性类型必填说明
idstring是zone 唯一标识
typeCardZoneType是zone 类型,决定其默认行为与可见性策略
ownerIdstring否归属玩家 ID;公共区域(如 deck / discard / board)可省略
cardsstring[]是该 zone 内的卡牌 ID 列表,顺序对抽牌/出牌规则有意义

CardZoneInit 接口 ​

ts
export interface CardZoneInit {
  id: string
  type: CardZoneType
  ownerId?: string
  cards?: string[]
}

创建 CardZone 时使用的初始化对象。与 CardZone 的区别:cards 可省略(默认空数组)。

createZone() ​

ts
export function createZone(init: CardZoneInit): CardZone

参数 ​

参数类型必填默认值说明
init.idstring是—zone id
init.typeCardZoneType是—zone 类型
init.ownerIdstring否undefined归属玩家
init.cardsstring[]否[]卡牌 id 列表

返回值 ​

CardZone —— 新建 zone(cards 已做浅拷贝以隔离外部数组)。

行为 ​

对 cards 做浅拷贝以隔离外部数组,ownerId 仅在显式提供时挂载。

ts
function createZone(init: CardZoneInit): CardZone {
  const zone: CardZone = {
    id: init.id,
    type: init.type,
    cards: init.cards ? [...init.cards] : []
  }
  if (init.ownerId !== undefined) zone.ownerId = init.ownerId
  return zone
}

zoneContainsCard() ​

判断指定 cardId 是否在 zone 内。

ts
export function zoneContainsCard(zone: CardZone, cardId: string): boolean

参数 ​

参数类型必填说明
zoneCardZone是目标 zone
cardIdstring是待查询的卡牌 id

返回值 ​

boolean —— 卡牌是否在 zone 内。

行为 ​

等价于 zone.cards.includes(cardId)。

addCardToZone() ​

向 zone 末尾追加一张卡牌(不可变更新)。

ts
export function addCardToZone(zone: CardZone, cardId: string): CardZone

返回值 ​

新的 CardZone(原 zone 不变)。

行为 ​

如果允许同一张牌在 zone 中出现多次会导致后续 remove 语义模糊,因此重复添加时直接抛错而非静默忽略。

抛错 ​

当 cardId 已存在于 zone 内:Error: Card ${cardId} already exists in zone ${zone.id}。

示例 ​

ts
import { createZone, addCardToZone } from 'decklet'

const zone = createZone({ id: 'deck', type: 'deck' })
const next = addCardToZone(zone, 'card-001')
console.log(next.cards)  // ['card-001']
console.log(zone.cards)  // []  原始 zone 不变

removeCardFromZone() ​

从 zone 中移除一张卡牌(不可变更新)。

ts
export function removeCardFromZone(zone: CardZone, cardId: string): CardZone

返回值 ​

新的 CardZone(原 zone 不变)。

行为 ​

通过 indexOf 定位后 splice,保证只移除首个匹配项。

抛错 ​

当 cardId 不在 zone 内:Error: Card ${cardId} not found in zone ${zone.id}。

示例 ​

ts
import {
  createZone, addCardToZone, removeCardFromZone, zoneContainsCard
} from 'decklet'

// 创建玩家手牌 zone
const hand = createZone({
  id: 'player:p1:hand',
  type: 'hand',
  ownerId: 'p1'
})
console.log(hand.cards)        // []
console.log(hand.ownerId)      // 'p1'

// 添加卡牌
let next = addCardToZone(hand, 'c1')
next = addCardToZone(next, 'c2')
console.log(next.cards)        // ['c1', 'c2']

// 查询
console.log(zoneContainsCard(next, 'c1'))  // true
console.log(zoneContainsCard(next, 'c3'))  // false

// 移除
next = removeCardFromZone(next, 'c1')
console.log(next.cards)        // ['c2']

与 stateUtils 的协作 ​

stateUtils 命名空间提供了 zone 在 GameState 层面的操作(同时更新 state 与 zone):

stateUtils 函数等价的 zone-only 函数
stateUtils.addCardToZone(state, cardId, zoneId)addCardToZone(zone, cardId)
stateUtils.removeCardFromZone(state, cardId, zoneId)removeCardFromZone(zone, cardId)
stateUtils.moveCard(state, cardId, fromZoneId, toZoneId)组合 removeCardFromZone + addCardToZone

stateUtils.* 在更新 zone 的同时返回新的 GameState;而本文件的纯函数只返回新的 CardZone。

注意事项 ​

  • cards 数组的浅拷贝在 createZone / addCardToZone / removeCardFromZone 中均会执行,避免外部数组与 zone 内部状态共享引用
  • ownerId 仅在 createZone 显式提供时挂载(避免序列化时出现 undefined)
  • stateUtils.removeZone 不会校验 zone 是否仍持有卡牌引用,调用方应自行清空
  • 在 StateValidator 中,player:{id}:hand 形式的 zone 会校验其归属玩家是否存在