Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/linux_build_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:

strategy:
matrix:
preset: [ clang_debug, clang_release ]
preset: [ clang_release ]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows_build_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

strategy:
matrix:
preset: [ msvc_debug, msvc_release ]
preset: [ msvc_release ]

steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 6 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@
"description": "Debug build with debug symbols and no optimizations",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_COMPILER": "clang++"
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_C_FLAGS_DEBUG": "-O0 -g -fno-inline -fno-omit-frame-pointer",
"CMAKE_CXX_FLAGS_DEBUG": "-O0 -g -fno-inline -fno-inline-functions -fno-omit-frame-pointer",
"CMAKE_EXE_LINKER_FLAGS_DEBUG": "-Wl,-force_load,${sourceDir}/build/${presetName}/src/bitbishop/libBitbishop.a",
"CMAKE_SHARED_LINKER_FLAGS_DEBUG": "-Wl,-no_dead_strip",
"CMAKE_MODULE_LINKER_FLAGS_DEBUG": "-Wl,-no_dead_strip"
}
},
{
Expand Down
2 changes: 1 addition & 1 deletion include/bitbishop/attacks/generate_attacks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
* @param enemy The side whose attacks are to be generated.
* @return A bitboard of all squares attacked by the given side.
*/
Bitboard generate_attacks(const Board& board, Color enemy) {
inline Bitboard generate_attacks(const Board& board, Color enemy) {
Bitboard attacks = Bitboard::Zeros();

Bitboard occupied_no_king = board.occupied() ^ board.king(ColorUtil::opposite(enemy));
Expand Down
12 changes: 1 addition & 11 deletions include/bitbishop/bitboard.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,17 +136,7 @@ class Bitboard {
*
* The output starts from rank 8 down to rank 1.
*/
void print() const {
using namespace Const;

for (int rank = RANK_8_IND; rank >= RANK_1_IND; --rank) {
for (int file = FILE_A_IND; file <= FILE_H_IND; ++file) {
const Square square(file, rank);
std::cout << (test(square) ? "1 " : ". ");
}
std::cout << "\n";
}
}
void print() const;

constexpr bool operator==(const Bitboard& other) const { return m_bb == other.m_bb; }
constexpr bool operator!=(const Bitboard& other) const { return m_bb != other.m_bb; }
Expand Down
36 changes: 36 additions & 0 deletions include/bitbishop/move.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,42 @@ struct Move {
bool is_en_passant; ///< True if the move is an en passant capture.
bool is_castling; ///< True if the move is a castling move (kingside or queenside).

/**
* @brief Converts move to UCI notation.
* @return String in UCI format (e.g., "e2e4", "e7e8q")
*/
[[nodiscard]] std::string to_uci() const {
static constexpr std::size_t MAX_NB_CHARS_IN_UCI_MOVE_REPR = 5;

std::string uci;
uci.reserve(MAX_NB_CHARS_IN_UCI_MOVE_REPR);

uci += from.to_string();
uci += to.to_string();

using namespace Squares;
if (promotion) {
switch (promotion.value().type()) {
case Piece::Type::QUEEN:
uci += 'q';
break;
case Piece::Type::ROOK:
uci += 'r';
break;
case Piece::Type::BISHOP:
uci += 'b';
break;
case Piece::Type::KNIGHT:
uci += 'n';
break;
default:
break;
}
}

return uci;
}

/**
* @brief Creates a normal (non-special) move.
* @param from The starting square of the move.
Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/bishop_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
* - This function assumes that @p check_mask and @p pins have already been
* computed for the current position.
*/
void generate_bishop_legal_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& check_mask,
const PinResult& pins) {
inline void generate_bishop_legal_moves(std::vector<Move>& moves, const Board& board, Color us,
const Bitboard& check_mask, const PinResult& pins) {
const Bitboard own = board.friendly(us);
const Bitboard enemy = board.enemy(us);
const Bitboard occupied = board.occupied();
Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/castling_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
* @param checkers Bitboard of pieces currently checking the king
* @param enemy_attacks Bitboard of squares attacked by the opponent
*/
void generate_castling_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& checkers,
const Bitboard& enemy_attacks) {
inline void generate_castling_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& checkers,
const Bitboard& enemy_attacks) {
using namespace Squares;

if (checkers.any()) {
Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/king_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
#include <utility>
#include <vector>

void generate_legal_king_moves(std::vector<Move>& moves, const Board& board, Color us, Square king_sq,
const Bitboard& enemy_attacks) {
inline void generate_legal_king_moves(std::vector<Move>& moves, const Board& board, Color us, Square king_sq,
const Bitboard& enemy_attacks) {
const Bitboard own = board.friendly(us);
const Bitboard enemy = board.enemy(us);

Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/knight_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#include <vector>

// pinned knights cannot move at all due to knight's l-shaped move geometry
void generate_knight_legal_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& check_mask,
const PinResult& pins) {
inline void generate_knight_legal_moves(std::vector<Move>& moves, const Board& board, Color us,
const Bitboard& check_mask, const PinResult& pins) {
const Bitboard own = board.friendly(us);
const Bitboard enemy = board.enemy(us);
Bitboard knights = board.knights(us);
Expand Down
7 changes: 4 additions & 3 deletions include/bitbishop/movegen/legal_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@
*
* @param moves Vector to append generated legal moves to
* @param board Current board position
* @param us Color of the side to generate moves for
*
* @note The move list is appended to; it is not cleared by this function.
* @note Assumes the board position is internally consistent and legal.
*/
void generate_legal_moves(std::vector<Move>& moves, const Board& board, Color us) {
Square king_sq = board.king_square(us).value();
inline void generate_legal_moves(std::vector<Move>& moves, const Board& board) {
Color us = board.get_state().m_is_white_turn ? Color::WHITE : Color::BLACK;
Color them = ColorUtil::opposite(us);

Square king_sq = board.king_square(us).value();

Bitboard checkers = compute_checkers(board, king_sq, them);
Bitboard check_mask = compute_check_mask(king_sq, checkers, board);
PinResult pins = compute_pins(king_sq, board, us);
Expand Down
14 changes: 3 additions & 11 deletions include/bitbishop/movegen/pawn_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ constexpr bool can_capture_en_passant(Square from, Square epsq, Color side) noex
* @param side Color of the promoting pawn
* @param is_capture Whether the promotion involves capturing an enemy piece
*/
void add_pawn_promotions(std::vector<Move>& moves, Square from, Square to, Color side, bool capture) {
inline void add_pawn_promotions(std::vector<Move>& moves, Square from, Square to, Color side, bool capture) {
const auto& promotion_pieces = (side == Color::WHITE) ? WHITE_PROMOTIONS : BLACK_PROMOTIONS;

for (auto piece : promotion_pieces) {
Expand Down Expand Up @@ -224,14 +224,6 @@ inline void generate_en_passant(std::vector<Move>& moves, Square from, Color us,

const Color them = ColorUtil::opposite(us);
Square epsq = epsq_opt.value();
auto bb = Bitboard(epsq);
bb &= check_mask;
bb &= pin_mask;

if (!bb) {
return;
}

Square cap_sq = (us == Color::WHITE) ? Square(epsq.flat_index() - Const::BOARD_WIDTH)
: Square(epsq.flat_index() + Const::BOARD_WIDTH);

Expand Down Expand Up @@ -260,8 +252,8 @@ inline void generate_en_passant(std::vector<Move>& moves, Square from, Color us,
* @param check_mask Bitboard mask to restrict moves under check
* @param pins Pin result structure indicating which pieces are pinned
*/
void generate_pawn_legal_moves(std::vector<Move>& moves, const Board& board, Color us, Square king_sq,
const Bitboard& check_mask, const PinResult& pins) {
inline void generate_pawn_legal_moves(std::vector<Move>& moves, const Board& board, Color us, Square king_sq,
const Bitboard& check_mask, const PinResult& pins) {
const Bitboard enemy = board.enemy(us);
const Bitboard occupied = board.occupied();
Bitboard pawns = board.pawns(us);
Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/queen_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
* computed for the current position.
* - Promotions, en passant, and castling are not applicable to queen moves.
*/
void generate_queen_legal_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& check_mask,
const PinResult& pins) {
inline void generate_queen_legal_moves(std::vector<Move>& moves, const Board& board, Color us,
const Bitboard& check_mask, const PinResult& pins) {
const Bitboard own = board.friendly(us);
const Bitboard enemy = board.enemy(us);
const Bitboard occupied = board.occupied();
Expand Down
4 changes: 2 additions & 2 deletions include/bitbishop/movegen/rook_moves.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
* computed for the current position.
* - Promotions, en passant, and castling are not applicable to rook moves.
*/
void generate_rook_legal_moves(std::vector<Move>& moves, const Board& board, Color us, const Bitboard& check_mask,
const PinResult& pins) {
inline void generate_rook_legal_moves(std::vector<Move>& moves, const Board& board, Color us,
const Bitboard& check_mask, const PinResult& pins) {
const Bitboard own = board.friendly(us);
const Bitboard enemy = board.enemy(us);
const Bitboard occupied = board.occupied();
Expand Down
80 changes: 15 additions & 65 deletions include/bitbishop/moves/position.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,94 +5,44 @@
#include <vector>

/**
* @brief Represents a game position, including board state and move history.
* @brief Represents a chess position and move history.
*
* A Position owns a Board instance and provides the ability to apply and revert moves.
* It is primarily responsible for maintaining a consistent board state across move
* generation, execution, and undo operations.
*
* Key responsibilities:
* - Track the current board state
* - Execute moves and record them for later rollback
* - Revert moves safely via the stored execution history
*
* The class is intentionally non-copyable and non-default-constructible to avoid
* ambiguous or partially-initialized board states. A fully-initialized Board must be
* provided at construction time.
* Tracks a Board (by reference) and allows applying/reverting moves. The Position
* itself does not own the Board; it modifies the provided board safely using
* MoveExecution history.
*/
class Position {
private:
/**
* @brief The current board state of the position.
*
* This board is updated every time a move is applied or reverted. The Position
* class provides controlled access to this board to ensure move history and
* board state remain synchronized.
*/
Board board;
/** Reference to the board being managed */
Board& board;

/**
* @brief History of executed moves, including auxiliary information.
*
* Each entry contains the data necessary to revert a move accurately, including
* captured pieces, castling rights changes, en passant information, etc.
*
* Moves are pushed when applied and popped when reverted.
*/
/** History of executed moves for rollback */
std::vector<MoveExecution> move_execution_history;

public:
Position() = delete; ///< Must be initialized with a Board
Position(Board&) = delete; ///< Prevent accidental copy from lvalue Board

/**
* @brief Constructs a Position from an initial board state.
*
* Although this is declared as a move constructor, Board is trivially copyable,
* so the actual internal assignment is effectively a copy. This constructor
* ensures that the Position starts with a valid, fully defined board.
*
* @param initial The initial board state (rvalue reference)
*/
explicit Position(Board&& initial) : board(initial) {
; // board is trivially copyable, no move, just a copy behind the scenes
}
Position() = delete; ///< Default construction not allowed
Position(Board& board) : board(board) { ; }

/**
* @brief Applies a move to the current position.
*
* This method:
* - Computes the full execution details of the move
* - Updates the board state accordingly
* - Stores the MoveExecution record so the move can be reverted later
*
* @param move The move to apply
* @brief Applies a move to the board and records it for undo.
* @param move Move to apply
*/
void apply_move(const Move& move);

/**
* @brief Reverts the last applied move.
*
* Pops the most recent entry from the move execution history and restores the
* board to its previous state. Calling this function when no moves have been
* applied have no effect..
* Safe to call only if can_unmake() returns true.
*/
void revert_move();

/**
* @brief Returns a const reference to the current board.
*
* Provides read-only access to the board; callers must not attempt to mutate
* the board directly to avoid desynchronizing board state and move history.
*
* @return Const reference to the current Board
* @brief Returns the current board (read-only).
*/
[[nodiscard]] const Board& get_board() const { return board; }

/**
* @brief Checks whether a previously applied move can be reverted.
*
* @return true if at least one move has been applied, false otherwise
* @brief Checks if a move can be reverted.
* @return true if move history is non-empty
*/
[[nodiscard]] bool can_unmake() const { return !move_execution_history.empty(); }
};
29 changes: 29 additions & 0 deletions include/bitbishop/tools/perft.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <bitbishop/board.hpp>

namespace Tools {

/**
* @brief Perft (Performance Test) debug function to walk through the move generation tree
* of legal moves of a certain depth.
*
* @param board Reference to the board on which perft must be executed
* @param depth Recursion depth
*
* @see https://www.chessprogramming.org/Perft
*/
uint64_t perft(Board& board, std::size_t depth);

/**
* @brief Perft Divide (Performance Test) debug function to walk through the move generation tree
* of legal moves of a certain depth and print move count for each move.
*
* @param board Reference to the board on which perft divide must be executed
* @param depth Recursion depth
*
* @see https://www.chessprogramming.org/Perft
*/
void perft_divide(Board& board, std::size_t depth);

} // namespace Tools
15 changes: 15 additions & 0 deletions src/bitbishop/bitboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include <bitbishop/bitboard.hpp>

void Bitboard::print() const {
// defined in cpp to allow usage inside debugger

using namespace Const;

for (int rank = RANK_8_IND; rank >= RANK_1_IND; --rank) {
for (int file = FILE_A_IND; file <= FILE_H_IND; ++file) {
const Square square(file, rank);
std::cout << (test(square) ? "1 " : ". ");
}
std::cout << "\n";
}
}
Loading