Initial work on search and eval
This commit is contained in:
parent
51ef1ff9db
commit
a94d9ae1f0
|
@ -0,0 +1,69 @@
|
||||||
|
# Copyright 2024 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
## Position evaluation utilities
|
||||||
|
import board
|
||||||
|
|
||||||
|
type
|
||||||
|
Score* = int16
|
||||||
|
|
||||||
|
|
||||||
|
proc getPieceValue(kind: PieceKind): Score =
|
||||||
|
## Returns the absolute value of a piece
|
||||||
|
case kind:
|
||||||
|
of Pawn:
|
||||||
|
return Score(100)
|
||||||
|
of Bishop:
|
||||||
|
return Score(330)
|
||||||
|
of Knight:
|
||||||
|
return Score(280)
|
||||||
|
of Rook:
|
||||||
|
return Score(525)
|
||||||
|
of Queen:
|
||||||
|
return Score(950)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
|
proc getPieceScore(board: Chessboard, square: Square): Score =
|
||||||
|
## Returns the value of the piece located at
|
||||||
|
## the given square
|
||||||
|
return board.getPiece(square).kind.getPieceValue()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
|
## Returns the material evaluation of the
|
||||||
|
## current position relative to white (positive
|
||||||
|
## if in white's favor, negative otherwise)
|
||||||
|
var
|
||||||
|
whiteScore: Score
|
||||||
|
blackScore: Score
|
||||||
|
|
||||||
|
for sq in board.getOccupancyFor(White):
|
||||||
|
whiteScore += board.getPieceScore(sq)
|
||||||
|
|
||||||
|
for sq in board.getOccupancyFor(Black):
|
||||||
|
blackScore += board.getPieceScore(sq)
|
||||||
|
|
||||||
|
result = whiteScore - blackScore
|
||||||
|
if board.position.sideToMove == Black:
|
||||||
|
result *= -1
|
||||||
|
|
||||||
|
|
||||||
|
proc evaluate*(board: Chessboard): Score =
|
||||||
|
## Evaluates the current position
|
||||||
|
|
||||||
|
|
||||||
|
result = board.evaluateMaterial()
|
|
@ -0,0 +1,74 @@
|
||||||
|
# Copyright 2024 Mattia Giambirtone & All Contributors
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
## Implementation of negamax with a/b pruning
|
||||||
|
import board
|
||||||
|
import movegen
|
||||||
|
import eval
|
||||||
|
|
||||||
|
|
||||||
|
import std/atomics
|
||||||
|
|
||||||
|
|
||||||
|
func lowestEval*: Score {.inline.} = Score(-32000'i16)
|
||||||
|
func highestEval*: Score {.inline.} = Score(32000'i16)
|
||||||
|
func mateScore*: Score {.inline.} = lowestEval() - Score(1)
|
||||||
|
|
||||||
|
|
||||||
|
type
|
||||||
|
SearchManager* = ref object
|
||||||
|
## A simple state storage
|
||||||
|
## for our search
|
||||||
|
stopFlag*: Atomic[bool] # Can be used to cancel the search from another thread
|
||||||
|
bestMove*: Move
|
||||||
|
|
||||||
|
|
||||||
|
proc search*(self: SearchManager, board: Chessboard, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
||||||
|
## Simple negamax search with alpha-beta pruning
|
||||||
|
if self.stopFlag.load():
|
||||||
|
# Search has been cancelled!
|
||||||
|
return
|
||||||
|
if depth == 0:
|
||||||
|
return board.evaluate()
|
||||||
|
var moves = MoveList()
|
||||||
|
board.generateMoves(moves)
|
||||||
|
if moves.len() == 0:
|
||||||
|
if board.inCheck():
|
||||||
|
# Checkmate! We add the current ply
|
||||||
|
# because mating in 3 is better than
|
||||||
|
# mating in 5 (and conversely being
|
||||||
|
# mated in 5 is better than being
|
||||||
|
# mated in 3)
|
||||||
|
return mateScore() + Score(ply)
|
||||||
|
# Stalemate
|
||||||
|
return Score(0)
|
||||||
|
var bestScore = lowestEval()
|
||||||
|
var alpha = alpha
|
||||||
|
for move in moves:
|
||||||
|
board.makeMove(move)
|
||||||
|
# Find the best move for us (worst move
|
||||||
|
# for our opponent, hence the negative sign)
|
||||||
|
let eval = -self.search(board, depth - 1, ply + 1, -beta, -alpha)
|
||||||
|
board.unmakeMove()
|
||||||
|
|
||||||
|
bestScore = max(eval, bestScore)
|
||||||
|
if eval >= beta:
|
||||||
|
# This move was too good for us, opponent will not search it
|
||||||
|
break
|
||||||
|
if eval > alpha:
|
||||||
|
alpha = eval
|
||||||
|
if ply == 0:
|
||||||
|
self.bestMove = move
|
||||||
|
|
||||||
|
return bestScore
|
|
@ -17,26 +17,31 @@ import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/random
|
import std/random
|
||||||
|
|
||||||
|
|
||||||
randomize()
|
randomize()
|
||||||
|
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import movegen
|
import movegen
|
||||||
|
import search
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
UCISession = ref object
|
UCISession = ref object
|
||||||
debug: bool
|
debug: bool
|
||||||
board: Chessboard
|
board: Chessboard
|
||||||
|
searching: bool
|
||||||
|
currentSearch: SearchManager
|
||||||
|
|
||||||
UCICommandType = enum
|
UCICommandType = enum
|
||||||
Unknown
|
Unknown,
|
||||||
IsReady
|
IsReady,
|
||||||
NewGame
|
NewGame,
|
||||||
Quit,
|
Quit,
|
||||||
Debug,
|
Debug,
|
||||||
Position,
|
Position,
|
||||||
Go
|
Go,
|
||||||
|
Stop
|
||||||
|
|
||||||
UCICommand = object
|
UCICommand = object
|
||||||
case kind: UCICommandType
|
case kind: UCICommandType
|
||||||
|
@ -213,6 +218,8 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand =
|
||||||
case cmd[current]:
|
case cmd[current]:
|
||||||
of "isready":
|
of "isready":
|
||||||
return UCICommand(kind: IsReady)
|
return UCICommand(kind: IsReady)
|
||||||
|
of "stop":
|
||||||
|
return UCICommand(kind: Stop)
|
||||||
of "ucinewgame":
|
of "ucinewgame":
|
||||||
return UCICommand(kind: NewGame)
|
return UCICommand(kind: NewGame)
|
||||||
of "quit":
|
of "quit":
|
||||||
|
@ -237,6 +244,16 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand =
|
||||||
inc(current)
|
inc(current)
|
||||||
|
|
||||||
|
|
||||||
|
proc bestMove(session: UCISession, command: UCICommand) =
|
||||||
|
## Finds the best move in the current position
|
||||||
|
session.searching = true
|
||||||
|
session.currentSearch = SearchManager()
|
||||||
|
session.currentSearch.search(session.board, 6, 0, lowestEval(), highestEval())
|
||||||
|
session.searching = false
|
||||||
|
let move = session.currentSearch.bestMove
|
||||||
|
echo &"bestmove {move.toAlgebraic()}"
|
||||||
|
|
||||||
|
|
||||||
proc startUCISession* =
|
proc startUCISession* =
|
||||||
## Begins listening for UCI commands
|
## Begins listening for UCI commands
|
||||||
echo "id name Nimfish 0.1"
|
echo "id name Nimfish 0.1"
|
||||||
|
@ -270,12 +287,7 @@ proc startUCISession* =
|
||||||
of NewGame:
|
of NewGame:
|
||||||
session.board = newDefaultChessboard()
|
session.board = newDefaultChessboard()
|
||||||
of Go:
|
of Go:
|
||||||
var moves = MoveList()
|
session.bestMove(cmd)
|
||||||
session.board.generateMoves(moves)
|
|
||||||
if session.debug:
|
|
||||||
echo &"info string generated {len(moves)} moves"
|
|
||||||
if moves.len() > 0:
|
|
||||||
echo &"bestmove {moves[rand(0..<moves.len())].toAlgebraic()}"
|
|
||||||
of Position:
|
of Position:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue