UNO 插件总览
CardGameEngine 内置的标准 UNO 牌类游戏插件,通过 UnoPlugin 装配到 GameEngine 即可对局。
概述
UNO 插件(UnoPlugin)基于引擎核心(GameEngine + GameContext)以及公共插件 TurnPlugin、DrawPlugin 实现,提供完整的标准 UNO 规则:108 张牌堆、初始 7 张手牌、出牌方向、Skip / Reverse / DrawTwo / Wild / Wild Draw Four 效果、胜负判定。
UNO 插件不单独使用——它依赖 TurnPlugin(回合推进)与 DrawPlugin(手牌 zone / 抽牌重洗),需要按顺序 engine.use() 装配。createDefaultPluginRegistry() 已封装该默认装配栈。
牌堆构成
标准 UNO 牌堆共 108 张(UNO_DECK_SIZE = 108),由 createUnoCards() 生成:
| 来源 | 颜色 / 类型 | 张数 |
|---|---|---|
| 数字牌 0 | 每色 × 1 | 4 × 1 = 4 |
| 数字牌 1–9 | 每色 × 2 | 4 × 9 × 2 = 72 |
| Skip | 每色 × 2 | 4 × 2 = 8 |
| Reverse | 每色 × 2 | 4 × 2 = 8 |
| Draw Two | 每色 × 2 | 4 × 2 = 8 |
| Wild | — | 4 |
| Wild Draw Four | — | 4 |
| 合计 | 108 |
四种实色(UNO_COLORS):red / yellow / green / blue。Wild 类牌自身颜色为 'wild',实际生效颜色由玩家在出牌后通过 CHOOSE_COLOR_ACTION 指定。
核心规则
- 初始手牌:每名玩家发 7 张(
UNO_INITIAL_HAND_SIZE = 7)。 - 初始弃牌:游戏开始时由
UNO_FLIP_INITIAL_ACTION从牌堆顶翻牌,直到出现数字牌作为起始弃牌;功能牌 / Wild 牌轮转回牌堆底部。 - 出牌方向:由
TurnPlugin的TURN_VARIABLE_DIRECTION变量维护(1 顺时针 / -1 逆时针),Reverse 牌切换方向。 - 合法出牌:与当前生效颜色相同,或与弃牌堆顶张牌面相同(数字牌按点数、功能牌按类型匹配);Wild 类牌可随时打出。
- Wild Draw Four 限制:仅当玩家手中无与当前颜色同色的牌时才允许打出(
canPlayWildDrawFourRule)。 - 胜利条件:某玩家手牌为 0 即获胜(
WinRule+handleCardPlayed判定)。
效果一览
| 卡牌类型 | 效果 | 触发事件 |
|---|---|---|
number | 无特殊效果,直接结束回合 | — |
skip | 跳过下家 | UNO_PLAYER_SKIPPED |
reverse | 反转出牌方向;两人局等价于跳过 | UNO_PLAYER_REVERSED(+ UNO_PLAYER_SKIPPED 当两人局) |
draw2 | 下家抽 2 张并跳过 | UNO_DRAW_TWO |
wild | 等待玩家选色 | UNO_COLOR_CHANGED(reason='choose')、UNO_COLOR_CHOSEN |
wild_draw4 | 等待玩家选色,选色后下家抽 4 张并跳过 | UNO_COLOR_CHOSEN → UNO_DRAW_FOUR |
流程图
createGame / engine.start()
│
▼
┌──────────────────────┐
│ GAME_STARTED │
│ handleGameStarted │
└─────────┬────────────┘
│
┌────────────────────┴─────────────────────┐
▼ ▼
每位玩家 DRAW_CARD(7) UNO_FLIP_INITIAL
(UNO_INITIAL_HAND_SIZE) 翻到数字牌作为初始弃牌
│ │
└────────────────────┬─────────────────────┘
▼
┌──────────────────────┐
│ 玩家轮到出牌 │◀────────┐
└─────────┬────────────┘ │
│ │
▼ │
┌──────────────────────────────┐ │
│ PLAY_CARD / DRAW_CARD / │ │
│ CHOOSE_COLOR │ │
└──────────────┬───────────────┘ │
│ │
▼ │
CARD_PLAYED 事件 │
│ │
┌──────────────▼───────────────┐ │
│ handleCardPlayed │ │
│ 判定胜负 → 派发对应 Effect │ │
└──────────────┬───────────────┘ │
│ │
┌──────────────▼───────────────┐ │
│ Effect (Skip/Reverse/... │ │
│ /Wild/WildDrawFour) │ │
│ → END_TURN / DRAW_CARD │ │
└──────────────┬───────────────┘ │
│ │
└───────────────────────┘
│
有人手牌为 0
▼
┌──────────────────────┐
│ GAME_WON │
│ UNO_GAME_WON │
│ GAME_FINISHED │
└──────────────────────┘模块组成
| 模块 | 说明 | 详细文档 |
|---|---|---|
UnoPlugin | 插件主对象,装配 setup / rules / actions / events | uno-plugin.md |
UnoState | zone id / variable key 常量与访问器 | state.md |
UnoActions | CHOOSE_COLOR、UNO_FLIP_INITIAL Action | actions.md |
| Effects | 5 种卡牌效果的副作用函数 | effects.md |
| Rules | 4 类校验规则 | rules.md |
| Cards | 颜色 / 类型 / 卡牌构造 | cards.md |
| Events | 8 种 UNO 专属事件 | events.md |
快速上手
ts
import { GameEngine, TurnPlugin, DrawPlugin, createPlayCardAction } from 'decklet'
import { UnoPlugin } from 'decklet/uno'
const engine = new GameEngine()
engine.use(TurnPlugin).use(DrawPlugin).use(UnoPlugin)
engine.createGame({
players: [
{ id: 'p1', name: 'Alice', seat: 1 },
{ id: 'p2', name: 'Bob', seat: 2 }
]
})
engine.start()
// 当前轮到的玩家出牌
const currentId = engine.getState().currentPlayerId!
engine.dispatch(createPlayCardAction(currentId, ['<cardId>']))完整示例参见 examples/uno.ts,演示了 3 人局从发牌到分出胜负的全流程。
注意事项
- UNO 插件必须与
TurnPlugin、DrawPlugin一起使用,且装配顺序为TurnPlugin → DrawPlugin → UnoPlugin(由RuleEngine短路顺序决定)。 UnoPlugin.setup通过 zone / cards 存在性判断保持幂等,兼容回放重放。- Wild 牌的"当前生效颜色"不存储在卡牌自身,而是记录在
UNO_VAR_CURRENT_COLOR变量中。 - 引擎内置事件(
CARD_PLAYED/GAME_WON/GAME_FINISHED)与 UNO 专属事件并存,前者用于通用流程,后者用于业务细节。