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