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
|
|||
|
|
}
|