Skip to content

DrawPlugin ​

处理 DRAW_CARD action,将指定数量的卡牌从 deck zone 的顶部移到执行玩家的手牌 zone。

概述 ​

DrawPlugin 位于 src/plugins/DrawPlugin.ts。默认情况下,它会将 count 张牌从 deck zone 的顶部移到执行玩家的 player:{id}:hand zone,如果手牌 zone 不存在则会创建。每次 DRAW_CARD action 派发一个 CARD_DRAWN event,其 payload 包含所有被抽到的牌 id。

可选的自动洗回支持:当 state.variables['draw.reshuffleFrom'] 被设置为某个 zone id(例如 'discard')且抽牌源耗尽时,plugin 会把该 zone 中除顶牌之外的所有牌移到抽牌源,洗牌后继续抽牌。

导出 ​

playerHandZoneId() ​

玩家手牌 zone id 的约定格式:player:{playerId}:hand。

ts
function playerHandZoneId(playerId: string): string

参数 ​

参数类型必填默认值说明
playerIdstring是—玩家 id

返回值 ​

string:player:${playerId}:hand。

示例 ​

ts
import { playerHandZoneId } from 'decklet/plugins'

playerHandZoneId('p1') // -> 'player:p1:hand'

DRAW_VAR_RESHUFFLE_FROM ​

可选的自动洗回支持 variable key。

ts
export const DRAW_VAR_RESHUFFLE_FROM = 'draw.reshuffleFrom'

设置为某个 zone id(例如 'discard')后,当抽牌源耗尽时,plugin 会把该 zone 中的牌洗入牌堆继续抽牌。

DRAW_VAR_RESHUFFLE_KEEP_TOP ​

ts
export const DRAW_VAR_RESHUFFLE_KEEP_TOP = 'draw.reshuffleKeepTop'

控制是否保留 discard 顶牌(最近打出的牌),默认 true。为 false 时整个弃牌堆都会被洗入牌堆。

DrawPlugin 对象 ​

ts
export const DrawPlugin: GamePlugin = {
  id: 'draw',
  name: 'Draw Cards',
  version: '1.0.0',
  actions: [DRAW_CARD handler]
}

字段 ​

字段值说明
id'draw'插件唯一标识
name'Draw Cards'插件名
version'1.0.0'插件版本
actions[DRAW_CARD handler]处理 DRAW_CARD action
setup / rules / events无未定义

actions — DRAW_CARD handler ​

ts
{
  type: DRAW_CARD_ACTION,
  execute(action: DrawCardAction, ctx): ActionHandlerResult
}

行为 ​

  1. 从 action.payload 解析:
    • count = payload.count ?? 1
    • fromZoneId = payload.fromZoneId ?? 'deck'
    • toZoneId = payload.toZoneId ?? (action.playerId !== undefined ? playerHandZoneId(action.playerId) : 'hand')
  2. 若 state.zones[toZoneId] 不存在,通过 addZone(state, createZone({ id: toZoneId, type: 'hand', ownerId: action.playerId })) 创建。
  3. 循环 count 次:
    1. 取 next.zones[fromZoneId];不存在则抛 Error,消息为 Cannot draw: source zone ${fromZoneId} not found。
    2. 若源 zone 为空:
      • 读取 next.variables[DRAW_VAR_RESHUFFLE_FROM];若为某个 zone id,则通过 reshuffleIntoDeck 把 discard 牌洗回 deck(保留顶牌的行为由 DRAW_VAR_RESHUFFLE_KEEP_TOP 决定,默认 true)。
      • 若 reshuffledCount > 0,更新 state 并继续;否则抛 Error,消息为 Cannot draw ${count} cards: zone ${fromZoneId} ran out after drawing ${i} (discard pile also empty)。
      • 若 reshuffleFrom 未设置,直接抛 Error,消息为 Cannot draw ${count} cards: zone ${fromZoneId} ran out after drawing ${i}。
    3. 取顶牌 cardId = fromZone.cards[0],通过 moveCard 移到 toZoneId,加入 drawnCardIds。
  4. 收集事件:
    • 若发生洗回,发出 DECK_RESHUFFLED,payload 为 { fromZoneId, count: next.zones[fromZoneId].cards.length }。
    • 若 action.playerId !== undefined,发出 CARD_DRAWN,payload 为 { playerId, cardIds: drawnCardIds, count } 且 playerId 字段同步设置。
    • 否则发出 CARD_DRAWN,payload 为 { cardIds: drawnCardIds, count }。
  5. 返回 { state: next, events }。

示例 ​

ts
import { createDrawCardAction } from 'decklet'
import { playerHandZoneId } from 'decklet/plugins'

// 抽 1 张到玩家手牌
const action = createDrawCardAction('p1', 1)
// engine.dispatch(action) -> CARD_DRAWN { playerId: 'p1', cardIds: ['...'], count: 1 }

// 自定义源/目标 zone
const action2 = createDrawCardAction('p1', 3, { fromZoneId: 'deck', toZoneId: 'board' })

启用自动洗回 ​

通过 initialVariables 启用:

ts
import { GameEngine } from 'decklet'
import {
  StandardDeckPlugin,
  DrawPlugin,
  DRAW_VAR_RESHUFFLE_FROM,
  DRAW_VAR_RESHUFFLE_KEEP_TOP
} from 'decklet/plugins'

const engine = new GameEngine({ seed: 42, gameId: 'g1' })
engine.use(StandardDeckPlugin)
engine.use(DrawPlugin)

engine.createGame({
  players: [{ id: 'p1', name: 'Alice', seat: 0, status: 'active', data: {} }],
  initialVariables: {
    [DRAW_VAR_RESHUFFLE_FROM]: 'discard',   // 启用从 discard 自动洗回
    [DRAW_VAR_RESHUFFLE_KEEP_TOP]: true     // 保留 discard 顶牌(默认即 true)
  }
})

内部函数 reshuffleIntoDeck ​

模块私有,不对外导出。仅供文档参考其行为。

ts
function reshuffleIntoDeck(
  state: GameState,
  deckZoneId: string,
  discardZoneId: string,
  keepTop: boolean,
  random: RandomProvider
): { state: GameState; reshuffledCount: number }
  • 取 deck 与 discard 两个 zone;任一不存在则返回 { state, reshuffledCount: 0 }。
  • 若 keepTop=true,仅取 discard.cards.slice(0, -1)(即除顶牌外的全部),保留顶牌;否则取整个 discard 数组。
  • 若无可动牌,返回 { state, reshuffledCount: 0 }。
  • 用 random.shuffle(movable) 洗牌,把结果追加到 deck.cards,更新 discard.cards 为 keepTop ? [顶牌] : []。
  • 返回 { state: 更新后的state, reshuffledCount: shuffled.length }。

注意事项 ​

  • 该插件未提供 setup 与 rules,使用前需确保 deck zone 已存在(通常由 StandardDeckPlugin 创建)。
  • 抽牌顺序遵循"从顶牌到底牌",即 fromZone.cards[0] 是顶牌。
  • 自动洗回是 opt-in 的:未设置 DRAW_VAR_RESHUFFLE_FROM 时,从空牌堆抽牌会抛错(原始行为),因此已有游戏不受影响。
  • CARD_DRAWN 事件的 playerId 字段取自 action.playerId;若 action 未带 playerId(如系统抽牌),事件 payload 中也不会带 playerId 字段。
  • RESHUFFLED 行为保留 discard 顶牌的设计:让"当前打出的牌"保持可见,匹配常见卡牌游戏中回收弃牌堆的规则。