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 是否能压过 other(other 为 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 }