Cards
UNO 卡牌的颜色、类型、卡牌对象与牌堆构造。
概述
UNO 卡牌系统由三个文件组成:
| 文件 | 内容 |
|---|---|
cards/UnoCardType.ts | 颜色与类型的基础定义、类型守卫 |
cards/UnoCard.ts | 卡牌数据结构、类型收窄与匹配函数 |
cards/createUnoDeck.ts | 标准 108 张牌堆构造与洗牌 |
类型
UnoColor
type UnoColor = 'red' | 'yellow' | 'green' | 'blue' | 'wild'UNO 卡牌颜色。实色牌(number / skip / reverse / draw2)取 red / yellow / green / blue;Wild 与 Wild Draw Four 取 'wild',表示尚未指定颜色。
UnoCardType
type UnoCardType =
| 'number'
| 'skip'
| 'reverse'
| 'draw2'
| 'wild'
| 'wild_draw4'UNO 卡牌类型:
| 类型 | 说明 |
|---|---|
number | 数字牌 0–9 |
skip | 跳过下家 |
reverse | 反转出牌方向 |
draw2 | 下家抽 2 张并跳过 |
wild | 百搭牌,可指定颜色 |
wild_draw4 | Wild Draw Four,下家抽 4 张并跳过 |
UnoColorChoice
type UnoColorChoice = 'red' | 'yellow' | 'green' | 'blue'玩家在 Wild 牌出牌后可选的颜色集合。与 UnoColor 的区别:不含 'wild',因为玩家必须指定一种实色。
常量
UNO_COLORS
const UNO_COLORS: readonly UnoColor[]值为 ['red', 'yellow', 'green', 'blue'](Object.freeze 冻结)。UNO 的四种实色集合(不含 'wild'),用于按色生成牌堆。
UNO_COLOR_CHOICES
const UNO_COLOR_CHOICES: readonly UnoColorChoice[]值为 ['red', 'yellow', 'green', 'blue'](冻结)。玩家可选的颜色集合,用于 CHOOSE_COLOR_ACTION 的合法值校验。
类型守卫
isUnoColorChoice()
function isUnoColorChoice(value: unknown): value is UnoColorChoice判断任意值是否为合法的 UnoColorChoice。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
value | unknown | 任意值 |
返回值
value is UnoColorChoice:当 value 为 red / yellow / green / blue 之一时返回 true。
行为
作为类型守卫用于 Rule 校验 CHOOSE_COLOR_ACTION 的 payload.color,拒绝 'wild' 及其它非法字符串。
卡牌数据结构
UnoCardData
interface UnoCardData {
color: UnoColor
}UNO 卡牌的业务数据,挂载在 Card.data 上。
color 即卡牌自身的颜色:实色牌为 red / yellow / green / blue,Wild 牌为 'wild'。当前生效颜色(Wild 牌被指定后的颜色)不存于此,而是记录在 GameState 的 UNO_VAR_CURRENT_COLOR 变量中。
UnoCard
type UnoCard = Card<UnoCardData>UNO 卡牌类型,即携带 UnoCardData 的通用 Card。
卡牌判定函数
isWildCard()
function isWildCard(card: UnoCard): boolean判断卡牌是否为 Wild 类(含 Wild 与 Wild Draw Four)。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
card | UnoCard | UNO 卡牌 |
返回值
boolean:card.type === 'wild' || card.type === 'wild_draw4'。
行为
Wild 牌在出牌规则上可随时打出,不受当前颜色 / 数字约束;出牌后需通过 CHOOSE_COLOR_ACTION 指定新的生效颜色。
isWildDrawFour()
function isWildDrawFour(card: UnoCard): boolean判断卡牌是否为 Wild Draw Four。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
card | UnoCard | UNO 卡牌 |
返回值
boolean:card.type === 'wild_draw4'。
行为
该牌有额外的规则限制(见 canPlayWildDrawFourRule):仅当玩家手中无与当前颜色同色的牌时才合法。
unoCardFace()
function unoCardFace(card: UnoCard): string卡牌的"牌面",用于匹配判定。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
card | UnoCard | UNO 卡牌 |
返回值
string:
- 数字牌:
'number:${rank}'(如'number:3') - 功能牌 / Wild 牌:直接返回
card.type(如'skip'/'wild'/'wild_draw4')
行为
用于 canPlayCardRule 中"与弃牌堆顶张牌面相同"的判定——数字牌按点数匹配,功能牌按类型匹配,Wild 牌无牌面(始终按 Wild 颜色匹配,由 isWildCard 短路返回 ok())。
asUnoCard()
function asUnoCard(
card: { type: string; data: unknown } | undefined
): UnoCard | undefined将通用卡牌对象收窄为 UnoCard。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
card | ` | undefined` |
返回值
UnoCard | undefined:收窄后的 UnoCard,若非 UNO 卡牌或输入为 undefined 则返回 undefined。
行为
通过检查 data.color 字段是否存在来判定该卡牌是否为 UNO 卡牌,用于在 Rule / Handler 中从 GameState.cards 取出卡牌后安全地按 UNO 卡牌使用。非 UNO 卡牌或 undefined 输入返回 undefined,调用方需自行判空。
牌堆构造
UNO_DECK_SIZE
const UNO_DECK_SIZE = 108标准 UNO 牌堆张数。
UNO_INITIAL_HAND_SIZE
const UNO_INITIAL_HAND_SIZE = 7UNO 标准初始手牌张数。
createUnoCards()
function createUnoCards(): UnoCard[]构造一副标准 108 张 UNO 牌(未洗牌)。
参数
无。
返回值
UnoCard[]:108 张 UNO 卡牌。
行为
牌 id 采用确定性命名(如 uno-red-0、uno-red-1-a / uno-red-1-b、uno-wild-1),同一副牌每次生成的 id 与内容完全一致,便于回放与测试断言。
排序顺序:
- 先按颜色(红 → 黄 → 绿 → 蓝)遍历:
- 数字 0:1 张(id
uno-${color}-0) - 数字 1–9:每个数字 2 张(id
uno-${color}-${r}-a/uno-${color}-${r}-b) - skip / reverse / draw2:各 2 张(id
uno-${color}-${t}-a/uno-${color}-${t}-b)
- 数字 0:1 张(id
- 最后追加 4 张
wild(iduno-wild-1…uno-wild-4)与 4 张wild_draw4(iduno-wild-draw4-1…uno-wild-draw4-4)。
数字牌会同时写入 rank 与 value(两者等价),便于按数字大小比较或展示;功能牌与 Wild 牌不设置 rank / value,仅以 type 区分。
createShuffledUnoDeck()
function createShuffledUnoDeck(random: RandomProvider): UnoCard[]返回洗好的 UNO 牌堆。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
random | RandomProvider | 随机数 provider,传入 ctx.random 或 SeededRandomProvider |
返回值
UnoCard[]:洗好的 108 张 UNO 卡牌。
行为
调用 random.shuffle(createUnoCards())。相同种子下,每次调用该函数都会得到相同的洗牌结果(当 random 为 SeededRandomProvider 时)。
示例
构造与检查牌堆
import { SeededRandomProvider } from 'decklet'
import {
createUnoCards,
createShuffledUnoDeck,
UNO_DECK_SIZE,
UNO_INITIAL_HAND_SIZE,
UNO_COLORS,
UNO_COLOR_CHOICES,
isUnoColorChoice,
asUnoCard,
isWildCard,
isWildDrawFour,
unoCardFace
} from 'decklet/uno'
// 1) 未洗牌的 108 张
const deck = createUnoCards()
console.log(deck.length === UNO_DECK_SIZE) // true
console.log(deck[0]!.id) // 'uno-red-0'
// 2) 确定性洗牌
const random = new SeededRandomProvider(12345)
const shuffled = createShuffledUnoDeck(random)
console.log(shuffled.length === UNO_DECK_SIZE) // true
// 3) 卡牌判定
const card = asUnoCard(deck[0]!)
console.log(card?.type, card?.data.color, card?.rank)
console.log(isWildCard(card!)) // false(red-0 不是 Wild)
console.log(isWildDrawFour(card!)) // false
console.log(unoCardFace(card!)) // 'number:0'
// 4) 颜色校验
console.log(isUnoColorChoice('red')) // true
console.log(isUnoColorChoice('wild')) // false在插件 setup 中使用
// UnoPlugin.setup 内部的用法
import { createShuffledUnoDeck, UNO_DECK_ZONE_ID } from 'decklet/uno'
// ctx.random 由 GameEngine 提供
const cards = createShuffledUnoDeck(ctx.random)
for (const card of cards) {
if (!ctx.state.cards[card.id]) {
ctx.addCard(card as unknown as Card)
}
ctx.addCardToZone(card.id, UNO_DECK_ZONE_ID)
}注意事项
createUnoCards()是确定性的——同一次调用产出的牌 id 与顺序完全一致,便于回放与测试。createShuffledUnoDeck(random)必须传入randomprovider:在 Plugin 内部传入ctx.random,确定性测试时传入SeededRandomProvider。asUnoCard通过data.color字段存在性判定 UNO 卡牌——若其他游戏也使用color字段,可能误判(待确认是否有此风险)。UNO_COLORS与UNO_COLOR_CHOICES值相同但类型不同:前者元素类型含'wild',后者不含。前者用于生成牌堆(Wild 牌单独追加),后者用于校验玩家选色。