295 lines
6.2 KiB
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! ***")
|
|
}
|
|
}
|
|
|