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

245 lines
5.5 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"
// 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
}