Skip to content

Cards ​

斗地主卡牌、牌堆组成与花色定义。

概述 ​

斗地主使用标准 54 张牌:4 种花色 × 13 个点数(共 52 张)+ 1 张小王 + 1 张大王。点数采用斗地主专用排序(3 最小,大王最大),与扑克的 StandardPokerRankResolver 不同。

模块说明
DoudizhuCardData卡牌 payload 接口(point + isJoker)
DoudizhuCard卡牌类型(Card<DoudizhuCardData>)
点数常量SMALL_JOKER_POINT / BIG_JOKER_POINT / MIN_CHAIN_POINT / MAX_CHAIN_POINT
卡牌函数getPoint / canChain / asDoudizhuCard / doudizhuCardFace / rankLabel
牌堆常量DOUDIZHU_DECK_SIZE / DOUDIZHU_HAND_SIZE / DOUDIZHU_BOTTOM_SIZE / DOUDIZHU_PLAYER_COUNT / DOUDIZHU_SUITS / DOUDIZHU_RANKS / DoudizhuSuit
牌堆工厂createDoudizhuCards / createShuffledDoudizhuDeck

点数排序 ​

斗地主卡牌的数值点数(point)遵循斗地主的牌力排序(数值越小越弱):

点数标签说明
3..103..10普通花色牌
11J普通花色牌
12Q普通花色牌
13K普通花色牌
14A普通花色牌,可参与连子
152不能进入顺子 / 连对 / 飞机
16SJ小王,无花色
17BJ大王;压小王,无花色

isJoker 区分两张王与花色牌。王无花色,不能参与任何连子型牌型;仅大小王组合的 ROCKET(火箭)牌型会使用它们。


DoudizhuCardData ​

ts
interface DoudizhuCardData {
  /** 用于比较大小与连子检测的数值点数。 */
  point: number
  /** 大小王为 true;花色牌为 false。 */
  isJoker: boolean
}

斗地主卡牌的 payload。

字段类型说明
pointnumber用于比较大小与连子检测的数值点数
isJokerboolean大小王为 true;花色牌为 false

DoudizhuCard ​

ts
type DoudizhuCard = Card<DoudizhuCardData>

斗地主卡牌类型,等价于 Card<DoudizhuCardData>。继承核心 Card 的 id / type / suit? / rank? / value / tags / data 字段,其中 data 即 DoudizhuCardData。

点数常量 ​

SMALL_JOKER_POINT ​

ts
const SMALL_JOKER_POINT = 16

小王点数值(16)。

BIG_JOKER_POINT ​

ts
const BIG_JOKER_POINT = 17

大王点数值(17)。

MIN_CHAIN_POINT ​

ts
const MIN_CHAIN_POINT = 3

可参与连子的最低点数(3)。2(15)和王不可连子。

MAX_CHAIN_POINT ​

ts
const MAX_CHAIN_POINT = 14

可参与连子的最高点数(A = 14)。


卡牌函数 ​

getPoint() ​

ts
function getPoint(card: DoudizhuCard | undefined): number

读取一张斗地主卡牌的数值点数(缺失时默认为 0)。

参数 ​

  • card: DoudizhuCard | undefined — 卡牌(可为 undefined)

返回值 ​

  • number — card?.data?.point ?? 0

canChain() ​

ts
function canChain(point: number): boolean

判断该点数是否可参与连子(顺子 / 连对 / 飞机)。

参数 ​

  • point: number — 点数

返回值 ​

  • boolean — point >= MIN_CHAIN_POINT && point <= MAX_CHAIN_POINT(即 3 <= point <= 14)

示例 ​

ts
import { canChain } from 'decklet/doudizhu'

console.log(canChain(3))   // true
console.log(canChain(14))  // true(A)
console.log(canChain(15))  // false(2 不能连子)
console.log(canChain(16))  // false(小王)

asDoudizhuCard() ​

ts
function asDoudizhuCard(
  card: { type: string; data: unknown } | undefined
): DoudizhuCard | undefined

通过检查 data payload 将通用 Card 收窄为 DoudizhuCard。若卡牌未携带斗地主数据则返回 undefined。供 Rule 与 Handler 安全地进行 Card -> DoudizhuCard 类型收窄使用。

参数 ​

  • card: { type: string; data: unknown } | undefined — 通用卡牌对象

返回值 ​

  • DoudizhuCard | undefined — 若 card.data 是对象且同时包含 point 与 isJoker 字段,则返回 card as DoudizhuCard;否则 undefined

行为 ​

  • card 为 null / undefined → undefined
  • card.data 为 null 或非对象 → undefined
  • card.data 不含 point 或 isJoker 字段 → undefined
  • 否则 → 将 card 视为 DoudizhuCard 返回

示例 ​

ts
import { asDoudizhuCard, createDoudizhuCards } from 'decklet/doudizhu'

const all = createDoudizhuCards()
const card = all[0]!
const ddz = asDoudizhuCard(card)
if (ddz) {
  console.log(ddz.data.point, ddz.data.isJoker)
}

doudizhuCardFace() ​

ts
function doudizhuCardFace(card: DoudizhuCard): string

人类可读的卡面标签,如 "♥A"、"♠2"、"SJ"、"BJ"。

参数 ​

  • card: DoudizhuCard — 斗地主卡牌

返回值 ​

  • string — 卡面标签:
    • 王:card.data.point === BIG_JOKER_POINT → 'BJ',否则 'SJ'
    • 花色牌:${suit}${rankLabel(point)}(suit 缺失时为 '?')

示例 ​

ts
import { doudizhuCardFace, createDoudizhuCards } from 'decklet/doudizhu'

const all = createDoudizhuCards()
console.log(doudizhuCardFace(all.find((c) => c.id === 'ddz-♥-A')!)) // '♥A'
console.log(doudizhuCardFace(all.find((c) => c.id === 'ddz-BJ')!))   // 'BJ'
console.log(doudizhuCardFace(all.find((c) => c.id === 'ddz-SJ')!))   // 'SJ'

rankLabel() ​

ts
function rankLabel(point: number): string

将数值点数(3..15)转换为显示标签。

参数 ​

  • point: number — 数值点数

返回值 ​

  • string — 显示标签:
    • 11 → 'J'
    • 12 → 'Q'
    • 13 → 'K'
    • 14 → 'A'
    • 15 → '2'
    • 其它 → String(point)(如 3 → '3',10 → '10')

牌堆常量 ​

DOUDIZHU_DECK_SIZE ​

ts
const DOUDIZHU_DECK_SIZE = 54

标准斗地主牌堆总张数(52 花色牌 + 2 王)。

DOUDIZHU_HAND_SIZE ​

ts
const DOUDIZHU_HAND_SIZE = 17

发给 3 位玩家每人手牌的张数(共 17 * 3 = 51 张,剩 3 张作底牌)。

DOUDIZHU_BOTTOM_SIZE ​

ts
const DOUDIZHU_BOTTOM_SIZE = 3

作为底牌保留的张数,后续亮给地主(地主最终持 17 + 3 = 20 张)。

DOUDIZHU_PLAYER_COUNT ​

ts
const DOUDIZHU_PLAYER_COUNT = 3

标准斗地主的玩家数量。

DOUDIZHU_SUITS ​

ts
const DOUDIZHU_SUITS = ['♠', '♥', '♣', '♦'] as const

斗地主使用的 4 种花色(黑桃 / 红桃 / 梅花 / 方块)。王不属于任何花色。

DoudizhuSuit ​

ts
type DoudizhuSuit = (typeof DOUDIZHU_SUITS)[number]

斗地主花色字面量类型,由 DOUDIZHU_SUITS 推导,即 '♠' | '♥' | '♣' | '♦'。

DOUDIZHU_RANKS ​

ts
const DOUDIZHU_RANKS = [
  '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'
] as const

按点数顺序(3..2)排列的稳定点数标签。注意 '2' 排在最后(点数 15,最大),'A' 排在 '2' 前(点数 14)。


牌堆工厂 ​

createDoudizhuCards() ​

ts
function createDoudizhuCards(): DoudizhuCard[]

按稳定的规范顺序构建 54 张斗地主牌堆(未洗牌)。

参数 ​

无。

返回值 ​

  • DoudizhuCard[] — 54 张卡牌,顺序为:先按 DOUDIZHU_RANKS 顺序遍历 13 个点数,每个点数按 DOUDIZHU_SUITS 顺序遍历 4 种花色(共 52 张花色牌),最后追加小王(ddz-SJ)和大王(ddz-BJ)。

卡牌 id 规范 ​

每张牌的 id 采用规范格式,保证同一副牌内 id 稳定可复现(便于回放与测试断言):

牌idtypedata
黑桃 3'ddz-♠-3''standard'{ point: 3, isJoker: false }
红桃 A'ddz-♥-A''standard'{ point: 14, isJoker: false }
方块 2'ddz-♦-2''standard'{ point: 15, isJoker: false }
小王'ddz-SJ''joker'{ point: 16, isJoker: true }
大王'ddz-BJ''joker'{ point: 17, isJoker: true }

行为 ​

内部使用两个工厂函数:

  • makeSuitedCard(id, suit, rank):构造花色牌,data = { point, isJoker: false },tags = [suit, rank],value = point;
  • makeJokerCard(id, point):构造王,data = { point, isJoker: true },rank = 'BJ' | 'SJ',tags = ['joker', 'BJ' | 'SJ'],value = point。

示例 ​

ts
import { createDoudizhuCards, doudizhuCardFace } from 'decklet/doudizhu'

const deck = createDoudizhuCards()
console.log(deck.length)                              // 54
console.log(deck[0]!.id)                              // 'ddz-♠-3'
console.log(deck[deck.length - 1]!.id)               // 'ddz-BJ'
console.log(doudizhuCardFace(deck.find((c) => c.id === 'ddz-♥-A')!)) // '♥A'

createShuffledDoudizhuDeck() ​

ts
function createShuffledDoudizhuDeck(random: RandomProvider): DoudizhuCard[]

返回洗牌后的斗地主牌堆。

参数 ​

  • random: RandomProvider — Engine 的 RandomProvider 实例。在 Plugin 内部传 ctx.random,确定性测试可传 SeededRandomProvider。

返回值 ​

  • DoudizhuCard[] — 洗牌后的 54 张卡牌。相同种子下每次调用得到的洗牌顺序相同。

行为 ​

内部调用 random.shuffle(createDoudizhuCards())。

示例 ​

ts
import {
  createShuffledDoudizhuDeck,
  SeededRandomProvider,
  generateRandomSeed
} from 'decklet/doudizhu'

const seed = generateRandomSeed()
const random = new SeededRandomProvider(seed)
const deck1 = createShuffledDoudizhuDeck(random)
const deck2 = createShuffledDoudizhuDeck(new SeededRandomProvider(seed))
console.log(deck1[0]!.id === deck2[0]!.id) // true(同种子可复现)

完整对照表 ​

常量 / 类型值 / 类型说明
DOUDIZHU_DECK_SIZE54牌堆总张数
DOUDIZHU_HAND_SIZE17每人手牌张数
DOUDIZHU_BOTTOM_SIZE3底牌张数
DOUDIZHU_PLAYER_COUNT3玩家数量
DOUDIZHU_SUITS['♠','♥','♣','♦'] as const4 种花色
DOUDIZHU_RANKS['3','4',...,'A','2'] as const13 个点数标签
DoudizhuSuit'♠' | '♥' | '♣' | '♦'花色字面量类型
SMALL_JOKER_POINT16小王点数
BIG_JOKER_POINT17大王点数
MIN_CHAIN_POINT3可连子最小点数
MAX_CHAIN_POINT14可连子最大点数(A)

注意事项 ​

  • 斗地主的点数排序(A=14, 2=15, SJ=16, BJ=17)与扑克不同(扑克中 A=14, 2=2),因此斗地主插件使用自己的 DoudizhuRankResolver,不复用 StandardPokerRankResolver。
  • 卡牌 id 采用规范格式(如 ddz-♠-3、ddz-BJ),同一副牌内稳定可复现——便于回放(ReplayEngine)与测试断言。
  • asDoudizhuCard 的类型守卫基于 data 对象的鸭子类型检查('point' in data && 'isJoker' in data),不依赖 card.type 字段。若其它插件的卡牌 payload 恰好也有 point 与 isJoker 字段,可能产生误判。
  • createShuffledDoudizhuDeck 的洗牌结果完全由传入的 RandomProvider 决定;在 DoudizhuPlugin.setup 中传入 ctx.random,故 Engine 的种子(new GameEngine({ seed }))决定整局洗牌。