golang-chess/ai.go

295 lines
6.2 KiB
Go

package main
import (
"fmt"
"sort"
)
const aiDepth = 3
var pieceValues = map[int]int{
Empty: 0, Pawn: 100, Knight: 320, Bishop: 330,
Rook: 500, Queen: 900, King: 20000,
}
var pawnTable = [8][8]int{
{0, 0, 0, 0, 0, 0, 0, 0},
{50, 50, 50, 50, 50, 50, 50, 50},
{10, 10, 20, 30, 30, 20, 10, 10},
{5, 5, 10, 25, 25, 10, 5, 5},
{0, 0, 0, 20, 20, 0, 0, 0},
{5, -5, -10, 0, 0, -10, -5, 5},
{5, 10, 10, -20, -20, 10, 10, 5},
{0, 0, 0, 0, 0, 0, 0, 0},
}
var knightTable = [8][8]int{
{-50, -40, -30, -30, -30, -30, -40, -50},
{-40, -20, 0, 0, 0, 0, -20, -40},
{-30, 0, 10, 15, 15, 10, 0, -30},
{-30, 5, 15, 20, 20, 15, 5, -30},
{-30, 0, 15, 20, 20, 15, 0, -30},
{-30, 5, 10, 15, 15, 10, 5, -30},
{-40, -20, 0, 5, 5, 0, -20, -40},
{-50, -40, -30, -30, -30, -30, -40, -50},
}
var bishopTable = [8][8]int{
{-20, -10, -10, -10, -10, -10, -10, -20},
{-10, 0, 0, 0, 0, 0, 0, -10},
{-10, 0, 5, 10, 10, 5, 0, -10},
{-10, 5, 5, 10, 10, 5, 5, -10},
{-10, 0, 10, 10, 10, 10, 0, -10},
{-10, 10, 10, 10, 10, 10, 10, -10},
{-10, 5, 0, 0, 0, 0, 5, -10},
{-20, -10, -10, -10, -10, -10, -10, -20},
}
var rookTable = [8][8]int{
{0, 0, 0, 0, 0, 0, 0, 0},
{5, 10, 10, 10, 10, 10, 10, 5},
{-5, 0, 0, 0, 0, 0, 0, -5},
{-5, 0, 0, 0, 0, 0, 0, -5},
{-5, 0, 0, 0, 0, 0, 0, -5},
{-5, 0, 0, 0, 0, 0, 0, -5},
{-5, 0, 0, 0, 0, 0, 0, -5},
{0, 0, 0, 5, 5, 0, 0, 0},
}
var queenTable = [8][8]int{
{-20, -10, -10, -5, -5, -10, -10, -20},
{-10, 0, 0, 0, 0, 0, 0, -10},
{-10, 0, 5, 5, 5, 5, 0, -10},
{-5, 0, 5, 5, 5, 5, 0, -5},
{0, 0, 5, 5, 5, 5, 0, -5},
{-10, 5, 5, 5, 5, 5, 0, -10},
{-10, 0, 5, 0, 0, 0, 0, -10},
{-20, -10, -10, -5, -5, -10, -10, -20},
}
var kingTable = [8][8]int{
{-30, -40, -40, -50, -50, -40, -40, -30},
{-30, -40, -40, -50, -50, -40, -40, -30},
{-30, -40, -40, -50, -50, -40, -40, -30},
{-30, -40, -40, -50, -50, -40, -40, -30},
{-20, -30, -30, -40, -40, -30, -30, -20},
{-10, -20, -20, -20, -20, -20, -20, -10},
{20, 20, 0, 0, 0, 0, 20, 20},
{20, 30, 10, 0, 0, 10, 30, 20},
}
func evaluate(b *Board) int {
score := 0
for r := 0; r < 8; r++ {
for c := 0; c < 8; c++ {
s := b.Grid[r][c]
if s.Piece == Empty {
continue
}
val := pieceValues[s.Piece]
var bonus int
switch s.Piece {
case Pawn:
if s.Color == White {
bonus = pawnTable[r][c]
} else {
bonus = pawnTable[7-r][c]
}
case Knight:
if s.Color == White {
bonus = knightTable[r][c]
} else {
bonus = knightTable[7-r][c]
}
case Bishop:
if s.Color == White {
bonus = bishopTable[r][c]
} else {
bonus = bishopTable[7-r][c]
}
case Rook:
if s.Color == White {
bonus = rookTable[r][c]
} else {
bonus = rookTable[7-r][c]
}
case Queen:
if s.Color == White {
bonus = queenTable[r][c]
} else {
bonus = queenTable[7-r][c]
}
case King:
if s.Color == White {
bonus = kingTable[r][c]
} else {
bonus = kingTable[7-r][c]
}
}
if s.Color == White {
score += val + bonus
} else {
score -= val + bonus
}
}
}
return score
}
func minimax(b *Board, depth int, alpha, beta int, color int) int {
if depth == 0 {
return evaluate(b)
}
moves := b.generateLegalMoves(color)
if len(moves) == 0 {
if b.isInCheck(color) {
if color == White {
return -100000 - depth
}
return 100000 + depth
}
return 0
}
oppColor := Black
if color == White {
oppColor = Black
} else {
oppColor = White
}
if color == White {
maxEval := -999999
for _, m := range moves {
clone := b.Clone()
clone.applyMove(m)
eval := minimax(clone, depth-1, alpha, beta, oppColor)
if eval > maxEval {
maxEval = eval
}
if maxEval > alpha {
alpha = maxEval
}
if alpha >= beta {
break
}
}
return maxEval
} else {
minEval := 999999
for _, m := range moves {
clone := b.Clone()
clone.applyMove(m)
eval := minimax(clone, depth-1, alpha, beta, oppColor)
if eval < minEval {
minEval = eval
}
if minEval < beta {
beta = minEval
}
if alpha >= beta {
break
}
}
return minEval
}
}
func GetBestMove(b *Board, color int) (string, error) {
moves := b.generateLegalMoves(color)
if len(moves) == 0 {
return "", fmt.Errorf("no legal moves")
}
oppColor := Black
if color == White {
oppColor = Black
} else {
oppColor = White
}
var bestMove *Move
var bestScore int
first := true
alpha := -999999
beta := 999999
sort.Slice(moves, func(i, j int) bool {
vi := 0
vj := 0
if b.Grid[moves[i].To.Row][moves[i].To.Col].Piece != Empty {
vi = pieceValues[b.Grid[moves[i].To.Row][moves[i].To.Col].Piece]
}
if b.Grid[moves[j].To.Row][moves[j].To.Col].Piece != Empty {
vj = pieceValues[b.Grid[moves[j].To.Row][moves[j].To.Col].Piece]
}
return vi > vj
})
for _, m := range moves {
clone := b.Clone()
clone.applyMove(m)
score := minimax(clone, aiDepth-1, alpha, beta, oppColor)
if first {
bestMove = &m
bestScore = score
first = false
} else if color == White && score > bestScore {
bestScore = score
bestMove = &m
alpha = score
} else if color == Black && score < bestScore {
bestScore = score
bestMove = &m
beta = score
}
}
if bestMove == nil {
return "", fmt.Errorf("no move found")
}
return MoveToAlgebraic(*bestMove), nil
}
func PlayVsComputer() {
fmt.Println("=== CHESS VS COMPUTER ===")
fmt.Println("You play WHITE. Enter moves like: e2e4, g1f3, e7e8q")
fmt.Println("Type 'quit' to exit.")
fmt.Println()
g := NewGame()
fmt.Print(g.Render())
for !g.Over {
fmt.Print("White> ")
var input string
fmt.Scanln(&input)
if input == "quit" || input == "q" {
fmt.Println("Goodbye!")
return
}
_, err := g.MakeMove(input)
if err != nil {
fmt.Printf(" Error: %v\n\n", err)
continue
}
fmt.Print(g.Render())
if g.Over {
break
}
fmt.Print(" Black thinking... ")
aiMove, err := GetBestMove(g.Board, Black)
if err != nil {
fmt.Printf("AI error: %v\n", err)
break
}
fmt.Printf("Black plays %s\n", aiMove)
_, err = g.MakeMove(aiMove)
if err != nil {
fmt.Printf(" Error: %v\n", err)
continue
}
fmt.Print(g.Render())
}
switch g.Result {
case WhiteWins:
fmt.Println(" *** YOU WIN! ***")
case BlackWins:
fmt.Println(" *** COMPUTER WINS! ***")
case Draw:
fmt.Println(" *** DRAW! ***")
}
}