go_games_collection/chess_bot.go

181 lines
4.8 KiB
Go
Raw 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 main
import "math"
// ChessBot — бот на основе минимакса с альфа-бета отсечением.
// Ветвление в шахматах гораздо шире, чем в шашках, поэтому глубина
// поиска по умолчанию заметно меньше.
type ChessBot struct {
Depth int // глубина поиска в полуходах; 0 -> используется значение по умолчанию
}
// Уровни сложности: разница только в глубине поиска.
const (
ChessDifficultyEasy = 2 // "Простой" — быстрый, играет более-менее случайно с точки зрения стратегии
ChessDifficultyHard = 4 // "Сильный" — заметно сильнее, всё ещё отвечает быстро
)
const defaultChessDepth = 3
// chessMoveStep — один ход (from->to), достаточный для одного
// вызова g.Move.
type chessMoveStep struct {
From ChessPos
To ChessPos
}
// legalChessSteps перечисляет все допустимые ходы для стороны color.
func legalChessSteps(g *ChessGameState, color ChessColor) []chessMoveStep {
var out []chessMoveStep
for r := 0; r < 8; r++ {
for c := 0; c < 8; c++ {
p := g.Board[r][c]
if p == nil || p.Color != color {
continue
}
pos := ChessPos{r, c}
for _, opt := range g.LegalMovesFrom(pos) {
out = append(out, chessMoveStep{From: pos, To: opt.To})
}
}
}
return out
}
var chessPieceValue = map[ChessPieceType]int{
ChessPawn: 100, ChessKnight: 320, ChessBishop: 330, ChessRook: 500, ChessQueen: 900, ChessKing: 0,
}
// chessCenterBonus — небольшой позиционный бонус за контроль центра
// (тем же духом, что и бонус за продвижение шашек к дамке): не
// претендует на глубокое понимание позиции, просто чуть предпочитает
// активные фигуры пассивным.
func chessCenterBonus(pos ChessPos) int {
dr := pos.Row - 3
if dr < 0 {
dr = -dr
}
dc := pos.Col - 3
if dc < 0 {
dc = -dc
}
dist := dr + dc
return 6 - dist
}
// evaluateChess оценивает позицию с точки зрения perspective:
// положительно — хорошо для perspective.
func evaluateChess(g *ChessGameState, perspective ChessColor) int {
if g.Result != nil {
if g.Result.Draw {
return 0
}
if g.Result.Winner == perspective {
return 1000000
}
return -1000000
}
score := 0
for r := 0; r < 8; r++ {
for c := 0; c < 8; c++ {
p := g.Board[r][c]
if p == nil {
continue
}
value := chessPieceValue[p.Type] + chessCenterBonus(ChessPos{r, c})
if p.Color == perspective {
score += value
} else {
score -= value
}
}
}
return score
}
func minimaxChess(g *ChessGameState, depth int, alpha, beta int, perspective ChessColor) int {
if g.Result != nil || depth == 0 {
return evaluateChess(g, perspective)
}
steps := legalChessSteps(g, g.Turn)
if len(steps) == 0 {
return evaluateChess(g, perspective)
}
maximizing := g.Turn == perspective
if maximizing {
best := math.MinInt32
for _, step := range steps {
child := cloneChess(g)
_ = child.Move(step.From, step.To)
val := minimaxChess(child, depth-1, alpha, beta, perspective)
if val > best {
best = val
}
if best > alpha {
alpha = best
}
if alpha >= beta {
break
}
}
return best
}
best := math.MaxInt32
for _, step := range steps {
child := cloneChess(g)
_ = child.Move(step.From, step.To)
val := minimaxChess(child, depth-1, alpha, beta, perspective)
if val < best {
best = val
}
if best < beta {
beta = best
}
if alpha >= beta {
break
}
}
return best
}
// DecideMove выбирает лучший ход для текущего хода в g согласно
// минимаксу.
func (b ChessBot) DecideMove(g *ChessGameState) (chessMoveStep, bool) {
steps := legalChessSteps(g, g.Turn)
if len(steps) == 0 {
return chessMoveStep{}, false
}
depth := b.Depth
if depth <= 0 {
depth = defaultChessDepth
}
perspective := g.Turn
bestVal := math.MinInt32
var bestStep chessMoveStep
alpha, beta := math.MinInt32, math.MaxInt32
for _, step := range steps {
child := cloneChess(g)
_ = child.Move(step.From, step.To)
val := minimaxChess(child, depth-1, alpha, beta, perspective)
if val > bestVal {
bestVal = val
bestStep = step
}
if bestVal > alpha {
alpha = bestVal
}
}
return bestStep, true
}
// PlayFullTurn исполняет один ход бота.
func (b ChessBot) PlayFullTurn(g *ChessGameState) error {
step, ok := b.DecideMove(g)
if !ok {
return nil
}
return g.Move(step.From, step.To)
}