Make test suite optionally parallel. Many bug fixes
This commit is contained in:
parent
04bfe74ad5
commit
0dfd647f4c
|
@ -2,4 +2,4 @@
|
||||||
-o:"bin/nimfish"
|
-o:"bin/nimfish"
|
||||||
-d:danger
|
-d:danger
|
||||||
--passL:"-flto"
|
--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 '-':
|
of '-':
|
||||||
discard
|
discard
|
||||||
of 'K':
|
of 'K':
|
||||||
result.position.castlingAvailability.white.king = true
|
result.position.castlingAvailability[White.int].king = true
|
||||||
of 'Q':
|
of 'Q':
|
||||||
result.position.castlingAvailability.white.queen = true
|
result.position.castlingAvailability[White.int].queen = true
|
||||||
of 'k':
|
of 'k':
|
||||||
result.position.castlingAvailability.black.king = true
|
result.position.castlingAvailability[Black.int].king = true
|
||||||
of 'q':
|
of 'q':
|
||||||
result.position.castlingAvailability.black.queen = true
|
result.position.castlingAvailability[Black.int].queen = true
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||||
of 3:
|
of 3:
|
||||||
|
@ -230,7 +230,8 @@ func getKingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bi
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
let
|
let
|
||||||
king = self.getBitboard(King, attacker)
|
king = self.getBitboard(King, attacker)
|
||||||
if (getKingAttacks(square) and king) != 0:
|
squareBB = square.toBitboard()
|
||||||
|
if (getKingAttacks(square) and squareBB) != 0:
|
||||||
result = result or king
|
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
|
## Returns the locations of the knights attacking the given square
|
||||||
let
|
let
|
||||||
knights = self.getBitboard(Knight, attacker)
|
knights = self.getBitboard(Knight, attacker)
|
||||||
|
squareBB = square.toBitboard()
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
for knight in knights:
|
for knight in knights:
|
||||||
let knightBB = knight.toBitboard()
|
if (getKnightAttacks(knight) and squareBB) != 0:
|
||||||
if (getKnightAttacks(knight) and knightBB) != 0:
|
result = result or knight.toBitboard()
|
||||||
result = result or knightBB
|
|
||||||
|
|
||||||
|
|
||||||
proc getSlidingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
proc getSlidingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
|
@ -369,10 +370,42 @@ func inCheck*(self: Chessboard): bool {.inline.} =
|
||||||
return self.position.checkers != 0
|
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
|
## 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) =
|
proc update*(self: Chessboard) =
|
||||||
## Updates the internal grid representation
|
## Updates the internal grid representation
|
||||||
|
@ -475,8 +508,8 @@ proc toFEN*(self: Chessboard): string =
|
||||||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||||
result &= " "
|
result &= " "
|
||||||
# Castling availability
|
# Castling availability
|
||||||
let castleWhite = self.position.castlingAvailability.white
|
let castleWhite = self.position.castlingAvailability[White.int]
|
||||||
let castleBlack = self.position.castlingAvailability.black
|
let castleBlack = self.position.castlingAvailability[Black.int]
|
||||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||||
result &= "-"
|
result &= "-"
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -38,7 +38,7 @@ type
|
||||||
## A magic bitboard entry
|
## A magic bitboard entry
|
||||||
mask: Bitboard
|
mask: Bitboard
|
||||||
value: uint64
|
value: uint64
|
||||||
indexBits: uint8
|
shift: uint8
|
||||||
|
|
||||||
|
|
||||||
# Yeah uh, don't look too closely at this...
|
# Yeah uh, don't look too closely at this...
|
||||||
|
@ -134,7 +134,7 @@ func getIndex*(magic: MagicEntry, blockers: Bitboard): uint {.inline.} =
|
||||||
let
|
let
|
||||||
blockers = blockers and magic.mask
|
blockers = blockers and magic.mask
|
||||||
hash = blockers * magic.value
|
hash = blockers * magic.value
|
||||||
index = hash shr (64'u8 - magic.indexBits)
|
index = hash shr magic.shift
|
||||||
return index.uint
|
return index.uint
|
||||||
|
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntr
|
||||||
## (true, table) if successful, (false, empty) otherwise
|
## (true, table) if successful, (false, empty) otherwise
|
||||||
|
|
||||||
# Initialize a new sequence with capacity 2^indexBits
|
# 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
|
result.success = true
|
||||||
for _ in 0..result.table.capacity:
|
for _ in 0..result.table.capacity:
|
||||||
result.table.add(Bitboard(0))
|
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
|
# hopefully better than a single one
|
||||||
let
|
let
|
||||||
magic = rand.next() and rand.next() and rand.next()
|
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)
|
var attempt = attemptMagicTableCreation(kind, square, entry)
|
||||||
if attempt.success:
|
if attempt.success:
|
||||||
# Huzzah! Our search for the mighty magic number is complete
|
# Huzzah! Our search for the mighty magic number is complete
|
||||||
|
|
|
@ -14,7 +14,8 @@
|
||||||
|
|
||||||
## Move generation logic
|
## Move generation logic
|
||||||
|
|
||||||
import std/strformat
|
when not defined(danger):
|
||||||
|
import std/strformat
|
||||||
|
|
||||||
|
|
||||||
import bitboards
|
import bitboards
|
||||||
|
@ -37,9 +38,8 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: B
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.getBitboard(Pawn, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
# We can only capture enemy pieces (except the king)
|
# 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
|
epTarget = self.position.enPassantSquare
|
||||||
checkers = self.position.checkers
|
|
||||||
diagonalPins = self.position.diagonalPins
|
diagonalPins = self.position.diagonalPins
|
||||||
orthogonalPins = self.position.orthogonalPins
|
orthogonalPins = self.position.orthogonalPins
|
||||||
promotionRank = if sideToMove == White: getRankMask(0) else: getRankMask(7)
|
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
|
# TODO: Give names to ranks and files so we don't have to assume a
|
||||||
# specific board layout when calling get(Rank|File)Mask
|
# specific board layout when calling get(Rank|File)Mask
|
||||||
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
||||||
|
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||||
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)
|
|
||||||
|
|
||||||
# Single and double pushes
|
# Single and double pushes
|
||||||
|
|
||||||
|
# If a pawn is pinned diagonally, it cannot push forward
|
||||||
let
|
let
|
||||||
# If a pawn is pinned diagonally, it cannot move
|
# If a pawn is pinned horizontally, it cannot move either. It can move vertically
|
||||||
pushablePawns = pawns and not diagonalPins
|
# though
|
||||||
# Neither can it move if it's pinned orthogonally
|
horizontalPins = Bitboard((0xFF'u64 shl (rankFromSquare(friendlyKing).uint64 * 8))) and orthogonalPins
|
||||||
singlePushes = (pushablePawns.forwardRelativeTo(sideToMove) and not enemyPieces) and destinationMask and not 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
|
# 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
|
# way to check if there's pieces on the two squares ahead of the pawn
|
||||||
var canDoublePush = pushablePawns and startingRank
|
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
|
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and destinationMask
|
||||||
|
|
||||||
for pawn in singlePushes:
|
for pawn in singlePushes and not orthogonalPins:
|
||||||
let pawnBB = pawn.toBitboard()
|
let pawnBB = pawn.toBitboard()
|
||||||
if promotionRank.contains(pawn):
|
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))
|
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion))
|
||||||
else:
|
else:
|
||||||
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn))
|
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))
|
moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush))
|
||||||
|
|
||||||
let canCapture = pawns and not orthogonalPins
|
let canCapture = pawns and not orthogonalPins
|
||||||
var
|
var
|
||||||
captureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
|
captureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
|
||||||
captureRight = canCapture.forwardRightRelativeTo(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 a piece is pinned on the right, it can only capture on the right and
|
||||||
if (diagonalPins and captureLeft) != 0:
|
# vice versa for the left
|
||||||
captureLeft = captureLeft and diagonalPins
|
if (let capture = diagonalPins and captureLeft; capture) != 0:
|
||||||
if (diagonalPins and captureRight) != 0:
|
captureRight = Bitboard(0)
|
||||||
captureRight = captureRight and diagonalPins
|
captureLeft = capture
|
||||||
for pawn in captureLeft:
|
if (let capture = diagonalPins and captureRight; capture) != 0:
|
||||||
let pawnBB = pawn.toBitboard()
|
captureLeft = Bitboard(0)
|
||||||
if promotionRank.contains(pawn):
|
captureRight = capture
|
||||||
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))
|
|
||||||
for pawn in captureRight:
|
for pawn in captureRight:
|
||||||
let pawnBB = pawn.toBitboard()
|
let pawnBB = pawn.toBitboard()
|
||||||
if promotionRank.contains(pawn):
|
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))
|
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion))
|
||||||
else:
|
else:
|
||||||
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture))
|
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) =
|
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
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)
|
rooks = self.getBitboard(Rook, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.getBitboard(Queen, sideToMove)
|
||||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||||
|
@ -137,7 +173,7 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask:
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
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)
|
bishops = self.getBitboard(Bishop, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.getBitboard(Queen, sideToMove)
|
||||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
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)
|
king = self.getBitboard(King, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||||
bitboard = getKingAttacks(king.toSquare())
|
bitboard = getKingAttacks(king.toSquare())
|
||||||
noKingOccupancy = occupancy and not king
|
noKingOccupancy = occupancy and not king
|
||||||
for square in bitboard and not occupancy:
|
for square in bitboard and not occupancy:
|
||||||
|
@ -186,20 +222,25 @@ proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask:
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
||||||
unpinnedKnights = knights and not pinned
|
unpinnedKnights = knights and not pinned
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||||
for square in unpinnedKnights:
|
for square in unpinnedKnights:
|
||||||
let bitboard = getKnightAttacks(square)
|
let bitboard = getKnightAttacks(square)
|
||||||
for target in bitboard and destinationMask and not enemyPieces:
|
for target in bitboard and destinationMask and not enemyPieces:
|
||||||
moves.add(createMove(square, target))
|
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))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateCastling(self: Chessboard, moves: var MoveList) =
|
proc generateCastling(self: Chessboard, moves: var MoveList) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
rooks = self.getBitboard(Rook, sideToMove)
|
castlingRights = self.canCastle()
|
||||||
# TODO
|
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) =
|
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
|
# King is in double check: no need to generate any more
|
||||||
# moves
|
# moves
|
||||||
return
|
return
|
||||||
if not self.inCheck():
|
|
||||||
self.generateCastling(moves)
|
self.generateCastling(moves)
|
||||||
|
|
||||||
# We pass a mask to our move generators to remove stuff
|
# We pass a mask to our move generators to remove stuff
|
||||||
# like our friendly pieces from the set of possible
|
# 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) =
|
proc removePiece(self: Chessboard, square: Square) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
var piece = self.grid[square]
|
|
||||||
when not defined(danger):
|
when not defined(danger):
|
||||||
|
let Piece = self.grid[square]
|
||||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||||
self.removePieceFromBitboard(square)
|
self.removePieceFromBitboard(square)
|
||||||
self.grid[square] = nullPiece()
|
self.grid[square] = nullPiece()
|
||||||
|
@ -291,6 +332,10 @@ proc movePiece(self: Chessboard, move: Move) =
|
||||||
self.spawnPiece(move.targetSquare, piece)
|
self.spawnPiece(move.targetSquare, piece)
|
||||||
|
|
||||||
|
|
||||||
|
proc movePiece(self: Chessboard, startSquare, targetSquare: Square) =
|
||||||
|
self.movePiece(createMove(startSquare, targetSquare))
|
||||||
|
|
||||||
|
|
||||||
proc doMove*(self: Chessboard, move: Move) =
|
proc doMove*(self: Chessboard, move: Move) =
|
||||||
## Internal function called by makeMove after
|
## Internal function called by makeMove after
|
||||||
## performing legality checks. Can be used in
|
## performing legality checks. Can be used in
|
||||||
|
@ -308,8 +353,8 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
var
|
var
|
||||||
halfMoveClock = self.position.halfMoveClock
|
halfMoveClock = self.position.halfMoveClock
|
||||||
fullMoveCount = self.position.fullMoveCount
|
fullMoveCount = self.position.fullMoveCount
|
||||||
castlingRights = self.position.castlingRights
|
|
||||||
enPassantTarget = nullSquare()
|
enPassantTarget = nullSquare()
|
||||||
|
|
||||||
# Needed to detect draw by the 50 move rule
|
# Needed to detect draw by the 50 move rule
|
||||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||||
# Number of half-moves since the last reversible half-move
|
# Number of half-moves since the last reversible half-move
|
||||||
|
@ -327,7 +372,6 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
halfMoveClock: halfMoveClock,
|
halfMoveClock: halfMoveClock,
|
||||||
fullMoveCount: fullMoveCount,
|
fullMoveCount: fullMoveCount,
|
||||||
sideToMove: self.position.sideToMove.opposite(),
|
sideToMove: self.position.sideToMove.opposite(),
|
||||||
castlingRights: castlingRights,
|
|
||||||
enPassantSquare: enPassantTarget,
|
enPassantSquare: enPassantTarget,
|
||||||
pieces: self.position.pieces,
|
pieces: self.position.pieces,
|
||||||
castlingAvailability: self.position.castlingAvailability
|
castlingAvailability: self.position.castlingAvailability
|
||||||
|
@ -338,13 +382,39 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
# Make the en passant pawn disappear
|
# Make the en passant pawn disappear
|
||||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
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():
|
if move.isCapture():
|
||||||
# Get rid of captured pieces
|
# Get rid of captured pieces
|
||||||
self.removePiece(move.targetSquare)
|
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
|
# Move the piece to its target square
|
||||||
self.movePiece(move)
|
self.movePiece(move)
|
||||||
# TODO: Castling!
|
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
# Move is a pawn promotion: get rid of the pawn
|
# Move is a pawn promotion: get rid of the pawn
|
||||||
# and spawn a new piece
|
# and spawn a new piece
|
||||||
|
@ -361,7 +431,7 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
# Updates checks and pins for the side to move
|
# Updates checks and pins for the (new) side to move
|
||||||
self.updateChecksAndPins()
|
self.updateChecksAndPins()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,12 @@ type
|
||||||
Capture = 2, # Move is a capture
|
Capture = 2, # Move is a capture
|
||||||
DoublePush = 4, # Move is a double pawn push
|
DoublePush = 4, # Move is a double pawn push
|
||||||
# Castling metadata
|
# Castling metadata
|
||||||
CastleLong = 8,
|
Castle = 8,
|
||||||
CastleShort = 16,
|
|
||||||
# Pawn promotion metadata
|
# Pawn promotion metadata
|
||||||
PromoteToQueen = 32,
|
PromoteToQueen = 16,
|
||||||
PromoteToRook = 64,
|
PromoteToRook = 32,
|
||||||
PromoteToBishop = 128,
|
PromoteToBishop = 64,
|
||||||
PromoteToKnight = 256
|
PromoteToKnight = 128
|
||||||
|
|
||||||
Move* = object
|
Move* = object
|
||||||
## A chess move
|
## 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})")
|
raise newException(IndexDefect, &"move list access out of bounds ({i} >= {self.len})")
|
||||||
result = self.data[i]
|
result = self.data[i]
|
||||||
|
|
||||||
|
|
||||||
iterator items*(self: MoveList): Move =
|
iterator items*(self: MoveList): Move =
|
||||||
var i = 0
|
var i = 0
|
||||||
while self.len > i:
|
while self.len > i:
|
||||||
|
@ -63,6 +61,15 @@ iterator pairs*(self: MoveList): tuple[i: int, move: Move] =
|
||||||
yield (i, item)
|
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.} =
|
func add*(self: var MoveList, move: Move) {.inline.} =
|
||||||
self.data[self.len] = move
|
self.data[self.len] = move
|
||||||
inc(self.len)
|
inc(self.len)
|
||||||
|
@ -122,24 +129,13 @@ func getPromotionType*(move: Move): MoveFlag {.inline.} =
|
||||||
func isCapture*(move: Move): bool {.inline.} =
|
func isCapture*(move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a
|
## Returns whether the given move is a
|
||||||
## cature
|
## cature
|
||||||
result = (move.flags and Capture.uint16) == Capture.uint16
|
result = (move.flags and Capture.uint16) != 0
|
||||||
|
|
||||||
|
|
||||||
func isCastling*(move: Move): bool {.inline.} =
|
func isCastling*(move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a
|
## Returns whether the given move is a
|
||||||
## castle
|
## castling move
|
||||||
for flag in [CastleLong, CastleShort]:
|
result = (move.flags and Castle.uint16) != 0
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
func isEnPassant*(move: Move): bool {.inline.} =
|
func isEnPassant*(move: Move): bool {.inline.} =
|
||||||
|
@ -156,9 +152,9 @@ func isDoublePush*(move: Move): bool {.inline.} =
|
||||||
|
|
||||||
func getFlags*(move: Move): seq[MoveFlag] =
|
func getFlags*(move: Move): seq[MoveFlag] =
|
||||||
## Gets all the flags of this move
|
## Gets all the flags of this move
|
||||||
for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort,
|
for flag in [EnPassant, Capture, DoublePush, Castle,
|
||||||
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
PromoteToBishop, PromoteToKnight, PromoteToQueen,
|
||||||
PromoteToRook]:
|
PromoteToRook]:
|
||||||
if (move.flags and flag.uint16) == flag.uint16:
|
if (move.flags and flag.uint16) == flag.uint16:
|
||||||
result.add(flag)
|
result.add(flag)
|
||||||
if result.len() == 0:
|
if result.len() == 0:
|
||||||
|
|
|
@ -93,11 +93,50 @@ proc toAlgebraic*(square: Square): string {.inline.} =
|
||||||
proc `$`*(square: Square): string = square.toAlgebraic()
|
proc `$`*(square: Square): string = square.toAlgebraic()
|
||||||
|
|
||||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
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 queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a1".toSquare() else: "a8".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 kingSideCastling*(piece: Piece): Square {.inline.} =
|
||||||
func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "d1".toSquare() else: "d8".toSquare())
|
case piece.kind:
|
||||||
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
|
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 =
|
proc toPretty*(piece: Piece): string =
|
||||||
|
|
|
@ -26,7 +26,7 @@ type
|
||||||
# of whether the king or the rooks on either side
|
# of whether the king or the rooks on either side
|
||||||
# moved, the actual checks for the legality of castling
|
# moved, the actual checks for the legality of castling
|
||||||
# are done elsewhere
|
# 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
|
# 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
|
||||||
|
|
|
@ -14,6 +14,11 @@
|
||||||
|
|
||||||
import bitboards
|
import bitboards
|
||||||
import magics
|
import magics
|
||||||
|
import pieces
|
||||||
|
|
||||||
|
|
||||||
|
export bitboards, pieces
|
||||||
|
|
||||||
|
|
||||||
# Stolen from https://github.com/Ciekce/voidstar/blob/main/src/rays.rs :D
|
# 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 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:
|
for move in moves:
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = board.canCastle(board.position.sideToMove)
|
let canCastle = board.canCastle()
|
||||||
echo &"Ply (from root): {board.position.plyFromRoot}"
|
echo &"Ply (from root): {board.position.plyFromRoot}"
|
||||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||||
echo &"Turn: {board.position.sideToMove}"
|
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
|
# Opponent king is in check
|
||||||
inc(result.checks)
|
inc(result.checks)
|
||||||
if verbose:
|
if verbose:
|
||||||
let canCastle = board.canCastle(board.position.sideToMove)
|
let canCastle = board.canCastle()
|
||||||
echo "\n"
|
echo "\n"
|
||||||
echo &"Opponent in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
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\")}"
|
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)
|
var move = createMove(startSquare, targetSquare, flags)
|
||||||
let piece = board.getPiece(move.startSquare)
|
let piece = board.getPiece(move.startSquare)
|
||||||
if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
||||||
if move.targetSquare == longCastleKing(piece.color):
|
if move.targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||||
move.flags = move.flags or CastleLong.uint16
|
move.flags = move.flags or Castle.uint16
|
||||||
elif move.targetSquare == shortCastleKing(piece.color):
|
elif move.targetSquare == board.position.enPassantSquare:
|
||||||
move.flags = move.flags or CastleShort.uint16
|
|
||||||
if move.targetSquare == board.position.enPassantSquare:
|
|
||||||
move.flags = move.flags or EnPassant.uint16
|
move.flags = move.flags or EnPassant.uint16
|
||||||
result = board.makeMove(move)
|
result = board.makeMove(move)
|
||||||
if result == nullMove():
|
if result == nullMove():
|
||||||
|
@ -258,8 +256,11 @@ proc handlePositionCommand(board: var Chessboard, command: seq[string]) =
|
||||||
# some error occurs
|
# some error occurs
|
||||||
var tempBoard: Chessboard
|
var tempBoard: Chessboard
|
||||||
case command[1]:
|
case command[1]:
|
||||||
of "startpos":
|
of "startpos", "kiwipete":
|
||||||
tempBoard = newDefaultChessboard()
|
if command[1] == "kiwipete":
|
||||||
|
tempBoard = newChessboardFromFen("r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -")
|
||||||
|
else:
|
||||||
|
tempBoard = newDefaultChessboard()
|
||||||
if command.len() > 2:
|
if command.len() > 2:
|
||||||
let args = command[2].splitWhitespace()
|
let args = command[2].splitWhitespace()
|
||||||
if args.len() > 0:
|
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
|
- 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
|
the current position as a FEN string if no arguments are given
|
||||||
- startpos: Set the board to the starting position
|
- startpos: Set the board to the starting position
|
||||||
|
- kiwipete: Set the board to famous kiwipete position
|
||||||
- pretty: Pretty-print the current position
|
- pretty: Pretty-print the current position
|
||||||
- print: Print the current position using ASCII characters only
|
- print: Print the current position using ASCII characters only
|
||||||
Options:
|
Options:
|
||||||
- moves {moveList}: Perform the given moves (space-separated, all-lowercase)
|
- moves {moveList}: Perform the given moves (space-separated, all-lowercase)
|
||||||
in algebraic notation after the position is loaded. This option only applies
|
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:
|
Examples:
|
||||||
- position startpos
|
- position startpos
|
||||||
- position fen "..." moves a2a3 a7a6
|
- position fen "..." moves a2a3 a7a6
|
||||||
- clear: Clear the screen
|
- clear: Clear the screen
|
||||||
- move <move>: Perform the given move in algebraic notation
|
- 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
|
- check: Print if the current side to move is in check
|
||||||
- unmove, u: Unmakes the last move. Can be used in succession
|
- unmove, u: Unmakes the last move. Can be used in succession
|
||||||
- stm: Print which side is to move
|
- stm: Print which side is to move
|
||||||
|
@ -358,7 +360,8 @@ const HELP_TEXT = """Nimfish help menu:
|
||||||
- fen: Shorthand for "position fen"
|
- fen: Shorthand for "position fen"
|
||||||
- pos <args>: Shorthand for "position <args>"
|
- pos <args>: Shorthand for "position <args>"
|
||||||
- get <square>: Get the piece on the given square
|
- 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
|
- pins: Print the current pin mask
|
||||||
- checks: Print the current checks mask
|
- checks: Print the current checks mask
|
||||||
- skip: Swap the side to move
|
- skip: Swap the side to move
|
||||||
|
@ -412,12 +415,21 @@ proc commandLoop*: int =
|
||||||
board.unmakeMove()
|
board.unmakeMove()
|
||||||
of "stm":
|
of "stm":
|
||||||
echo &"Side to move: {board.position.sideToMove}"
|
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":
|
of "atk":
|
||||||
if len(cmd) != 2:
|
if len(cmd) != 2:
|
||||||
echo "error: atk: invalid number of arguments"
|
echo "error: atk: invalid number of arguments"
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove)
|
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "error: atk: invalid square"
|
echo "error: atk: invalid square"
|
||||||
continue
|
continue
|
||||||
|
@ -437,13 +449,15 @@ proc commandLoop*: int =
|
||||||
echo "error: get: invalid square"
|
echo "error: get: invalid square"
|
||||||
continue
|
continue
|
||||||
of "castle":
|
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\")}"
|
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":
|
of "check":
|
||||||
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
of "pins":
|
of "pins":
|
||||||
echo &"Ortogonal pins:\n{board.position.orthogonalPins}"
|
if board.position.orthogonalPins != 0:
|
||||||
echo &"Diagonal pins:\n{board.position.diagonalPins}"
|
echo &"Orthogonal pins:\n{board.position.orthogonalPins}"
|
||||||
|
if board.position.diagonalPins != 0:
|
||||||
|
echo &"Diagonal pins:\n{board.position.diagonalPins}"
|
||||||
of "checks":
|
of "checks":
|
||||||
echo board.position.checkers
|
echo board.position.checkers
|
||||||
of "quit":
|
of "quit":
|
||||||
|
|
|
@ -19,7 +19,7 @@ def main(args: Namespace) -> int:
|
||||||
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
print(f"Could not locate stockfish executable -> {type(e).__name__}: {e}")
|
||||||
return 2
|
return 2
|
||||||
try:
|
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:
|
except Exception as e:
|
||||||
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
print(f"Could not locate nimfish executable -> {type(e).__name__}: {e}")
|
||||||
return 2
|
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("--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("--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)
|
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 b KQk - 0 1
|
||||||
1r2k2r/8/8/8/8/8/8/R3K2R w 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
|
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 b k - 0 1
|
||||||
4k2r/8/8/8/8/8/8/4K3 w 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/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/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 b K - 0 1
|
||||||
4k3/8/8/8/8/8/8/4K2R w 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 b KQ - 0 1
|
||||||
4k3/8/8/8/8/8/8/R3K2R w 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 b Q - 0 1
|
||||||
4k3/8/8/8/8/8/8/R3K3 w 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
|
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
|
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
|
6kq/8/8/8/8/8/8/7K w - - 0 1
|
||||||
6qk/8/8/8/8/8/8/7K b - - 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 b - - 0 1
|
||||||
7K/7p/7k/8/8/8/8/8 w - - 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 b - - 0 1
|
||||||
7k/8/1p6/8/8/P7/8/7K w - - 0 1
|
7k/8/1p6/8/8/P7/8/7K w - - 0 1
|
||||||
7k/8/8/1p6/P7/8/8/7K b - - 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/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 b - - 0 1
|
||||||
8/8/1B6/7b/7k/8/2B1b3/7K w - - 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/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/2k5/5q2/5n2/8/5K2/8 b - - 0 1
|
||||||
8/8/3K4/3Nn3/3nN3/4k3/8/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
|
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 b - - 0 1
|
||||||
8/8/7k/7p/7P/7K/8/8 w - - 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/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/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 b - - 0 1
|
||||||
8/8/8/8/8/7K/7P/7k w - - 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/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 b - - 0 1
|
||||||
8/8/k7/p7/P7/K7/8/8 w - - 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/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 b - - 0 1
|
||||||
8/PPPk4/8/8/8/8/4Kppp/8 w - - 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/4k3/8/b6B w - - 0 1
|
||||||
B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1
|
B6b/8/8/8/2K5/5k2/8/b6B b - - 0 1
|
||||||
K1k5/8/P7/8/8/8/8/8 w - - 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 b - - 0 1
|
||||||
k7/6p1/8/8/8/8/7P/K7 w - - 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 b - - 0 1
|
||||||
k7/7p/8/8/8/8/6P1/K7 w - - 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 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/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 b - - 0 1
|
||||||
k7/8/3p4/8/3P4/8/8/7K w - - 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 b - - 0 1
|
||||||
k7/8/3p4/8/8/4P3/8/7K w - - 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 b - - 0 1
|
||||||
k7/8/6p1/8/8/7P/8/K7 w - - 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 b - - 0 1
|
||||||
k7/8/7p/8/8/6P1/8/K7 w - - 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 b - - 0 1
|
||||||
k7/8/8/3p4/4p3/8/8/7K w - - 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 b - - 0 1
|
||||||
k7/8/8/6p1/7P/8/8/K7 w - - 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 b - - 0 1
|
||||||
k7/8/8/7p/6P1/8/8/K7 w - - 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 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/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 b - - 0 1
|
||||||
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1
|
n1n5/1Pk5/8/8/8/8/5Kp1/5N1N w - - 0 1
|
||||||
n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 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
|
r3k3/8/8/8/8/8/8/4K3 w q - 0 1
|
||||||
r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10
|
r4rk1/1pp1qppp/p1np1n2/2b1p1B1/2B1P1b1/P1NP1N2/1PP1QPPP/R4RK1 w - - 0 10
|
||||||
r6r/1b2k1bq/8/8/7B/8/8/R3K2R b KQ - 3 2
|
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
|
rnb2k1r/pp1Pbppp/2p5/q7/2B5/8/PPPQNnPP/RNB1K2R w KQ - 3 9
|
||||||
rnbq1k1r/pp1Pbppp/2p5/8/2B5/8/PPP1NnPP/RNBQK2R w KQ - 1 8
|
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
|
rnbqkb1r/ppppp1pp/7n/4Pp2/8/8/PPPP1PPP/RNBQKBNR w KQkq f6 0 3
|
||||||
|
|
|
@ -3,31 +3,62 @@ import timeit
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from argparse import Namespace, ArgumentParser
|
from argparse import Namespace, ArgumentParser
|
||||||
from compare_positions import main as test
|
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:
|
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 = []
|
successful = []
|
||||||
failed = []
|
failed = []
|
||||||
positions = args.positions.read_text().splitlines()
|
positions = args.positions.read_text().splitlines()
|
||||||
start = timeit.default_timer()
|
|
||||||
longest_fen = max(sorted([len(fen) for fen in positions]))
|
longest_fen = max(sorted([len(fen) for fen in positions]))
|
||||||
for i, fen in enumerate(positions):
|
start = timeit.default_timer()
|
||||||
fen = fen.strip(" ")
|
if not args.parallel:
|
||||||
fen += " " * (longest_fen - len(fen))
|
for i, fen in enumerate(positions):
|
||||||
sys.stdout.write(f"\r[S] Testing {fen} ({i + 1}/{len(positions)})\033[K")
|
fen = fen.strip(" ")
|
||||||
args.fen = fen
|
fen += " " * (longest_fen - len(fen))
|
||||||
args.silent = not args.no_silent
|
sys.stdout.write(f"\r[S] Testing {fen} ({i + 1}/{len(positions)})\033[K")
|
||||||
if test(args) == 0:
|
args.fen = fen
|
||||||
successful.append(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:
|
else:
|
||||||
failed.append(fen)
|
stop = timeit.default_timer()
|
||||||
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")
|
||||||
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:
|
||||||
if failed and args.show_failures:
|
print("[S] The following FENs failed to pass the test:", end="")
|
||||||
print("[S] The following FENs failed to pass the test:", end="")
|
print("\n\t".join(failed))
|
||||||
print("\n\t".join(failed))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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("--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'",
|
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"))
|
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)
|
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