Skip to content

Cards ​

UNO 卡牌的颜色、类型、卡牌对象与牌堆构造。

概述 ​

UNO 卡牌系统由三个文件组成:

文件内容
cards/UnoCardType.ts颜色与类型的基础定义、类型守卫
cards/UnoCard.ts卡牌数据结构、类型收窄与匹配函数
cards/createUnoDeck.ts标准 108 张牌堆构造与洗牌

类型 ​

UnoColor ​

ts
type UnoColor = 'red' | 'yellow' | 'green' | 'blue' | 'wild'

UNO 卡牌颜色。实色牌(number / skip / reverse / draw2)取 red / yellow / green / blue;Wild 与 Wild Draw Four 取 'wild',表示尚未指定颜色。

UnoCardType ​

ts
type UnoCardType =
  | 'number'
  | 'skip'
  | 'reverse'
  | 'draw2'
  | 'wild'
  | 'wild_draw4'

UNO 卡牌类型:

类型说明
number数字牌 0–9
skip跳过下家
reverse反转出牌方向
draw2下家抽 2 张并跳过
wild百搭牌,可指定颜色
wild_draw4Wild Draw Four,下家抽 4 张并跳过

UnoColorChoice ​

ts
type UnoColorChoice = 'red' | 'yellow' | 'green' | 'blue'

玩家在 Wild 牌出牌后可选的颜色集合。与 UnoColor 的区别:不含 'wild',因为玩家必须指定一种实色。

常量 ​

UNO_COLORS ​

ts
const UNO_COLORS: readonly UnoColor[]

值为 ['red', 'yellow', 'green', 'blue'](Object.freeze 冻结)。UNO 的四种实色集合(不含 'wild'),用于按色生成牌堆。

UNO_COLOR_CHOICES ​

ts
const UNO_COLOR_CHOICES: readonly UnoColorChoice[]

值为 ['red', 'yellow', 'green', 'blue'](冻结)。玩家可选的颜色集合,用于 CHOOSE_COLOR_ACTION 的合法值校验。

类型守卫 ​

isUnoColorChoice() ​

ts
function isUnoColorChoice(value: unknown): value is UnoColorChoice

判断任意值是否为合法的 UnoColorChoice。

参数 ​

名称类型说明
valueunknown任意值

返回值 ​

value is UnoColorChoice:当 value 为 red / yellow / green / blue 之一时返回 true。

行为 ​

作为类型守卫用于 Rule 校验 CHOOSE_COLOR_ACTION 的 payload.color,拒绝 'wild' 及其它非法字符串。

卡牌数据结构 ​

UnoCardData ​

ts
interface UnoCardData {
  color: UnoColor
}

UNO 卡牌的业务数据,挂载在 Card.data 上。

color 即卡牌自身的颜色:实色牌为 red / yellow / green / blue,Wild 牌为 'wild'。当前生效颜色(Wild 牌被指定后的颜色)不存于此,而是记录在 GameState 的 UNO_VAR_CURRENT_COLOR 变量中。

UnoCard ​

ts
type UnoCard = Card<UnoCardData>

UNO 卡牌类型,即携带 UnoCardData 的通用 Card。

卡牌判定函数 ​

isWildCard() ​

ts
function isWildCard(card: UnoCard): boolean

判断卡牌是否为 Wild 类(含 Wild 与 Wild Draw Four)。

参数 ​

名称类型说明
cardUnoCardUNO 卡牌

返回值 ​

boolean:card.type === 'wild' || card.type === 'wild_draw4'。

行为 ​

Wild 牌在出牌规则上可随时打出,不受当前颜色 / 数字约束;出牌后需通过 CHOOSE_COLOR_ACTION 指定新的生效颜色。

isWildDrawFour() ​

ts
function isWildDrawFour(card: UnoCard): boolean

判断卡牌是否为 Wild Draw Four。

参数 ​

名称类型说明
cardUnoCardUNO 卡牌

返回值 ​

boolean:card.type === 'wild_draw4'。

行为 ​

该牌有额外的规则限制(见 canPlayWildDrawFourRule):仅当玩家手中无与当前颜色同色的牌时才合法。

unoCardFace() ​

ts
function unoCardFace(card: UnoCard): string

卡牌的"牌面",用于匹配判定。

参数 ​

名称类型说明
cardUnoCardUNO 卡牌

返回值 ​

string:

  • 数字牌:'number:${rank}'(如 'number:3')
  • 功能牌 / Wild 牌:直接返回 card.type(如 'skip' / 'wild' / 'wild_draw4')

行为 ​

用于 canPlayCardRule 中"与弃牌堆顶张牌面相同"的判定——数字牌按点数匹配,功能牌按类型匹配,Wild 牌无牌面(始终按 Wild 颜色匹配,由 isWildCard 短路返回 ok())。

asUnoCard() ​

ts
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 ​

ts
const UNO_DECK_SIZE = 108

标准 UNO 牌堆张数。

UNO_INITIAL_HAND_SIZE ​

ts
const UNO_INITIAL_HAND_SIZE = 7

UNO 标准初始手牌张数。

createUnoCards() ​

ts
function createUnoCards(): UnoCard[]

构造一副标准 108 张 UNO 牌(未洗牌)。

参数 ​

无。

返回值 ​

UnoCard[]:108 张 UNO 卡牌。

行为 ​

牌 id 采用确定性命名(如 uno-red-0、uno-red-1-a / uno-red-1-b、uno-wild-1),同一副牌每次生成的 id 与内容完全一致,便于回放与测试断言。

排序顺序:

  1. 先按颜色(红 → 黄 → 绿 → 蓝)遍历:
    • 数字 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)
  2. 最后追加 4 张 wild(id uno-wild-1 … uno-wild-4)与 4 张 wild_draw4(id uno-wild-draw4-1 … uno-wild-draw4-4)。

数字牌会同时写入 rank 与 value(两者等价),便于按数字大小比较或展示;功能牌与 Wild 牌不设置 rank / value,仅以 type 区分。

createShuffledUnoDeck() ​

ts
function createShuffledUnoDeck(random: RandomProvider): UnoCard[]

返回洗好的 UNO 牌堆。

参数 ​

名称类型说明
randomRandomProvider随机数 provider,传入 ctx.random 或 SeededRandomProvider

返回值 ​

UnoCard[]:洗好的 108 张 UNO 卡牌。

行为 ​

调用 random.shuffle(createUnoCards())。相同种子下,每次调用该函数都会得到相同的洗牌结果(当 random 为 SeededRandomProvider 时)。

示例 ​

构造与检查牌堆 ​

ts
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 中使用 ​

ts
// 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) 必须传入 random provider:在 Plugin 内部传入 ctx.random,确定性测试时传入 SeededRandomProvider。
  • asUnoCard 通过 data.color 字段存在性判定 UNO 卡牌——若其他游戏也使用 color 字段,可能误判(待确认是否有此风险)。
  • UNO_COLORS 与 UNO_COLOR_CHOICES 值相同但类型不同:前者元素类型含 'wild',后者不含。前者用于生成牌堆(Wild 牌单独追加),后者用于校验玩家选色。