More work on attack handling and some bug fixes

This commit is contained in:
Mattia Giambirtone 2024-04-19 23:28:46 +02:00
parent 6fbcd4ff74
commit 2b16b5ec61
5 changed files with 165 additions and 267 deletions

View File

@ -14,8 +14,6 @@
import std/strutils
import std/strformat
import std/times
import std/math
import std/bitops
@ -29,8 +27,6 @@ export bitboards, magics, pieces, moves
type
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
Position* = object
## A chess position
@ -56,8 +52,8 @@ type
pieces: tuple[white, black: tuple[king, queens, rooks, bishops, knights, pawns: Bitboard]]
# Pinned pieces for each side
pins: tuple[white, black: Bitboard]
# Checking pieces
checkers: tuple[white, black: Bitboard]
# Pieces checking the current side to move
checkers: Bitboard
ChessBoard* = ref object
@ -83,10 +79,6 @@ proc movePiece(self: ChessBoard, move: Move)
proc removePiece(self: ChessBoard, square: Square)
proc extend[T](self: var seq[T], other: openarray[T]) {.inline.} =
for x in other:
self.add(x)
proc update*(self: ChessBoard)
@ -121,27 +113,6 @@ func getHalfMoveCount*(self: ChessBoard): int {.inline.} =
return self.position.halfMoveClock
func getStartRank(piece: Piece): int {.inline.} =
## Retrieves the starting row of
## the given piece inside our 8x8
## grid
case piece.color:
of None:
return -1
of White:
case piece.kind:
of Pawn:
return 6
else:
return 7
of Black:
case piece.kind:
of Pawn:
return 1
else:
return 0
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
## Retrieves the starting square of the king
## for the given color
@ -163,7 +134,7 @@ func longCastleRook*(color: PieceColor): Square {.inline.} = (if color == White:
func shortCastleRook*(color: PieceColor): Square {.inline.} = (if color == White: "f1".toSquare() else: "f8".toSquare())
proc inCheck*(self: ChessBoard, side: PieceColor): bool
proc inCheck*(self: ChessBoard): bool
proc newChessboard: ChessBoard =
@ -178,13 +149,7 @@ func `[]`(self: array[64, Piece], square: Square): Piece {.inline.} = self[squar
func `[]=`(self: var array[64, Piece], square: Square, piece: Piece) {.inline.} = self[square.int8] = piece
func getDirectionMask(self: ChessBoard, square: Square, direction: Direction): Bitboard =
## Like getDirectionMask(), but used within the board context
## with a piece square and direction only
return getDirectionMask(square, self.grid[square].color, direction)
func getBitboard(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard =
func getBitboard*(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard =
## Returns the positional bitboard for the given piece kind and color
case color:
of White:
@ -223,7 +188,7 @@ func getBitboard(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard
discard
func getBitboard(self: ChessBoard, piece: Piece): Bitboard =
func getBitboard*(self: ChessBoard, piece: Piece): Bitboard =
## Returns the positional bitboard for the given piece type
return self.getBitboard(piece.kind, piece.color)
@ -242,7 +207,6 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
index = 0
# Temporary variable to store a piece
piece: Piece
pieces: int
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
while index <= fen.high():
var c = fen[index]
@ -376,9 +340,6 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
else:
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
inc(index)
if result.inCheck(result.getSideToMove().opposite):
# Opponent king cannot be captured on the next move
raise newException(ValueError, "invalid position: opponent king can be captured")
if result.position.pieces.white.king == 0 or result.position.pieces.black.king == 0:
# Both kings must be on the board
raise newException(ValueError, "invalid position: exactly one king of each color must be present")
@ -511,18 +472,6 @@ func getFlags*(move: Move): seq[MoveFlag] =
result.add(Default)
func getKingSquare*(self: ChessBoard, color: PieceColor): Square {.inline.} =
## Returns the square of the king for the given
## side
case color:
of White:
return self.position.pieces.white.king.toSquare()
of Black:
return self.position.pieces.black.king.toSquare()
else:
discard
proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard =
## Get the occupancy bitboard for every piece of the given color
case color:
@ -559,57 +508,48 @@ proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit
## the king of the given side
result = Bitboard(0)
let
sq = square.toBitboard()
king = self.getBitboard(King, attacker)
if (KING_BITBOARDS[square.uint] and king) != 0:
result = result or sq
result = result or king
proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Returns the attack bitboard for the given square from
## the knights of the given side
let
sq = square.toBitboard()
knights = self.getBitboard(Knight, attacker)
result = Bitboard(0)
for knight in knights:
if (KNIGHT_BITBOARDS[square.uint] and knight.toBitboard()) != 0:
result = result or knight.toBitboard()
let knightBB = knight.toBitboard()
if (KNIGHT_BITBOARDS[square.uint] and knightBB) != 0:
result = result or knightBB
proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Returns the attack bitboard for the given square from
## the sliding pieces of the given side
let
sq = square.toBitboard()
queens = self.getBitboard(Queen, attacker)
rooks = self.getBitboard(Rook, attacker)
bishops = self.getBitboard(Bishop, attacker)
occupancy = self.getOccupancy()
rooks = self.getBitboard(Rook, attacker) or queens
bishops = self.getBitboard(Bishop, attacker) or queens
result = Bitboard(0)
for rook in rooks:
let blockers = Rook.getRelevantBlockers(square)
if (getRookMoves(square, blockers) and rook.toBitboard()) != 0:
result = result or rook.toBitboard()
let rookBB = rook.toBitboard()
if (getRookMoves(square, blockers) and rookBB) != 0:
result = result or rookBB
for bishop in bishops:
let blockers = Bishop.getRelevantBlockers(square)
if (getBishopMoves(square, blockers) and bishop.toBitboard()) != 0:
result = result or bishop.toBitboard()
for queen in queens:
let rookBlockers = Rook.getRelevantBlockers(square)
if (getRookMoves(square, rookBlockers) and queen.toBitboard()) != 0:
result = result or queen.toBitboard()
let bishopBlockers = Bishop.getRelevantBlockers(square)
if (getBishopMoves(square, bishopBlockers) and queen.toBitboard()) != 0:
result = result or queen.toBitboard()
let
blockers = Bishop.getRelevantBlockers(square)
bishopBB = bishop.toBitboard()
if (getBishopMoves(square, blockers) and bishopBB) != 0:
result = result or bishopBB
proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Computes the attack bitboard for the given square from
## the given side
result = Bitboard(0)
let
squareBitboard = square.toBitboard()
result = result or self.getPawnAttacks(square, attacker)
result = result or self.getKingAttacks(square, attacker)
result = result or self.getKnightAttacks(square, attacker)
@ -617,26 +557,15 @@ proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitb
proc updateCheckers(self: ChessBoard) =
let side = self.getSideToMove()
let checkers = self.getAttacksTo(self.getKingSquare(side), side.opposite())
case side:
of White:
self.position.checkers.white = checkers
of Black:
self.position.checkers.black = checkers
else:
discard
let
side = self.getSideToMove()
king = self.getBitboard(King, side).toSquare()
self.position.checkers = self.getAttacksTo(king, side.opposite())
proc inCheck(self: ChessBoard, side: PieceColor): bool =
proc inCheck(self: ChessBoard): bool =
## Returns if the current side to move is in check
case self.getSideToMove():
of White:
return self.position.checkers.white != 0
of Black:
return self.position.checkers.black != 0
else:
discard
return self.position.checkers != 0
proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
@ -644,14 +573,6 @@ proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
return (false, false) # TODO
proc getCapturablePieces(self: ChessBoard, side: PieceColor): Bitboard {.inline.} =
## Returns the set of pieces of the given color that can
## be captured
# Just a handy helper to filter out the king and avoid code duplication
return self.getOccupancyFor(side) and not self.getBitboard(King, side)
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
## Helper of generatePawnMoves for generating all non-capture
## and non-promotion pawn moves
@ -679,7 +600,7 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
nonSideToMove = sideToMove.opposite()
pawns = self.getBitboard(Pawn, sideToMove)
# We can only capture enemy pieces (except the king)
enemyPieces = self.getCapturablePieces(nonSideToMove)
enemyPieces = self.getOccupancyFor(nonSideToMove)
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
@ -726,12 +647,12 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
let
sideToMove = self.getSideToMove()
occupancy = self.getOccupancy()
enemyPieces = self.getCapturablePieces(sideToMove.opposite())
rooks = self.getBitboard(Rook, sideToMove)
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
rooks = self.getBitboard(Rook, sideToMove) or self.getBitboard(Queen, sideToMove)
for square in rooks:
let blockers = occupancy and Rook.getRelevantBlockers(square)
# Quiet moves
var moveset = getRookMoves(square, blockers)
let
blockers = occupancy and Rook.getRelevantBlockers(square)
moveset = getRookMoves(square, blockers)
for target in moveset and not occupancy:
moves.add(createMove(square, target))
# Captures
@ -741,51 +662,27 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
proc generateBishopMoves(self: ChessBoard, moves: var MoveList) =
## Helper of generateSlidingMoves to generate bishop moves
# self.generateBishopMovements(moves)
# self.generateBishopCaptures(moves)
let
sideToMove = self.getSideToMove()
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
occupancy = self.getOccupancy()
bishops = self.getBitboard(Bishop, sideToMove)
bishops = self.getBitboard(Bishop, sideToMove) or self.getBitboard(Queen, sideToMove)
for square in bishops:
let blockers = occupancy and Bishop.getRelevantBlockers(square)
let moveset = getBishopMoves(square, blockers)
# Can't move over other pieces
let
blockers = occupancy and Bishop.getRelevantBlockers(square)
moveset = getBishopMoves(square, blockers)
for target in moveset and not occupancy:
moves.add(createMove(square, target))
for target in moveset and enemyPieces:
moves.add(createMove(square, target, Capture))
proc generateQueenMoves(self: ChessBoard, moves: var MoveList) =
## Helper of generateSlidingMoves to generate queen moves
let
sideToMove = self.getSideToMove()
occupancy = self.getOccupancy()
enemyPieces = self.getCapturablePieces(sideToMove.opposite())
queens = self.getBitboard(Queen, sideToMove)
for square in queens:
# A queen is just a rook plus a bishop in terms of move
# generation
let
rookBlockers = Rook.getRelevantBlockers(square) and occupancy
bishopBlockers = Bishop.getRelevantBlockers(square) and occupancy
rookMoves = getRookMoves(square, rookBlockers)
bishopMoves = getBishopMoves(square, bishopBlockers)
let moveset = rookMoves or bishopMoves
# Can't move over other pieces
for target in moveset and not occupancy:
moves.add(createMove(square, target))
for target in moveset and enemyPieces:
moves.add(createMove(square, target))
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
## Generates all legal sliding moves for the side to move
self.generateRookMoves(moves)
self.generateBishopMoves(moves)
self.generateQueenMoves(moves)
# Queens are just handled rooks + bishops
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
@ -794,11 +691,11 @@ proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
sideToMove = self.getSideToMove()
king = self.getBitboard(King, sideToMove)
moveIdx = king.toSquare().uint64
allowedSquares = not self.getOccupancy()
occupancy = self.getOccupancy()
nonSideToMove = sideToMove.opposite()
enemyPieces = self.getCapturablePieces(nonSideToMove)
enemyPieces = self.getOccupancyFor(nonSideToMove)
# Regular moves
for square in KING_BITBOARDS[moveIdx] and allowedSquares:
for square in KING_BITBOARDS[moveIdx] and not occupancy:
moves.add(createMove(king, square))
# Captures
for square in KING_BITBOARDS[moveIdx] and enemyPieces:
@ -810,12 +707,12 @@ proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
let
sideToMove = self.getSideToMove()
knights = self.getBitboard(Knight, sideToMove)
allowedSquares = not self.getOccupancy()
occupancy = self.getOccupancy()
nonSideToMove = sideToMove.opposite()
enemyPieces = self.getCapturablePieces(nonSideToMove)
enemyPieces = self.getOccupancyFor(nonSideToMove)
for square in knights:
# Regular moves
for target in KNIGHT_BITBOARDS[square.uint64] and allowedSquares:
for target in KNIGHT_BITBOARDS[square.uint64] and not occupancy:
moves.add(createMove(square, target))
# Captures
for target in KNIGHT_BITBOARDS[square.uint64] and enemyPieces:
@ -834,6 +731,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
self.generatePawnMoves(moves)
self.generateKnightMoves(moves)
self.generateSlidingMoves(moves)
# self.updateCheckers()
proc removePieceFromBitboard(self: ChessBoard, square: Square) =
@ -942,11 +840,6 @@ proc movePiece(self: ChessBoard, move: Move) =
self.spawnPiece(move.targetSquare, piece)
proc movePiece(self: ChessBoard, startSquare, targetSquare: Square) =
## Like the other movePiece(), but with two squares
self.movePiece(Move(startSquare: startSquare, targetSquare: targetSquare))
proc doMove*(self: ChessBoard, move: Move) =
## Internal function called by makeMove after
## performing legality checks. Can be used in
@ -1220,113 +1113,11 @@ proc toFEN*(self: ChessBoard): string =
when isMainModule:
import nimfishpkg/tui
import nimfishpkg/misc
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
proc testPieceCount(board: ChessBoard, kind: PieceKind, color: PieceColor, count: int) =
let pieces = board.countPieces(kind, color)
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
proc testPieceBitboard(bitboard: Bitboard, squares: seq[Square]) =
var i = 0
for square in bitboard:
doAssert squares[i] == square, &"squares[{i}] != bitboard[i]: {squares[i]} != {square}"
inc(i)
if i != squares.len():
doAssert false, &"bitboard.len() ({i}) != squares.len() ({squares.len()})"
var b = newDefaultChessboard()
# Ensure correct number of pieces
testPieceCount(b, Pawn, White, 8)
testPieceCount(b, Pawn, Black, 8)
testPieceCount(b, Knight, White, 2)
testPieceCount(b, Knight, Black, 2)
testPieceCount(b, Bishop, White, 2)
testPieceCount(b, Bishop, Black, 2)
testPieceCount(b, Rook, White, 2)
testPieceCount(b, Rook, Black, 2)
testPieceCount(b, Queen, White, 1)
testPieceCount(b, Queen, Black, 1)
testPieceCount(b, King, White, 1)
testPieceCount(b, King, Black, 1)
# Ensure pieces are in the correct squares. This is testing the FEN
# parser
# Pawns
for loc in ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"]:
testPiece(b.getPiece(loc), Pawn, White)
for loc in ["a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7"]:
testPiece(b.getPiece(loc), Pawn, Black)
# Rooks
testPiece(b.getPiece("a1"), Rook, White)
testPiece(b.getPiece("h1"), Rook, White)
testPiece(b.getPiece("a8"), Rook, Black)
testPiece(b.getPiece("h8"), Rook, Black)
# Knights
testPiece(b.getPiece("b1"), Knight, White)
testPiece(b.getPiece("g1"), Knight, White)
testPiece(b.getPiece("b8"), Knight, Black)
testPiece(b.getPiece("g8"), Knight, Black)
# Bishops
testPiece(b.getPiece("c1"), Bishop, White)
testPiece(b.getPiece("f1"), Bishop, White)
testPiece(b.getPiece("c8"), Bishop, Black)
testPiece(b.getPiece("f8"), Bishop, Black)
# Kings
testPiece(b.getPiece("e1"), King, White)
testPiece(b.getPiece("e8"), King, Black)
# Queens
testPiece(b.getPiece("d1"), Queen, White)
testPiece(b.getPiece("d8"), Queen, Black)
# Ensure our bitboards match with the board
let
whitePawns = b.getBitboard(Pawn, White)
whiteKnights = b.getBitboard(Knight, White)
whiteBishops = b.getBitboard(Bishop, White)
whiteRooks = b.getBitboard(Rook, White)
whiteQueens = b.getBitboard(Queen, White)
whiteKing = b.getBitboard(King, White)
blackPawns = b.getBitboard(Pawn, Black)
blackKnights = b.getBitboard(Knight, Black)
blackBishops = b.getBitboard(Bishop, Black)
blackRooks = b.getBitboard(Rook, Black)
blackQueens = b.getBitboard(Queen, Black)
blackKing = b.getBitboard(King, Black)
whitePawnSquares = @[makeSquare(6'i8, 0'i8), makeSquare(6, 1), makeSquare(6, 2), makeSquare(6, 3), makeSquare(6, 4), makeSquare(6, 5), makeSquare(6, 6), makeSquare(6, 7)]
whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)]
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
whiteRookSquares = @[makeSquare(7'i8, 0'i8), makeSquare(7, 7)]
whiteQueenSquares = @[makeSquare(7'i8, 3'i8)]
whiteKingSquares = @[makeSquare(7'i8, 4'i8)]
blackPawnSquares = @[makeSquare(1'i8, 0'i8), makeSquare(1, 1), makeSquare(1, 2), makeSquare(1, 3), makeSquare(1, 4), makeSquare(1, 5), makeSquare(1, 6), makeSquare(1, 7)]
blackKnightSquares = @[makeSquare(0'i8, 1'i8), makeSquare(0, 6)]
blackBishopSquares = @[makeSquare(0'i8, 2'i8), makeSquare(0, 5)]
blackRookSquares = @[makeSquare(0'i8, 0'i8), makeSquare(0, 7)]
blackQueenSquares = @[makeSquare(0'i8, 3'i8)]
blackKingSquares = @[makeSquare(0'i8, 4'i8)]
testPieceBitboard(whitePawns, whitePawnSquares)
testPieceBitboard(whiteKnights, whiteKnightSquares)
testPieceBitboard(whiteBishops, whiteBishopSquares)
testPieceBitboard(whiteRooks, whiteRookSquares)
testPieceBitboard(whiteQueens, whiteQueenSquares)
testPieceBitboard(whiteKing, whiteKingSquares)
testPieceBitboard(blackPawns, blackPawnSquares)
testPieceBitboard(blackKnights, blackKnightSquares)
testPieceBitboard(blackBishops, blackBishopSquares)
testPieceBitboard(blackRooks, blackRookSquares)
testPieceBitboard(blackQueens, blackQueenSquares)
testPieceBitboard(blackKing, blackKingSquares)
basicTests()
setControlCHook(proc () {.noconv.} = quit(0))
import tui
quit(tui.commandLoop())
quit(commandLoop())

View File

@ -132,7 +132,7 @@ var
BISHOP_MOVES: array[64, seq[Bitboard]]
proc getRookMoves*(square: Square, blockers: Bitboard): Bitboard =
proc getRookMoves*(square: Square, blockers: Bitboard): Bitboard =
## Returns the move bitboard for the rook at the given
## square with the given blockers bitboard
let
@ -141,7 +141,7 @@ proc getRookMoves*(square: Square, blockers: Bitboard): Bitboard =
return moves[getIndex(magic, blockers)]
proc getBishopMoves*(square: Square, blockers: Bitboard): Bitboard =
proc getBishopMoves*(square: Square, blockers: Bitboard): Bitboard =
## Returns the move bitboard for the bishop at the given
## square with the given blockers bitboard
let
@ -159,7 +159,7 @@ const
BISHOP_BLOCKERS = generateBishopBlockers()
func getRelevantBlockers*(kind: PieceKind, square: Square): Bitboard =
func getRelevantBlockers*(kind: PieceKind, square: Square): Bitboard {.inline.} =
## Returns the relevant blockers mask for the given piece
## type at the given square
case kind:

View File

@ -0,0 +1,107 @@
import ../nimfish
import std/strformat
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
proc testPieceCount(board: ChessBoard, kind: PieceKind, color: PieceColor, count: int) =
let pieces = board.countPieces(kind, color)
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
proc testPieceBitboard(bitboard: Bitboard, squares: seq[Square]) =
var i = 0
for square in bitboard:
doAssert squares[i] == square, &"squares[{i}] != bitboard[i]: {squares[i]} != {square}"
inc(i)
if i != squares.len():
doAssert false, &"bitboard.len() ({i}) != squares.len() ({squares.len()})"
proc basicTests* =
var b = newDefaultChessboard()
# Ensure correct number of pieces
testPieceCount(b, Pawn, White, 8)
testPieceCount(b, Pawn, Black, 8)
testPieceCount(b, Knight, White, 2)
testPieceCount(b, Knight, Black, 2)
testPieceCount(b, Bishop, White, 2)
testPieceCount(b, Bishop, Black, 2)
testPieceCount(b, Rook, White, 2)
testPieceCount(b, Rook, Black, 2)
testPieceCount(b, Queen, White, 1)
testPieceCount(b, Queen, Black, 1)
testPieceCount(b, King, White, 1)
testPieceCount(b, King, Black, 1)
# Ensure pieces are in the correct squares. This is testing the FEN
# parser
# Pawns
for loc in ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"]:
testPiece(b.getPiece(loc), Pawn, White)
for loc in ["a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7"]:
testPiece(b.getPiece(loc), Pawn, Black)
# Rooks
testPiece(b.getPiece("a1"), Rook, White)
testPiece(b.getPiece("h1"), Rook, White)
testPiece(b.getPiece("a8"), Rook, Black)
testPiece(b.getPiece("h8"), Rook, Black)
# Knights
testPiece(b.getPiece("b1"), Knight, White)
testPiece(b.getPiece("g1"), Knight, White)
testPiece(b.getPiece("b8"), Knight, Black)
testPiece(b.getPiece("g8"), Knight, Black)
# Bishops
testPiece(b.getPiece("c1"), Bishop, White)
testPiece(b.getPiece("f1"), Bishop, White)
testPiece(b.getPiece("c8"), Bishop, Black)
testPiece(b.getPiece("f8"), Bishop, Black)
# Kings
testPiece(b.getPiece("e1"), King, White)
testPiece(b.getPiece("e8"), King, Black)
# Queens
testPiece(b.getPiece("d1"), Queen, White)
testPiece(b.getPiece("d8"), Queen, Black)
# Ensure our bitboards match with the board
let
whitePawns = b.getBitboard(Pawn, White)
whiteKnights = b.getBitboard(Knight, White)
whiteBishops = b.getBitboard(Bishop, White)
whiteRooks = b.getBitboard(Rook, White)
whiteQueens = b.getBitboard(Queen, White)
whiteKing = b.getBitboard(King, White)
blackPawns = b.getBitboard(Pawn, Black)
blackKnights = b.getBitboard(Knight, Black)
blackBishops = b.getBitboard(Bishop, Black)
blackRooks = b.getBitboard(Rook, Black)
blackQueens = b.getBitboard(Queen, Black)
blackKing = b.getBitboard(King, Black)
whitePawnSquares = @[makeSquare(6'i8, 0'i8), makeSquare(6, 1), makeSquare(6, 2), makeSquare(6, 3), makeSquare(6, 4), makeSquare(6, 5), makeSquare(6, 6), makeSquare(6, 7)]
whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)]
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
whiteRookSquares = @[makeSquare(7'i8, 0'i8), makeSquare(7, 7)]
whiteQueenSquares = @[makeSquare(7'i8, 3'i8)]
whiteKingSquares = @[makeSquare(7'i8, 4'i8)]
blackPawnSquares = @[makeSquare(1'i8, 0'i8), makeSquare(1, 1), makeSquare(1, 2), makeSquare(1, 3), makeSquare(1, 4), makeSquare(1, 5), makeSquare(1, 6), makeSquare(1, 7)]
blackKnightSquares = @[makeSquare(0'i8, 1'i8), makeSquare(0, 6)]
blackBishopSquares = @[makeSquare(0'i8, 2'i8), makeSquare(0, 5)]
blackRookSquares = @[makeSquare(0'i8, 0'i8), makeSquare(0, 7)]
blackQueenSquares = @[makeSquare(0'i8, 3'i8)]
blackKingSquares = @[makeSquare(0'i8, 4'i8)]
testPieceBitboard(whitePawns, whitePawnSquares)
testPieceBitboard(whiteKnights, whiteKnightSquares)
testPieceBitboard(whiteBishops, whiteBishopSquares)
testPieceBitboard(whiteRooks, whiteRookSquares)
testPieceBitboard(whiteQueens, whiteQueenSquares)
testPieceBitboard(whiteKing, whiteKingSquares)
testPieceBitboard(blackPawns, blackPawnSquares)
testPieceBitboard(blackKnights, blackKnightSquares)
testPieceBitboard(blackBishops, blackBishopSquares)
testPieceBitboard(blackRooks, blackRookSquares)
testPieceBitboard(blackQueens, blackQueenSquares)
testPieceBitboard(blackKing, blackKingSquares)

View File

@ -9,8 +9,8 @@ type
PieceColor* = enum
## A piece color enumeration
None = 0'i8,
White,
None = 0'i8
White
Black
PieceKind* = enum

View File

@ -1,4 +1,4 @@
import nimfish
import ../nimfish
import std/strformat
@ -20,7 +20,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
var moves = MoveList()
self.generateMoves(moves)
if not bulk:
if len(moves) == 0 and self.inCheck(self.getSideToMove()):
if len(moves) == 0 and self.inCheck():
result.checkmates = 1
# TODO: Should we count stalemates/draws?
if ply == 0:
@ -54,7 +54,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
echo &"Turn: {self.getSideToMove()}"
echo &"Piece: {self.getPiece(move.startSquare).kind}"
echo &"Flags: {move.getFlags()}"
echo &"In check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
echo &"Position before move: {self.toFEN()}"
stdout.write("En Passant target: ")
@ -73,13 +73,13 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
inc(result.promotions)
if move.isEnPassant():
inc(result.enPassant)
if self.inCheck(self.getSideToMove()):
if self.inCheck():
# Opponent king is in check
inc(result.checks)
if verbose:
let canCastle = self.canCastle(self.getSideToMove())
echo "\n"
echo &"Opponent in check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
echo &"Opponent in check: {(if self.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 &"Position after move: {self.toFEN()}"
echo "\n", self.pretty()
@ -420,7 +420,7 @@ proc commandLoop*: int =
let canCastle = board.canCastle(board.getSideToMove())
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
of "check":
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \"yes\" else: \"no\")}"
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
else:
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
except IOError: