diff --git a/Chess/nimfish/nimfish.nim b/Chess/nimfish/nimfish.nim index d3514af..60ae013 100644 --- a/Chess/nimfish/nimfish.nim +++ b/Chess/nimfish/nimfish.nim @@ -149,6 +149,8 @@ func shortCastleKing(color: PieceColor): Square {.inline.} = (if color == White: func longCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 3) else: makeSquare(7, 5)) func shortCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(0, 0) else: makeSquare(0, 2)) +proc inCheck(self: ChessBoard, side: PieceColor): bool + proc newChessboard: ChessBoard = ## Returns a new, empty chessboard @@ -358,10 +360,9 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = else: raise newException(ValueError, "invalid FEN: too many fields in FEN string") inc(index) - # result.updateAttackedSquares() - #[if result.inCheck(result.getSideToMove().opposite): + 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")]# + 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") @@ -548,7 +549,7 @@ proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit let sq = square.toBitboard() king = self.getBitboard(King, attacker) - if (sq and KING_BITBOARDS[square.uint64]) != 0: + if (KING_BITBOARDS[square.uint] and king) != 0: result = result or sq @@ -560,10 +561,37 @@ proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): B knights = self.getBitboard(Knight, attacker) result = Bitboard(0) for knight in knights: - if (sq and KNIGHT_BITBOARDS[knight.uint64]) != 0: + if (KNIGHT_BITBOARDS[square.uint] and knight.toBitboard()) != 0: result = result or knight.toBitboard() +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() + 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() + 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() + + proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = ## Computes the attack bitboard for the given square from ## the given side @@ -573,6 +601,17 @@ proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitbo result = result or self.getPawnAttacks(square, attacker) result = result or self.getKingAttacks(square, attacker) result = result or self.getKnightAttacks(square, attacker) + result = result or self.getSlidingAttacks(square, attacker) + + +proc inCheck(self: ChessBoard, side: PieceColor): bool = + ## Returns if the current side to move is in check + return self.getAttacksTo(self.getKingSquare(side), side.opposite()) != 0 + + +proc canCastle(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] = + ## Returns if the current side to move can castle + return (false, false) # TODO proc getCapturablePieces(self: ChessBoard, side: PieceColor): Bitboard {.inline.} = @@ -597,7 +636,7 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = for square in pawns.forwardRelativeTo(sideToMove) and allowedSquares: moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square)) # Double push - let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can double push + let rank = sideToMove.getFirstRank() # Only pawns on their starting rank can double push for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares: moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush)) @@ -661,7 +700,7 @@ proc generateRookMovements(self: ChessBoard, moves: var MoveList) = for square in rooks: let blockers = occupancy and Rook.getRelevantBlockers(square) var moveset = getRookMoves(square, blockers) - # Can't capture our own pieces + # Can't move over our own pieces moveset = moveset and not friendlyPieces for target in moveset: moves.add(createMove(square, target)) @@ -679,9 +718,10 @@ proc generateRookCaptures(self: ChessBoard, moves: var MoveList) = let blockers = occupancy and Rook.getRelevantBlockers(square) var moveset = getRookMoves(square, blockers) # Can only cature enemy pieces - moveset = moveset and not enemyPieces + moveset = moveset and enemyPieces for target in moveset: - moves.add(createMove(square, target)) + moves.add(createMove(square, target, Capture)) + proc generateRookMoves(self: ChessBoard, moves: var MoveList) = ## Helper of generateSlidingMoves to generate rook moves @@ -689,9 +729,103 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) = self.generateRookCaptures(moves) +proc generateBishopMovements(self: ChessBoard, moves: var MoveList) = + ## Helper of generateBishopMoves to generate all non-capture + ## bishop moves + let + sideToMove = self.getSideToMove() + occupancy = self.getOccupancy() + friendlyPieces = self.getOccupancyFor(sideToMove) + bishops = self.getBitboard(Bishop, sideToMove) + for square in bishops: + let blockers = occupancy and Bishop.getRelevantBlockers(square) + var moveset = getBishopMoves(square, blockers) + # Can't move over our own pieces + moveset = moveset and not friendlyPieces + for target in moveset: + moves.add(createMove(square, target)) + + +proc generateBishopCaptures(self: ChessBoard, moves: var MoveList) = + ## Helper of generateBishopMoves to generate all capture + ## bishop moves + let + sideToMove = self.getSideToMove() + occupancy = self.getOccupancy() + enemyPieces = self.getCapturablePieces(sideToMove.opposite()) + bishops = self.getBitboard(Bishop, sideToMove) + for square in bishops: + let blockers = occupancy and Bishop.getRelevantBlockers(square) + var moveset = getRookMoves(square, blockers) + # Can only cature enemy pieces + moveset = moveset and enemyPieces + for target in moveset: + moves.add(createMove(square, target, Capture)) + + +proc generateBishopMoves(self: ChessBoard, moves: var MoveList) = + ## Helper of generateSlidingMoves to generate bishop moves + self.generateBishopMovements(moves) + self.generateBishopCaptures(moves) + + +proc generateQueenMovements(self: ChessBoard, moves: var MoveList) = + ## Helper of generateQueenMoves to generate all non-capture + ## bishop moves + let + sideToMove = self.getSideToMove() + occupancy = self.getOccupancy() + friendlyPieces = self.getOccupancyFor(sideToMove) + 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) + var moveset = rookMoves or bishopMoves + # Can't move over our own pieces + moveset = moveset and not friendlyPieces + for target in moveset: + moves.add(createMove(square, target)) + + +proc generateQueenCaptures(self: ChessBoard, moves: var MoveList) = + ## Helper of generateQueenMoves to generate all capture + ## 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) + var moveset = rookMoves or bishopMoves + # Can only capture the enemy pieces + moveset = moveset and enemyPieces + for target in moveset: + moves.add(createMove(square, target, Capture)) + + +proc generateQueenMoves(self: ChessBoard, moves: var MoveList) = + ## Helper of generateSlidingMoves to generate queen moves + self.generateQueenMovements(moves) + self.generateQueenCaptures(moves) + + 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) proc generateKingMoves(self: ChessBoard, moves: var MoveList) = @@ -800,8 +934,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) = self.generatePawnMoves(moves) self.generateKingMoves(moves) self.generateKnightMoves(moves) - self.generateRookMoves(moves) - # TODO: all pieces + self.generateSlidingMoves(moves) proc removePieceFromBitboard(self: ChessBoard, square: Square) = @@ -1329,7 +1462,7 @@ proc toFEN*(self: ChessBoard): string = # Fullmove number result &= $self.getMoveCount() -#[ + proc perft*(self: 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 @@ -1337,7 +1470,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(): + if len(moves) == 0 and self.inCheck(self.getSideToMove()): result.checkmates = 1 # TODO: Should we count stalemates/draws? if ply == 0: @@ -1367,11 +1500,11 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa if verbose: let canCastle = self.canCastle(self.getSideToMove()) echo &"Ply (from root): {self.position.plyFromRoot}" - echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file})" + echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}" echo &"Turn: {self.getSideToMove()}" echo &"Piece: {self.grid[move.startSquare].kind}" echo &"Flags: {move.getFlags()}" - echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}" + echo &"In check: {(if self.inCheck(self.getSideToMove()): \"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: ") @@ -1390,13 +1523,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(): + if self.inCheck(self.getSideToMove()): # 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(): \"yes\" else: \"no\")}" + echo &"Opponent in check: {(if self.inCheck(self.getSideToMove()): \"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() @@ -1433,7 +1566,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa result.castles += next.castles result.enPassant += next.enPassant result.checkmates += next.checkmates -]# + proc handleGoCommand(board: ChessBoard, command: seq[string]) = if len(command) < 2: @@ -1462,7 +1595,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) = break if not ok: return - #[try: + try: let ply = parseInt(args[0]) if bulk: let t = cpuTime() @@ -1485,7 +1618,6 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) = echo "Error: go: perft: invalid depth" else: echo &"Error: go: unknown subcommand '{command[1]}'" - ]# proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} = @@ -1660,6 +1792,7 @@ const HELP_TEXT = """Nimfish help menu: - fen: Shorthand for "position fen" - pos : Shorthand for "position " - get : Get the piece on the given square + - atk: Print the attack bitboard of the given square for the side to move - uci: enter UCI mode (WIP) """ @@ -1703,6 +1836,15 @@ proc main: int = board.unmakeMove() of "turn": echo &"Active color: {board.getSideToMove()}" + of "atk": + if len(cmd) != 2: + echo "error: atk: invalid number of arguments" + continue + try: + echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove()) + except ValueError: + echo "error: atk: invalid square" + continue of "ep": let target = board.getEnPassantTarget() if target != nullSquare(): @@ -1718,12 +1860,11 @@ proc main: int = except ValueError: echo "error: get: invalid square" continue - #[of "castle": - let canCastle = board.canCastle() + 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\")}" of "check": - echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}" - ]# + echo &"{board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \"yes\" else: \"no\")}" else: echo &"Unknown command '{cmd[0]}'. Type 'help' for more information." except IOError: @@ -1839,14 +1980,5 @@ when isMainModule: testPieceBitboard(blackQueens, blackQueenSquares) testPieceBitboard(blackKing, blackKingSquares) - - b = newChessboardFromFEN("8/5R2/8/8/7k/8/5K2/8 w - - 0 1") - var m = MoveList() - b.generateRookMovements(m) - echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: " - for move in m: - echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags() - echo b.pretty() - - # setControlCHook(proc () {.noconv.} = quit(0)) - # quit(main()) + setControlCHook(proc () {.noconv.} = quit(0)) + quit(main()) diff --git a/Chess/nimfish/src/magics.nim b/Chess/nimfish/src/magics.nim index 05f2bcf..548e131 100644 --- a/Chess/nimfish/src/magics.nim +++ b/Chess/nimfish/src/magics.nim @@ -342,9 +342,10 @@ else: var path = joinPath(getCurrentDir(), "src/resources") if path.lastPathPart() == "nimfish": path = joinPath("src", path) + echo "Loading magic bitboards" var magics = readFile(joinPath(path, "magics.json")).fromJson(TableRef[string, array[64, MagicEntry]]) var moves = readFile(joinPath(path, "movesets.json")).fromJson(TableRef[string, array[64, seq[Bitboard]]]) ROOK_MAGICS = magics["rooks"] BISHOP_MAGICS = magics["bishops"] ROOK_MOVES = moves["rooks"] - BISHOP_MOVES = moves["bishops"] \ No newline at end of file + BISHOP_MOVES = moves["bishops"]