245 lines
5.5 KiB
Go
245 lines
5.5 KiB
Go
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
|
||
}
|