181 lines
6.3 KiB
Go
181 lines
6.3 KiB
Go
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
|
||
}
|