CardZone
CardZone 表示一个卡牌容器,管理其中卡牌的 ID 顺序。zone 只保存 cardId(不直接持有 Card 对象),实际 Card 实体由 GameState 统一管理。
概述
设计要点:
- zone 只保存 cardId 列表,Card 实体由
GameState.cards统一管理 - 同一张牌可在多个 zone 间迁移而无需复制对象,也便于回放与快照
- 通过
createZone工厂函数构造,对cards做浅拷贝以隔离外部数组 - 提供
zoneContainsCard/addCardToZone/removeCardFromZone等纯函数操作
CardZoneType
export type CardZoneType =
| 'deck'
| 'hand'
| 'discard'
| 'board'
| 'removed'
| 'custom'| 类型 | 说明 |
|---|---|
deck | 牌堆(通常不可见,供抽牌) |
hand | 玩家手牌 |
discard | 弃牌堆 |
board | 桌面/出牌区 |
removed | 已移出游戏的牌(不参与后续流程) |
custom | 插件自定义区域 |
CardZone 接口
export interface CardZone {
id: string
type: CardZoneType
ownerId?: string
cards: string[]
}| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
id | string | 是 | zone 唯一标识 |
type | CardZoneType | 是 | zone 类型,决定其默认行为与可见性策略 |
ownerId | string | 否 | 归属玩家 ID;公共区域(如 deck / discard / board)可省略 |
cards | string[] | 是 | 该 zone 内的卡牌 ID 列表,顺序对抽牌/出牌规则有意义 |
CardZoneInit 接口
export interface CardZoneInit {
id: string
type: CardZoneType
ownerId?: string
cards?: string[]
}创建 CardZone 时使用的初始化对象。与 CardZone 的区别:cards 可省略(默认空数组)。
createZone()
export function createZone(init: CardZoneInit): CardZone参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
init.id | string | 是 | — | zone id |
init.type | CardZoneType | 是 | — | zone 类型 |
init.ownerId | string | 否 | undefined | 归属玩家 |
init.cards | string[] | 否 | [] | 卡牌 id 列表 |
返回值
CardZone —— 新建 zone(cards 已做浅拷贝以隔离外部数组)。
行为
对 cards 做浅拷贝以隔离外部数组,ownerId 仅在显式提供时挂载。
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 内。
export function zoneContainsCard(zone: CardZone, cardId: string): boolean参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
zone | CardZone | 是 | 目标 zone |
cardId | string | 是 | 待查询的卡牌 id |
返回值
boolean —— 卡牌是否在 zone 内。
行为
等价于 zone.cards.includes(cardId)。
addCardToZone()
向 zone 末尾追加一张卡牌(不可变更新)。
export function addCardToZone(zone: CardZone, cardId: string): CardZone返回值
新的 CardZone(原 zone 不变)。
行为
如果允许同一张牌在 zone 中出现多次会导致后续 remove 语义模糊,因此重复添加时直接抛错而非静默忽略。
抛错
当 cardId 已存在于 zone 内:Error: Card ${cardId} already exists in zone ${zone.id}。
示例
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 中移除一张卡牌(不可变更新)。
export function removeCardFromZone(zone: CardZone, cardId: string): CardZone返回值
新的 CardZone(原 zone 不变)。
行为
通过 indexOf 定位后 splice,保证只移除首个匹配项。
抛错
当 cardId 不在 zone 内:Error: Card ${cardId} not found in zone ${zone.id}。
示例
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 会校验其归属玩家是否存在