Initial work on Zobrist hashing. Add Pohl opening book
This commit is contained in:
parent
720645092e
commit
b74bb3b4ba
|
@ -3,5 +3,7 @@ nimcache/
|
|||
nimblecache/
|
||||
htmldocs/
|
||||
bin
|
||||
Chess/nimfish/nimfishpkg/resources/Pohl.epd
|
||||
Chess/nimfish/nimfishpkg/resources/*.pgn
|
||||
# Python
|
||||
__pycache__
|
||||
|
|
|
@ -23,6 +23,8 @@ import moves
|
|||
import rays
|
||||
import bitboards
|
||||
import position
|
||||
import zobrist
|
||||
|
||||
|
||||
|
||||
export pieces, position, bitboards, moves, magics, rays
|
||||
|
@ -39,11 +41,14 @@ type
|
|||
position*: Position
|
||||
# List of all previously reached positions
|
||||
positions*: seq[Position]
|
||||
# Zobrist hash of the given position
|
||||
zobristKey*: ZobristKey
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
proc toFEN*(self: Chessboard): string
|
||||
proc updateChecksAndPins*(self: Chessboard)
|
||||
proc hash*(self: Chessboard)
|
||||
|
||||
|
||||
proc newChessboard*: Chessboard =
|
||||
|
@ -170,6 +175,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
|||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
result.updateChecksAndPins()
|
||||
result.hash()
|
||||
|
||||
|
||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||
|
@ -582,3 +588,27 @@ proc toFEN*(self: Chessboard): string =
|
|||
result &= " "
|
||||
# Fullmove number
|
||||
result &= $self.position.fullMoveCount
|
||||
|
||||
|
||||
proc hash*(self: Chessboard) =
|
||||
## Computes the zobrist hash of the current
|
||||
## position
|
||||
self.zobristKey = ZobristKey(0)
|
||||
|
||||
if self.position.sideToMove == Black:
|
||||
self.zobristKey = self.zobristKey xor getBlackToMoveKey()
|
||||
|
||||
for sq in self.getOccupancy():
|
||||
self.zobristKey = self.zobristKey xor self.getPiece(sq).getKey(sq)
|
||||
|
||||
if self.position.castlingAvailability[White.int].king:
|
||||
self.zobristKey = self.zobristKey xor getKingSideCastlingKey(White)
|
||||
if self.position.castlingAvailability[White.int].queen:
|
||||
self.zobristKey = self.zobristKey xor getQueenSideCastlingKey(White)
|
||||
if self.position.castlingAvailability[Black.int].king:
|
||||
self.zobristKey = self.zobristKey xor getKingSideCastlingKey(Black)
|
||||
if self.position.castlingAvailability[Black.int].king:
|
||||
self.zobristKey = self.zobristKey xor getQueenSideCastlingKey(Black)
|
||||
|
||||
if self.position.enPassantSquare != nullSquare():
|
||||
self.zobristKey = self.zobristKey xor getEnPassantKey(fileFromSquare(self.position.enPassantSquare))
|
|
@ -196,19 +196,6 @@ proc getPieceScore*(board: Chessboard, square: Square): Score =
|
|||
return board.getPiece(square).kind.getPieceValue()
|
||||
|
||||
|
||||
proc getEstimatedMoveScore*(board: Chessboard, move: Move): Score =
|
||||
## Returns an estimated static score for the move
|
||||
result = Score(0)
|
||||
if move.isCapture():
|
||||
# Implementation of MVVLVA: Most Valuable Victim Least Valuable Attacker
|
||||
# We prioritize moves that capture the most valuable pieces, and as a
|
||||
# second goal we want to use our least valuable pieces to do so (this
|
||||
# is why we multiply the piece of the captured score by 100, to give
|
||||
# it priority)
|
||||
result = 100 * board.getPieceScore(move.targetSquare) -
|
||||
board.getPieceScore(move.startSquare)
|
||||
|
||||
|
||||
proc getGamePhase(board: Chessboard): int =
|
||||
## Computes the game phase according to
|
||||
## how many pieces are left on the board
|
||||
|
|
|
@ -424,6 +424,8 @@ proc doMove*(self: Chessboard, move: Move) =
|
|||
discard
|
||||
# Updates checks and pins for the (new) side to move
|
||||
self.updateChecksAndPins()
|
||||
# Update zobrist key
|
||||
self.hash()
|
||||
|
||||
|
||||
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
|
||||
|
@ -447,3 +449,4 @@ proc unmakeMove*(self: Chessboard) =
|
|||
## if one exists
|
||||
self.position = self.positions.pop()
|
||||
self.update()
|
||||
self.hash()
|
||||
|
|
Binary file not shown.
|
@ -42,13 +42,29 @@ type
|
|||
nodeCount: uint64
|
||||
maxNodes: uint64
|
||||
searchMoves: seq[Move]
|
||||
previousBestMove: Move
|
||||
|
||||
|
||||
proc newSearchManager*(board: Chessboard): SearchManager =
|
||||
new(result)
|
||||
result.board = board
|
||||
result.bestMoveRoot = nullMove()
|
||||
result.searchMoves = @[]
|
||||
|
||||
|
||||
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||
## Returns an estimated static score for the move
|
||||
result = Score(0)
|
||||
if self.previousBestMove != nullMove() and move == self.previousBestMove:
|
||||
result = highestEval() + 1
|
||||
elif move.isCapture():
|
||||
# Implementation of MVVLVA: Most Valuable Victim Least Valuable Attacker
|
||||
# We prioritize moves that capture the most valuable pieces, and as a
|
||||
# second goal we want to use our least valuable pieces to do so (this
|
||||
# is why we multiply the score of the captured piece by 100, to give
|
||||
# it priority)
|
||||
result = 100 * self.board.getPieceScore(move.targetSquare) -
|
||||
self.board.getPieceScore(move.startSquare)
|
||||
|
||||
|
||||
|
||||
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||
|
@ -56,7 +72,7 @@ proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
|||
## to place the best ones first
|
||||
|
||||
proc orderer(a, b: Move): int {.closure.} =
|
||||
return cmp(self.board.getEstimatedMoveScore(a), self.board.getEstimatedMoveScore(b))
|
||||
return cmp(self.getEstimatedMoveScore(a), self.getEstimatedMoveScore(b))
|
||||
|
||||
moves.data.sort(orderer, SortOrder.Descending)
|
||||
|
||||
|
@ -110,7 +126,7 @@ proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.
|
|||
# Stalemate
|
||||
return Score(0)
|
||||
var bestScore = lowestEval()
|
||||
var alpha = alpha
|
||||
var alpha = alpha
|
||||
for i, move in moves:
|
||||
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||
continue
|
||||
|
@ -156,8 +172,15 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
|||
maxDepth = 30
|
||||
# Iterative deepening loop
|
||||
for i in 1..maxDepth:
|
||||
# Search the previous best move first
|
||||
self.previousBestMove = self.bestMoveRoot
|
||||
self.search(i, 0, lowestEval(), highestEval())
|
||||
self.log(i)
|
||||
# Since we always search the best move from the
|
||||
# previous iteration, we can use partial search
|
||||
# results: the engine will either not have changed
|
||||
# its mind, or it will have found an even better move
|
||||
# in the meantime, which we should obviously use!
|
||||
result = self.bestMoveRoot
|
||||
if self.shouldStop():
|
||||
break
|
||||
result = self.bestMoveRoot
|
||||
|
|
|
@ -359,8 +359,9 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- pins: Print the current pin mask
|
||||
- checks: Print the current checks mask
|
||||
- skip: Swap the side to move
|
||||
- uci: enter UCI mode (WIP)
|
||||
- uci: enter UCI mode
|
||||
- quit: exit
|
||||
- zobrist: Print the zobrist key for the current position
|
||||
"""
|
||||
|
||||
|
||||
|
@ -445,6 +446,8 @@ proc commandLoop*: int =
|
|||
echo board.position.checkers
|
||||
of "quit":
|
||||
return 0
|
||||
of "zobrist":
|
||||
echo board.zobristKey.uint64
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
|
|
|
@ -37,6 +37,7 @@ type
|
|||
Quit,
|
||||
Debug,
|
||||
Position,
|
||||
SetOption,
|
||||
Go,
|
||||
Stop
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
# 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 Zobrist hashing
|
||||
|
||||
import std/random
|
||||
|
||||
|
||||
import pieces
|
||||
|
||||
|
||||
type
|
||||
ZobristKey* = distinct uint64
|
||||
## A zobrist key
|
||||
|
||||
|
||||
func `xor`*(a, b: ZobristKey): ZobristKey = ZobristKey(a.uint64 xor b.uint64)
|
||||
|
||||
|
||||
proc computeZobristKeys: array[781, ZobristKey] =
|
||||
## Precomputes our zobrist keys
|
||||
var prng = initRand(69420) # Nice.
|
||||
|
||||
# One for each piece on each square
|
||||
for i in 0..767:
|
||||
result[i] = ZobristKey(prng.next())
|
||||
# One to indicate that it is black's turn
|
||||
# to move
|
||||
result[768] = ZobristKey(prng.next())
|
||||
# Four numbers to indicate castling rights
|
||||
for i in 769..773:
|
||||
result[i] = ZobristKey(prng.next())
|
||||
# Eight numbers to indicate the file of a valid
|
||||
# En passant square, if any
|
||||
for i in 774..781:
|
||||
result[i] = ZobristKey(prng.next())
|
||||
|
||||
|
||||
|
||||
let ZOBRIST_KEYS = computeZobristKeys()
|
||||
const PIECE_TO_INDEX = [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]]
|
||||
|
||||
|
||||
proc getKey*(piece: Piece, square: Square): ZobristKey =
|
||||
let index = PIECE_TO_INDEX[piece.color.int][piece.kind.int] + square.int
|
||||
return ZOBRIST_KEYS[index]
|
||||
|
||||
|
||||
proc getBlackToMoveKey*: ZobristKey = ZOBRIST_KEYS[768]
|
||||
|
||||
|
||||
proc getQueenSideCastlingKey*(color: PieceColor): ZobristKey =
|
||||
case color:
|
||||
of White:
|
||||
return ZOBRIST_KEYS[769]
|
||||
of Black:
|
||||
return ZOBRIST_KEYS[771]
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc getKingSideCastlingKey*(color: PieceColor): ZobristKey =
|
||||
case color:
|
||||
of White:
|
||||
return ZOBRIST_KEYS[770]
|
||||
of Black:
|
||||
return ZOBRIST_KEYS[772]
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc getEnPassantKey*(file: SomeInteger): ZobristKey = ZOBRIST_KEYS[774 + file]
|
Loading…
Reference in New Issue