Profiling work

This commit is contained in:
Mattia Giambirtone 2023-10-18 10:45:54 +02:00
parent 1bde8b623e
commit 8f56d9f89e
1 changed files with 56 additions and 40 deletions

View File

@ -21,7 +21,7 @@ import std/strformat
type type
# Useful type aliases # Useful type aliases
Location* = tuple[row, col: int] Location* = tuple[row, col: int8]
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location], Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
bishops: seq[Location], knights: seq[Location], bishops: seq[Location], knights: seq[Location],
@ -29,7 +29,7 @@ type
PieceColor* = enum PieceColor* = enum
## A piece color enumeration ## A piece color enumeration
None = 0, None = 0'i8,
White, White,
Black Black
@ -50,7 +50,7 @@ type
MoveFlag* = enum MoveFlag* = enum
## An enumeration of move flags ## An enumeration of move flags
Default, # Move is a regular move Default = 0'i8, # Move is a regular move
# Castling # Castling
CastleLong, CastleLong,
CastleShort, CastleShort,
@ -76,14 +76,14 @@ type
# Number of half-moves that were performed # Number of half-moves that were performed
# to reach this position starting from the # to reach this position starting from the
# root of the tree # root of the tree
plyFromRoot: int plyFromRoot: int16
# Number of half moves since # Number of half moves since
# last piece capture or pawn movement. # last piece capture or pawn movement.
# Used for the 50-move rule # Used for the 50-move rule
halfMoveClock: int halfMoveClock: int8
# Full move counter. Increments # Full move counter. Increments
# every 2 ply # every 2 ply
fullMoveCount: int fullMoveCount: int16
# En passant target square (see https://en.wikipedia.org/wiki/En_passant) # En passant target square (see https://en.wikipedia.org/wiki/En_passant)
# If en passant is not possible, both the row and # If en passant is not possible, both the row and
# column of the position will be set to -1 # column of the position will be set to -1
@ -122,11 +122,12 @@ proc makeMove*(self: ChessBoard, move: Move): Move
proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move proc makeMove*(self: ChessBoard, startSquare, targetSquare: Location): Move
func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece()) func emptyMove*: Move {.inline.} = Move(startSquare: emptyLocation(), targetSquare: emptyLocation(), piece: emptyPiece())
func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col) func `+`*(a, b: Location): Location = (a.row + b.row, a.col + b.col)
func isValid*(a: Location): bool = a.row in 0..7 and a.col in 0..7 func isValid*(a: Location): bool {.inline.} = a.row in 0..7 and a.col in 0..7
proc generateMoves(self: ChessBoard, location: Location): seq[Move] proc generateMoves(self: ChessBoard, location: Location): seq[Move]
proc isAttacked*(self: ChessBoard, loc: Location): bool proc isAttacked*(self: ChessBoard, loc: Location): bool
proc undoLastMove*(self: ChessBoard): Move proc undoLastMove*(self: ChessBoard): Move {.discardable.}
proc isLegal(self: ChessBoard, move: Move): bool proc isLegal(self: ChessBoard, move: Move): bool
proc doMove(self: ChessBoard, move: Move)
# Due to our board layout, directions of movement are reversed for white/black so # Due to our board layout, directions of movement are reversed for white/black so
# we need these helpers to avoid going mad with integer tuples and minus signs # we need these helpers to avoid going mad with integer tuples and minus signs
@ -152,12 +153,12 @@ func queenSideRook(color: PieceColor): Location {.inline.} = (if color == White:
func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} = func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
if color == White: if color == White:
if long: if long:
return (-2, 1) return (2, -1)
else: else:
return (1, -2) return (-1, -2)
elif color == Black: elif color == Black:
if long: if long:
return (2, -1) return (-2, 1)
else: else:
return (1, -2) return (1, -2)
@ -201,24 +202,24 @@ func topRightKnightMove(color: PieceColor, long: bool = true): Location {.inline
return (-1, 2) return (-1, 2)
proc getActiveColor*(self: ChessBoard): PieceColor = func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
## Returns the currently active color ## Returns the currently active color
## (turn of who has to move) ## (turn of who has to move)
return self.position.turn return self.position.turn
proc getEnPassantTarget*(self: ChessBoard): Location = func getEnPassantTarget*(self: ChessBoard): Location =
## Returns the current en passant target square ## Returns the current en passant target square
return self.position.enPassantSquare.targetSquare return self.position.enPassantSquare.targetSquare
proc getMoveCount*(self: ChessBoard): int = func getMoveCount*(self: ChessBoard): int =
## Returns the number of full moves that ## Returns the number of full moves that
## have been played ## have been played
return self.position.fullMoveCount return self.position.fullMoveCount
proc getHalfMoveCount*(self: ChessBoard): int = func getHalfMoveCount*(self: ChessBoard): int {.inline.} =
## Returns the current number of half-moves ## Returns the current number of half-moves
## since the last irreversible move ## since the last irreversible move
return self.position.halfMoveClock return self.position.halfMoveClock
@ -275,8 +276,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
result = newChessboard() result = newChessboard()
var var
# Current location in the grid # Current location in the grid
row = 0 row: int8 = 0
column = 0 column: int8 = 0
# Current section in the FEN string # Current section in the FEN string
section = 0 section = 0
# Current index into the FEN string # Current index into the FEN string
@ -349,7 +350,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
let x = int(uint8(c) - uint8('0')) - 1 let x = int(uint8(c) - uint8('0')) - 1
if x > 7: if x > 7:
raise newException(ValueError, "invalid skip value (> 8) in FEN string") raise newException(ValueError, "invalid skip value (> 8) in FEN string")
column += x column += int8(x)
else: else:
raise newException(ValueError, "invalid piece identifier in FEN string") raise newException(ValueError, "invalid piece identifier in FEN string")
of 1: of 1:
@ -402,14 +403,14 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
# Backtrack so the space is seen by the # Backtrack so the space is seen by the
# next iteration of the loop # next iteration of the loop
dec(index) dec(index)
result.position.halfMoveClock = parseInt(s) result.position.halfMoveClock = parseInt(s).int8
of 5: of 5:
# Fullmove number # Fullmove number
var s = "" var s = ""
while index <= state.high(): while index <= state.high():
s.add(state[index]) s.add(state[index])
inc(index) inc(index)
result.position.fullMoveCount = parseInt(s) result.position.fullMoveCount = parseInt(s).int8
else: else:
raise newException(ValueError, "too many fields in FEN string") raise newException(ValueError, "too many fields in FEN string")
inc(index) inc(index)
@ -469,13 +470,13 @@ proc countPieces*(self: ChessBoard, piece: Piece): int =
return self.countPieces(piece.kind, piece.color) return self.countPieces(piece.kind, piece.color)
func rankToColumn(rank: int): int = func rankToColumn(rank: int): int8 =
## Converts a chess rank (1-indexed) ## Converts a chess rank (1-indexed)
## into a 0-indexed column value for our ## into a 0-indexed column value for our
## board. This converter is necessary because ## board. This converter is necessary because
## chess positions are indexed differently with ## chess positions are indexed differently with
## respect to our internal representation ## respect to our internal representation
const indeces = [7, 6, 5, 4, 3, 2, 1, 0] const indeces: array[8, int8] = [7, 6, 5, 4, 3, 2, 1, 0]
return indeces[rank - 1] return indeces[rank - 1]
@ -497,9 +498,9 @@ proc algebraicToLocation*(s: string): Location =
if s[1] notin '1'..'8': if s[1] notin '1'..'8':
raise newException(ValueError, &"algebraic position has invalid second character ('{s[1]}')") raise newException(ValueError, &"algebraic position has invalid second character ('{s[1]}')")
let rank = int(uint8(s[0]) - uint8('a')) let rank = int8(uint8(s[0]) - uint8('a'))
# Convert the file character to a number # Convert the file character to a number
let file = rankToColumn(int(uint8(s[1]) - uint8('0'))) let file = rankToColumn(int8(uint8(s[1]) - uint8('0')))
return (file, rank) return (file, rank)
@ -828,7 +829,7 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
return @[] return @[]
proc generateLegalMoves(self: ChessBoard, location: Location): seq[Move] = proc generateLegalMoves*(self: ChessBoard, location: Location): seq[Move] =
## Returns the list of possible legal chess moves for the ## Returns the list of possible legal chess moves for the
## piece in the given location ## piece in the given location
for move in self.generateMoves(location): for move in self.generateMoves(location):
@ -836,6 +837,29 @@ proc generateLegalMoves(self: ChessBoard, location: Location): seq[Move] =
result.add(move) result.add(move)
proc generateAllMoves*(self: ChessBoard): seq[Move] =
## Returns the list of all possible pseudo-legal moves
## in the current position
for i, row in self.grid:
for j, piece in row:
if self.grid[i, j].color != self.getActiveColor():
continue
for move in self.generateMoves((int8(i), int8(j))):
result.add(move)
proc countLegalMoves*(self: ChessBoard, ply: int): int =
## Counts the number of legal positions reached after
## the given number of half moves
if ply == 0:
return 1
for move in self.generateAllMoves():
if self.isLegal(move):
#echo &"{move.piece.color} {move.piece.kind} {move.startSquare.locationToAlgebraic()} {move.targetSquare.locationtoAlgebraic()}"
result += self.countLegalMoves(ply - 1)
proc getAttackers*(self: ChessBoard, square: Location): seq[Piece] = proc getAttackers*(self: ChessBoard, square: Location): seq[Piece] =
## Returns all the attackers of the given square ## Returns all the attackers of the given square
for move in self.position.attacked.black: for move in self.position.attacked.black:
@ -1161,10 +1185,6 @@ proc doMove(self: ChessBoard, move: Move) =
castlingAvailable: castlingAvailable, castlingAvailable: castlingAvailable,
# Updated at the next call to doMove() # Updated at the next call to doMove()
move: emptyMove(), move: emptyMove(),
# Inherit values from current position
# (they are updated later anyway)
pieces: self.position.pieces,
attacked: self.position.attacked,
) )
# Check for double pawn push # Check for double pawn push
if move.piece.kind == Pawn and abs(move.startSquare.row - move.targetSquare.row) == 2: if move.piece.kind == Pawn and abs(move.startSquare.row - move.targetSquare.row) == 2:
@ -1246,17 +1266,7 @@ proc undoLastMove*(self: ChessBoard): Move {.discardable.} =
proc isLegal(self: ChessBoard, move: Move): bool = proc isLegal(self: ChessBoard, move: Move): bool =
## Returns whether the given move is legal ## Returns whether the given move is legal
var move = move var move = move
# Start square doesn't contain a piece (and it isn't the en passant square)
# or it's not this player's turn to move
if (move.piece.kind == Empty and move.targetSquare != self.getEnPassantTarget()) or move.piece.color != self.getActiveColor():
return false
var destination = self.grid[move.targetSquare.row, move.targetSquare.col]
# Destination square is occupied by a friendly piece
if destination.kind != Empty and destination.color == self.getActiveColor():
return false
if move.piece.kind == King and move.piece.color.longCastleKing() + move.startSquare == move.targetSquare: if move.piece.kind == King and move.piece.color.longCastleKing() + move.startSquare == move.targetSquare:
move.flag = CastleLong move.flag = CastleLong
elif move.piece.kind == King and move.piece.color.shortCastleKing() + move.startSquare == move.targetSquare: elif move.piece.kind == King and move.piece.color.shortCastleKing() + move.startSquare == move.targetSquare:
@ -1266,7 +1276,7 @@ proc isLegal(self: ChessBoard, move: Move): bool =
# or otherwise invalid move) # or otherwise invalid move)
return false return false
self.doMove(move) self.doMove(move)
defer: discard self.undoLastMove() defer: self.undoLastMove()
# Move would reveal an attack # Move would reveal an attack
# on our king: not allowed # on our king: not allowed
if self.inCheck(move.piece.color): if self.inCheck(move.piece.color):
@ -1403,4 +1413,10 @@ when isMainModule:
# Queens # Queens
testPiece(b.getPiece("d1"), Queen, White) testPiece(b.getPiece("d1"), Queen, White)
testPiece(b.getPiece("d8"), Queen, Black) testPiece(b.getPiece("d8"), Queen, Black)
when compileOption("profiler"):
import nimprof
echo b.countLegalMoves(3)
echo "All tests were successful" echo "All tests were successful"