diff --git a/Chess/nimfish/nimfish.nim b/Chess/nimfish/nimfish.nim index a597d57..6d96bfa 100644 --- a/Chess/nimfish/nimfish.nim +++ b/Chess/nimfish/nimfish.nim @@ -20,111 +20,41 @@ import nimfishpkg/bitboards import nimfishpkg/magics import nimfishpkg/pieces import nimfishpkg/moves +import nimfishpkg/position -export bitboards, magics, pieces, moves + +export bitboards, magics, pieces, moves, position type - Position* = object - ## A chess position - - # Castling metadata. Updated on every move - castlingRights: array[64, uint8] - # Number of half-moves that were performed - # to reach this position starting from the - # root of the tree - plyFromRoot: int8 - # Number of half moves since - # last piece capture or pawn movement. - # Used for the 50-move rule - halfMoveClock: int8 - # Full move counter. Increments - # every 2 ply (half-moves) - fullMoveCount: int8 - # En passant target square (see https://en.wikipedia.org/wiki/En_passant) - enPassantSquare*: Square - - # The side to move - sideToMove: PieceColor - # Positional bitboards for all pieces - pieces: array[2, array[6, Bitboard]] - # Pinned pieces for the current side to move - pins: Bitboard - # Pieces checking the current side to move - checkers: Bitboard - - - ChessBoard* = ref object - ## A chess board object + Chessboard* = ref object + ## A chessboard # The actual board where pieces live grid: array[64, Piece] # The current position - position: Position + position*: Position # List of all previously reached positions positions: seq[Position] # A bunch of simple utility functions and forward declarations -proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} -proc isLegal(self: ChessBoard, move: Move): bool {.inline.} -proc doMove*(self: ChessBoard, move: Move) -proc pretty*(self: ChessBoard): string -proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) -proc toFEN*(self: ChessBoard): string -proc unmakeMove*(self: ChessBoard) -proc movePiece(self: ChessBoard, move: Move) -proc removePiece(self: ChessBoard, square: Square) +proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} +proc isLegal(self: Chessboard, move: Move): bool {.inline.} +proc doMove*(self: Chessboard, move: Move) +proc pretty*(self: Chessboard): string +proc spawnPiece(self: Chessboard, square: Square, piece: Piece) +proc toFEN*(self: Chessboard): string +proc unmakeMove*(self: Chessboard) +proc movePiece(self: Chessboard, move: Move) +proc removePiece(self: Chessboard, square: Square) +proc update*(self: Chessboard) +func inCheck*(self: Chessboard): bool {.inline.} +proc fromChar*(c: char): Piece -proc update*(self: ChessBoard) - -func setSideToMove*(self: ChessBoard, side: PieceColor) {.inline.} = - self.position.sideToMove = side - -# A bunch of getters -func getSideToMove*(self: ChessBoard): PieceColor {.inline.} = - ## Returns the currently side to move - return self.position.sideToMove - - -func getEnPassantTarget*(self: ChessBoard): Square {.inline.} = - ## Returns the current en passant target square - return self.position.enPassantSquare - - -func getPlyFromRoot*(self: ChessBoard): int8 {.inline.} = - ## Returns the current distance from the root in plys - return self.position.plyFromRoot - - -func getMoveCount*(self: ChessBoard): int {.inline.} = - ## Returns the number of full moves that - ## have been played - return self.position.fullMoveCount - - -func getHalfMoveCount*(self: ChessBoard): int {.inline.} = - ## Returns the current number of half-moves - ## since the last irreversible move - return self.position.halfMoveClock - - -func getKingStartingSquare*(color: PieceColor): Square {.inline.} = - ## Retrieves the starting square of the king - ## for the given color - case color: - of White: - return "e1".toSquare() - of Black: - return "e8".toSquare() - else: - discard - - -# FIXME: Check this shit. func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare()) func queenSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "a8".toSquare() else: "a1".toSquare()) func longCastleKing*(color: PieceColor): Square {.inline.} = (if color == White: "c1".toSquare() else: "c8".toSquare()) @@ -133,18 +63,12 @@ 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): bool -proc fromChar*(c: char): Piece - - -proc newChessboard: ChessBoard = +proc newChessboard: Chessboard = ## Returns a new, empty chessboard new(result) for i in 0..63: result.grid[i] = nullPiece() - result.position = Position(enPassantSquare: nullSquare(), sideToMove: White, pieces: [ - [Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)], - [Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0), Bitboard(0)]]) + result.position = Position(enPassantSquare: nullSquare(), sideToMove: White) # Indexing operations @@ -152,23 +76,17 @@ 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 `[]`(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int] -func `[]`(self: array[6, Bitboard], kind: PieceKind): Bitboard {.inline.} = self[kind.int] -func `[]=`(self: var array[6, Bitboard], kind: PieceKind, bitboard: Bitboard) {.inline.} = self[kind.int] = bitboard - - -func getBitboard*(self: ChessBoard, kind: PieceKind, color: PieceColor): Bitboard = +func getBitboard*(self: Chessboard, kind: PieceKind, color: PieceColor): Bitboard {.inline.} = ## Returns the positional bitboard for the given piece kind and color - return self.position.pieces[color.int][kind.int] + return self.position.getBitboard(kind, color) - -func getBitboard*(self: ChessBoard, piece: Piece): Bitboard = +func getBitboard*(self: Chessboard, piece: Piece): Bitboard {.inline.} = ## Returns the positional bitboard for the given piece type return self.getBitboard(piece.kind, piece.color) -proc newChessboardFromFEN*(fen: string): ChessBoard = +proc newChessboardFromFEN*(fen: string): Chessboard = ## Initializes a chessboard with the ## position encoded by the given FEN string result = newChessboard() @@ -196,11 +114,9 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = case c.toLowerAscii(): # Piece of 'r', 'n', 'b', 'q', 'k', 'p': - let square: Square = makeSquare(row, column) + let square = makeSquare(row, column) piece = c.fromChar() - var b = result.position.pieces[piece.color][piece.kind] - b.setBit(square) - result.position.pieces[piece.color][piece.kind] = b + result.position.pieces[piece.color][piece.kind][].setBit(square) result.grid[square] = piece inc(column) of '/': @@ -232,16 +148,12 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = discard of 'K': discard - # result.position.castlingRightsAvailable.white.king = true of 'Q': discard - # result.position.castlingRightsAvailable.white.queen = true of 'k': discard - # result.position.castlingRightsAvailable.black.king = true of 'q': discard - # result.position.castlingRightsAvailable.black.queen = true else: raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section") of 3: @@ -276,114 +188,50 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = inc(index) -proc newDefaultChessboard*: ChessBoard {.inline.} = +proc newDefaultChessboard*: Chessboard {.inline.} = ## Initializes a chessboard with the ## starting position return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") -proc countPieces*(self: ChessBoard, kind: PieceKind, color: PieceColor): int = +func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.inline.} = ## Returns the number of pieces with ## the given color and type in the ## current position - return self.position.pieces[color][kind].countSetBits() + return self.position.pieces[color][kind][].countSquares() - -func countPieces*(self: ChessBoard, piece: Piece): int {.inline.} = +func countPieces*(self: Chessboard, piece: Piece): int {.inline.} = ## Returns the number of pieces on the board that ## are of the same type and color as the given piece return self.countPieces(piece.kind, piece.color) -proc getPiece*(self: ChessBoard, square: Square): Piece {.inline.} = +func getPiece*(self: Chessboard, square: Square): Piece {.inline.} = ## Gets the piece at the given square return self.grid[square] -proc getPiece*(self: ChessBoard, square: string): Piece {.inline.} = +func getPiece*(self: Chessboard, square: string): Piece {.inline.} = ## Gets the piece on the given square ## in algebraic notation return self.getPiece(square.toSquare()) -func isPromotion*(move: Move): bool {.inline.} = - ## Returns whether the given move is a - ## pawn promotion - for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]: - if (move.flags and promotion.uint16) != 0: - return true - - -func getPromotionType*(move: Move): MoveFlag {.inline.} = - ## Returns the promotion type of the given move. - ## The return value of this function is only valid - ## if isPromotion() returns true - for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]: - if (move.flags and promotion.uint16) != 0: - return promotion - - -func isCapture*(move: Move): bool {.inline.} = - ## Returns whether the given move is a - ## cature - result = (move.flags and Capture.uint16) == Capture.uint16 - - -func isCastling*(move: Move): bool {.inline.} = - ## Returns whether the given move is a - ## castle - for flag in [CastleLong, CastleShort]: - if (move.flags and flag.uint16) != 0: - return true - - -func getCastlingType*(move: Move): MoveFlag {.inline.} = - ## Returns the castlingRights type of the given move. - ## The return value of this function is only valid - ## if isCastling() returns true - for flag in [CastleLong, CastleShort]: - if (move.flags and flag.uint16) != 0: - return flag - - -func isEnPassant*(move: Move): bool {.inline.} = - ## Returns whether the given move is an - ## en passant capture - result = (move.flags and EnPassant.uint16) != 0 - - -func isDoublePush*(move: Move): bool {.inline.} = - ## Returns whether the given move is a - ## double pawn push - result = (move.flags and DoublePush.uint16) != 0 - - -func getFlags*(move: Move): seq[MoveFlag] = - ## Gets all the flags of this move - for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort, - PromoteToBishop, PromoteToKnight, PromoteToQueen, - PromoteToRook]: - if (move.flags and flag.uint16) == flag.uint16: - result.add(flag) - if result.len() == 0: - result.add(Default) - - -proc getOccupancyFor(self: ChessBoard, color: PieceColor): Bitboard = +func getOccupancyFor(self: Chessboard, color: PieceColor): Bitboard = ## Get the occupancy bitboard for every piece of the given color result = Bitboard(0) for b in self.position.pieces[color][]: result = result or b -proc getOccupancy(self: ChessBoard): Bitboard = +func getOccupancy(self: Chessboard): Bitboard {.inline.} = ## Get the occupancy bitboard for every piece on ## the chessboard result = self.getOccupancyFor(Black) or self.getOccupancyFor(White) -proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = +func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} = ## Returns the attack bitboard for the given square from ## the pawns of the given side let @@ -394,17 +242,17 @@ proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit return pawns and (bottomLeft or bottomRight) -proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = +func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} = ## Returns the attack bitboard for the given square from ## the king of the given side result = Bitboard(0) let king = self.getBitboard(King, attacker) - if (KING_BITBOARDS[square.uint] and king) != 0: + if (getKingBitboard(square) and king) != 0: result = result or king -proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = +func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard = ## Returns the attack bitboard for the given square from ## the knights of the given side let @@ -412,11 +260,11 @@ proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): B result = Bitboard(0) for knight in knights: let knightBB = knight.toBitboard() - if (KNIGHT_BITBOARDS[square.uint] and knightBB) != 0: + if (getKnightBitboard(knight) and knightBB) != 0: result = result or knightBB -proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = +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 @@ -437,7 +285,7 @@ proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): result = result or bishopBB -proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = +proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard = ## Computes the attack bitboard for the given square from ## the given side result = Bitboard(0) @@ -447,28 +295,195 @@ proc getAttacksTo*(self: ChessBoard, square: Square, attacker: PieceColor): Bitb result = result or self.getSlidingAttacks(square, attacker) -proc updateChecksAndPins(self: ChessBoard) = +proc updateChecksAndPins(self: Chessboard) = + ## Updates internal metadata about checks and + ## pinned pieces let - side = self.getSideToMove() - king = self.getBitboard(King, side).toSquare() - self.position.checkers = self.getAttacksTo(king, side.opposite()) + sideToMove = self.position.sideToMove + nonSideToMove = sideToMove.opposite() + friendlyKing = self.getBitboard(King, sideToMove).toSquare() + friendlyPieces = self.getOccupancyFor(sideToMove) + enemyPieces = self.getOccupancyFor(nonSideToMove) + + # Update checks + self.position.checkers = self.getAttacksTo(friendlyKing, nonSideToMove) + # Update pins + self.position.diagonalPins = Bitboard(0) + self.position.orthogonalPins = Bitboard(0) -proc inCheck(self: ChessBoard): bool = +func inCheck(self: Chessboard): bool {.inline.} = ## Returns if the current side to move is in check return self.position.checkers != 0 -proc canCastle*(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] = +proc canCastle*(self: Chessboard, side: PieceColor): tuple[king, queen: bool] = ## Returns if the current side to move can castle return (false, false) # TODO -proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = +proc removePieceFromBitboard(self: Chessboard, square: Square) = + ## Removes a piece at the given square in the chessboard from + ## its respective bitboard + let piece = self.grid[square] + self.position.pieces[piece.color][piece.kind][].clearBit(square) + + +proc addPieceToBitboard(self: Chessboard, square: Square, piece: Piece) = + ## Adds the given piece at the given square in the chessboard to + ## its respective bitboard + self.position.pieces[piece.color][piece.kind][].setBit(square) + + +proc spawnPiece(self: Chessboard, square: Square, piece: Piece) = + ## Internal helper to "spawn" a given piece at the given + ## square + when not defined(danger): + doAssert self.grid[square].kind == Empty + self.addPieceToBitboard(square, piece) + self.grid[square] = piece + + +proc removePiece(self: Chessboard, square: Square) = + ## Removes a piece from the board, updating necessary + ## metadata + var piece = self.grid[square] + when not defined(danger): + doAssert piece.kind != Empty and piece.color != None, self.toFEN() + self.removePieceFromBitboard(square) + self.grid[square] = nullPiece() + + +proc movePiece(self: Chessboard, move: Move) = + ## Internal helper to move a piece from + ## its current square to a target square + let piece = self.grid[move.startSquare] + when not defined(danger): + let targetSquare = self.getPiece(move.targetSquare) + if targetSquare.color != None: + raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}") + # Update positional metadata + self.removePiece(move.startSquare) + self.spawnPiece(move.targetSquare, piece) + + +proc doMove*(self: Chessboard, move: Move) = + ## Internal function called by makeMove after + ## performing legality checks. Can be used in + ## performance-critical paths where a move is + ## already known to be legal (i.e. during search) + + # Record final position for future reference + self.positions.add(self.position) + + # Final checks + let piece = self.grid[move.startSquare] + when not defined(danger): + doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}" + + var + halfMoveClock = self.position.halfMoveClock + fullMoveCount = self.position.fullMoveCount + castlingRights = self.position.castlingRights + enPassantTarget = nullSquare() + # Needed to detect draw by the 50 move rule + if piece.kind == Pawn or move.isCapture() or move.isEnPassant(): + # Number of half-moves since the last reversible half-move + halfMoveClock = 0 + else: + inc(halfMoveClock) + if piece.color == Black: + inc(fullMoveCount) + + if move.isDoublePush(): + enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare() + + # Create new position + self.position = Position(plyFromRoot: self.position.plyFromRoot + 1, + halfMoveClock: halfMoveClock, + fullMoveCount: fullMoveCount, + sideToMove: self.position.sideToMove.opposite(), + castlingRights: castlingRights, + enPassantSquare: enPassantTarget, + pieces: self.position.pieces + ) + # Update position metadata + + if move.isEnPassant(): + # Make the en passant pawn disappear + self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()) + + if move.isCapture(): + # Get rid of captured pieces + self.removePiece(move.targetSquare) + + # Move the piece to its target square + self.movePiece(move) + # TODO: Castling! + if move.isPromotion(): + # Move is a pawn promotion: get rid of the pawn + # and spawn a new piece + self.removePiece(move.targetSquare) + case move.getPromotionType(): + of PromoteToBishop: + self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color)) + of PromoteToKnight: + self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color)) + of PromoteToRook: + self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color)) + of PromoteToQueen: + self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color)) + else: + # Unreachable + discard + # Updates checks and pins for the side to move + self.updateChecksAndPins() + + +proc update*(self: Chessboard) = + ## Updates the internal grid representation + ## according to the positional data stored + ## in the chessboard + for i in 0..63: + self.grid[i] = nullPiece() + for sq in self.position.pieces[White][Pawn][]: + self.grid[sq] = Piece(color: White, kind: Pawn) + for sq in self.position.pieces[Black][Pawn][]: + self.grid[sq] = Piece(color: Black, kind: Pawn) + for sq in self.position.pieces[White][Bishop][]: + self.grid[sq] = Piece(color: White, kind: Bishop) + for sq in self.position.pieces[Black][Bishop][]: + self.grid[sq] = Piece(color: Black, kind: Bishop) + for sq in self.position.pieces[White][Knight][]: + self.grid[sq] = Piece(color: White, kind: Knight) + for sq in self.position.pieces[Black][Knight][]: + self.grid[sq] = Piece(color: Black, kind: Knight) + for sq in self.position.pieces[White][Rook][]: + self.grid[sq] = Piece(color: White, kind: Rook) + for sq in self.position.pieces[Black][Rook][]: + self.grid[sq] = Piece(color: Black, kind: Rook) + for sq in self.position.pieces[White][Queen][]: + self.grid[sq] = Piece(color: White, kind: Queen) + for sq in self.position.pieces[Black][Queen][]: + self.grid[sq] = Piece(color: Black, kind: Queen) + for sq in self.position.pieces[White][King][]: + self.grid[sq] = Piece(color: White, kind: King) + for sq in self.position.pieces[Black][King][]: + self.grid[sq] = Piece(color: Black, kind: King) + + +proc unmakeMove*(self: Chessboard) = + ## Reverts to the previous board position, + ## if one exists + self.position = self.positions.pop() + self.update() + + +proc generatePawnMovements(self: Chessboard, moves: var MoveList) = ## Helper of generatePawnMoves for generating all non-capture ## and non-promotion pawn moves let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove pawns = self.getBitboard(Pawn, sideToMove) # We can only move to squares that are *not* occupied by another piece. # We also cannot move to the last rank, as that will result in a promotion @@ -483,11 +498,11 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush)) -proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) = +proc generatePawnCaptures(self: Chessboard, moves: var MoveList) = ## Helper of generatePawnMoves for generating all capture ## pawn moves let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove nonSideToMove = sideToMove.opposite() pawns = self.getBitboard(Pawn, sideToMove) # We can only capture enemy pieces (except the king) @@ -495,7 +510,7 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) = enemyPawns = self.getBitboard(Pawn, nonSideToMove) rightMovement = pawns.forwardRightRelativeTo(sideToMove) leftMovement = pawns.forwardLeftRelativeTo(sideToMove) - epTarget = self.getEnPassantTarget() + epTarget = self.position.enPassantSquare var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0) epBitboard = epBitboard and enemyPawns # Top right attacks @@ -514,11 +529,11 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) = moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant)) -proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) = +proc generatePawnPromotions(self: Chessboard, moves: var MoveList) = ## Helper of generatePawnMoves for generating all pawn promotion ## moves let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove pawns = self.getBitboard(Pawn, sideToMove) occupancy = self.getOccupancy() for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank(): @@ -526,17 +541,17 @@ proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) = moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion)) -proc generatePawnMoves(self: ChessBoard, moves: var MoveList) = +proc generatePawnMoves(self: Chessboard, moves: var MoveList) = ## Generates all the legal pawn moves for the side to move self.generatePawnMovements(moves) self.generatePawnCaptures(moves) self.generatePawnPromotions(moves) -proc generateRookMoves(self: ChessBoard, moves: var MoveList) = +proc generateRookMoves(self: Chessboard, moves: var MoveList) = ## Helper of generateSlidingMoves to generate rook moves let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove occupancy = self.getOccupancy() nonSideToMove = sideToMove.opposite() enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove) @@ -552,10 +567,10 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) = moves.add(createMove(square, target, Capture)) -proc generateBishopMoves(self: ChessBoard, moves: var MoveList) = +proc generateBishopMoves(self: Chessboard, moves: var MoveList) = ## Helper of generateSlidingMoves to generate bishop moves let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove nonSideToMove = sideToMove.opposite() enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove) occupancy = self.getOccupancy() @@ -570,18 +585,17 @@ proc generateBishopMoves(self: ChessBoard, moves: var MoveList) = moves.add(createMove(square, target, Capture)) - -proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) = +proc generateSlidingMoves(self: Chessboard, moves: var MoveList) = ## Generates all legal sliding moves for the side to move self.generateRookMoves(moves) self.generateBishopMoves(moves) # Queens are just handled rooks + bishops -proc generateKingMoves(self: ChessBoard, moves: var MoveList) = +proc generateKingMoves(self: Chessboard, moves: var MoveList) = ## Generates all legal king moves for the side to move let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove king = self.getBitboard(King, sideToMove) moveIdx = king.toSquare().uint64 occupancy = self.getOccupancy() @@ -595,10 +609,10 @@ proc generateKingMoves(self: ChessBoard, moves: var MoveList) = moves.add(createMove(king, square, Capture)) -proc generateKnightMoves(self: ChessBoard, moves: var MoveList)= +proc generateKnightMoves(self: Chessboard, moves: var MoveList)= ## Generates all the legal knight moves for the side to move let - sideToMove = self.getSideToMove() + sideToMove = self.position.sideToMove knights = self.getBitboard(Knight, sideToMove) occupancy = self.getOccupancy() nonSideToMove = sideToMove.opposite() @@ -612,7 +626,7 @@ proc generateKnightMoves(self: ChessBoard, moves: var MoveList)= moves.add(createMove(square, target, Capture)) -proc generateMoves*(self: ChessBoard, moves: var MoveList) = +proc generateMoves*(self: Chessboard, moves: var MoveList) = ## Generates the list of all possible legal moves ## in the current position if self.position.halfMoveClock >= 100: @@ -626,174 +640,14 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) = self.generateSlidingMoves(moves) -proc removePieceFromBitboard(self: ChessBoard, square: Square) = - ## Removes a piece at the given square in the chessboard from - ## its respective bitboard - let piece = self.grid[square] - var b = self.position.pieces[piece.color][piece.kind] - b.clearBit(square) - self.position.pieces[piece.color][piece.kind] = b - - -proc addPieceToBitboard(self: ChessBoard, square: Square, piece: Piece) = - ## Adds the given piece at the given square in the chessboard to - ## its respective bitboard - var b = self.position.pieces[piece.color][piece.kind] - b.setBit(square) - self.position.pieces[piece.color][piece.kind] = b - - -proc removePiece(self: ChessBoard, square: Square) = - ## Removes a piece from the board, updating necessary - ## metadata - var piece = self.grid[square] - when not defined(danger): - doAssert piece.kind != Empty and piece.color != None, self.toFEN() - self.removePieceFromBitboard(square) - self.grid[square] = nullPiece() - - -proc movePiece(self: ChessBoard, move: Move) = - ## Internal helper to move a piece. If attack - ## is set to false, then this function does - ## not update attacked squares metadata, just - ## positional info and the grid itself - let piece = self.grid[move.startSquare] - when not defined(danger): - let targetSquare = self.getPiece(move.targetSquare) - if targetSquare.color != None: - raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}") - # Update positional metadata - self.removePiece(move.startSquare) - self.spawnPiece(move.targetSquare, piece) - - -proc doMove*(self: ChessBoard, move: Move) = - ## Internal function called by makeMove after - ## performing legality checks. Can be used in - ## performance-critical paths where a move is - ## already known to be legal - - # Record final position for future reference - self.positions.add(self.position) - - # Final checks - let piece = self.grid[move.startSquare] - when not defined(danger): - doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}" - - var - halfMoveClock = self.position.halfMoveClock - fullMoveCount = self.position.fullMoveCount - castlingRights = self.position.castlingRights - enPassantTarget = nullSquare() - # Needed to detect draw by the 50 move rule - if piece.kind == Pawn or move.isCapture() or move.isEnPassant(): - halfMoveClock = 0 - else: - inc(halfMoveClock) - if piece.color == Black: - inc(fullMoveCount) - - if move.isDoublePush(): - enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare() - - # Create new position - self.position = Position(plyFromRoot: self.position.plyFromRoot + 1, - halfMoveClock: halfMoveClock, - fullMoveCount: fullMoveCount, - sideToMove: self.getSideToMove().opposite, - castlingRights: castlingRights, - enPassantSquare: enPassantTarget, - pieces: self.position.pieces - ) - # Update position metadata - - if move.isEnPassant(): - # Make the en passant pawn disappear - self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()) - - if move.isCapture(): - # Get rid of captured pieces - self.removePiece(move.targetSquare) - - # Move the piece to its target square - self.movePiece(move) - if move.isPromotion(): - # Move is a pawn promotion: get rid of the pawn - # and spawn a new piece - self.removePiece(move.targetSquare) - case move.getPromotionType(): - of PromoteToBishop: - self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color)) - of PromoteToKnight: - self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color)) - of PromoteToRook: - self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color)) - of PromoteToQueen: - self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color)) - else: - # Unreachable - discard - self.updateChecksAndPins() - - -proc spawnPiece(self: ChessBoard, square: Square, piece: Piece) = - ## Internal helper to "spawn" a given piece at the given - ## square - when not defined(danger): - doAssert self.grid[square].kind == Empty - self.addPieceToBitboard(square, piece) - self.grid[square] = piece - - -proc update*(self: ChessBoard) = - ## Updates the internal grid representation - ## according to the positional data stored - ## in the chessboard - for i in 0..63: - self.grid[i] = nullPiece() - for sq in self.position.pieces[White][Pawn]: - self.grid[sq] = Piece(color: White, kind: Pawn) - for sq in self.position.pieces[Black][Pawn]: - self.grid[sq] = Piece(color: Black, kind: Pawn) - for sq in self.position.pieces[White][Bishop]: - self.grid[sq] = Piece(color: White, kind: Bishop) - for sq in self.position.pieces[Black][Bishop]: - self.grid[sq] = Piece(color: Black, kind: Bishop) - for sq in self.position.pieces[White][Knight]: - self.grid[sq] = Piece(color: White, kind: Knight) - for sq in self.position.pieces[Black][Knight]: - self.grid[sq] = Piece(color: Black, kind: Knight) - for sq in self.position.pieces[White][Rook]: - self.grid[sq] = Piece(color: White, kind: Rook) - for sq in self.position.pieces[Black][Rook]: - self.grid[sq] = Piece(color: Black, kind: Rook) - for sq in self.position.pieces[White][Queen]: - self.grid[sq] = Piece(color: White, kind: Queen) - for sq in self.position.pieces[Black][Queen]: - self.grid[sq] = Piece(color: Black, kind: Queen) - for sq in self.position.pieces[White][King]: - self.grid[sq] = Piece(color: White, kind: King) - for sq in self.position.pieces[Black][King]: - self.grid[sq] = Piece(color: Black, kind: King) - - -proc unmakeMove*(self: ChessBoard) = - ## Reverts to the previous board position, - ## if one exists - self.position = self.positions.pop() - self.update() - - -proc isLegal(self: ChessBoard, move: Move): bool {.inline.} = +proc isLegal(self: Chessboard, move: Move): bool {.inline.} = ## Returns whether the given move is legal var moves = MoveList() self.generateMoves(moves) return move in moves -proc makeMove*(self: ChessBoard, move: Move): Move {.discardable.} = +proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} = ## Makes a move on the board result = move if not self.isLegal(move): @@ -845,7 +699,7 @@ proc fromChar*(c: char): Piece = result = Piece(kind: kind, color: color) -proc `$`*(self: ChessBoard): string = +proc `$`*(self: Chessboard): string = result &= "- - - - - - - -" var file = 8 for i in 0..7: @@ -900,7 +754,7 @@ proc toPretty*(piece: Piece): string = discard -proc pretty*(self: ChessBoard): string = +proc pretty*(self: Chessboard): string = ## Returns a colored version of the ## board for easier visualization var file = 8 @@ -926,7 +780,7 @@ proc pretty*(self: ChessBoard): string = result &= "\x1b[0m" -proc toFEN*(self: ChessBoard): string = +proc toFEN*(self: Chessboard): string = ## Returns a FEN string of the current ## position in the chessboard var skip: int @@ -948,7 +802,7 @@ proc toFEN*(self: ChessBoard): string = result &= "/" result &= " " # Active color - result &= (if self.getSideToMove() == White: "w" else: "b") + result &= (if self.position.sideToMove == White: "w" else: "b") result &= " " # Castling availability result &= "-" @@ -967,16 +821,16 @@ proc toFEN*(self: ChessBoard): string = # result &= "q" result &= " " # En passant target - if self.getEnPassantTarget() == nullSquare(): + if self.position.enPassantSquare == nullSquare(): result &= "-" else: - result &= self.getEnPassantTarget().toAlgebraic() + result &= self.position.enPassantSquare.toAlgebraic() result &= " " # Halfmove clock - result &= $self.getHalfMoveCount() + result &= $self.position.halfMoveClock result &= " " # Fullmove number - result &= $self.getMoveCount() + result &= $self.position.fullMoveCount when isMainModule: diff --git a/Chess/nimfish/nimfishpkg/bitboards.nim b/Chess/nimfish/nimfishpkg/bitboards.nim index db7c26b..379c51d 100644 --- a/Chess/nimfish/nimfishpkg/bitboards.nim +++ b/Chess/nimfish/nimfishpkg/bitboards.nim @@ -54,12 +54,19 @@ func clearBit*(a: var Bitboard, bit: Square) = a.uint64.clearBit(bit.int) func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int) +func countSquares*(self: Bitboard): int {.inline.} = + ## Returns the number of active squares + ## in the bitboard + result = self.countSetBits() + + func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64 func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank) func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64 func toBitboard*(square: Square): Bitboard = toBitboard(square.int8) proc toSquare*(b: Bitboard): Square = Square(b.uint64.countTrailingZeroBits()) + func createMove*(startSquare: Bitboard, targetSquare: Square, flags: varargs[MoveFlag]): Move = result = createMove(startSquare.toSquare(), targetSquare, flags) @@ -236,7 +243,7 @@ func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard # We precompute as much stuff as possible: lookup tables are fast! -func computeKingBitboards: array[64, Bitboard] = +func computeKingBitboards: array[64, Bitboard] {.compileTime.} = ## Precomputes all the movement bitboards for the king for i in 0'u64..63: let king = i.toBitboard() @@ -258,7 +265,7 @@ func computeKingBitboards: array[64, Bitboard] = result[i] = movements -func computeKnightBitboards: array[64, Bitboard] = +func computeKnightBitboards: array[64, Bitboard] {.compileTime.} = ## Precomputes all the movement bitboards for knights for i in 0'u64..63: let knight = i.toBitboard() @@ -276,8 +283,10 @@ func computeKnightBitboards: array[64, Bitboard] = result[i] = movements -# For some reason nim freaks out if I try to call computeKingBitboards() -# at compile-time. ¯\_(ツ)_/¯ -let +const KING_BITBOARDS* = computeKingBitboards() KNIGHT_BITBOARDS* = computeKnightBitboards() + + +func getKingBitboard*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int] +func getKnightBitboard*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int] \ No newline at end of file diff --git a/Chess/nimfish/nimfishpkg/misc.nim b/Chess/nimfish/nimfishpkg/misc.nim index 53b5128..16b91e8 100644 --- a/Chess/nimfish/nimfishpkg/misc.nim +++ b/Chess/nimfish/nimfishpkg/misc.nim @@ -1,12 +1,13 @@ 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) = +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" diff --git a/Chess/nimfish/nimfishpkg/moves.nim b/Chess/nimfish/nimfishpkg/moves.nim index 030c023..71fb2ad 100644 --- a/Chess/nimfish/nimfishpkg/moves.nim +++ b/Chess/nimfish/nimfishpkg/moves.nim @@ -86,4 +86,66 @@ func createMove*(startSquare: Square, targetSquare: SomeInteger, flags: varargs[ result = createMove(startSquare, Square(targetSquare.int8), flags) -func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare()) \ No newline at end of file +func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare()) + +func isPromotion*(move: Move): bool {.inline.} = + ## Returns whether the given move is a + ## pawn promotion + for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]: + if (move.flags and promotion.uint16) != 0: + return true + + +func getPromotionType*(move: Move): MoveFlag {.inline.} = + ## Returns the promotion type of the given move. + ## The return value of this function is only valid + ## if isPromotion() returns true + for promotion in [PromoteToBishop, PromoteToKnight, PromoteToRook, PromoteToQueen]: + if (move.flags and promotion.uint16) != 0: + return promotion + + +func isCapture*(move: Move): bool {.inline.} = + ## Returns whether the given move is a + ## cature + result = (move.flags and Capture.uint16) == Capture.uint16 + + +func isCastling*(move: Move): bool {.inline.} = + ## Returns whether the given move is a + ## castle + for flag in [CastleLong, CastleShort]: + if (move.flags and flag.uint16) != 0: + return true + + +func getCastlingType*(move: Move): MoveFlag {.inline.} = + ## Returns the castlingRights type of the given move. + ## The return value of this function is only valid + ## if isCastling() returns true + for flag in [CastleLong, CastleShort]: + if (move.flags and flag.uint16) != 0: + return flag + + +func isEnPassant*(move: Move): bool {.inline.} = + ## Returns whether the given move is an + ## en passant capture + result = (move.flags and EnPassant.uint16) != 0 + + +func isDoublePush*(move: Move): bool {.inline.} = + ## Returns whether the given move is a + ## double pawn push + result = (move.flags and DoublePush.uint16) != 0 + + +func getFlags*(move: Move): seq[MoveFlag] = + ## Gets all the flags of this move + for flag in [EnPassant, Capture, DoublePush, CastleLong, CastleShort, + PromoteToBishop, PromoteToKnight, PromoteToQueen, + PromoteToRook]: + if (move.flags and flag.uint16) == flag.uint16: + result.add(flag) + if result.len() == 0: + result.add(Default) \ No newline at end of file diff --git a/Chess/nimfish/nimfishpkg/tui.nim b/Chess/nimfish/nimfishpkg/tui.nim index 63a4491..a70f885 100644 --- a/Chess/nimfish/nimfishpkg/tui.nim +++ b/Chess/nimfish/nimfishpkg/tui.nim @@ -13,14 +13,14 @@ type CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64] -proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData = +proc perft*(board: Chessboard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData = ## Counts (and debugs) the number of legal positions reached after ## the given number of ply var moves = MoveList() - self.generateMoves(moves) + board.generateMoves(moves) if not bulk: - if len(moves) == 0 and self.inCheck(): + if len(moves) == 0 and board.inCheck(): result.checkmates = 1 # TODO: Should we count stalemates/draws? if ply == 0: @@ -48,22 +48,22 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa for move in moves: if verbose: - let canCastle = self.canCastle(self.getSideToMove()) - echo &"Ply (from root): {self.getPlyFromRoot()}" + let canCastle = board.canCastle(board.position.sideToMove) + echo &"Ply (from root): {board.position.plyFromRoot}" echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}" - echo &"Turn: {self.getSideToMove()}" - echo &"Piece: {self.getPiece(move.startSquare).kind}" + echo &"Turn: {board.position.sideToMove}" + echo &"Piece: {board.getPiece(move.startSquare).kind}" echo &"Flags: {move.getFlags()}" - echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}" + echo &"In check: {(if board.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()}" + echo &"Position before move: {board.toFEN()}" stdout.write("En Passant target: ") - if self.getEnPassantTarget() != nullSquare(): - echo self.getEnPassantTarget().toAlgebraic() + if board.position.enPassantSquare != nullSquare(): + echo board.position.enPassantSquare.toAlgebraic() else: echo "None" - echo "\n", self.pretty() - self.doMove(move) + echo "\n", board.pretty() + board.doMove(move) if ply == 1: if move.isCapture(): inc(result.captures) @@ -73,16 +73,16 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa inc(result.promotions) if move.isEnPassant(): inc(result.enPassant) - if self.inCheck(): + if board.inCheck(): # Opponent king is in check inc(result.checks) if verbose: - let canCastle = self.canCastle(self.getSideToMove()) + let canCastle = board.canCastle(board.position.sideToMove) echo "\n" - echo &"Opponent in check: {(if self.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 &"Position after move: {self.toFEN()}" - echo "\n", self.pretty() + echo &"Position after move: {board.toFEN()}" + echo "\n", board.pretty() stdout.write("nextpos>> ") try: discard readLine(stdin) @@ -90,8 +90,8 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa discard except EOFError: discard - let next = self.perft(ply - 1, verbose, bulk=bulk) - self.unmakeMove() + let next = board.perft(ply - 1, verbose, bulk=bulk) + board.unmakeMove() if divide and (not bulk or ply > 1): var postfix = "" if move.isPromotion(): @@ -118,7 +118,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa result.checkmates += next.checkmates -proc handleGoCommand(board: ChessBoard, command: seq[string]) = +proc handleGoCommand(board: Chessboard, command: seq[string]) = if len(command) < 2: echo &"Error: go: invalid number of arguments" return @@ -172,7 +172,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) = echo &"Error: go: unknown subcommand '{command[1]}'" -proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} = +proc handleMoveCommand(board: Chessboard, command: seq[string]): Move {.discardable.} = if len(command) != 2: echo &"Error: move: invalid number of arguments" return @@ -224,25 +224,25 @@ proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discarda var move = createMove(startSquare, targetSquare, flags) let piece = board.getPiece(move.startSquare) - if piece.kind == King and move.startSquare == board.getSideToMove().getKingStartingSquare(): + if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare(): if move.targetSquare == longCastleKing(piece.color): move.flags = move.flags or CastleLong.uint16 elif move.targetSquare == shortCastleKing(piece.color): move.flags = move.flags or CastleShort.uint16 - if move.targetSquare == board.getEnPassantTarget(): + if move.targetSquare == board.position.enPassantSquare: move.flags = move.flags or EnPassant.uint16 result = board.makeMove(move) if result == nullMove(): echo &"Error: move: {moveString} is illegal" -proc handlePositionCommand(board: var ChessBoard, command: seq[string]) = +proc handlePositionCommand(board: var Chessboard, command: seq[string]) = if len(command) < 2: echo "Error: position: invalid number of arguments" return # Makes sure we don't leave the board in an invalid state if # some error occurs - var tempBoard: ChessBoard + var tempBoard: Chessboard case command[1]: of "startpos": tempBoard = newDefaultChessboard() @@ -302,7 +302,7 @@ proc handlePositionCommand(board: var ChessBoard, command: seq[string]) = return -proc handleUCICommand(board: var ChessBoard, command: seq[string]) = +proc handleUCICommand(board: var Chessboard, command: seq[string]) = echo "id name Nimfish 0.1" echo "id author Nocturn9x & Contributors (see LICENSE)" # TODO @@ -379,7 +379,7 @@ proc commandLoop*: int = of "help": echo HELP_TEXT of "skip": - board.setSideToMove(board.getSideToMove().opposite()) + board.position.sideToMove = board.position.sideToMove.opposite() of "go": handleGoCommand(board, cmd) of "position", "pos": @@ -391,18 +391,18 @@ proc commandLoop*: int = of "unmove", "u": board.unmakeMove() of "stm": - echo &"Side to move: {board.getSideToMove()}" + echo &"Side to move: {board.position.sideToMove}" of "atk": if len(cmd) != 2: echo "error: atk: invalid number of arguments" continue try: - echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove()) + echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove) except ValueError: echo "error: atk: invalid square" continue of "ep": - let target = board.getEnPassantTarget() + let target = board.position.enPassantSquare if target != nullSquare(): echo &"En passant target: {target.toAlgebraic()}" else: @@ -417,10 +417,10 @@ proc commandLoop*: int = echo "error: get: invalid square" continue of "castle": - 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\")}" + let canCastle = board.canCastle(board.position.sideToMove) + echo &"Castling rights for {($board.position.sideToMove).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}" of "check": - echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}" + echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}" of "quit": return 0 else: