Profiling work

This commit is contained in:
Mattia Giambirtone 2023-10-18 10:45:54 +02:00
parent 82a203c98b
commit 60c4f28ec0
1 changed files with 56 additions and 40 deletions

View File

@ -21,7 +21,7 @@ import std/strformat
type
# Useful type aliases
Location* = tuple[row, col: int]
Location* = tuple[row, col: int8]
Pieces = tuple[king: Location, queens: seq[Location], rooks: seq[Location],
bishops: seq[Location], knights: seq[Location],
@ -29,7 +29,7 @@ type
PieceColor* = enum
## A piece color enumeration
None = 0,
None = 0'i8,
White,
Black
@ -50,7 +50,7 @@ type
MoveFlag* = enum
## An enumeration of move flags
Default, # Move is a regular move
Default = 0'i8, # Move is a regular move
# Castling
CastleLong,
CastleShort,
@ -76,14 +76,14 @@ type
# Number of half-moves that were performed
# to reach this position starting from the
# root of the tree
plyFromRoot: int
plyFromRoot: int16
# Number of half moves since
# last piece capture or pawn movement.
# Used for the 50-move rule
halfMoveClock: int
halfMoveClock: int8
# Full move counter. Increments
# every 2 ply
fullMoveCount: int
fullMoveCount: int16
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
# If en passant is not possible, both the row and
# 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
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 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 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 doMove(self: ChessBoard, move: Move)
# 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
@ -152,12 +153,12 @@ func queenSideRook(color: PieceColor): Location {.inline.} = (if color == White:
func bottomLeftKnightMove(color: PieceColor, long: bool = true): Location {.inline.} =
if color == White:
if long:
return (-2, 1)
return (2, -1)
else:
return (1, -2)
return (-1, -2)
elif color == Black:
if long:
return (2, -1)
return (-2, 1)
else:
return (1, -2)
@ -201,24 +202,24 @@ func topRightKnightMove(color: PieceColor, long: bool = true): Location {.inline
return (-1, 2)
proc getActiveColor*(self: ChessBoard): PieceColor =
func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
## Returns the currently active color
## (turn of who has to move)
return self.position.turn
proc getEnPassantTarget*(self: ChessBoard): Location =
func getEnPassantTarget*(self: ChessBoard): Location =
## Returns the current en passant target square
return self.position.enPassantSquare.targetSquare
proc getMoveCount*(self: ChessBoard): int =
func getMoveCount*(self: ChessBoard): int =
## Returns the number of full moves that
## have been played
return self.position.fullMoveCount
proc getHalfMoveCount*(self: ChessBoard): int =
func getHalfMoveCount*(self: ChessBoard): int {.inline.} =
## Returns the current number of half-moves
## since the last irreversible move
return self.position.halfMoveClock
@ -275,8 +276,8 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
result = newChessboard()
var
# Current location in the grid
row = 0
column = 0
row: int8 = 0
column: int8 = 0
# Current section in the FEN string
section = 0
# Current index into the FEN string
@ -349,7 +350,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
let x = int(uint8(c) - uint8('0')) - 1
if x > 7:
raise newException(ValueError, "invalid skip value (> 8) in FEN string")
column += x
column += int8(x)
else:
raise newException(ValueError, "invalid piece identifier in FEN string")
of 1:
@ -402,14 +403,14 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
# Backtrack so the space is seen by the
# next iteration of the loop
dec(index)
result.position.halfMoveClock = parseInt(s)
result.position.halfMoveClock = parseInt(s).int8
of 5:
# Fullmove number
var s = ""
while index <= state.high():
s.add(state[index])
inc(index)
result.position.fullMoveCount = parseInt(s)
result.position.fullMoveCount = parseInt(s).int8
else:
raise newException(ValueError, "too many fields in FEN string")
inc(index)
@ -469,13 +470,13 @@ proc countPieces*(self: ChessBoard, piece: Piece): int =
return self.countPieces(piece.kind, piece.color)
func rankToColumn(rank: int): int =
func rankToColumn(rank: int): int8 =
## Converts a chess rank (1-indexed)
## into a 0-indexed column value for our
## board. This converter is necessary because
## chess positions are indexed differently with
## 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]
@ -497,9 +498,9 @@ proc algebraicToLocation*(s: string): Location =
if s[1] notin '1'..'8':
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
let file = rankToColumn(int(uint8(s[1]) - uint8('0')))
let file = rankToColumn(int8(uint8(s[1]) - uint8('0')))
return (file, rank)
@ -828,7 +829,7 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move] =
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
## piece in the given location
for move in self.generateMoves(location):
@ -836,6 +837,29 @@ proc generateLegalMoves(self: ChessBoard, location: Location): seq[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] =
## Returns all the attackers of the given square
for move in self.position.attacked.black:
@ -1161,10 +1185,6 @@ proc doMove(self: ChessBoard, move: Move) =
castlingAvailable: castlingAvailable,
# Updated at the next call to doMove()
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
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 =
## Returns whether the given move is legal
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:
move.flag = CastleLong
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)
return false
self.doMove(move)
defer: discard self.undoLastMove()
defer: self.undoLastMove()
# Move would reveal an attack
# on our king: not allowed
if self.inCheck(move.piece.color):
@ -1403,4 +1413,10 @@ when isMainModule:
# Queens
testPiece(b.getPiece("d1"), Queen, White)
testPiece(b.getPiece("d8"), Queen, Black)
when compileOption("profiler"):
import nimprof
echo b.countLegalMoves(3)
echo "All tests were successful"