Files
2026-08-14 13:17:03 +08:00

181 lines
6.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}