Initial work on Zobrist hashing. Add Pohl opening book
This commit is contained in:
parent
40b47053c1
commit
1aa37d8af8
|
@ -3,5 +3,7 @@ nimcache/
|
||||||
nimblecache/
|
nimblecache/
|
||||||
htmldocs/
|
htmldocs/
|
||||||
bin
|
bin
|
||||||
|
Chess/nimfish/nimfishpkg/resources/Pohl.epd
|
||||||
|
Chess/nimfish/nimfishpkg/resources/*.pgn
|
||||||
# Python
|
# Python
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
|
@ -23,6 +23,8 @@ import moves
|
||||||
import rays
|
import rays
|
||||||
import bitboards
|
import bitboards
|
||||||
import position
|
import position
|
||||||
|
import zobrist
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export pieces, position, bitboards, moves, magics, rays
|
export pieces, position, bitboards, moves, magics, rays
|
||||||
|
@ -39,11 +41,14 @@ type
|
||||||
position*: Position
|
position*: Position
|
||||||
# List of all previously reached positions
|
# List of all previously reached positions
|
||||||
positions*: seq[Position]
|
positions*: seq[Position]
|
||||||
|
# Zobrist hash of the given position
|
||||||
|
zobristKey*: ZobristKey
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions and forward declarations
|
# A bunch of simple utility functions and forward declarations
|
||||||
proc toFEN*(self: Chessboard): string
|
proc toFEN*(self: Chessboard): string
|
||||||
proc updateChecksAndPins*(self: Chessboard)
|
proc updateChecksAndPins*(self: Chessboard)
|
||||||
|
proc hash*(self: Chessboard)
|
||||||
|
|
||||||
|
|
||||||
proc newChessboard*: Chessboard =
|
proc newChessboard*: Chessboard =
|
||||||
|
@ -170,6 +175,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
result.updateChecksAndPins()
|
result.updateChecksAndPins()
|
||||||
|
result.hash()
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||||
|
@ -582,3 +588,27 @@ proc toFEN*(self: Chessboard): string =
|
||||||
result &= " "
|
result &= " "
|
||||||
# Fullmove number
|
# Fullmove number
|
||||||
result &= $self.position.fullMoveCount
|
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()
|
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 =
|
proc getGamePhase(board: Chessboard): int =
|
||||||
## Computes the game phase according to
|
## Computes the game phase according to
|
||||||
## how many pieces are left on the board
|
## how many pieces are left on the board
|
||||||
|
|
|
@ -424,6 +424,8 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
discard
|
discard
|
||||||
# Updates checks and pins for the (new) side to move
|
# Updates checks and pins for the (new) side to move
|
||||||
self.updateChecksAndPins()
|
self.updateChecksAndPins()
|
||||||
|
# Update zobrist key
|
||||||
|
self.hash()
|
||||||
|
|
||||||
|
|
||||||
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
|
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
|
||||||
|
@ -447,3 +449,4 @@ proc unmakeMove*(self: Chessboard) =
|
||||||
## if one exists
|
## if one exists
|
||||||
self.position = self.positions.pop()
|
self.position = self.positions.pop()
|
||||||
self.update()
|
self.update()
|
||||||
|
self.hash()
|
||||||
|
|
Binary file not shown.
|
@ -42,13 +42,29 @@ type
|
||||||
nodeCount: uint64
|
nodeCount: uint64
|
||||||
maxNodes: uint64
|
maxNodes: uint64
|
||||||
searchMoves: seq[Move]
|
searchMoves: seq[Move]
|
||||||
|
previousBestMove: Move
|
||||||
|
|
||||||
|
|
||||||
proc newSearchManager*(board: Chessboard): SearchManager =
|
proc newSearchManager*(board: Chessboard): SearchManager =
|
||||||
new(result)
|
new(result)
|
||||||
result.board = board
|
result.board = board
|
||||||
result.bestMoveRoot = nullMove()
|
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) =
|
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||||
|
@ -56,7 +72,7 @@ proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||||
## to place the best ones first
|
## to place the best ones first
|
||||||
|
|
||||||
proc orderer(a, b: Move): int {.closure.} =
|
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)
|
moves.data.sort(orderer, SortOrder.Descending)
|
||||||
|
|
||||||
|
@ -156,8 +172,15 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
||||||
maxDepth = 30
|
maxDepth = 30
|
||||||
# Iterative deepening loop
|
# Iterative deepening loop
|
||||||
for i in 1..maxDepth:
|
for i in 1..maxDepth:
|
||||||
|
# Search the previous best move first
|
||||||
|
self.previousBestMove = self.bestMoveRoot
|
||||||
self.search(i, 0, lowestEval(), highestEval())
|
self.search(i, 0, lowestEval(), highestEval())
|
||||||
self.log(i)
|
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():
|
if self.shouldStop():
|
||||||
break
|
break
|
||||||
result = self.bestMoveRoot
|
|
||||||
|
|
|
@ -359,8 +359,9 @@ const HELP_TEXT = """Nimfish help menu:
|
||||||
- pins: Print the current pin mask
|
- pins: Print the current pin mask
|
||||||
- checks: Print the current checks mask
|
- checks: Print the current checks mask
|
||||||
- skip: Swap the side to move
|
- skip: Swap the side to move
|
||||||
- uci: enter UCI mode (WIP)
|
- uci: enter UCI mode
|
||||||
- quit: exit
|
- quit: exit
|
||||||
|
- zobrist: Print the zobrist key for the current position
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@ -445,6 +446,8 @@ proc commandLoop*: int =
|
||||||
echo board.position.checkers
|
echo board.position.checkers
|
||||||
of "quit":
|
of "quit":
|
||||||
return 0
|
return 0
|
||||||
|
of "zobrist":
|
||||||
|
echo board.zobristKey.uint64
|
||||||
else:
|
else:
|
||||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||||
except IOError:
|
except IOError:
|
||||||
|
|
|
@ -37,6 +37,7 @@ type
|
||||||
Quit,
|
Quit,
|
||||||
Debug,
|
Debug,
|
||||||
Position,
|
Position,
|
||||||
|
SetOption,
|
||||||
Go,
|
Go,
|
||||||
Stop
|
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