Skip to content

UnoPlugin ​

UNO 游戏插件主对象,把 setup、rules、actions、events 装配为一个 GamePlugin。

概述 ​

UnoPlugin 是一个 GamePlugin 对象(非 class),通过 engine.use(UnoPlugin) 注册到引擎后即提供完整的 UNO 对局能力。它本身不持有状态——所有运行时状态都写入 GameState(zones / cards / variables)。

UnoPlugin 内部按职责拆分:

职责实现位置
setup创建牌堆 / 弃牌堆 zone、生成并洗入 108 张 UNO 牌、配置抽牌堆重洗策略
actionsPLAY_CARD(出牌并更新颜色 / 发出事件)、CHOOSE_COLOR(玩家选色)、UNO_FLIP_INITIAL(翻初始弃牌)
eventsGAME_STARTED(发初始手牌 + 翻初始牌)、CARD_PLAYED(判定胜负并按卡牌类型派发 Effect)、UNO_COLOR_CHOSEN(选色后推进回合或执行罚抽)
rules组合胜负 / 回合 / 归属 / 出牌合法性 / 选色合法性等校验规则

Effect(applyXxxEffect)作为 EventHandler 内副作用的纯函数拆分,集中位于 effects/ 目录。

属性/字段 ​

UnoPlugin 为对象字面量,符合 GamePlugin 接口:

名称类型说明
id'uno'插件 id,与 gameType 一致
name'UNO'展示名
version'1.0.0'版本号
setup(ctx: GameContext) => void引擎 createGame 时执行一次(回放会再次执行,故通过存在性判断保持幂等)
rulesRule[]注册的 8 条规则
actionsActionHandler[]注册的 3 个 ActionHandler
eventsEventHandler[]注册的 3 个事件处理器

setup ​

ts
setup(ctx: GameContext): void

行为 ​

  1. 若 UNO_DECK_ZONE_ID('deck')zone 不存在则创建。
  2. 若 UNO_DISCARD_ZONE_ID('discard')zone 不存在则创建。
  3. 调用 createShuffledUnoDeck(ctx.random) 生成洗好的 108 张 UNO 牌,逐张 ctx.addCard + ctx.addCardToZone(card.id, UNO_DECK_ZONE_ID) 入牌堆(已存在的卡牌跳过 addCard 以保持幂等)。
  4. ctx.setVariable(UNO_VAR_PENDING_COLOR, false) 初始化"等待选色"标记。
  5. 配置 DrawPlugin 的重洗策略:
    • DRAW_VAR_RESHUFFLE_FROM = UNO_DISCARD_ZONE_ID:抽牌堆耗尽时从弃牌堆回收洗回。
    • DRAW_VAR_RESHUFFLE_KEEP_TOP = true:保留弃牌堆顶张以维持当前生效颜色 / 牌面不变。

幂等性设计使得回放重放 setup 时不会重复注册 zone / 卡牌。

注册的 Rules ​

按数组顺序注册(RuleEngine 短路执行):

顺序Rule说明
1gameNotWonRule已有获胜者后拒绝所有 Action
2gameStartedRule仅在 status === 'playing' 时允许 Action
3playerTurnRulePLAY_CARD / CHOOSE_COLOR 必须由当前玩家发起
4playerOwnsCardRulePLAY_CARD 的卡牌必须在发起玩家手牌 zone 中
5singleCardRulePLAY_CARD 必须且只能打出一张牌
6canPlayCardRule出牌合法性(同色 / 同牌面 / Wild)
7canPlayWildDrawFourRuleWild Draw Four 仅在手中无同色牌时允许
8chooseColorValidRuleCHOOSE_COLOR 颜色值有效且当前处于等待选色状态

详见 rules.md。

注册的 Actions ​

ActionHandlertype说明
playCardHandlerPLAY_CARD_ACTION出牌:移牌手→弃牌堆、发出 CARD_PLAYED 与 UNO_CARD_PLAYED;实色牌额外更新当前色并发出 UNO_COLOR_CHANGED
chooseColorHandlerCHOOSE_COLOR_ACTION选色:更新当前色、解除 pending、发出 UNO_COLOR_CHANGED 与 UNO_COLOR_CHOSEN
flipInitialCardHandlerUNO_FLIP_INITIAL_ACTION翻初始弃牌:从牌堆顶翻牌直到数字牌

详见 actions.md。

注册的 Events ​

事件类型处理函数说明
GAME_STARTEDhandleGameStarted给每位玩家派发 DRAW_CARD(7) + 派发 UNO_FLIP_INITIAL
CARD_PLAYEDhandleCardPlayed判定胜负;按卡牌类型派发对应 Effect
UNO_COLOR_CHOSENhandleColorChosen选色完成后推进回合或执行 Wild Draw Four 罚抽

handleGameStarted ​

ts
function handleGameStarted(_event: GameEvent, ctx: GameContext): void

行为 ​

  • 取 playersInSeatOrder(ctx.state.players) 按座次遍历,每位玩家 ctx.dispatch(createDrawCardAction(p.id, UNO_INITIAL_HAND_SIZE)) 发 7 张。
  • ctx.dispatch(createUnoFlipInitialAction()) 翻初始弃牌。

handleCardPlayed ​

ts
function handleCardPlayed(event: GameEvent, ctx: GameContext): void

行为 ​

  1. 从事件 payload 取 playerId 与 cardIds[0],取出卡牌。
  2. 胜负判定:若发起玩家手牌 zone 为空,则 ctx.addWinner(playerId)、ctx.setStatus('finished'),并发出 GAME_WON、GAME_FINISHED、UNO_GAME_WON 三个事件后 return。
  3. 否则按 card.type 派发对应 Effect:
card.typeEffect 调用
'number'ctx.dispatch(createEndTurnAction(playerId))
'skip'applySkipEffect(ctx, playerId)
'reverse'applyReverseEffect(ctx, playerId, playerCount)
'draw2'applyDrawTwoEffect(ctx, playerId)
'wild'applyWildEffect(ctx, playerId)
'wild_draw4'applyWildDrawFourEffect(ctx, playerId)

其中 playerCount = Object.keys(ctx.state.players).length。

handleColorChosen ​

ts
function handleColorChosen(event: GameEvent, ctx: GameContext): void

行为 ​

  • 从 payload 取 playerId 与 cardType。
  • 若 cardType === 'wild_draw4':调用 applyWildDrawFourPostChoiceEffect(ctx, playerId) 执行下家罚抽 4 张并跳过。
  • 否则(普通 wild):ctx.dispatch(createEndTurnAction(playerId)) 结束当前玩家回合。

示例 ​

ts
import { GameEngine, TurnPlugin, DrawPlugin } from 'decklet'
import { UnoPlugin } from 'decklet/uno'

const engine = new GameEngine()
engine.use(TurnPlugin).use(DrawPlugin).use(UnoPlugin)

engine.subscribeAll((e) => {
  if (e.type === 'UNO_GAME_WON') {
    console.log('winner:', (e.payload as { winnerIds: string[] }).winnerIds)
  }
})

engine.createGame({
  players: [
    { id: 'p1', name: 'Alice', seat: 1 },
    { id: 'p2', name: 'Bob', seat: 2 },
    { id: 'p3', name: 'Carol', seat: 3 }
  ]
})
engine.start()

注意事项 ​

  • UnoPlugin 必须在 TurnPlugin 与 DrawPlugin 之后注册,否则 setup 阶段引用的 DRAW_VAR_RESHUFFLE_FROM 等变量无效果,回合推进也不会发生。
  • handleCardPlayed 内的胜负判定发生在 Effect 派发之前——一旦判定获胜,本次出牌不再触发任何 Effect。
  • handleCardPlayed 中发出的 GAME_WON / GAME_FINISHED 是引擎通用事件,UNO_GAME_WON 是 UNO 专属事件,三者并存供不同订阅方使用。