Skip to content
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@ CheckOptions:
- key: readability-identifier-length.IgnoredParameterNames
value: "^(sq|to|from|bb|us)$"
- key: readability-identifier-length.IgnoredLoopCounterNames
value: "^(r|f)$"
value: "^(r|f|i)$"
7 changes: 6 additions & 1 deletion include/bitbishop/bitboard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ class Bitboard {
constexpr Bitboard(uint64_t value) : m_bb(value) {}

/** @brief Constructs a bitboard from another bitboard by copy. */
constexpr Bitboard(const Bitboard& bitboard) : m_bb(bitboard.value()) {}
constexpr Bitboard(const Bitboard& bitboard) noexcept = default;

/** @brief Move-constructs a bitboard. */
constexpr explicit Bitboard(Bitboard&& other) noexcept = default;

/** @brief Constructs a bitboard with the given square being the only bit set to one. */
constexpr Bitboard(Square square) : m_bb(0ULL) { set(square); }
Expand Down Expand Up @@ -166,6 +169,8 @@ class Bitboard {
return *this;
}
constexpr operator bool() const noexcept { return m_bb != 0ULL; }
constexpr Bitboard& operator=(const Bitboard& other) noexcept = default;
constexpr Bitboard& operator=(Bitboard&& other) noexcept = default;

/**
* @brief Counts the number of set bits in the bitboard.
Expand Down
63 changes: 44 additions & 19 deletions include/bitbishop/board.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,42 @@

#include <bitbishop/bitboard.hpp>
#include <bitbishop/color.hpp>
#include <bitbishop/move.hpp>
#include <bitbishop/piece.hpp>
#include <bitbishop/square.hpp>
#include <optional>
#include <vector>

struct BoardState {
bool m_is_white_turn; ///< True if it is White's turn
std::optional<Square> m_en_passant_sq; ///< En passant target square, or nullopt if none

// Castling abilities
bool m_white_castle_kingside; ///< White may castle kingside
bool m_white_castle_queenside; ///< White may castle queenside
bool m_black_castle_kingside; ///< Black may castle kingside
bool m_black_castle_queenside; ///< Black may castle queenside

// 50-move rule state
int m_halfmove_clock; ///< Counts halfmoves since last pawn move or capture

// Move number (starts at 1, incremented after Black’s move)
int m_fullmove_number;

bool operator==(const BoardState& other) const {
if (this == &other) {
return true;
}
return m_is_white_turn == other.m_is_white_turn && m_en_passant_sq == other.m_en_passant_sq &&
m_white_castle_kingside == other.m_white_castle_kingside &&
m_white_castle_queenside == other.m_white_castle_queenside &&
m_black_castle_kingside == other.m_black_castle_kingside &&
m_black_castle_queenside == other.m_black_castle_queenside && m_halfmove_clock == other.m_halfmove_clock &&
m_fullmove_number == other.m_fullmove_number;
}

bool operator!=(const BoardState& other) const { return !(*this == other); }
};

/**
* @class Board
Expand All @@ -31,20 +64,7 @@ class Board {
Bitboard m_b_pawns, m_b_rooks, m_b_bishops, m_b_knights, m_b_king, m_b_queens;

// Game state
bool m_is_white_turn; ///< True if it is White's turn
std::optional<Square> m_en_passant_sq; ///< En passant target square, or nullopt if none

// Castling abilities
bool m_white_castle_kingside; ///< White may castle kingside
bool m_white_castle_queenside; ///< White may castle queenside
bool m_black_castle_kingside; ///< Black may castle kingside
bool m_black_castle_queenside; ///< Black may castle queenside

// 50-move rule state
int m_halfmove_clock; ///< Counts halfmoves since last pawn move or capture

// Move number (starts at 1, incremented after Black’s move)
int m_fullmove_number;
BoardState m_state;

public:
/**
Expand All @@ -56,7 +76,8 @@ class Board {
*/
Board();

Board(const Board&) = default;
Board(const Board&) noexcept = default;
explicit Board(Board&& other) noexcept = default;

/**
* @brief Constructs a board from a FEN string.
Expand Down Expand Up @@ -221,6 +242,9 @@ class Board {
*/
[[nodiscard]] Bitboard friendly(Color side) const { return (side == Color::WHITE) ? white_pieces() : black_pieces(); }

[[nodiscard]] BoardState get_state() const { return m_state; }
void set_state(BoardState state) { m_state = state; }

/**
* @brief Returns the current en passant target square, if any.
*
Expand All @@ -234,15 +258,15 @@ class Board {
* }
* @endcode
*/
[[nodiscard]] std::optional<Square> en_passant_square() const noexcept { return m_en_passant_sq; }
[[nodiscard]] std::optional<Square> en_passant_square() const noexcept { return m_state.m_en_passant_sq; }

/**
* @brief Checks if the given side has kingside castling rights.
* @param side The color corresponding to the side to move (Color::WHITE or Color::BLACK).
* @return true if kingside castling rights is available, false otherwise
*/
[[nodiscard]] bool has_kingside_castling_rights(Color side) const {
return (side == Color::WHITE) ? m_white_castle_kingside : m_black_castle_kingside;
return (side == Color::WHITE) ? m_state.m_white_castle_kingside : m_state.m_black_castle_kingside;
}

/**
Expand All @@ -251,7 +275,7 @@ class Board {
* @return true if queenside castling rights is available, false otherwise
*/
[[nodiscard]] bool has_queenside_castling_rights(Color side) const {
return (side == Color::WHITE) ? m_white_castle_queenside : m_black_castle_queenside;
return (side == Color::WHITE) ? m_state.m_white_castle_queenside : m_state.m_black_castle_queenside;
}

/**
Expand Down Expand Up @@ -282,7 +306,8 @@ class Board {
*/
[[nodiscard]] bool can_castle_queenside(Color side) const noexcept;

Board& operator=(const Board& other) = default;
Board& operator=(const Board& other) noexcept = default;
Board& operator=(Board&& other) noexcept = default;

/**
* @brief Checks if two boards represent the same chess position.
Expand Down
63 changes: 63 additions & 0 deletions include/bitbishop/moves/move_builder.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <bitbishop/moves/move_execution.hpp>

/**
* @brief Constructs a sequence of low-level effects for a move.
*
* MoveBuilder takes a high-level Move and a Board, and generates a
* MoveExecution that includes piece placements, removals, promotions,
* castling, en passant, and board state updates.
*/
class MoveBuilder {
private:
MoveExecution effects; ///< Stores the sequence of effects for the move

const Move& move; ///< Reference to the move being built
const Board& board; ///< Reference to the board on which the move occurs

Piece final_piece = Pieces::WHITE_KING; ///< Piece to place at destination
Piece moving_piece = Pieces::WHITE_KING; ///< Piece moving from origin
std::optional<Piece> opt_captured_piece; ///< Optional captured piece
BoardState prev_state, next_state; ///< Board states before and after the move

public:
MoveBuilder() = delete;

/**
* @brief Constructs a MoveBuilder.
* @param board Board on which the move is applied
* @param move Move to build
*/
MoveBuilder(const Board& board, const Move& move);

/**
* @brief Generates and returns the MoveExecution.
* @return MoveExecution representing all low-level effects of the move
*/
MoveExecution build();

private:
// Utilities for castling rights
void revoke_castling_if_rook_at(Square sq); ///< Revoke castling if rook moves or is captured
void revoke_castling_if_king_at(Square sq); ///< Revoke castling if king moves

// Board state preparation
void prepare_base_state(); ///< Flip side, reset en passant, update half/full move counters
void prepare_next_state(); ///< Handle castling, en passant, commit state

// Move effect steps
void remove_moving_piece(); ///< Removes moving piece from origin
void place_final_piece(); ///< Places final piece at destination
void handle_regular_capture(); ///< Adds effect for normal captures
void handle_en_passant_capture(); ///< Adds effect for en passant capture
void handle_promotion(); ///< Updates piece if promotion occurs
void handle_rook_castling(); ///< Handles rook movement during castling
void update_castling_rights(); ///< Updates castling rights if king/rook moves or rook captured
void update_en_passant_square(); ///< Sets en passant target square if pawn moved two squares
void commit_state(); ///< Records board state change in effects
void update_half_move_clock(); ///< Updates half-move counter for 50-move rule
void update_full_move_number(); ///< Updates full move number if black has moved
void flip_side_to_move(); ///< Flips active player
void reset_en_passant_square(); ///< Clears en passant square if move doesn't set one
};
58 changes: 58 additions & 0 deletions include/bitbishop/moves/move_effect.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#pragma once

#include <bitbishop/board.hpp>
#include <bitbishop/piece.hpp>
#include <bitbishop/square.hpp>

/**
* @brief Represents a single low-level board modification.
*
* A chess move may consist of multiple effects: placing a piece, removing a
* piece, or updating board state (e.g., castling rights, en passant). Each
* MoveEffect describes exactly one such change and can be applied or reverted.
*/
struct MoveEffect {
/**
* @brief Type of board modification.
*/
enum class Type : uint8_t { Place, Remove, BoardState };

Type type; ///< Effect category
Square square = Squares::A1; ///< Target square (for Place/Remove)
Piece piece = Pieces::WHITE_KING; ///< Piece involved (for Place/Remove)
BoardState prev_state; ///< State before change (for BoardState effect)
BoardState next_state; ///< State after change (for BoardState effect)

/**
* @brief Creates a piece placement effect.
* @param sq Destination square
* @param piece Piece to place
*/
static MoveEffect place(Square sq, Piece piece);

/**
* @brief Creates a piece removal effect.
* @param sq Square to clear
* @param piece Piece being removed
*/
static MoveEffect remove(Square sq, Piece piece);

/**
* @brief Creates a board state update effect.
* @param prev The previous state
* @param next The new state
*/
static MoveEffect state_change(const BoardState& prev, const BoardState& next);

/**
* @brief Applies the effect to the board.
* @param board Board to modify
*/
void apply(Board& board) const;

/**
* @brief Reverts the effect on the board.
* @param board Board to restore
*/
void revert(Board& board) const;
};
38 changes: 38 additions & 0 deletions include/bitbishop/moves/move_execution.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <array>
#include <bitbishop/moves/move_effect.hpp>

/**
* @brief Aggregates the individual effects of a single move.
*
* A chess move may consist of several low-level effects
* (piece placements/removals, board state updates).
* MoveExecution stores these in order, allowing the move
* to be applied and fully reverted.
*/
struct MoveExecution {
static constexpr int MAX_EFFECTS = 6; ///< Maximum number of effects per move

std::array<MoveEffect, MAX_EFFECTS> effects; ///< Ordered list of effects
int count = 0; ///< Number of effects currently stored

/**
* @brief Adds a new effect to the execution.
* @param effect The effect to append
* @warning Exceeding MAX_EFFECTS is undefined behaviour
*/
void add(const MoveEffect& effect);

/**
* @brief Applies all effects in order.
* @param board Board to modify
*/
void apply(Board& board) const;

/**
* @brief Reverts all effects in reverse order.
* @param board Board to restore
*/
void revert(Board& board) const;
};
Loading