diff --git a/Chess/nimfish/nimfishpkg/eval.nim b/Chess/nimfish/nimfishpkg/eval.nim new file mode 100644 index 0000000..0da4a5a --- /dev/null +++ b/Chess/nimfish/nimfishpkg/eval.nim @@ -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() \ No newline at end of file diff --git a/Chess/nimfish/nimfishpkg/search.nim b/Chess/nimfish/nimfishpkg/search.nim new file mode 100644 index 0000000..6200b91 --- /dev/null +++ b/Chess/nimfish/nimfishpkg/search.nim @@ -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 \ No newline at end of file diff --git a/Chess/nimfish/nimfishpkg/uci.nim b/Chess/nimfish/nimfishpkg/uci.nim index 686c861..f213daf 100644 --- a/Chess/nimfish/nimfishpkg/uci.nim +++ b/Chess/nimfish/nimfishpkg/uci.nim @@ -17,26 +17,31 @@ import std/strutils import std/strformat import std/random + randomize() import board import movegen +import search type UCISession = ref object debug: bool board: Chessboard + searching: bool + currentSearch: SearchManager UCICommandType = enum - Unknown - IsReady - NewGame + Unknown, + IsReady, + NewGame, Quit, Debug, Position, - Go + Go, + Stop UCICommand = object case kind: UCICommandType @@ -213,6 +218,8 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand = case cmd[current]: of "isready": return UCICommand(kind: IsReady) + of "stop": + return UCICommand(kind: Stop) of "ucinewgame": return UCICommand(kind: NewGame) of "quit": @@ -237,6 +244,16 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand = 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* = ## Begins listening for UCI commands echo "id name Nimfish 0.1" @@ -270,12 +287,7 @@ proc startUCISession* = of NewGame: session.board = newDefaultChessboard() of Go: - var moves = MoveList() - session.board.generateMoves(moves) - if session.debug: - echo &"info string generated {len(moves)} moves" - if moves.len() > 0: - echo &"bestmove {moves[rand(0..