package ddz import "sort" // cardsByRank 把手牌按点数分组(点数 → 该点数的牌列表) func cardsByRank(hand []int) map[int][]int { m := map[int][]int{} for _, c := range hand { r := Rank(c) m[r] = append(m[r], c) } return m } // sortedRanks 返回分组后升序的点数列表 func sortedRanks(byRank map[int][]int) []int { ranks := make([]int, 0, len(byRank)) for r := range byRank { ranks = append(ranks, r) } sort.Ints(ranks) return ranks } // GenMoves 生成手牌中所有能压过 last 的候选出牌(last 为 nil 时生成所有可首出牌型) // 返回结果按"牌力从小到大"排序,方便规则 AI 优先出小牌 func GenMoves(hand []int, last *Combo) [][]int { byRank := cardsByRank(hand) ranks := sortedRanks(byRank) var moves [][]int // add 尝试把一组牌解析并校验能否压过 last,可以则加入候选 add := func(cards []int) { if combo := Parse(cards); combo != nil && combo.Beats(last) { moves = append(moves, cards) } } // 单张:每个点数取一张 for _, r := range ranks { add([]int{byRank[r][0]}) } // 对子 for _, r := range ranks { if len(byRank[r]) >= 2 { add(byRank[r][:2]) } } // 三张 / 三带一 / 三带二 for _, r := range ranks { if len(byRank[r]) < 3 { continue } triple := byRank[r][:3] add(triple) // 三带一:附一张最小的其他单牌 for _, r2 := range ranks { if r2 != r { add(append(append([]int{}, triple...), byRank[r2][0])) break } } // 三带二:附一对最小的其他对子 for _, r2 := range ranks { if r2 != r && len(byRank[r2]) >= 2 { add(append(append([]int{}, triple...), byRank[r2][:2]...)) break } } } // 顺子:从每个起点尝试每种长度(5张起,点数<=A) for length := 5; length <= 12; length++ { for start := 3; start+length-1 <= 14; start++ { cards := make([]int, 0, length) ok := true for r := start; r < start+length; r++ { if len(byRank[r]) == 0 { ok = false break } cards = append(cards, byRank[r][0]) } if ok { add(cards) } } } // 连对:3对起(点数<=A) for pairs := 3; pairs <= 10; pairs++ { for start := 3; start+pairs-1 <= 14; start++ { cards := make([]int, 0, pairs*2) ok := true for r := start; r < start+pairs; r++ { if len(byRank[r]) < 2 { ok = false break } cards = append(cards, byRank[r][:2]...) } if ok { add(cards) } } } // 飞机(2组连续三张,带单/带对各生成一种最小附牌方案) for groups := 2; groups <= 5; groups++ { for start := 3; start+groups-1 <= 14; start++ { main := make([]int, 0, groups*3) mainRanks := map[int]bool{} ok := true for r := start; r < start+groups; r++ { if len(byRank[r]) < 3 { ok = false break } main = append(main, byRank[r][:3]...) mainRanks[r] = true } if !ok { continue } // 飞机不带 add(main) // 飞机带单:取最小的 groups 张非主牌单牌 singles := []int{} for _, r := range ranks { if mainRanks[r] { continue } for _, c := range byRank[r] { singles = append(singles, c) if len(singles) == groups { break } } if len(singles) == groups { break } } if len(singles) == groups { add(append(append([]int{}, main...), singles...)) } // 飞机带对:取最小的 groups 对非主牌对子 pairs := []int{} pairCnt := 0 for _, r := range ranks { if mainRanks[r] || len(byRank[r]) < 2 { continue } pairs = append(pairs, byRank[r][:2]...) pairCnt++ if pairCnt == groups { break } } if pairCnt == groups { add(append(append([]int{}, main...), pairs...)) } } } // 四带二 / 四带两对 for _, r := range ranks { if len(byRank[r]) < 4 { continue } four := byRank[r][:4] // 两张最小单牌 singles := []int{} for _, r2 := range ranks { if r2 == r { continue } for _, c := range byRank[r2] { singles = append(singles, c) if len(singles) == 2 { break } } if len(singles) == 2 { break } } if len(singles) == 2 { add(append(append([]int{}, four...), singles...)) } // 两对最小对子 pairs := []int{} cnt := 0 for _, r2 := range ranks { if r2 == r || len(byRank[r2]) < 2 { continue } pairs = append(pairs, byRank[r2][:2]...) cnt++ if cnt == 2 { break } } if cnt == 2 { add(append(append([]int{}, four...), pairs...)) } } // 炸弹与王炸 for _, r := range ranks { if len(byRank[r]) >= 4 { add(byRank[r][:4]) } } if len(byRank[16]) == 1 && len(byRank[17]) == 1 { add([]int{byRank[16][0], byRank[17][0]}) } // 排序:非炸弹在前(按主牌点数与张数),炸弹王炸垫后(规则 AI 不轻易放炸弹) sort.SliceStable(moves, func(i, j int) bool { ci, cj := Parse(moves[i]), Parse(moves[j]) bi := ci.Type == ComboBomb || ci.Type == ComboRocket bj := cj.Type == ComboBomb || cj.Type == ComboRocket if bi != bj { return !bi } if ci.MainRank != cj.MainRank { return ci.MainRank < cj.MainRank } return len(moves[i]) < len(moves[j]) }) return moves } // HandStrength 粗略评估手牌强度(叫地主用):大王4分、小王3分、2各2分、A各1分、炸弹3分 func HandStrength(hand []int) int { score := 0 cnt := rankCount(hand) if cnt[17] > 0 { score += 4 } if cnt[16] > 0 { score += 3 } score += cnt[15] * 2 score += cnt[14] for r, k := range cnt { if k == 4 && r <= 15 { score += 3 } } return score }