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参数
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
playerId | string | 是 | — | 玩家 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
}行为
- 从
action.payload解析:count = payload.count ?? 1fromZoneId = payload.fromZoneId ?? 'deck'toZoneId = payload.toZoneId ?? (action.playerId !== undefined ? playerHandZoneId(action.playerId) : 'hand')
- 若
state.zones[toZoneId]不存在,通过addZone(state, createZone({ id: toZoneId, type: 'hand', ownerId: action.playerId }))创建。 - 循环
count次:- 取
next.zones[fromZoneId];不存在则抛Error,消息为Cannot draw: source zone ${fromZoneId} not found。 - 若源 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}。
- 读取
- 取顶牌
cardId = fromZone.cards[0],通过moveCard移到toZoneId,加入drawnCardIds。
- 取
- 收集事件:
- 若发生洗回,发出
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 }。
- 若发生洗回,发出
- 返回
{ 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,使用前需确保deckzone 已存在(通常由StandardDeckPlugin创建)。 - 抽牌顺序遵循"从顶牌到底牌",即
fromZone.cards[0]是顶牌。 - 自动洗回是 opt-in 的:未设置
DRAW_VAR_RESHUFFLE_FROM时,从空牌堆抽牌会抛错(原始行为),因此已有游戏不受影响。 CARD_DRAWN事件的playerId字段取自action.playerId;若 action 未带playerId(如系统抽牌),事件 payload 中也不会带playerId字段。RESHUFFLED行为保留 discard 顶牌的设计:让"当前打出的牌"保持可见,匹配常见卡牌游戏中回收弃牌堆的规则。