More work on attack handling and some bug fixes
This commit is contained in:
parent
6fbcd4ff74
commit
2b16b5ec61
|
@ -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())
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
|
@ -9,8 +9,8 @@ type
|
|||
|
||||
PieceColor* = enum
|
||||
## A piece color enumeration
|
||||
None = 0'i8,
|
||||
White,
|
||||
None = 0'i8
|
||||
White
|
||||
Black
|
||||
|
||||
PieceKind* = enum
|
||||
|
|
|
@ -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:
|
Loading…
Reference in New Issue