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

138 lines
3.2 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 xiangqi
import (
"math/rand"
"sort"
)
// pieceValues 兵种基础分值(评估函数用)
var pieceValues = map[int8]int{
King: 100000, Advisor: 200, Bishop: 200,
Knight: 420, Rook: 900, Cannon: 460, Pawn: 100,
}
// Eval 评估当前局面(红方视角:正数红优,负数黑优)
// 物质分 + 少量位置分(兵过河、子力靠近中路)
func (b *Board) Eval() int {
score := 0
for r := 0; r < 10; r++ {
for c := 0; c < 9; c++ {
p := b[r][c]
if p == 0 {
continue
}
v := pieceValues[abs8(p)]
// 过河兵大幅升值
if abs8(p) == Pawn && crossedRiver(r, side(p)) {
v += 120
// 越深入敌阵越值钱
if side(p) == Red {
v += (4 - r) * 20
} else {
v += (r - 5) * 20
}
}
// 车马炮靠近中路略有加分(简易位置评估)
if abs8(p) == Rook || abs8(p) == Knight || abs8(p) == Cannon {
center := c
if center > 4 {
center = 8 - center
}
v += center * 4
}
if p > 0 {
score += v
} else {
score -= v
}
}
}
return score
}
// orderMoves 着法排序:吃子优先(提高剪枝效率)
func (b *Board) orderMoves(moves []Move) {
sort.SliceStable(moves, func(i, j int) bool {
vi := pieceValues[abs8(b[moves[i].ToR][moves[i].ToC])]
vj := pieceValues[abs8(b[moves[j].ToR][moves[j].ToC])]
return vi > vj
})
}
// alphaBeta 极大极小搜索 + Alpha-Beta 剪枝,返回 s 方视角的最优分
func (b *Board) alphaBeta(s, depth, alpha, beta int) int {
if depth == 0 {
return b.Eval() * s // 转成当前方视角
}
moves := b.LegalMoves(s)
if len(moves) == 0 {
return -90000 - depth // 无子可动=被将死,越早被将死分越低
}
b.orderMoves(moves)
for _, m := range moves {
captured := b.Apply(m)
score := -b.alphaBeta(-s, depth-1, -beta, -alpha)
b.Undo(m, captured)
if score >= beta {
return beta
}
if score > alpha {
alpha = score
}
}
return alpha
}
// BestMove 搜索指定深度的最优着法depth 1-3越深越强
// 返回着法与该着法的评估分
func (b *Board) BestMove(s, depth int) (Move, int) {
moves := b.LegalMoves(s)
if len(moves) == 0 {
return Move{}, -100000
}
b.orderMoves(moves)
best := moves[0]
bestScore := -1000000
for _, m := range moves {
captured := b.Apply(m)
score := -b.alphaBeta(-s, depth-1, -1000000, 1000000)
b.Undo(m, captured)
if score > bestScore {
bestScore = score
best = m
}
}
return best, bestScore
}
// RankedMoves 按评估分从高到低返回全部合法着法LLM 候选着法用)
func (b *Board) RankedMoves(s, depth int) []Move {
moves := b.LegalMoves(s)
type scored struct {
m Move
v int
}
list := make([]scored, 0, len(moves))
for _, m := range moves {
captured := b.Apply(m)
v := -b.alphaBeta(-s, depth-1, -1000000, 1000000)
b.Undo(m, captured)
list = append(list, scored{m, v})
}
sort.SliceStable(list, func(i, j int) bool { return list[i].v > list[j].v })
result := make([]Move, len(list))
for i, it := range list {
result[i] = it.m
}
return result
}
// RandomMove 随机合法着法(简单难度的扰动用)
func (b *Board) RandomMove(s int) (Move, bool) {
moves := b.LegalMoves(s)
if len(moves) == 0 {
return Move{}, false
}
return moves[rand.Intn(len(moves))], true
}