Profiling work
This commit is contained in:
parent
1bde8b623e
commit
8f56d9f89e
|
@ -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"
|
Loading…
Reference in New Issue