Files

181 lines
6.3 KiB
Go
Raw Permalink Normal View History

2026-08-14 13:17:03 +08:00
package ddz
import "sort"
// ComboType 牌型枚举
type ComboType int
// 全部合法牌型
const (
ComboInvalid ComboType = iota // 非法牌型
ComboSingle // 单张
ComboPair // 对子
ComboTriple // 三张(不带)
ComboTripleOne // 三带一
ComboTriplePair // 三带二(一对)
ComboStraight // 顺子(>=5张连续单牌最大到A
ComboPairStraight // 连对(>=3对连续最大到A
ComboPlane // 飞机不带(>=2组连续三张
ComboPlaneSingle // 飞机带单(每组三张带一单)
ComboPlanePair // 飞机带对(每组三张带一对)
ComboFourTwo // 四带二(两张单牌)
ComboFourTwoPair // 四带两对
ComboBomb // 炸弹(四张同点)
ComboRocket // 王炸(双王)
)
// comboNames 牌型中文名(结算展示与 AI 提示词用)
var comboNames = map[ComboType]string{
ComboSingle: "单张", ComboPair: "对子", ComboTriple: "三张",
ComboTripleOne: "三带一", ComboTriplePair: "三带二",
ComboStraight: "顺子", ComboPairStraight: "连对",
ComboPlane: "飞机", ComboPlaneSingle: "飞机带单", ComboPlanePair: "飞机带对",
ComboFourTwo: "四带二", ComboFourTwoPair: "四带两对",
ComboBomb: "炸弹", ComboRocket: "王炸",
}
// Combo 一手牌的解析结果
type Combo struct {
Type ComboType `json:"type"` // 牌型
MainRank int `json:"main_rank"` // 主牌点数(同型比较依据)
Length int `json:"length"` // 连续组数(顺子张数/连对对数/飞机组数非连续型为1
Cards []int `json:"cards"` // 原始牌
}
// TypeName 牌型中文名
func (c *Combo) TypeName() string { return comboNames[c.Type] }
// Desc 牌面描述(如 "三带一 888+4"简化为牌名列表)
func (c *Combo) Desc() string { return c.TypeName() + " " + CardsName(c.Cards) }
// Parse 解析一组牌的牌型,非法返回 nil
func Parse(cards []int) *Combo {
n := len(cards)
if n == 0 {
return nil
}
cnt := rankCount(cards)
// 王炸:恰好双王
if n == 2 && cnt[16] == 1 && cnt[17] == 1 {
return &Combo{Type: ComboRocket, MainRank: 17, Length: 1, Cards: cards}
}
// 按张数分桶ranksOf[k] = 恰好出现 k 次的点数列表(升序)
ranksOf := map[int][]int{}
for r, k := range cnt {
ranksOf[k] = append(ranksOf[k], r)
}
for k := range ranksOf {
sort.Ints(ranksOf[k])
}
switch {
case n == 1:
return &Combo{Type: ComboSingle, MainRank: Rank(cards[0]), Length: 1, Cards: cards}
case n == 2 && len(ranksOf[2]) == 1:
return &Combo{Type: ComboPair, MainRank: ranksOf[2][0], Length: 1, Cards: cards}
case n == 3 && len(ranksOf[3]) == 1:
return &Combo{Type: ComboTriple, MainRank: ranksOf[3][0], Length: 1, Cards: cards}
case n == 4 && len(ranksOf[4]) == 1:
return &Combo{Type: ComboBomb, MainRank: ranksOf[4][0], Length: 1, Cards: cards}
case n == 4 && len(ranksOf[3]) == 1:
// 三带一3+1
return &Combo{Type: ComboTripleOne, MainRank: ranksOf[3][0], Length: 1, Cards: cards}
case n == 5 && len(ranksOf[3]) == 1 && len(ranksOf[2]) == 1:
// 三带二3+2
return &Combo{Type: ComboTriplePair, MainRank: ranksOf[3][0], Length: 1, Cards: cards}
case n == 6 && len(ranksOf[4]) == 1 && (len(ranksOf[1]) == 2 || len(ranksOf[2]) == 1):
// 四带二4+1+1 或 4+2带一对也按四带二算
return &Combo{Type: ComboFourTwo, MainRank: ranksOf[4][0], Length: 1, Cards: cards}
case n == 8 && len(ranksOf[4]) == 1 && len(ranksOf[2]) == 2:
// 四带两对4+2+2
return &Combo{Type: ComboFourTwoPair, MainRank: ranksOf[4][0], Length: 1, Cards: cards}
}
// 顺子:>=5 张互不相同且连续,不能含 2 与王(最大到 A=14
if n >= 5 && len(ranksOf[1]) == n && isConsecutive(ranksOf[1]) && ranksOf[1][n-1] <= 14 {
return &Combo{Type: ComboStraight, MainRank: ranksOf[1][n-1], Length: n, Cards: cards}
}
// 连对:>=3 对连续,不能含 2 与王
if n >= 6 && n%2 == 0 && len(ranksOf[2]) == n/2 && isConsecutive(ranksOf[2]) && ranksOf[2][n/2-1] <= 14 {
return &Combo{Type: ComboPairStraight, MainRank: ranksOf[2][n/2-1], Length: n / 2, Cards: cards}
}
// 飞机系列:找出连续的三张组
triples := ranksOf[3]
if len(triples) >= 2 {
// 取最长的连续三张段(要求 <= A
seq := longestConsecutive(triples, 14)
if len(seq) >= 2 {
k := len(seq)
switch n {
case 3 * k:
// 飞机不带
if len(ranksOf[3]) == k {
return &Combo{Type: ComboPlane, MainRank: seq[k-1], Length: k, Cards: cards}
}
case 4 * k:
// 飞机带单3k + k 张单(其余牌任意,不校验细节组合)
return &Combo{Type: ComboPlaneSingle, MainRank: seq[k-1], Length: k, Cards: cards}
case 5 * k:
// 飞机带对3k + k 对
if len(ranksOf[2]) == k {
return &Combo{Type: ComboPlanePair, MainRank: seq[k-1], Length: k, Cards: cards}
}
}
}
}
return nil
}
// isConsecutive 判断升序点数列表是否连续
func isConsecutive(ranks []int) bool {
for i := 1; i < len(ranks); i++ {
if ranks[i] != ranks[i-1]+1 {
return false
}
}
return len(ranks) > 0
}
// longestConsecutive 从升序列表中取最长连续段(元素需 <= maxRank返回该段
func longestConsecutive(ranks []int, maxRank int) []int {
best, cur := []int{}, []int{}
for _, r := range ranks {
if r > maxRank {
break
}
if len(cur) == 0 || r == cur[len(cur)-1]+1 {
cur = append(cur, r)
} else {
cur = []int{r}
}
if len(cur) > len(best) {
best = append([]int{}, cur...)
}
}
return best
}
// Beats 判断 c 是否能压过 otherother 为 nil 表示自由出牌,恒可出)
func (c *Combo) Beats(other *Combo) bool {
if other == nil {
return true
}
// 王炸压一切
if c.Type == ComboRocket {
return true
}
if other.Type == ComboRocket {
return false
}
// 炸弹压所有非炸弹;炸弹之间比点数
if c.Type == ComboBomb && other.Type != ComboBomb {
return true
}
if c.Type != ComboBomb && other.Type == ComboBomb {
return false
}
// 同型同长才可比较,比主牌点数
if c.Type != other.Type || c.Length != other.Length {
return false
}
return c.MainRank > other.MainRank
}