138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
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
|
||
}
|