Various bug fixes, improvements and optimizations
This commit is contained in:
parent
0b9b24b8e1
commit
3dca208123
|
@ -92,16 +92,17 @@ type
|
|||
# every 2 ply
|
||||
fullMoveCount: int16
|
||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||
enPassantSquare*: Move
|
||||
enPassantSquare*: Location
|
||||
# Locations of all pieces
|
||||
pieces: tuple[white: Pieces, black: Pieces]
|
||||
# Potential attacking moves for black and white
|
||||
# Squares attacked by both sides
|
||||
attacked: tuple[white: Attacked, black: Attacked]
|
||||
# Pieces pinned by both sides
|
||||
pinned: tuple[white: Attacked, black: Attacked]
|
||||
# The original piece captured to reach this position (may be empty)
|
||||
captured: Piece
|
||||
#captured: Piece
|
||||
# The piece that moved to reach this position (needed to undo moves)
|
||||
moved: Piece
|
||||
#moved: Piece
|
||||
# Active color
|
||||
turn: PieceColor
|
||||
|
||||
|
@ -121,7 +122,7 @@ type
|
|||
positions: seq[Position]
|
||||
# Cached results of expensive
|
||||
# functions in the current position
|
||||
cache: Cache
|
||||
#cache: Cache
|
||||
|
||||
|
||||
# Initialized only once, copied every time
|
||||
|
@ -144,7 +145,6 @@ proc generateMoves(self: ChessBoard, location: Location): seq[Move]
|
|||
proc getAttackers*(self: ChessBoard, loc: Location, color: PieceColor): seq[Location]
|
||||
proc getAttackFor*(self: ChessBoard, source, target: Location): tuple[source, target, direction: Location]
|
||||
proc isAttacked*(self: ChessBoard, loc: Location, color: PieceColor = None): bool
|
||||
proc undoMove*(self: ChessBoard, move: Move)
|
||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.}
|
||||
proc doMove(self: ChessBoard, move: Move)
|
||||
proc pretty*(self: ChessBoard): string
|
||||
|
@ -153,7 +153,7 @@ proc updateAttackedSquares(self: ChessBoard)
|
|||
proc getPinnedDirections(self: ChessBoard, loc: Location): seq[Location]
|
||||
proc getAttacks*(self: ChessBoard, loc: Location): Attacked
|
||||
proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked, pins: Attacked]
|
||||
func invalidateCache(self: ChessBoard) {.inline.}
|
||||
#[func invalidateCache(self: ChessBoard) {.inline.}]#
|
||||
proc inCheck*(self: ChessBoard, color: PieceColor = None): bool
|
||||
|
||||
|
||||
|
@ -243,7 +243,7 @@ func getActiveColor*(self: ChessBoard): PieceColor {.inline.} =
|
|||
|
||||
func getEnPassantTarget*(self: ChessBoard): Location {.inline.} =
|
||||
## Returns the current en passant target square
|
||||
return self.position.enPassantSquare.targetSquare
|
||||
return self.position.enPassantSquare
|
||||
|
||||
|
||||
func getMoveCount*(self: ChessBoard): int {.inline.} =
|
||||
|
@ -296,10 +296,10 @@ proc newChessboard: ChessBoard =
|
|||
new(result)
|
||||
# Turns our flat sequence into an 8x8 grid
|
||||
result.grid = newMatrixFromSeq[Piece](empty, (8, 8))
|
||||
result.cache = Cache(canCastle: (white: CacheEntry[tuple[queen, king: bool]](), black: CacheEntry[tuple[queen, king: bool]]()),
|
||||
inCheck: (white: CacheEntry[bool](), black: CacheEntry[bool]()))
|
||||
#result.cache = Cache(canCastle: (white: CacheEntry[tuple[queen, king: bool]](), black: CacheEntry[tuple[queen, king: bool]]()),
|
||||
# inCheck: (white: CacheEntry[bool](), black: CacheEntry[bool]()))
|
||||
result.position = Position(attacked: (@[], @[]),
|
||||
enPassantSquare: emptyMove(),
|
||||
enPassantSquare: emptyLocation(),
|
||||
move: emptyMove(),
|
||||
turn: White,
|
||||
fullMoveCount: 1,
|
||||
|
@ -435,7 +435,7 @@ proc newChessboardFromFEN*(state: string): ChessBoard =
|
|||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.position.enPassantSquare.targetSquare = state[index..index+1].algebraicToLocation()
|
||||
result.position.enPassantSquare = state[index..index+1].algebraicToLocation()
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
|
@ -588,17 +588,17 @@ proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
|||
color = self.getActiveColor()
|
||||
case color:
|
||||
of White:
|
||||
if self.cache.inCheck.white.valid:
|
||||
return self.cache.inCheck.white.data
|
||||
#[if self.cache.inCheck.white.valid:
|
||||
return self.cache.inCheck.white.data]#
|
||||
result = self.isAttacked(self.position.pieces.white.king, Black)
|
||||
of Black:
|
||||
if self.cache.inCheck.black.valid:
|
||||
return self.cache.inCheck.black.data
|
||||
#[if self.cache.inCheck.black.valid:
|
||||
return self.cache.inCheck.black.data]#
|
||||
result = self.isAttacked(self.position.pieces.black.king, White)
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
case color:
|
||||
#[case color:
|
||||
of White:
|
||||
self.cache.inCheck.white.valid = true
|
||||
self.cache.inCheck.white.data = result
|
||||
|
@ -607,7 +607,7 @@ proc inCheck*(self: ChessBoard, color: PieceColor = None): bool =
|
|||
self.cache.inCheck.black.data = result
|
||||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
discard]#
|
||||
|
||||
|
||||
proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: bool] {.inline.} =
|
||||
|
@ -621,26 +621,26 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
|||
# rights have been lost
|
||||
case color:
|
||||
of White:
|
||||
if self.cache.canCastle.white.valid:
|
||||
return self.cache.canCastle.white.data
|
||||
#[if self.cache.canCastle.white.valid:
|
||||
return self.cache.canCastle.white.data]#
|
||||
result.king = self.position.castlingAvailable.white.king
|
||||
result.queen = self.position.castlingAvailable.white.queen
|
||||
of Black:
|
||||
if self.cache.canCastle.black.valid:
|
||||
return self.cache.canCastle.black.data
|
||||
#[if self.cache.canCastle.black.valid:
|
||||
return self.cache.canCastle.black.data#]#
|
||||
result.king = self.position.castlingAvailable.black.king
|
||||
result.queen = self.position.castlingAvailable.black.queen
|
||||
of None:
|
||||
# Unreachable
|
||||
discard
|
||||
if self.inCheck(color):
|
||||
# King can not castle out of check
|
||||
return (false, false)
|
||||
if result.king or result.queen:
|
||||
var
|
||||
loc: Location
|
||||
queenSide: Location
|
||||
kingSide: Location
|
||||
if self.inCheck(color):
|
||||
# King can not castle out of check
|
||||
return (false, false)
|
||||
# If the path between the king and rook on a given side is blocked or any of the
|
||||
# squares where the king would travel to are attacked by the opponent,
|
||||
# then castling is (temporarily) prohibited on that side
|
||||
|
@ -662,53 +662,37 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
|
|||
var
|
||||
location = loc
|
||||
otherPiece: Piece
|
||||
while true:
|
||||
while location != loc + shortCastleKing():
|
||||
location = location + kingSide
|
||||
if not location.isValid():
|
||||
break
|
||||
|
||||
otherPiece = self.grid[location.row, location.col]
|
||||
|
||||
if otherPiece.color == None:
|
||||
continue
|
||||
|
||||
if otherPiece.color == color.opposite() or otherPiece.kind != Rook or self.isAttacked(location, color.opposite()):
|
||||
if otherPiece.color != None or self.isAttacked(location, color.opposite()):
|
||||
result.king = false
|
||||
break
|
||||
if location == color.kingSideRook():
|
||||
result.king = self.grid[location.row, location.col].kind == Rook
|
||||
break
|
||||
|
||||
|
||||
if result.queen:
|
||||
# Long castle
|
||||
var
|
||||
location = loc
|
||||
otherPiece: Piece
|
||||
while true:
|
||||
while location != loc + longCastleKing():
|
||||
location = location + queenSide
|
||||
if not location.isValid():
|
||||
break
|
||||
otherPiece = self.grid[location.row, location.col]
|
||||
|
||||
if otherPiece.color == None:
|
||||
continue
|
||||
|
||||
if otherPiece.color == color.opposite() or otherPiece.kind != Rook or self.isAttacked(location, color.opposite()):
|
||||
if otherPiece.color != None or self.isAttacked(location, color.opposite()):
|
||||
result.queen = false
|
||||
break
|
||||
|
||||
if location == color.queenSideRook():
|
||||
result.queen = self.grid[location.row, location.col].kind == Rook
|
||||
break
|
||||
case color:
|
||||
|
||||
#[case color:
|
||||
of White:
|
||||
self.cache.canCastle.white.data = result
|
||||
self.cache.canCastle.white.valid = true
|
||||
of Black:
|
||||
self.cache.canCastle.black.data = result
|
||||
self.cache.canCastle.white.valid = true
|
||||
self.cache.canCastle.black.valid = true
|
||||
else:
|
||||
discard
|
||||
discard]#
|
||||
|
||||
|
||||
proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
|
||||
|
@ -782,11 +766,13 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
|
|||
if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None:
|
||||
locations.add(double)
|
||||
flags.add(DoublePush)
|
||||
let enPassantPiece = self.getEnPassantTarget() + piece.color.opposite().topSide()
|
||||
let enPassantPawn = self.grid[enPassantPiece.row, enPassantPiece.col]
|
||||
# They can also move on either diagonal one
|
||||
# square, but only to capture or for en passant
|
||||
for diagonal in [location + piece.color.topRightDiagonal(), location + piece.color.topLeftDiagonal()]:
|
||||
if diagonal.isValid():
|
||||
if diagonal == self.position.enPassantSquare.targetSquare:
|
||||
if enPassantPawn.color == piece.color.opposite() and diagonal == self.position.enPassantSquare:
|
||||
# Ensure en passant doesn't create a check
|
||||
let king = self.getKing(piece.color)
|
||||
var ok = true
|
||||
|
@ -1207,9 +1193,9 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
|||
if behindPiece.color == None:
|
||||
continue
|
||||
if behindPiece.color == piece.color.opposite and behindPiece.kind == King:
|
||||
# The enemy king is behind this enemy piece: pin it in
|
||||
# this direction relative to them (that's why we have the
|
||||
# minus sign: up for us is down for them and vice versa)
|
||||
# The enemy king is behind this enemy piece: pin it along
|
||||
# this axis in both directions
|
||||
result.pins.add((loc, square, direction))
|
||||
result.pins.add((loc, square, -direction))
|
||||
else:
|
||||
break
|
||||
|
@ -1219,7 +1205,7 @@ proc getSlidingAttacks(self: ChessBoard, loc: Location): tuple[attacks: Attacked
|
|||
# valid)
|
||||
let target = square + direction
|
||||
if target.isValid():
|
||||
result.attacks.add((square, target, direction))
|
||||
result.attacks.add((loc, target, direction))
|
||||
break
|
||||
|
||||
|
||||
|
@ -1269,7 +1255,7 @@ proc updateAttackedSquares(self: ChessBoard) =
|
|||
self.updateKingAttacks()
|
||||
# Invalidate the cache whenever updates to the
|
||||
# metadata are made
|
||||
self.invalidateCache()
|
||||
#self.invalidateCache()
|
||||
|
||||
|
||||
proc removePiece(self: ChessBoard, location: Location, attack: bool = true) =
|
||||
|
@ -1375,9 +1361,9 @@ proc movePiece(self: ChessBoard, move: Move, attack: bool = true) =
|
|||
self.grid[move.targetSquare.row, move.targetSquare.col] = piece
|
||||
if attack:
|
||||
self.updateAttackedSquares()
|
||||
else:
|
||||
#else:
|
||||
# Just to be sure
|
||||
self.invalidateCache()
|
||||
# self.invalidateCache()
|
||||
|
||||
|
||||
proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bool = true) =
|
||||
|
@ -1385,23 +1371,12 @@ proc movePiece(self: ChessBoard, startSquare, targetSquare: Location, attack: bo
|
|||
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare), attack)
|
||||
|
||||
|
||||
proc updateLocations(self: ChessBoard, move: Move) =
|
||||
## Internal helper to update the position of
|
||||
## the pieces on the board after a move
|
||||
if move.flag == Capture:
|
||||
self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
self.removePiece(move.targetSquare, attack=false)
|
||||
|
||||
# Update the positional metadata of the moving piece
|
||||
self.movePiece(move)
|
||||
|
||||
|
||||
func invalidateCache(self: ChessBoard) {.inline.} =
|
||||
#[func invalidateCache(self: ChessBoard) {.inline.} =
|
||||
## Invalidates the internal caches
|
||||
self.cache.canCastle.white.valid = false
|
||||
self.cache.canCastle.black.valid = false
|
||||
self.cache.inCheck.white.valid = false
|
||||
self.cache.inCheck.black.valid = false
|
||||
self.cache.inCheck.black.valid = false]#
|
||||
|
||||
|
||||
proc doMove(self: ChessBoard, move: Move) =
|
||||
|
@ -1409,13 +1384,13 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
## performing legality checks on the given move. Can
|
||||
## be used in performance-critical paths where
|
||||
## a move is already known to be legal
|
||||
|
||||
|
||||
# Record final position for future reference
|
||||
self.positions.add(self.position)
|
||||
|
||||
# Final checks
|
||||
|
||||
# Record the final move in the position
|
||||
self.position.move = move
|
||||
let piece = self.grid[move.startSquare.row, move.startSquare.col]
|
||||
self.position.moved = piece
|
||||
|
||||
# Needed to detect draw by the 50 move rule
|
||||
var
|
||||
|
@ -1423,7 +1398,7 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
fullMoveCount = self.position.fullMoveCount
|
||||
castlingAvailable = self.position.castlingAvailable
|
||||
if piece.kind == Pawn or move.flag == Capture:
|
||||
self.position.halfMoveClock = 0
|
||||
halfMoveClock = 0
|
||||
else:
|
||||
inc(halfMoveClock)
|
||||
if piece.color == Black:
|
||||
|
@ -1451,21 +1426,21 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
discard
|
||||
# Has a rook been captured?
|
||||
if move.flag == Capture:
|
||||
let piece = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
if piece.kind == Rook:
|
||||
let captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
if captured.kind == Rook:
|
||||
case piece.color:
|
||||
of White:
|
||||
if move.targetSquare == piece.color.queenSideRook():
|
||||
if move.targetSquare == captured.color.queenSideRook():
|
||||
# Queen side
|
||||
castlingAvailable.white.queen = false
|
||||
elif move.targetSquare == piece.color.kingSideRook():
|
||||
elif move.targetSquare == captured.color.kingSideRook():
|
||||
# King side
|
||||
castlingAvailable.white.king = false
|
||||
of Black:
|
||||
if move.targetSquare == piece.color.queenSideRook():
|
||||
if move.targetSquare == captured.color.queenSideRook():
|
||||
# Queen side
|
||||
castlingAvailable.black.queen = false
|
||||
elif move.targetSquare == piece.color.kingSideRook():
|
||||
elif move.targetSquare == captured.color.kingSideRook():
|
||||
# King side
|
||||
castlingAvailable.black.king = false
|
||||
else:
|
||||
|
@ -1483,23 +1458,22 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
castlingAvailable.black.queen = false
|
||||
else:
|
||||
discard
|
||||
let previous = self.position
|
||||
# Record final position for future reference
|
||||
self.positions.add(previous)
|
||||
# Create new position
|
||||
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
captured: emptyPiece(),
|
||||
turn: self.getActiveColor().opposite,
|
||||
castlingAvailable: castlingAvailable,
|
||||
# Updated at the next call to doMove()
|
||||
move: emptyMove(),
|
||||
pieces: previous.pieces,
|
||||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
#captured: if move.flag == Capture: self.grid[move.targetSquare.row, move.targetSquare.col] else: emptyPiece(),
|
||||
turn: self.getActiveColor().opposite,
|
||||
castlingAvailable: castlingAvailable,
|
||||
move: move,
|
||||
pieces: self.position.pieces,
|
||||
enPassantSquare: if move.flag == EnPassant: move.targetSquare + piece.color.bottomSide() else: emptyLocation()
|
||||
)
|
||||
# Update position metadata
|
||||
|
||||
if move.flag in [CastleShort, CastleLong]:
|
||||
# Move the rook onto the
|
||||
# correct file
|
||||
# correct file when castling
|
||||
var
|
||||
location: Location
|
||||
target: Location
|
||||
|
@ -1513,13 +1487,18 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
let move = Move(startSquare: location, targetSquare: location + target, flag: move.flag)
|
||||
self.movePiece(move, attack=false)
|
||||
|
||||
# Update position and attack metadata
|
||||
self.updateLocations(move)
|
||||
if move.flag == Capture:
|
||||
# Get rid of captured pieces
|
||||
#self.position.captured = self.grid[move.targetSquare.row, move.targetSquare.col]
|
||||
self.removePiece(move.targetSquare, attack=false)
|
||||
|
||||
if move.flag == EnPassant:
|
||||
self.removePiece(move.targetSquare + piece.color.bottomSide())
|
||||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare + piece.color.bottomSide(), attack=false)
|
||||
|
||||
elif move.isPromotion():
|
||||
self.movePiece(move, attack=false)
|
||||
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
self.removePiece(move.targetSquare, attack=false)
|
||||
|
@ -1534,13 +1513,9 @@ proc doMove(self: ChessBoard, move: Move) =
|
|||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
||||
else:
|
||||
discard
|
||||
self.updateAttackedSquares()
|
||||
# Check for double pawn push
|
||||
if move.flag == DoublePush:
|
||||
self.position.enPassantSquare = Move(startSquare: (move.startSquare.row, move.startSquare.col),
|
||||
targetSquare: move.targetSquare + piece.color.bottomSide())
|
||||
else:
|
||||
self.position.enPassantSquare = emptyMove()
|
||||
# Update attack metadata
|
||||
self.updateAttackedSquares()
|
||||
|
||||
|
||||
|
||||
proc spawnPiece(self: ChessBoard, location: Location, piece: Piece) =
|
||||
|
@ -1619,42 +1594,11 @@ proc resetBoard*(self: ChessBoard) =
|
|||
self.grid[self.position.pieces.black.king.row, self.position.pieces.black.king.col] = Piece(color: Black, kind: King)
|
||||
|
||||
|
||||
proc undoMove*(self: ChessBoard, move: Move) =
|
||||
## Undoes the given move
|
||||
proc undoLastMove*(self: ChessBoard) =
|
||||
if self.positions.len() == 0:
|
||||
return
|
||||
self.invalidateCache()
|
||||
let previous = self.position
|
||||
var position = self.positions[^1]
|
||||
while position.move != move:
|
||||
discard self.positions.pop()
|
||||
position = self.positions[^1]
|
||||
|
||||
|
||||
self.position = position
|
||||
self.grid[move.startSquare.row, move.startSquare.col] = self.position.moved
|
||||
if move.flag == Capture:
|
||||
self.grid[move.targetSquare.row, move.targetSquare.col] = previous.captured
|
||||
else:
|
||||
self.grid[move.targetSquare.row, move.targetSquare.col] = emptyPiece()
|
||||
# Reset the location of the rook in the
|
||||
# grid (it's already correct in the piece
|
||||
# list)
|
||||
if move.flag == CastleLong:
|
||||
let
|
||||
rookOld = move.targetSquare + (if self.getActiveColor() == White: self.getActiveColor().rightSide() else: self.getActiveColor().leftSide())
|
||||
rookNew = self.getActiveColor().queenSideRook()
|
||||
self.grid[rookNew.row, rookNew.col] = self.grid[rookOld.row, rookOld.col]
|
||||
self.grid[rookOld.row, rookOld.col] = emptyPiece()
|
||||
if move.flag == CastleShort:
|
||||
let
|
||||
rookOld = move.targetSquare + (if self.getActiveColor() == White: self.getActiveColor().leftSide() else:self.getActiveColor().rightSide())
|
||||
rookNew = self.getActiveColor().kingSideRook()
|
||||
self.grid[rookNew.row, rookNew.col] = self.grid[rookOld.row, rookOld.col]
|
||||
self.grid[rookOld.row, rookOld.col] = emptyPiece()
|
||||
if move.flag == EnPassant:
|
||||
let target = self.getEnPassantTarget() + self.getActiveColor().topSide()
|
||||
self.grid[target.row, target.col] = Piece(kind: Pawn, color: self.getActiveColor().opposite())
|
||||
self.position = self.positions.pop()
|
||||
self.resetBoard()
|
||||
|
||||
|
||||
proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
||||
|
@ -1665,11 +1609,9 @@ proc isLegal(self: ChessBoard, move: Move): bool {.inline.} =
|
|||
proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} =
|
||||
## Makes a move on the board
|
||||
result = move
|
||||
self.position.move = move
|
||||
if not self.isLegal(move):
|
||||
return emptyMove()
|
||||
self.doMove(move)
|
||||
result = self.position.move
|
||||
|
||||
|
||||
proc `$`*(self: ChessBoard): string =
|
||||
|
@ -1778,10 +1720,28 @@ proc toFEN*(self: ChessBoard): string =
|
|||
proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData =
|
||||
## Counts (and debugs) the number of legal positions reached after
|
||||
## the given number of ply
|
||||
let moves = self.generateAllMoves()
|
||||
if not bulk and ply == 0:
|
||||
if ply == 0:
|
||||
return (1, 0, 0, 0, 0, 0, 0)
|
||||
if bulk and ply == 1:
|
||||
|
||||
let moves = self.generateAllMoves()
|
||||
if ply == 1 and bulk:
|
||||
if divide:
|
||||
var postfix = ""
|
||||
for move in moves:
|
||||
case move.flag:
|
||||
of PromoteToBishop:
|
||||
postfix = "b"
|
||||
of PromoteToKnight:
|
||||
postfix = "n"
|
||||
of PromoteToRook:
|
||||
postfix = "r"
|
||||
of PromoteToQueen:
|
||||
postfix = "q"
|
||||
else:
|
||||
postfix = ""
|
||||
echo &"{move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}{postfix}: 1"
|
||||
if verbose:
|
||||
echo ""
|
||||
return (uint64(len(moves)), 0, 0, 0, 0, 0, 0)
|
||||
|
||||
if len(moves) == 0:
|
||||
|
@ -1790,7 +1750,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getActiveColor())
|
||||
echo &"Ply: {self.position.plyFromRoot}"
|
||||
echo &"Ply (from root): {self.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.locationToAlgebraic()}{move.targetSquare.locationToAlgebraic()}, from ({move.startSquare.row}, {move.startSquare.col}) to ({move.targetSquare.row}, {move.targetSquare.col})"
|
||||
echo &"Turn: {self.getActiveColor()}"
|
||||
echo &"Piece: {self.grid[move.startSquare.row, move.startSquare.col].kind}"
|
||||
|
@ -1826,7 +1786,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Position after move: {self.toFEN()}"
|
||||
echo "\n", self.pretty()
|
||||
stdout.write(">>> ")
|
||||
stdout.write("nextpos>> ")
|
||||
try:
|
||||
discard readLine(stdin)
|
||||
except IOError:
|
||||
|
@ -1834,7 +1794,8 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
except EOFError:
|
||||
discard
|
||||
let next = self.perft(ply - 1, verbose, bulk=bulk)
|
||||
if divide:
|
||||
self.undoLastMove()
|
||||
if divide and (not bulk or ply > 1):
|
||||
var postfix = ""
|
||||
case move.flag:
|
||||
of PromoteToBishop:
|
||||
|
@ -1857,18 +1818,23 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
result.castles += next.castles
|
||||
result.enPassant += next.enPassant
|
||||
result.checkmates += next.checkmates
|
||||
self.undoMove(move)
|
||||
|
||||
|
||||
proc main: int =
|
||||
## Nimfish's control interface
|
||||
echo "Nimfish by nocturn9x (see LICENSE)"
|
||||
var board = newDefaultChessboard()
|
||||
while true:
|
||||
var cmd: seq[string]
|
||||
var
|
||||
cmd: seq[string]
|
||||
cmdStr: string
|
||||
try:
|
||||
stdout.write(">>> ")
|
||||
stdout.flushFile()
|
||||
cmd = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '}).splitWhitespace(maxsplit=2)
|
||||
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
||||
if cmdStr.len() == 0:
|
||||
continue
|
||||
cmd = cmdStr.splitWhitespace(maxsplit=2)
|
||||
|
||||
case cmd[0]:
|
||||
of "clear":
|
||||
|
@ -1887,20 +1853,28 @@ proc main: int =
|
|||
var
|
||||
args = cmd[2].splitWhitespace()
|
||||
bulk = false
|
||||
verbose = false
|
||||
if args.len() > 1:
|
||||
case args[1]:
|
||||
of "bulk-count", "bulk":
|
||||
bulk = true
|
||||
else:
|
||||
echo &"Error: go: perft: invalid argument '{args[1]}'"
|
||||
continue
|
||||
var ok = true
|
||||
for arg in args[1..^1]:
|
||||
case arg:
|
||||
of "bulk-count", "bulk":
|
||||
bulk = true
|
||||
of "verbose":
|
||||
verbose = true
|
||||
else:
|
||||
echo &"Error: go: perft: invalid argument '{args[1]}'"
|
||||
ok = false
|
||||
break
|
||||
if not ok:
|
||||
continue
|
||||
try:
|
||||
let ply = parseInt(args[0])
|
||||
if bulk:
|
||||
echo &"\nNodes searched (bulk-counting enabled): {board.perft(ply, divide=true, bulk=true).nodes}\n"
|
||||
echo &"\nNodes searched (bulk-counting: on): {board.perft(ply, divide=true, bulk=true, verbose=verbose).nodes}\n"
|
||||
else:
|
||||
let data = board.perft(ply, divide=true)
|
||||
echo &"\nNodes searched: {data.nodes}"
|
||||
let data = board.perft(ply, divide=true, verbose=verbose)
|
||||
echo &"\nNodes searched (bulk-counting: off): {data.nodes}"
|
||||
echo &" - Captures: {data.captures}"
|
||||
echo &" - Checks: {data.checks}"
|
||||
echo &" - E.P: {data.enPassant}"
|
||||
|
@ -2003,5 +1977,10 @@ when isMainModule:
|
|||
# Queens
|
||||
testPiece(b.getPiece("d1"), Queen, White)
|
||||
testPiece(b.getPiece("d8"), Queen, Black)
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
|
||||
|
||||
#b = newChessboardFromFEN("r3k2r/Pppp1ppp/1b3nbN/nP6/BBPPP3/q4N2/Pp4PP/R2Q1RK1 b kq d3 0 1")
|
||||
#let m = Move(startSquare: "c7".algebraicToLocation, targetSquare: "c5".algebraicToLocation, flag: DoublePush)
|
||||
#b.makeMove()
|
||||
quit(main())
|
|
@ -86,12 +86,14 @@ def main(args: Namespace) -> int:
|
|||
total_nodes = {"stockfish": sum(positions["stockfish"][move] for move in positions["stockfish"]),
|
||||
"nimfish": sum(positions["nimfish"][move] for move in positions["nimfish"])}
|
||||
total_difference = total_nodes["stockfish"] - total_nodes["nimfish"]
|
||||
print(f"Stockfish searched {total_nodes['stockfish']} nodes in {stockfish_time:.2f} seconds")
|
||||
print(f"Nimfish searched {total_nodes['nimfish']} nodes in {nimfish_time:.2f} seconds")
|
||||
print(f"Stockfish searched {total_nodes['stockfish']} node{'' if total_nodes['stockfish'] == 1 else 's'} in {stockfish_time:.2f} seconds")
|
||||
print(f"Nimfish searched {total_nodes['nimfish']} node{'' if total_nodes['nimfish'] == 1 else 's'} in {nimfish_time:.2f} seconds")
|
||||
|
||||
if total_difference > 0:
|
||||
print(f"Stockfish searched {total_difference} more nodes than Nimfish")
|
||||
elif total_difference != 0:
|
||||
print(f"Nimfish searched {-total_difference} more nodes than Stockfish")
|
||||
print(f"Nimfish searched {total_difference} less node{'' if total_difference == 1 else 's'} than Stockfish")
|
||||
elif total_difference < 0:
|
||||
total_difference = abs(total_difference)
|
||||
print(f"Nimfish searched {total_difference} more node{'' if total_difference == 1 else 's'} than Stockfish")
|
||||
else:
|
||||
print("Node count is identical")
|
||||
pattern = re.compile(r"(?:\s\s-\sCaptures:\s(?P<captures>[0-9]+))\n"
|
||||
|
@ -104,8 +106,9 @@ def main(args: Namespace) -> int:
|
|||
extra: re.Match | None = None
|
||||
if not args.bulk:
|
||||
extra = pattern.search(nimfish_output)
|
||||
missed_total = len(missing['stockfish']) + len(missing['nimfish'])
|
||||
if missing["stockfish"] or missing["nimfish"] or mistakes:
|
||||
print(f"Found {len(missing['stockfish']) + len(missing['nimfish'])} missed moves and {len(mistakes)} counting mistakes, more info below: ")
|
||||
print(f"Found {missed_total} missed move{'' if missed_total == 1 else 's'} and {len(mistakes)} counting mistake{'' if len(mistakes) == 1 else 's'}, more info below: ")
|
||||
if args.bulk:
|
||||
print("Note: Nimfish was run in bulk-counting mode, so a detailed breakdown of each move type is not available. "
|
||||
"To fix this, re-run the program without the --bulk option")
|
||||
|
@ -122,21 +125,22 @@ def main(args: Namespace) -> int:
|
|||
elif not args.bulk:
|
||||
print("Unable to locate move breakdown in Nimfish output")
|
||||
if missing["stockfish"] or missing["nimfish"]:
|
||||
print("\n Missed moves:")
|
||||
print("\n Move count breakdown:")
|
||||
if missing["stockfish"]:
|
||||
print(" Legal moves missed by Nimfish: ")
|
||||
print(" Legal moves missed: ")
|
||||
for move in missing["stockfish"]:
|
||||
print(f" - {move}: {positions['stockfish'][move]}")
|
||||
if missing["nimfish"]:
|
||||
print(" Illegal moves missed by Nimfish: ")
|
||||
print(" Illegal moves generated: ")
|
||||
for move in missing["nimfish"]:
|
||||
print(f" - {move}: {positions['nimfish'][move]}")
|
||||
if mistakes:
|
||||
print(" Mistakes:")
|
||||
print(" Counting mistakes made:")
|
||||
for move in mistakes:
|
||||
print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]}")
|
||||
missed = positions["stockfish"][move] - positions["nimfish"][move]
|
||||
print(f" - {move}: expected {positions['stockfish'][move]}, got {positions['nimfish'][move]} ({'-' if missed > 0 else '+'}{abs(missed)})")
|
||||
else:
|
||||
print("No mistakes were detected")
|
||||
print("No discrepancies detected")
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue