Make test suite optionally parallel. Many bug fixes
This commit is contained in:
parent
fbb21f19d1
commit
e42c1fd95d
|
@ -2,4 +2,4 @@
|
|||
-o:"bin/nimfish"
|
||||
-d:danger
|
||||
--passL:"-flto"
|
||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||
|
|
|
@ -130,13 +130,13 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
|||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
result.position.castlingAvailability.white.king = true
|
||||
result.position.castlingAvailability[White.int].king = true
|
||||
of 'Q':
|
||||
result.position.castlingAvailability.white.queen = true
|
||||
result.position.castlingAvailability[White.int].queen = true
|
||||
of 'k':
|
||||
result.position.castlingAvailability.black.king = true
|
||||
result.position.castlingAvailability[Black.int].king = true
|
||||
of 'q':
|
||||
result.position.castlingAvailability.black.queen = true
|
||||
result.position.castlingAvailability[Black.int].queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
|
@ -230,7 +230,8 @@ func getKingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bi
|
|||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
squareBB = square.toBitboard()
|
||||
if (getKingAttacks(square) and squareBB) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
|
@ -238,11 +239,11 @@ func getKnightAttacks*(self: Chessboard, square: Square, attacker: PieceColor):
|
|||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
let knightBB = knight.toBitboard()
|
||||
if (getKnightAttacks(knight) and knightBB) != 0:
|
||||
result = result or knightBB
|
||||
if (getKnightAttacks(knight) and squareBB) != 0:
|
||||
result = result or knight.toBitboard()
|
||||
|
||||
|
||||
proc getSlidingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
|
@ -369,10 +370,42 @@ func inCheck*(self: Chessboard): bool {.inline.} =
|
|||
return self.position.checkers != 0
|
||||
|
||||
|
||||
proc canCastle*(self: Chessboard, side: PieceColor): tuple[king, queen: bool] =
|
||||
proc canCastle*(self: Chessboard): tuple[queen, king: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
return (false, false) # TODO
|
||||
|
||||
if self.inCheck():
|
||||
return (false, false)
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
result = self.position.castlingAvailability[sideToMove.int]
|
||||
if result.king:
|
||||
result.king = (kingSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.queen:
|
||||
result.queen = (queenSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.king:
|
||||
# There are no pieces in between our friendly king and
|
||||
# rook: check for attacks
|
||||
let
|
||||
king = self.getBitboard(King, sideToMove).toSquare()
|
||||
for square in getRayBetween(king, sideToMove.kingSideRook()):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.king = false
|
||||
break
|
||||
|
||||
if result.queen:
|
||||
let
|
||||
king: Square = self.getBitboard(King, sideToMove).toSquare()
|
||||
# The king always moves two squares, but the queen side rook moves
|
||||
# 3 squares. We only need to check for attacks on the squares where
|
||||
# the king moves to and not any further. We subtract 3 instead of 2
|
||||
# because getRayBetween ignores the start and target squares in the
|
||||
# ray it returns so we have to extend it by one
|
||||
destination = makeSquare(rankFromSquare(king), fileFromSquare(king) - 3)
|
||||
for square in getRayBetween(king, destination):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.queen = false
|
||||
break
|
||||
|
||||
|
||||
proc update*(self: Chessboard) =
|
||||
## Updates the internal grid representation
|
||||
|
@ -475,8 +508,8 @@ proc toFEN*(self: Chessboard): string =
|
|||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.position.castlingAvailability.white
|
||||
let castleBlack = self.position.castlingAvailability.black
|
||||
let castleWhite = self.position.castlingAvailability[White.int]
|
||||
let castleBlack = self.position.castlingAvailability[Black.int]
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
|
|
|
@ -38,7 +38,7 @@ type
|
|||
## A magic bitboard entry
|
||||
mask: Bitboard
|
||||
value: uint64
|
||||
indexBits: uint8
|
||||
shift: uint8
|
||||
|
||||
|
||||
# Yeah uh, don't look too closely at this...
|
||||
|
@ -134,7 +134,7 @@ func getIndex*(magic: MagicEntry, blockers: Bitboard): uint {.inline.} =
|
|||
let
|
||||
blockers = blockers and magic.mask
|
||||
hash = blockers * magic.value
|
||||
index = hash shr (64'u8 - magic.indexBits)
|
||||
index = hash shr magic.shift
|
||||
return index.uint
|
||||
|
||||
|
||||
|
@ -236,7 +236,7 @@ proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntr
|
|||
## (true, table) if successful, (false, empty) otherwise
|
||||
|
||||
# Initialize a new sequence with capacity 2^indexBits
|
||||
result.table = newSeqOfCap[Bitboard](1 shl entry.indexBits)
|
||||
result.table = newSeqOfCap[Bitboard](1 shl (64'u8 - entry.shift))
|
||||
result.success = true
|
||||
for _ in 0..result.table.capacity:
|
||||
result.table.add(Bitboard(0))
|
||||
|
@ -294,7 +294,7 @@ proc findMagic(kind: PieceKind, square: Square, indexBits: uint8): tuple[entry:
|
|||
# hopefully better than a single one
|
||||
let
|
||||
magic = rand.next() and rand.next() and rand.next()
|
||||
entry = MagicEntry(mask: mask, value: magic, indexBits: indexBits)
|
||||
entry = MagicEntry(mask: mask, value: magic, shift: 64'u8 - indexBits)
|
||||
var attempt = attemptMagicTableCreation(kind, square, entry)
|
||||
if attempt.success:
|
||||
# Huzzah! Our search for the mighty magic number is complete
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
|
||||
## Move generation logic
|
||||
|
||||
import std/strformat
|
||||
when not defined(danger):
|
||||
import std/strformat
|
||||
|
||||
|
||||
import bitboards
|
||||
|
@ -37,9 +38,8 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B
|
|||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
occupancy = self.getOccupancy()
|
||||
# We can only capture enemy pieces (except the king)
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
||||
epTarget = self.position.enPassantSquare
|
||||
checkers = self.position.checkers
|
||||
diagonalPins = self.position.diagonalPins
|
||||
orthogonalPins = self.position.orthogonalPins
|
||||
promotionRank = if sideToMove == White: getRankMask(0) else: getRankMask(7)
|
||||
|
@ -47,68 +47,104 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B
|
|||
# TODO: Give names to ranks and files so we don't have to assume a
|
||||
# specific board layout when calling get(Rank|File)Mask
|
||||
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
||||
|
||||
var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0)
|
||||
let epPawn = if epBitboard == 0: Bitboard(0) else: epBitboard.forwardRelativeTo(sideToMove)
|
||||
# If we are in check, en passant is only possible if we'd capture the (only)
|
||||
# checking pawn with it
|
||||
if epBitboard != 0 and self.inCheck() and (epPawn and checkers).countSquares() == 0:
|
||||
epBitboard = Bitboard(0)
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
|
||||
# Single and double pushes
|
||||
|
||||
# If a pawn is pinned diagonally, it cannot push forward
|
||||
let
|
||||
# If a pawn is pinned diagonally, it cannot move
|
||||
pushablePawns = pawns and not diagonalPins
|
||||
# Neither can it move if it's pinned orthogonally
|
||||
singlePushes = (pushablePawns.forwardRelativeTo(sideToMove) and not enemyPieces) and destinationMask and not orthogonalPins
|
||||
# If a pawn is pinned horizontally, it cannot move either. It can move vertically
|
||||
# though
|
||||
horizontalPins = Bitboard((0xFF'u64 shl (rankFromSquare(friendlyKing).uint64 * 8))) and orthogonalPins
|
||||
pushablePawns = pawns and not diagonalPins and not horizontalPins
|
||||
singlePushes = (pushablePawns.forwardRelativeTo(sideToMove) and not occupancy) and destinationMask
|
||||
# We do this weird dance instead of using doubleForwardRelativeTo() because that doesn't have any
|
||||
# way to check if there's pieces on the two squares ahead of the pawn
|
||||
var canDoublePush = pushablePawns and startingRank
|
||||
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and not orthogonalPins
|
||||
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy
|
||||
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and destinationMask
|
||||
|
||||
for pawn in singlePushes:
|
||||
for pawn in singlePushes and not orthogonalPins:
|
||||
let pawnBB = pawn.toBitboard()
|
||||
if promotionRank.contains(pawn):
|
||||
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion))
|
||||
else:
|
||||
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn))
|
||||
|
||||
for pawn in canDoublePush:
|
||||
for pawn in singlePushes and orthogonalPins:
|
||||
let pawnBB = pawn.toBitboard()
|
||||
if promotionRank.contains(pawn):
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion))
|
||||
else:
|
||||
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn))
|
||||
|
||||
for pawn in canDoublePush and orthogonalPins:
|
||||
moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush))
|
||||
|
||||
for pawn in canDoublePush and not orthogonalPins:
|
||||
moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush))
|
||||
|
||||
let canCapture = pawns and not orthogonalPins
|
||||
var
|
||||
var
|
||||
captureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
|
||||
captureRight = canCapture.forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask
|
||||
# If a capturing pawn is pinned diagonally, it is allowed to capture only
|
||||
# in the direction of the pin
|
||||
if (diagonalPins and captureLeft) != 0:
|
||||
captureLeft = captureLeft and diagonalPins
|
||||
if (diagonalPins and captureRight) != 0:
|
||||
captureRight = captureRight and diagonalPins
|
||||
for pawn in captureLeft:
|
||||
let pawnBB = pawn.toBitboard()
|
||||
if promotionRank.contains(pawn):
|
||||
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion))
|
||||
else:
|
||||
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture))
|
||||
|
||||
# If a piece is pinned on the right, it can only capture on the right and
|
||||
# vice versa for the left
|
||||
if (let capture = diagonalPins and captureLeft; capture) != 0:
|
||||
captureRight = Bitboard(0)
|
||||
captureLeft = capture
|
||||
if (let capture = diagonalPins and captureRight; capture) != 0:
|
||||
captureLeft = Bitboard(0)
|
||||
captureRight = capture
|
||||
for pawn in captureRight:
|
||||
let pawnBB = pawn.toBitboard()
|
||||
if promotionRank.contains(pawn):
|
||||
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion))
|
||||
else:
|
||||
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture))
|
||||
for pawn in captureLeft:
|
||||
let pawnBB = pawn.toBitboard()
|
||||
if promotionRank.contains(pawn):
|
||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
||||
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion))
|
||||
else:
|
||||
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture))
|
||||
# En passant captures
|
||||
var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0)
|
||||
if epBitboard != 0:
|
||||
# See if en passant would create a check
|
||||
let
|
||||
epPawn = epBitboard.backwardRelativeTo(sideToMove)
|
||||
epLeft = pawns.forwardLeftRelativeTo(sideToMove) and epBitboard and destinationMask
|
||||
epRight = pawns.forwardRightRelativeTo(sideToMove) and epBitboard and destinationMask
|
||||
var
|
||||
newOccupancy = occupancy and not epPawn
|
||||
friendlyPawn: Bitboard = Bitboard(0)
|
||||
if epLeft != 0:
|
||||
friendlyPawn = epBitboard.backwardRightRelativeTo(sideToMove)
|
||||
elif epRight != 0:
|
||||
friendlyPawn = epBitboard.backwardLeftRelativeTo(sideToMove)
|
||||
if friendlyPawn != 0:
|
||||
# We basically simulate the en passant and see if the resulting
|
||||
# occupancy bitboard has the king in check
|
||||
newOccupancy = newOccupancy and not friendlyPawn
|
||||
newOccupancy = newOccupancy or epBitboard
|
||||
|
||||
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||
# En passant does not create a check on the king: all good
|
||||
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
||||
|
||||
|
||||
|
||||
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
||||
rooks = self.getBitboard(Rook, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||
|
@ -137,7 +173,7 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask:
|
|||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
||||
|
@ -168,7 +204,7 @@ proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
|||
king = self.getBitboard(King, sideToMove)
|
||||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
bitboard = getKingAttacks(king.toSquare())
|
||||
noKingOccupancy = occupancy and not king
|
||||
for square in bitboard and not occupancy:
|
||||
|
@ -186,20 +222,25 @@ proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask:
|
|||
nonSideToMove = sideToMove.opposite()
|
||||
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
||||
unpinnedKnights = knights and not pinned
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
for square in unpinnedKnights:
|
||||
let bitboard = getKnightAttacks(square)
|
||||
for target in bitboard and destinationMask and not enemyPieces:
|
||||
moves.add(createMove(square, target))
|
||||
for target in bitboard and enemyPieces:
|
||||
for target in bitboard and destinationMask and enemyPieces:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc generateCastling(self: Chessboard, moves: var MoveList) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
rooks = self.getBitboard(Rook, sideToMove)
|
||||
# TODO
|
||||
castlingRights = self.canCastle()
|
||||
kingSquare = self.getBitboard(King, sideToMove).toSquare()
|
||||
kingPiece = self.grid[kingSquare]
|
||||
if castlingRights.king:
|
||||
moves.add(createMove(kingSquare, kingPiece.kingSideCastling(), Castle))
|
||||
if castlingRights.queen:
|
||||
moves.add(createMove(kingSquare, kingPiece.queenSideCastling(), Castle))
|
||||
|
||||
|
||||
proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
||||
|
@ -216,8 +257,8 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|||
# King is in double check: no need to generate any more
|
||||
# moves
|
||||
return
|
||||
if not self.inCheck():
|
||||
self.generateCastling(moves)
|
||||
|
||||
self.generateCastling(moves)
|
||||
|
||||
# We pass a mask to our move generators to remove stuff
|
||||
# like our friendly pieces from the set of possible
|
||||
|
@ -271,8 +312,8 @@ proc spawnPiece(self: Chessboard, square: Square, piece: Piece) =
|
|||
proc removePiece(self: Chessboard, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
var piece = self.grid[square]
|
||||
when not defined(danger):
|
||||
let Piece = self.grid[square]
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.grid[square] = nullPiece()
|
||||
|
@ -291,6 +332,10 @@ proc movePiece(self: Chessboard, move: Move) =
|
|||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc movePiece(self: Chessboard, startSquare, targetSquare: Square) =
|
||||
self.movePiece(createMove(startSquare, targetSquare))
|
||||
|
||||
|
||||
proc doMove*(self: Chessboard, move: Move) =
|
||||
## Internal function called by makeMove after
|
||||
## performing legality checks. Can be used in
|
||||
|
@ -308,8 +353,8 @@ proc doMove*(self: Chessboard, move: Move) =
|
|||
var
|
||||
halfMoveClock = self.position.halfMoveClock
|
||||
fullMoveCount = self.position.fullMoveCount
|
||||
castlingRights = self.position.castlingRights
|
||||
enPassantTarget = nullSquare()
|
||||
|
||||
# Needed to detect draw by the 50 move rule
|
||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||
# Number of half-moves since the last reversible half-move
|
||||
|
@ -327,7 +372,6 @@ proc doMove*(self: Chessboard, move: Move) =
|
|||
halfMoveClock: halfMoveClock,
|
||||
fullMoveCount: fullMoveCount,
|
||||
sideToMove: self.position.sideToMove.opposite(),
|
||||
castlingRights: castlingRights,
|
||||
enPassantSquare: enPassantTarget,
|
||||
pieces: self.position.pieces,
|
||||
castlingAvailability: self.position.castlingAvailability
|
||||
|
@ -338,13 +382,39 @@ proc doMove*(self: Chessboard, move: Move) =
|
|||
# Make the en passant pawn disappear
|
||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
||||
|
||||
if move.isCastling() or piece.kind == King:
|
||||
# If the king has moved, all castling rights for the side to
|
||||
# move are revoked
|
||||
self.position.castlingAvailability[piece.color.int] = (false, false)
|
||||
if move.isCastling():
|
||||
# Move the rook where it belongs
|
||||
if move.targetSquare == piece.kingSideCastling():
|
||||
let rook = self.grid[piece.color.kingSideRook()]
|
||||
self.movePiece(piece.color.kingSideRook(), rook.kingSideCastling())
|
||||
if move.targetSquare == piece.queenSideCastling():
|
||||
let rook = self.grid[piece.color.queenSideRook()]
|
||||
self.movePiece(piece.color.queenSideRook(), rook.queenSideCastling())
|
||||
|
||||
if piece.kind == Rook:
|
||||
# If a rook on either side moves, castling rights are permanently revoked
|
||||
# on that side
|
||||
if move.startSquare == piece.color.kingSideRook():
|
||||
self.position.castlingAvailability[piece.color.int].king = false
|
||||
elif move.startSquare == piece.color.queenSideRook():
|
||||
self.position.castlingAvailability[piece.color.int].queen = false
|
||||
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
self.removePiece(move.targetSquare)
|
||||
# If a rook has been captured, castling on that side is prohibited
|
||||
if piece.kind == Rook:
|
||||
if move.targetSquare == piece.color.kingSideRook():
|
||||
self.position.castlingAvailability[piece.color.int].king = false
|
||||
elif move.targetSquare == piece.color.queenSideRook():
|
||||
self.position.castlingAvailability[piece.color.int].queen = false
|
||||
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
# TODO: Castling!
|
||||
self.movePiece(move)
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
|
@ -361,7 +431,7 @@ proc doMove*(self: Chessboard, move: Move) =
|
|||
else:
|
||||
# Unreachable
|
||||
discard
|
||||
# Updates checks and pins for the side to move
|
||||
# Updates checks and pins for the (new) side to move
|
||||
self.updateChecksAndPins()
|
||||
|
||||
|
||||
|
|
|
@ -24,13 +24,12 @@ type
|
|||
Capture = 2, # Move is a capture
|
||||
DoublePush = 4, # Move is a double pawn push
|
||||
# Castling metadata
|
||||
CastleLong = 8,
|
||||
CastleShort = 16,
|
||||
Castle = 8,
|
||||
# Pawn promotion metadata
|
||||
PromoteToQueen = 32,
|
||||
PromoteToRook = 64,
|
||||
PromoteToBishop = 128,
|
||||
PromoteToKnight = 256
|
||||
PromoteToQueen = 16,
|
||||
PromoteToRook = 32,
|
||||
PromoteToBishop = 64,
|
||||
PromoteToKnight = 128
|
||||
|
||||
Move* = object
|
||||
## A chess move
|
||||
|
@ -49,7 +48,6 @@ func `[]`*(self: MoveList, i: SomeInteger): Move =
|
|||
raise newException(IndexDefect, &"move list access out of bounds ({i} >= {self.len})")
|
||||
result = self.data[i]
|
||||
|
||||
|
||||
iterator items*(self: MoveList): Move =
|
||||
var i = 0
|
||||
while self.len > i:
|
||||
|
@ -63,6 +61,15 @@ iterator pairs*(self: MoveList): tuple[i: int, move: Move] =
|
|||
yield (i, item)
|
||||
|
||||
|
||||
func `$`*(self: MoveList): string =
|
||||
result &= "["
|
||||
for i, move in self:
|
||||
result &= $move
|
||||
if i < self.len:
|
||||
result &= ", "
|
||||
result &= "]"
|
||||
|
||||
|
||||
func add*(self: var MoveList, move: Move) {.inline.} =
|
||||
self.data[self.len] = move
|
||||
inc(self.len)
|
||||
|
@ -122,24 +129,13 @@ func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
|||
func isCapture*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a
|
||||
## cature
|
||||
result = (move.flags and Capture.uint16) == Capture.uint16
|
||||
result = (move.flags and Capture.uint16) != 0
|
||||
|
||||
|
||||
func isCastling*(move: Move): bool {.inline.} =
|
||||
## Returns whether the given move is a
|
||||
## castle
|
||||
for flag in [CastleLong, CastleShort]:
|
||||
if (move.flags and flag.uint16) != 0:
|
||||
return true
|
||||
|
||||
|
||||
func getCastlingType*(move: Move): MoveFlag {.inline.} =
|
||||
## Returns the castlingRights type of the given move.
|
||||
## The return value of this function is only valid
|
||||
## if isCastling() returns true
|
||||
for flag in [CastleLong, CastleShort]:
|
||||
if (move.flags and flag.uint16) != 0:
|
||||
return flag
|
||||
## castling move
|
||||
result = (move.flags and Castle.uint16) != 0
|
||||
|
||||
|
||||
func isEnPassant*(move: Move): bool {.inline.} =
|
||||
|
@ -156,9 +152,9 @@ func isDoublePush*(move: Move): bool {.inline.} =
|
|||
|
||||
func getFlags*(move: Move): seq[MoveFlag] =
|
||||
## Gets all the flags of this move
|
||||
for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort,
|
||||
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
||||
PromoteToRook]:
|
||||
for flag in [EnPassant, Capture, DoublePush, Castle,
|
||||
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
||||
PromoteToRook]:
|
||||
if (move.flags and flag.uint16) == flag.uint16:
|
||||
result.add(flag)
|
||||
if result.len() == 0:
|
||||
|
|
|
@ -93,11 +93,50 @@ proc toAlgebraic*(square: Square): string {.inline.} =
|
|||
proc `$`*(square: Square): string = square.toAlgebraic()
|
||||
|
||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare())
|
||||
func longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare())
|
||||
func shortCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "g1".toSquare() else: "g8".toSquare())
|
||||
func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "d1".toSquare() else: "d8".toSquare())
|
||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
||||
func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a1".toSquare() else: "a8".toSquare())
|
||||
|
||||
func kingSideCastling*(piece: Piece): Square {.inline.} =
|
||||
case piece.kind:
|
||||
of Rook:
|
||||
case piece.color:
|
||||
of White:
|
||||
return "f1".toSquare()
|
||||
of Black:
|
||||
return "f8".toSquare()
|
||||
else:
|
||||
discard
|
||||
of King:
|
||||
case piece.color:
|
||||
of White:
|
||||
return "g1".toSquare()
|
||||
of Black:
|
||||
return "g8".toSquare()
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
func queenSideCastling*(piece: Piece): Square {.inline.} =
|
||||
case piece.kind:
|
||||
of Rook:
|
||||
case piece.color:
|
||||
of White:
|
||||
return "d1".toSquare()
|
||||
of Black:
|
||||
return "d8".toSquare()
|
||||
else:
|
||||
discard
|
||||
of King:
|
||||
case piece.color:
|
||||
of White:
|
||||
return "c1".toSquare()
|
||||
of Black:
|
||||
return "c8".toSquare()
|
||||
else:
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc toPretty*(piece: Piece): string =
|
||||
|
|
|
@ -26,7 +26,7 @@ type
|
|||
# of whether the king or the rooks on either side
|
||||
# moved, the actual checks for the legality of castling
|
||||
# are done elsewhere
|
||||
castlingAvailability*: tuple[white, black: tuple[queen, king: bool]]
|
||||
castlingAvailability*: array[2, tuple[queen, king: bool]]
|
||||
# Number of half-moves that were performed
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
|
||||
import bitboards
|
||||
import magics
|
||||
import pieces
|
||||
|
||||
|
||||
export bitboards, pieces
|
||||
|
||||
|
||||
# Stolen from https://github.com/Ciekce/voidstar/blob/main/src/rays.rs :D
|
||||
|
||||
|
@ -46,3 +51,22 @@ let BETWEEN_RAYS = computeRaysBetweenSquares()
|
|||
|
||||
|
||||
proc getRayBetween*(source, target: Square): Bitboard {.inline.} = BETWEEN_RAYS[source.int][target.int]
|
||||
|
||||
proc queenSideCastleRay*(color: PieceColor): Bitboard {.inline.} =
|
||||
case color:
|
||||
of White:
|
||||
return getRayBetween("e1".toSquare(), "a1".toSquare())
|
||||
of Black:
|
||||
return getRayBetween("e8".toSquare(), "a8".toSquare())
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
proc kingSideCastleRay*(color: PieceColor): Bitboard {.inline.} =
|
||||
case color:
|
||||
of White:
|
||||
return getRayBetween("e1".toSquare(), "h1".toSquare())
|
||||
of Black:
|
||||
return getRayBetween("e8".toSquare(), "h8".toSquare())
|
||||
else:
|
||||
discard
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -62,7 +62,7 @@ proc perft*(board: Chessboard, ply: int, verbose: bool = false, divide: bool = f
|
|||
|
||||
for move in moves:
|
||||
if verbose:
|
||||
let canCastle = board.canCastle(board.position.sideToMove)
|
||||
let canCastle = board.canCastle()
|
||||
echo &"Ply (from root): {board.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||
echo &"Turn: {board.position.sideToMove}"
|
||||
|
@ -91,7 +91,7 @@ proc perft*(board: Chessboard, ply: int, verbose: bool = false, divide: bool = f
|
|||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
if verbose:
|
||||
let canCastle = board.canCastle(board.position.sideToMove)
|
||||
let canCastle = board.canCastle()
|
||||
echo "\n"
|
||||
echo &"Opponent in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
|
@ -239,11 +239,9 @@ proc handleMoveCommand(board: Chessboard, command: seq[string]): Move {.discarda
|
|||
var move = createMove(startSquare, targetSquare, flags)
|
||||
let piece = board.getPiece(move.startSquare)
|
||||
if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
||||
if move.targetSquare == longCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleLong.uint16
|
||||
elif move.targetSquare == shortCastleKing(piece.color):
|
||||
move.flags = move.flags or CastleShort.uint16
|
||||
if move.targetSquare == board.position.enPassantSquare:
|
||||
if move.targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||
move.flags = move.flags or Castle.uint16
|
||||
elif move.targetSquare == board.position.enPassantSquare:
|
||||
move.flags = move.flags or EnPassant.uint16
|
||||
result = board.makeMove(move)
|
||||
if result == nullMove():
|
||||
|
@ -258,8 +256,11 @@ proc handlePositionCommand(board: var Chessboard, command: seq[string]) =
|
|||
# some error occurs
|
||||
var tempBoard: Chessboard
|
||||
case command[1]:
|
||||
of "startpos":
|
||||
tempBoard = newDefaultChessboard()
|
||||
of "startpos", "kiwipete":
|
||||
if command[1] == "kiwipete":
|
||||
tempBoard = newChessboardFromFen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -")
|
||||
else:
|
||||
tempBoard = newDefaultChessboard()
|
||||
if command.len() > 2:
|
||||
let args = command[2].splitWhitespace()
|
||||
if args.len() > 0:
|
||||
|
@ -337,18 +338,19 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- fen [string]: Set the board to the given fen string if one is provided, or print
|
||||
the current position as a FEN string if no arguments are given
|
||||
- startpos: Set the board to the starting position
|
||||
- kiwipete: Set the board to famous kiwipete position
|
||||
- pretty: Pretty-print the current position
|
||||
- print: Print the current position using ASCII characters only
|
||||
Options:
|
||||
- moves {moveList}: Perform the given moves (space-separated, all-lowercase)
|
||||
in algebraic notation after the position is loaded. This option only applies
|
||||
to the "startpos" and "fen" subcommands: it is ignored otherwise
|
||||
to the subcommands that set a position, it is ignored otherwise
|
||||
Examples:
|
||||
- position startpos
|
||||
- position fen "..." moves a2a3 a7a6
|
||||
- clear: Clear the screen
|
||||
- move <move>: Perform the given move in algebraic notation
|
||||
- castle: Print castlingRights rights for each side
|
||||
- castle: Print castling rights for the side to move
|
||||
- check: Print if the current side to move is in check
|
||||
- unmove, u: Unmakes the last move. Can be used in succession
|
||||
- stm: Print which side is to move
|
||||
|
@ -358,7 +360,8 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- fen: Shorthand for "position fen"
|
||||
- pos <args>: Shorthand for "position <args>"
|
||||
- get <square>: Get the piece on the given square
|
||||
- atk <square>: Print the attack bitboard of the given square for the side to move
|
||||
- def <square>: Print the attack bitboard of the given square for the side to move
|
||||
- atk <square>: Print the attack bitboard of the given square for the opponent side
|
||||
- pins: Print the current pin mask
|
||||
- checks: Print the current checks mask
|
||||
- skip: Swap the side to move
|
||||
|
@ -412,12 +415,21 @@ proc commandLoop*: int =
|
|||
board.unmakeMove()
|
||||
of "stm":
|
||||
echo &"Side to move: {board.position.sideToMove}"
|
||||
of "def":
|
||||
if len(cmd) != 2:
|
||||
echo "error: def: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove)
|
||||
except ValueError:
|
||||
echo "error: def: invalid square"
|
||||
continue
|
||||
of "atk":
|
||||
if len(cmd) != 2:
|
||||
echo "error: atk: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove)
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
||||
except ValueError:
|
||||
echo "error: atk: invalid square"
|
||||
continue
|
||||
|
@ -437,13 +449,15 @@ proc commandLoop*: int =
|
|||
echo "error: get: invalid square"
|
||||
continue
|
||||
of "castle":
|
||||
let canCastle = board.canCastle(board.position.sideToMove)
|
||||
let canCastle = board.canCastle()
|
||||
echo &"Castling rights for {($board.position.sideToMove).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
of "check":
|
||||
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
of "pins":
|
||||
echo &"Ortogonal pins:\n{board.position.orthogonalPins}"
|
||||
echo &"Diagonal pins:\n{board.position.diagonalPins}"
|
||||
if board.position.orthogonalPins != 0:
|
||||
echo &"Orthogonal pins:\n{board.position.orthogonalPins}"
|
||||
if board.position.diagonalPins != 0:
|
||||
echo &"Diagonal pins:\n{board.position.diagonalPins}"
|
||||
of "checks":
|
||||
echo board.position.checkers
|
||||
of "quit":
|
||||
|
|
|
@ -19,7 +19,7 @@ def main(args: Namespace) -> int:
|
|||
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
||||
return 2
|
||||
try:
|
||||
NIMFISH = (args.nimfish or (Path.cwd() / "bin" / "nimfish")).resolve(strict=True)
|
||||
NIMFISH = (args.nimfish or Path(which("nimfish"))).resolve(strict=True)
|
||||
except Exception as e:
|
||||
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
||||
return 2
|
||||
|
@ -166,4 +166,7 @@ if __name__ == "__main__":
|
|||
parser.add_argument("--stockfish", type=Path, help="Path to the stockfish executable. Defaults to '' (detected automatically)", default=None)
|
||||
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
|
||||
parser.add_argument("--silent", action="store_true", help="Disable all output (a return code of 0 means the test was successful)", default=False)
|
||||
sys.exit(main(parser.parse_args()))
|
||||
try:
|
||||
sys.exit(main(parser.parse_args()))
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(255)
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
1B6/8/8/8/5pP1/8/7k/4K3 b - g3 0 1
|
||||
1k6/8/8/8/4Pp2/8/7B/4K3 b - e3 0 1
|
||||
1k6/8/8/8/5pP1/8/7B/4K3 b - g3 0 1
|
||||
1r2k2r/8/8/8/8/8/8/R3K2R b KQk - 0 1
|
||||
1r2k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1
|
||||
2K2r2/4P3/8/8/8/8/8/3k4 w - - 0 1
|
||||
|
@ -14,21 +17,56 @@
|
|||
4k2r/8/8/8/8/8/8/4K3 b k - 0 1
|
||||
4k2r/8/8/8/8/8/8/4K3 w k - 0 1
|
||||
4k3/1P6/8/8/8/8/K7/8 w - - 0 1
|
||||
4k3/2rn4/8/2K1pP2/8/8/8/8 w - e6 0 1
|
||||
4k3/4p3/4K3/8/8/8/8/8 b - - 0 1
|
||||
4k3/7K/8/5Pp1/8/8/8/1b6 w - g6 0 1
|
||||
4k3/7b/8/4pP2/4K3/8/8/8 w - e6 0 1
|
||||
4k3/7b/8/4pP2/8/8/8/1K6 w - e6 0 1
|
||||
4k3/7b/8/5Pp1/8/8/8/1K6 w - g6 0 1
|
||||
4k3/8/1b6/2Pp4/3K4/8/8/8 w - d6 0 1
|
||||
4k3/8/3K4/1pP5/8/q7/8/8 w - b6 0 1
|
||||
4k3/8/4q3/8/8/8/3b4/4K3 w - - 0 1
|
||||
4k3/8/4r3/8/8/8/3p4/4K3 w - - 0 1
|
||||
4k3/8/6b1/4pP2/4K3/8/8/8 w - e6 0 1
|
||||
4k3/8/7b/5pP1/5K2/8/8/8 w - f6 0 1
|
||||
4k3/8/8/2PpP3/8/8/8/4K3 w - d6 0 1
|
||||
4k3/8/8/4pP2/3K4/8/8/8 w - e6 0 1
|
||||
4k3/8/8/8/1b2r3/8/3Q4/4K3 w - - 0 1
|
||||
4k3/8/8/8/1b2r3/8/3QP3/4K3 w - - 0 1
|
||||
4k3/8/8/8/1b5b/2Q5/5P2/4K3 w - - 0 1
|
||||
4k3/8/8/8/1b5b/2R5/5P2/4K3 w - - 0 1
|
||||
4k3/8/8/8/1b5b/8/3Q4/4K3 w - - 0 1
|
||||
4k3/8/8/8/1b5b/8/3R4/4K3 w - - 0 1
|
||||
4k3/8/8/8/2pPp3/8/8/4K3 b - d3 0 1
|
||||
4k3/8/8/8/8/8/8/4K2R b K - 0 1
|
||||
4k3/8/8/8/8/8/8/4K2R w K - 0 1
|
||||
4k3/8/8/8/8/8/8/R3K2R b KQ - 0 1
|
||||
4k3/8/8/8/8/8/8/R3K2R w KQ - 0 1
|
||||
4k3/8/8/8/8/8/8/R3K3 b Q - 0 1
|
||||
4k3/8/8/8/8/8/8/R3K3 w Q - 0 1
|
||||
4k3/8/8/K2pP2q/8/8/8/8 w - d6 0 1
|
||||
4k3/8/8/K2pP2r/8/8/8/8 w - d6 0 1
|
||||
4k3/8/8/q2pP2K/8/8/8/8 w - d6 0 1
|
||||
4k3/8/8/r2pP2K/8/8/8/8 w - d6 0 1
|
||||
4k3/8/K6q/3pP3/8/8/8/8 w - d6 0 1
|
||||
4k3/8/K6r/3pP3/8/8/8/8 w - d6 0 1
|
||||
4k3/8/b7/1Pp5/2K5/8/8/8 w - c6 0 1
|
||||
4k3/K7/8/1pP5/8/8/8/6b1 w - b6 0 1
|
||||
4k3/b7/8/1pP5/8/8/8/6K1 w - b6 0 1
|
||||
4k3/b7/8/2Pp4/3K4/8/8/8 w - d6 0 1
|
||||
4k3/b7/8/2Pp4/8/8/8/6K1 w - d6 0 1
|
||||
5k2/8/8/8/8/8/8/4K2R w K - 0 1
|
||||
6B1/8/8/8/1Pp5/8/k7/4K3 b - b3 0 1
|
||||
6KQ/8/8/8/8/8/8/7k b - - 0 1
|
||||
6k1/8/8/8/1Pp5/8/B7/4K3 b - b3 0 1
|
||||
6k1/8/8/8/2pP4/8/B7/3K4 b - d3 0 1
|
||||
6kq/8/8/8/8/8/8/7K w - - 0 1
|
||||
6qk/8/8/8/8/8/8/7K b - - 0 1
|
||||
7k/3p4/8/8/3P4/8/8/K7 b - - 0 1
|
||||
7k/3p4/8/8/3P4/8/8/K7 w - - 0 1
|
||||
7K/7p/7k/8/8/8/8/8 b - - 0 1
|
||||
7K/7p/7k/8/8/8/8/8 w - - 0 1
|
||||
7k/3p4/8/8/3P4/8/8/K7 b - - 0 1
|
||||
7k/3p4/8/8/3P4/8/8/K7 w - - 0 1
|
||||
7k/4K3/8/1pP5/8/q7/8/8 w - b6 0 1
|
||||
7k/8/1p6/8/8/P7/8/7K b - - 0 1
|
||||
7k/8/1p6/8/8/P7/8/7K w - - 0 1
|
||||
7k/8/8/1p6/P7/8/8/7K b - - 0 1
|
||||
|
@ -52,8 +90,8 @@
|
|||
8/3k4/3p4/8/3P4/3K4/8/8 w - - 0 1
|
||||
8/8/1B6/7b/7k/8/2B1b3/7K b - - 0 1
|
||||
8/8/1B6/7b/7k/8/2B1b3/7K w - - 0 1
|
||||
8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1
|
||||
8/8/1P2K3/8/2n5/1q6/8/5k2 b - - 0 1
|
||||
8/8/1k6/2b5/2pP4/8/5K2/8 b - d3 0 1
|
||||
8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1
|
||||
8/8/3K4/3Nn3/3nN3/4k3/8/8 b - - 0 1
|
||||
8/8/3k4/3p4/3P4/3K4/8/8 b - - 0 1
|
||||
|
@ -65,6 +103,11 @@
|
|||
8/8/7k/7p/7P/7K/8/8 b - - 0 1
|
||||
8/8/7k/7p/7P/7K/8/8 w - - 0 1
|
||||
8/8/8/2k5/2pP4/8/B7/4K3 b - d3 0 3
|
||||
8/8/8/4k3/5Pp1/8/8/3K4 b - f3 0 1
|
||||
8/8/8/8/1R1Pp2k/8/8/4K3 b - d3 0 1
|
||||
8/8/8/8/1k1Pp2R/8/8/4K3 b - d3 0 1
|
||||
8/8/8/8/1k1PpN1R/8/8/4K3 b - d3 0 1
|
||||
8/8/8/8/1k1Ppn1R/8/8/4K3 b - d3 0 1
|
||||
8/8/8/8/8/4k3/4P3/4K3 w - - 0 1
|
||||
8/8/8/8/8/7K/7P/7k b - - 0 1
|
||||
8/8/8/8/8/7K/7P/7k w - - 0 1
|
||||
|
@ -76,45 +119,49 @@
|
|||
8/8/8/8/8/K7/P7/k7 w - - 0 1
|
||||
8/8/k7/p7/P7/K7/8/8 b - - 0 1
|
||||
8/8/k7/p7/P7/K7/8/8 w - - 0 1
|
||||
8/k1P5/8/1K6/8/8/8/8 w - - 0 1
|
||||
8/P1k5/K7/8/8/8/8/8 w - - 0 1
|
||||
8/Pk6/8/8/8/8/6Kp/8 b - - 0 1
|
||||
8/Pk6/8/8/8/8/6Kp/8 w - - 0 1
|
||||
8/PPPk4/8/8/8/8/4Kppp/8 b - - 0 1
|
||||
8/PPPk4/8/8/8/8/4Kppp/8 w - - 0 1
|
||||
8/Pk6/8/8/8/8/6Kp/8 b - - 0 1
|
||||
8/Pk6/8/8/8/8/6Kp/8 w - - 0 1
|
||||
8/k1P5/8/1K6/8/8/8/8 w - - 0 1
|
||||
B6b/8/8/8/2K5/4k3/8/b6B w - - 0 1
|
||||
B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1
|
||||
K1k5/8/P7/8/8/8/8/8 w - - 0 1
|
||||
K7/8/2n5/1n6/8/8/8/k6N b - - 0 1
|
||||
K7/8/2n5/1n6/8/8/8/k6N w - - 0 1
|
||||
K7/8/8/3Q4/4q3/8/8/7k b - - 0 1
|
||||
K7/8/8/3Q4/4q3/8/8/7k w - - 0 1
|
||||
K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1
|
||||
K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1
|
||||
K7/p7/k7/8/8/8/8/8 b - - 0 1
|
||||
K7/p7/k7/8/8/8/8/8 w - - 0 1
|
||||
R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1
|
||||
R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1
|
||||
k3K3/8/8/3pP3/8/8/8/4r3 w - d6 0 1
|
||||
k7/6p1/8/8/8/8/7P/K7 b - - 0 1
|
||||
k7/6p1/8/8/8/8/7P/K7 w - - 0 1
|
||||
k7/7p/8/8/8/8/6P1/K7 b - - 0 1
|
||||
k7/7p/8/8/8/8/6P1/K7 w - - 0 1
|
||||
K7/8/2n5/1n6/8/8/8/k6N b - - 0 1
|
||||
k7/8/2N5/1N6/8/8/8/K6n b - - 0 1
|
||||
K7/8/2n5/1n6/8/8/8/k6N w - - 0 1
|
||||
k7/8/2N5/1N6/8/8/8/K6n w - - 0 1
|
||||
k7/8/3p4/8/3P4/8/8/7K b - - 0 1
|
||||
k7/8/3p4/8/3P4/8/8/7K w - - 0 1
|
||||
k7/8/3p4/8/8/4P3/8/7K b - - 0 1
|
||||
k7/8/3p4/8/8/4P3/8/7K w - - 0 1
|
||||
k7/8/4r3/3pP3/8/8/8/4K3 w - d6 0 1
|
||||
k7/8/6p1/8/8/7P/8/K7 b - - 0 1
|
||||
k7/8/6p1/8/8/7P/8/K7 w - - 0 1
|
||||
k7/8/7p/8/8/6P1/8/K7 b - - 0 1
|
||||
k7/8/7p/8/8/6P1/8/K7 w - - 0 1
|
||||
k7/8/8/3p4/4p3/8/8/7K b - - 0 1
|
||||
k7/8/8/3p4/4p3/8/8/7K w - - 0 1
|
||||
K7/8/8/3Q4/4q3/8/8/7k b - - 0 1
|
||||
K7/8/8/3Q4/4q3/8/8/7k w - - 0 1
|
||||
k7/8/8/6p1/7P/8/8/K7 b - - 0 1
|
||||
k7/8/8/6p1/7P/8/8/K7 w - - 0 1
|
||||
k7/8/8/7p/6P1/8/8/K7 b - - 0 1
|
||||
k7/8/8/7p/6P1/8/8/K7 w - - 0 1
|
||||
k7/B7/1B6/1B6/8/8/8/K6b b - - 0 1
|
||||
K7/b7/1b6/1b6/8/8/8/k6B b - - 0 1
|
||||
k7/B7/1B6/1B6/8/8/8/K6b w - - 0 1
|
||||
K7/b7/1b6/1b6/8/8/8/k6B w - - 0 1
|
||||
K7/p7/k7/8/8/8/8/8 b - - 0 1
|
||||
K7/p7/k7/8/8/8/8/8 w - - 0 1
|
||||
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N b - - 0 1
|
||||
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1
|
||||
n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1
|
||||
|
@ -142,8 +189,6 @@ r3k3/8/8/8/8/8/8/4K3 b q - 0 1
|
|||
r3k3/8/8/8/8/8/8/4K3 w q - 0 1
|
||||
r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10
|
||||
r6r/1b2k1bq/8/8/7B/8/8/R3K2R b KQ - 3 2
|
||||
R6r/8/8/2K5/5k2/8/8/r6R b - - 0 1
|
||||
R6r/8/8/2K5/5k2/8/8/r6R w - - 0 1
|
||||
rnb2k1r/pp1Pbppp/2p5/q7/2B5/8/PPPQNnPP/RNB1K2R w KQ - 3 9
|
||||
rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8
|
||||
rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3
|
||||
|
|
|
@ -3,31 +3,62 @@ import timeit
|
|||
from pathlib import Path
|
||||
from argparse import Namespace, ArgumentParser
|
||||
from compare_positions import main as test
|
||||
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from multiprocessing import cpu_count
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
def main(args: Namespace) -> int:
|
||||
print("[S] Starting test suite")
|
||||
# We try to be polite with resource usage
|
||||
if not args.parallel:
|
||||
print("[S] Starting test suite")
|
||||
else:
|
||||
print(f"[S] Starting test suite with {args.workers} workers")
|
||||
successful = []
|
||||
failed = []
|
||||
positions = args.positions.read_text().splitlines()
|
||||
start = timeit.default_timer()
|
||||
longest_fen = max(sorted([len(fen) for fen in positions]))
|
||||
for i, fen in enumerate(positions):
|
||||
fen = fen.strip(" ")
|
||||
fen += " " * (longest_fen - len(fen))
|
||||
sys.stdout.write(f"\r[S] Testing {fen} ({i + 1}/{len(positions)})\033[K")
|
||||
args.fen = fen
|
||||
args.silent = not args.no_silent
|
||||
if test(args) == 0:
|
||||
successful.append(fen)
|
||||
start = timeit.default_timer()
|
||||
if not args.parallel:
|
||||
for i, fen in enumerate(positions):
|
||||
fen = fen.strip(" ")
|
||||
fen += " " * (longest_fen - len(fen))
|
||||
sys.stdout.write(f"\r[S] Testing {fen} ({i + 1}/{len(positions)})\033[K")
|
||||
args.fen = fen
|
||||
args.silent = not args.no_silent
|
||||
if test(args) == 0:
|
||||
successful.append(fen)
|
||||
else:
|
||||
failed.append(fen)
|
||||
else:
|
||||
# There is no compute going on in the Python thread,
|
||||
# it's just I/O waiting for the processes to finish,
|
||||
# so using a thread as opposed to a process doesn't
|
||||
# make much different w.r.t. the GIL (and threads are
|
||||
# cheaper than processes on some platforms)
|
||||
futures = {}
|
||||
try:
|
||||
pool = ThreadPoolExecutor(args.workers)
|
||||
for fen in positions:
|
||||
args = deepcopy(args)
|
||||
args.fen = fen.strip(" ")
|
||||
args.silent = not args.no_silent
|
||||
futures[pool.submit(test, args)] = args.fen
|
||||
for i, future in enumerate(as_completed(futures)):
|
||||
sys.stdout.write(f"\r[S] Testing in progress ({i + 1}/{len(positions)})\033[K")
|
||||
if future.result() == 0:
|
||||
successful.append(futures[future])
|
||||
else:
|
||||
failed.append(futures[future])
|
||||
except KeyboardInterrupt:
|
||||
pool.shutdown(cancel_futures=True)
|
||||
print(f"\r[S] Interrupted\033[K")
|
||||
else:
|
||||
failed.append(fen)
|
||||
stop = timeit.default_timer()
|
||||
print(f"\r[S] Ran {len(positions)} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)\033[K")
|
||||
if failed and args.show_failures:
|
||||
print("[S] The following FENs failed to pass the test:", end="")
|
||||
print("\n\t".join(failed))
|
||||
stop = timeit.default_timer()
|
||||
print(f"\r[S] Ran {len(positions)} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)\033[K")
|
||||
if failed and args.show_failures:
|
||||
print("[S] The following FENs failed to pass the test:", end="")
|
||||
print("\n\t".join(failed))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -38,6 +69,11 @@ if __name__ == "__main__":
|
|||
parser.add_argument("--nimfish", type=Path, help="Path to the nimfish executable. Defaults to '' (detected automatically)", default=None)
|
||||
parser.add_argument("--positions", type=Path, help="Location of the file containing FENs to test, one per line. Defaults to 'tests/positions.txt'",
|
||||
default=Path("tests/positions.txt"))
|
||||
parser.add_argument("--no-silent", action="store_true", help="Do not suppress output from compare_positions.py (defaults)", default=False)
|
||||
parser.add_argument("--no-silent", action="store_true", help="Do not suppress output from compare_positions.py (defaults to False)", default=False)
|
||||
parser.add_argument("-p", "--parallel", action="store_true", help="Run multiple tests in parallel", default=False)
|
||||
parser.add_argument("--workers", "-w", type=int, required=False, help="How many workers to use in parallel mode (defaults to cpu_count() / 2)", default=cpu_count() // 2)
|
||||
parser.add_argument("--show-failures", action="store_true", help="Show which FENs failed to pass the test", default=False)
|
||||
sys.exit(main(parser.parse_args()))
|
||||
try:
|
||||
sys.exit(main(parser.parse_args()))
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(255)
|
Loading…
Reference in New Issue