Files

245 lines
5.5 KiB
Go
Raw Permalink Normal View History

2026-08-14 13:17:03 +08:00
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
}