diff --git a/src/Chess/bitboards.nim b/src/Chess/bitboards.nim index f1b8620..2b98497 100644 --- a/src/Chess/bitboards.nim +++ b/src/Chess/bitboards.nim @@ -154,9 +154,11 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi of BackwardRight: return bitboard shl 9 of BackwardLeft: - return bitboard shr 7 - else: - discard + return bitboard shl 7 + of Left: + return bitboard shr 1 + of Right: + return bitboard shl 1 of Black: # The directions for black are just the opposite of those for white, # so we avoid duplicating any code @@ -172,14 +174,18 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi of BackwardRight: return bitboard shr 9 of BackwardLeft: - return bitboard shl 7 - else: - discard + return bitboard shr 7 + of Left: + return bitboard shl 1 + of Right: + return bitboard shr 1 else: discard func getLastRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(0) else: getRankMask(7)) +func getFirstRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(7) else: getRankMask(0)) + func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard = ## Get a bitmask for the given direction for a piece @@ -193,6 +199,9 @@ func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward) func doubleBackwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).backwardRelativeTo(side) +func leftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Left) and not getFileMask(0) +func rightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Right) and not getFileMask(7) + # We mask off the first and last ranks for # left and right movements respectively to @@ -205,9 +214,12 @@ func forwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, ForwardLeft) and not getFileMask(0) -func bottomRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = - getDirectionMask(self, side, BackwardRight) and not getFileMask(7) +func backwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + getDirectionMask(self, side, BackwardRight) -func bottomLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = - getDirectionMask(self, side, BackwardLeft) and not getFileMask(0) \ No newline at end of file +func backwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = + getDirectionMask(self, side, BackwardLeft) + + +# func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward) diff --git a/src/Chess/board.nim b/src/Chess/board.nim index a926fdf..37a90b5 100644 --- a/src/Chess/board.nim +++ b/src/Chess/board.nim @@ -260,7 +260,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = of Queen: result.position.pieces.black.queens.uint64.setBit(bitIndex) of King: - if result.position.pieces.black.king != Bitboard(0'u64): + if result.position.pieces.black.king != 0: raise newException(ValueError, "invalid position: exactly one king of each color must be present") result.position.pieces.black.king.uint64.setBit(bitIndex) else: @@ -360,7 +360,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard = #[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 == Bitboard(0) or result.position.pieces.black.king == Bitboard(0): + 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") @@ -492,17 +492,17 @@ func getFlags*(move: Move): seq[MoveFlag] = result.add(Default) -func getKing(self: ChessBoard, color: PieceColor = None): Square {.inline.} = +func getKingSquare(self: ChessBoard, color: PieceColor = None): Square {.inline.} = ## Returns the square of the king for the given - ## color (if it is None, the active color is used) + ## side (if it is None, the side to move is used) var color = color if color == None: color = self.getSideToMove() case color: of White: - return Square(self.position.pieces.white.king.uint64.countTrailingZeroBits()) + return self.position.pieces.white.king.toSquare() of Black: - return Square(self.position.pieces.black.king.uint64.countTrailingZeroBits()) + return self.position.pieces.black.king.toSquare() else: discard @@ -527,6 +527,28 @@ proc getOccupancy(self: ChessBoard): Bitboard = result = self.getOccupancyFor(Black) or self.getOccupancyFor(White) + +proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard = + ## Returns the attack bitboard for the given square from + ## the pawns of the given side + let + sq = square.toBitboard() + pawns = self.getBitboard(Pawn, attacker) + bottomLeft = sq.backwardLeftRelativeTo(attacker) + bottomRight = sq.backwardRightRelativeTo(attacker) + return pawns and (bottomLeft or bottomRight) + + +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) + + + proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = ## Helper of generatePawnMoves for generating all non-capture ## and non-promotion pawn moves @@ -536,13 +558,13 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = # 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 # and is handled elsewhere - occupancy = not (self.getOccupancy() or sideToMove.getLastRank()) + allowedSquares = not (self.getOccupancy() or sideToMove.getLastRank()) # Single push - for square in pawns.forwardRelativeTo(sideToMove) and occupancy: + 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 - for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and occupancy: + for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares: moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush)) @@ -553,17 +575,18 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) = sideToMove = self.getSideToMove() nonSideToMove = sideToMove.opposite() pawns = self.getBitboard(Pawn, sideToMove) - # We can only capture enemy pieces - enemyPieces = self.getOccupancyFor(nonSideToMove) + # We can only capture enemy pieces (except the king) + enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove) rightMovement = pawns.forwardRightRelativeTo(sideToMove) leftMovement = pawns.forwardLeftRelativeTo(sideToMove) epTarget = self.getEnPassantTarget() let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0) # Top right attacks for square in rightMovement and enemyPieces: - moves.add(createMove(square.toBitboard().bottomLeftRelativeTo(sideToMove), square, Capture)) + moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture)) + # Top left attacks for square in leftMovement and enemyPieces: - moves.add(createMove(square.toBitboard().bottomRightRelativeTo(sideToMove), square, Capture)) + moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture)) # Special case for en passant let epLeft = epBitboard and leftMovement @@ -580,31 +603,47 @@ proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) = let sideToMove = self.getSideToMove() pawns = self.getBitboard(Pawn, sideToMove) - occupancy = not self.getOccupancy() - for square in pawns.forwardRelativeTo(sideToMove) and occupancy and sideToMove.getLastRank(): + occupancy = self.getOccupancy() + for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank(): for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion)) proc generatePawnMoves(self: ChessBoard, moves: var MoveList) = - ## Generates the possible moves for the pawn in the given - ## square - + ## Generates all the legal pawn moves for the side to move self.generatePawnMovements(moves) self.generatePawnCaptures(moves) self.generatePawnPromotions(moves) -proc generateSlidingMoves(self: ChessBoard, square: Square): seq[Move] = - ## Generates moves for the sliding piece in the given square +proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) = + ## Generates all legal sliding moves for the side to move + + +proc generateKingMoves(self: ChessBoard, moves: var MoveList) = + ## Generates all legal king moves for the side to move + let + sideToMove = self.getSideToMove() + king = self.getBitboard(King, sideToMove) + allowedSquares = not self.getOccupancy() + nonSideToMove = sideToMove.opposite() + enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove) + var movements = king.forwardRelativeTo(sideToMove) + movements = movements or king.rightRelativeTo(sideToMove) + movements = movements or king.backwardRelativeTo(sideToMove) + movements = movements or king.leftRelativeTo(sideToMove) + movements = movements or king.forwardRightRelativeTo(sideToMove) + movements = movements or king.forwardLeftRelativeTo(sideToMove) + # Regular moves + for square in movements and allowedSquares: + moves.add(createMove(king, square)) + # Captures + for square in movements and enemyPieces: + moves.add(createMove(king, square, Capture)) -proc generateKingMoves(self: ChessBoard, square: Square): seq[Move] = - ## Generates moves for the king in the given square - - -proc generateKnightMoves(self: ChessBoard, square: Square): seq[Move] = - ## Generates moves for the knight in the given square +proc generateKnightMoves(self: ChessBoard, moves: var MoveList)= + ## Generates all the legal knight moves for the side to move @@ -678,6 +717,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) = ]# # TODO: Check for repetitions (requires zobrist hashing + table) self.generatePawnMoves(moves) + self.generateKingMoves(moves) # TODO: all pieces @@ -1716,17 +1756,12 @@ when isMainModule: testPieceBitboard(blackKing, blackKingSquares) b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1") + b.doMove(createMove("d1", "e1")) var m = MoveList() - b.generatePawnMoves(m) - echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: " + b.generateMoves(m) + echo &"Legal moves for {b.getSideToMove()} at {b.toFEN()}: " for move in m: echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags() - #[b.doMove(createMove("d1", "c1")) - m.clear() - b.generatePawnMoves(m) - echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: " - for move in m: - echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags() - echo b.pretty()]# + echo b.pretty() # setControlCHook(proc () {.noconv.} = quit(0)) # quit(main())