Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions include/bitbishop/lookups/rook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,60 @@ constexpr std::array<Bitboard, Const::BOARD_SIZE> ROOK_ATTACKS = []() constexpr
return table;
}();

/**
* @brief Precomputed lookup table of rook north attacks for every square.
* Indexed by square (0-63).
*/
constexpr std::array<Bitboard, Const::BOARD_SIZE> ROOK_NORTH_ATTACKS = []() constexpr {
using namespace Const;

std::array<Bitboard, BOARD_SIZE> table{};
for (int sq = 0; sq < BOARD_SIZE; ++sq) {
table[sq] = Bitboard(rook_north_attacks(sq));
}
return table;
}();

/**
* @brief Precomputed lookup table of rook south attacks for every square.
* Indexed by square (0-63).
*/
constexpr std::array<Bitboard, Const::BOARD_SIZE> ROOK_SOUTH_ATTACKS = []() constexpr {
using namespace Const;

std::array<Bitboard, BOARD_SIZE> table{};
for (int sq = 0; sq < BOARD_SIZE; ++sq) {
table[sq] = Bitboard(rook_south_attacks(sq));
}
return table;
}();

/**
* @brief Precomputed lookup table of rook east attacks for every square.
* Indexed by square (0-63).
*/
constexpr std::array<Bitboard, Const::BOARD_SIZE> ROOK_EAST_ATTACKS = []() constexpr {
using namespace Const;

std::array<Bitboard, BOARD_SIZE> table{};
for (int sq = 0; sq < BOARD_SIZE; ++sq) {
table[sq] = Bitboard(rook_east_attacks(sq));
}
return table;
}();

/**
* @brief Precomputed lookup table of rook west attacks for every square.
* Indexed by square (0-63).
*/
constexpr std::array<Bitboard, Const::BOARD_SIZE> ROOK_WEST_ATTACKS = []() constexpr {
using namespace Const;

std::array<Bitboard, BOARD_SIZE> table{};
for (int sq = 0; sq < BOARD_SIZE; ++sq) {
table[sq] = Bitboard(rook_west_attacks(sq));
}
return table;
}();

} // namespace Lookups
80 changes: 80 additions & 0 deletions include/bitbishop/moves/rook_move_gen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#pragma once
#include <bitbishop/board.hpp>
#include <bitbishop/color.hpp>
#include <bitbishop/move.hpp>
#include <vector>

/**
* @brief Generates rook moves.
*
* Rooks can move horizontally and vertically in the four directions (N, S, E, W) until a friendly
* or enemy piece is encountered.
* This namespace provides functions for generating both pseudo-legal and legal rook moves.
*/
namespace RookMoveGenerator {

/**
* @brief Generates all pseudo-legal rook moves for a given side.
*
* Pseudo-legal moves follow piece movement rules but may leave the king in check.
* These moves must be validated separately to ensure legality.
*
* @param moves Vector to append generated moves to
* @param board Current board state
* @param side Color of the side to generate moves for
*/
void generate_pseudo_legal_moves(std::vector<Move>& moves, const Board& board, Color side);

/**
* @brief Adds castling moves to the move list if conditions are met.
*
* Validates castling legality and adds both kingside and queenside castling
* moves when available. Castling requires that the king and rook haven't moved,
* squares between them are empty, and the king doesn't move through check.
*
* @param moves Vector to append castling moves to
* @param from Square the king is currently on
* @param side Color of the side attempting to castle
* @param board Current board state
*
* @see https://www.chess.com/article/view/how-to-castle-in-chess
*/
void add_rook_castling(std::vector<Move>& moves, Square from, Color side, const Board& board);

/**
* @brief Computes the north ray from a square, stopping at the first blocker.
*
* @param from The starting square
* @param occupied Bitboard of all occupied squares on the board
* @return Bitboard containing all squares occupied by friendly or enemy pieces
*/
Bitboard north_ray(Square from, const Bitboard& occupied);

/**
* @brief Computes the south ray from a square, stopping at the first blocker.
*
* @param from The starting square
* @param occupied Bitboard of all occupied squares on the board
* @return Bitboard containing all squares occupied by friendly or enemy pieces
*/
Bitboard south_ray(Square from, const Bitboard& occupied);

/**
* @brief Computes the east ray from a square, stopping at the first blocker.
*
* @param from The starting square
* @param occupied Bitboard of all occupied squares on the board
* @return Bitboard containing all squares occupied by friendly or enemy pieces
*/
Bitboard east_ray(Square from, const Bitboard& occupied);

/**
* @brief Computes the west ray from a square, stopping at the first blocker.
*
* @param from The starting square
* @param occupied Bitboard of all occupied squares on the board
* @return Bitboard containing all squares occupied by friendly or enemy pieces
*/
Bitboard west_ray(Square from, const Bitboard& occupied);

}; // namespace RookMoveGenerator
121 changes: 121 additions & 0 deletions src/bitbishop/moves/rook_move_gen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#include <bitbishop/lookups/rook.hpp>
#include <bitbishop/moves/king_move_gen.hpp>
#include <bitbishop/moves/rook_move_gen.hpp>

void RookMoveGenerator::generate_pseudo_legal_moves(std::vector<Move>& moves, const Board& board, Color side) {
Bitboard rooks = board.rooks(side);
Bitboard empty = board.unoccupied();
Bitboard enemy = board.enemy(side);
Bitboard occupied = board.occupied();

// warning: this loop is destructive on Bitboard rooks
while (auto from_opt = rooks.pop_lsb()) {
Square from = from_opt.value();

Bitboard rook_attacks;
rook_attacks |= north_ray(from, occupied);
rook_attacks |= south_ray(from, occupied);
rook_attacks |= east_ray(from, occupied);
rook_attacks |= west_ray(from, occupied);

// Silent moves
Bitboard rook_moves = rook_attacks & empty;
for (auto to : rook_moves) {
moves.emplace_back(from, to, std::nullopt, false, false, false);
}

// Captures
Bitboard rook_captures = rook_attacks & enemy;
for (auto to : rook_captures) {
moves.emplace_back(from, to, std::nullopt, true, false, false);
}

// Castling moves (pseudo-legal only checks piece positions, not attacks)
add_rook_castling(moves, from, side, board);
}
}

void RookMoveGenerator::add_rook_castling(std::vector<Move>& moves, Square from, Color side, const Board& board) {
// TODO: These Castling utilities must be moved out of the KingMoveGenerator namespace
// as it is common code with RookMoveGenerator
Square kingside_rook = (side == Color::WHITE) ? Squares::H1 : Squares::H8;
Square queenside_rook = (side == Color::WHITE) ? Squares::A1 : Squares::A8;

// Kingside castling
const bool can_castle_kingside = KingMoveGenerator::can_castle_kingside(board, side) && (from == kingside_rook);
if (can_castle_kingside) {
Square to = (side == Color::WHITE) ? Squares::F1 : Squares::F8;
moves.emplace_back(from, to, std::nullopt, false, false, true);
}

// Queenside castling
const bool can_castle_queenside = KingMoveGenerator::can_castle_queenside(board, side) && (from == queenside_rook);
if (can_castle_queenside) {
Square to = (side == Color::WHITE) ? Squares::D1 : Squares::D8;
moves.emplace_back(from, to, std::nullopt, false, false, true);
}
}

Bitboard RookMoveGenerator::north_ray(Square from, const Bitboard& occupied) {
Bitboard n_ray = Lookups::ROOK_NORTH_ATTACKS[from.value()];
Bitboard blockers = n_ray & occupied;

// Closest blocker (N goes up towards msb, so use lsb)
std::optional<Square> first_blocker = blockers.lsb();

if (first_blocker.has_value()) {
Square blocker_square = first_blocker.value();
const Bitboard& beyond_blocker = Lookups::ROOK_NORTH_ATTACKS[blocker_square.value()];
n_ray &= ~beyond_blocker;
}

return n_ray;
}

Bitboard RookMoveGenerator::south_ray(Square from, const Bitboard& occupied) {
Bitboard s_ray = Lookups::ROOK_SOUTH_ATTACKS[from.value()];
Bitboard blockers = s_ray & occupied;

// Closest blocker (S goes down towards lsb, so use msb)
std::optional<Square> first_blocker = blockers.msb();

if (first_blocker.has_value()) {
Square blocker_square = first_blocker.value();
const Bitboard& beyond_blocker = Lookups::ROOK_SOUTH_ATTACKS[blocker_square.value()];
s_ray &= ~beyond_blocker;
}

return s_ray;
}

Bitboard RookMoveGenerator::east_ray(Square from, const Bitboard& occupied) {
Bitboard e_ray = Lookups::ROOK_EAST_ATTACKS[from.value()];
Bitboard blockers = e_ray & occupied;

// Closest blocker (E goes twoards msb, so use lsb)
std::optional<Square> first_blocker = blockers.lsb();

if (first_blocker.has_value()) {
Square blocker_square = first_blocker.value();
const Bitboard& beyond_blocker = Lookups::ROOK_EAST_ATTACKS[blocker_square.value()];
e_ray &= ~beyond_blocker;
}

return e_ray;
}

Bitboard RookMoveGenerator::west_ray(Square from, const Bitboard& occupied) {
Bitboard w_ray = Lookups::ROOK_WEST_ATTACKS[from.value()];
Bitboard blockers = w_ray & occupied;

// Closest blocker (W goes towards lsb, so use msb)
std::optional<Square> first_blocker = blockers.msb();

if (first_blocker.has_value()) {
Square blocker_square = first_blocker.value();
const Bitboard& beyond_blocker = Lookups::ROOK_WEST_ATTACKS[blocker_square.value()];
w_ray &= ~beyond_blocker;
}

return w_ray;
}
32 changes: 26 additions & 6 deletions tests/bitbishop/helpers/moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,39 @@ int count_quiet_moves(const std::vector<Move>& moves) {
}

/**
* @brief Counts kingside castling moves for the given side.
* @brief Counts kingside castling moves for the king for the given side.
*/
int count_kingside_castling(const std::vector<Move>& moves, Color side) {
int count_king_kingside_castling(const std::vector<Move>& moves, Color side) {
Square from = (side == Color::WHITE) ? Squares::E1 : Squares::E8;
Square target = (side == Color::WHITE) ? Squares::G1 : Squares::G8;
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target; });
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target && m.from == from; });
}

/**
* @brief Counts queenside castling moves for the given side.
* @brief Counts queenside castling moves for the king for the given side.
*/
int count_queenside_castling(const std::vector<Move>& moves, Color side) {
int count_king_queenside_castling(const std::vector<Move>& moves, Color side) {
Square from = (side == Color::WHITE) ? Squares::E1 : Squares::E8;
Square target = (side == Color::WHITE) ? Squares::C1 : Squares::C8;
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target; });
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target && m.from == from; });
}

/**
* @brief Counts kingside castling moves for the rook for the given side.
*/
int count_rook_kingside_castling(const std::vector<Move>& moves, Color side) {
Square from = (side == Color::WHITE) ? Squares::H1 : Squares::H8;
Square target = (side == Color::WHITE) ? Squares::F1 : Squares::F8;
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target && m.from == from; });
}

/**
* @brief Counts queenside castling moves for the rook for the given side.
*/
int count_rook_queenside_castling(const std::vector<Move>& moves, Color side) {
Square from = (side == Color::WHITE) ? Squares::A1 : Squares::A8;
Square target = (side == Color::WHITE) ? Squares::D1 : Squares::D8;
return count_if(moves, [&](const Move& m) { return m.is_castling && m.to == target && m.from == from; });
}

/**
Expand Down
Loading