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
32 changes: 32 additions & 0 deletions .github/workflows/doxygen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Doxygen docs

on:
push:
branches: [ main ]

jobs:
build-docs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false

- name: Install dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y doxygen graphviz

- name: Build docs
working-directory: ${{ github.workspace }}
run: |
if [ ! -f Doxyfile ]; then echo "Doxyfile not found in repo root"; exit 1; fi
doxygen Doxyfile

- name: Upload docs artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: doxygen-html
path: doc/html
retention-days: 7
10 changes: 6 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
commit_sha: ${{ steps.auto-commit.outputs.commit_hash }}

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
ref: ${{ github.head_ref }}

persist-credentials: false
- name: Install clang-format
run: sudo apt-get update && sudo apt-get install -y clang-format

Expand All @@ -31,7 +31,7 @@ jobs:
- name: Commit and push changes
id: auto-commit
if: github.actor != 'github-actions[bot]' && github.event.pull_request.head.repo.full_name == github.repository
uses: stefanzweifel/git-auto-commit-action@v7
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9

build:
needs: format
Expand All @@ -52,7 +52,9 @@ jobs:
error_mode: ASSERT

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false

- name: Set build dir
id: vars
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
!.gitattributes
!.github/**
!.github/
!Doxyfile
!doc/
# Ignore build/editor junk
build/
out/
Expand Down
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
endif()

target_compile_definitions(chesslib PUBLIC _CHESSLIB_ERROR_MODE_${CHESSLIB_ERROR_MODE})
target_compile_definitions(chesslib INTERFACE
"$<$<CONFIG:Release>:NDEBUG>"
"$<$<CONFIG:RelWithDebInfo>:NDEBUG>"
"$<$<CONFIG:MinSizeRel>:NDEBUG>"
)
# --- Enable CTest integration ---
include(CTest)

Expand Down
20 changes: 20 additions & 0 deletions Doxyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Doxyfile 1.9.1
PROJECT_NAME = "chesslib"
OUTPUT_DIRECTORY = doc
INPUT = .
RECURSIVE = YES
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
GENERATE_HTML = YES
GENERATE_LATEX = NO
GENERATE_XML = YES
XML_OUTPUT = xml
QUIET = YES
GENERATE_TREEVIEW = YES
INLINE_SOURCES = YES
STRIP_FROM_PATH = "$(PWD)"
FILE_PATTERNS = *.h *.hpp *.cpp
EXCLUDE = build
WARNINGS = YES
WARN_IF_UNDOCUMENTED = YES
WARN_AS_ERROR = YES
147 changes: 140 additions & 7 deletions attacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ static constexpr Bitboard rank_mask(Square sq) { return attacks::MASK_RANK[rank_
/// @brief File mask for a square.
static constexpr Bitboard file_mask(Square sq) { return attacks::MASK_FILE[file_of(sq)]; }

/// @brief Rook attacks via hyperbola quintessence.
/**
* @brief Computes all squares a rook can attack from a given position.
*
* @param sq The rook's square.
* @param occ Board occupancy.
* @return Bitboard of attacked squares.
*/
static constexpr Bitboard _HyperbolaRookAttacks(Square sq, Bitboard occ) {
Bitboard slider = 1ULL << sq;
Bitboard r_mask = rank_mask(sq);
Expand All @@ -102,6 +108,115 @@ static constexpr Bitboard _HyperbolaRookAttacks(Square sq, Bitboard occ) {
}
} // namespace chess::_chess
namespace chess::attacks {

// Precompute rays for each square and each of 8 directions.
const std::array<std::array<Bitboard, 64>, 8> RAYS = []() {
std::array<std::array<Bitboard, 64>, 8> r{};
for (int dir = 0; dir < 8; ++dir) {
for (Square sq = SQ_A1; sq < SQ_NONE; ++sq) {
Bitboard cur = 1ULL << sq;
Bitboard accum = 0ULL;
while (true) {
switch (dir) {
case RD_NORTH:
cur = cur << 8;
break;
case RD_SOUTH:
cur = cur >> 8;
break;
case RD_EAST:
cur = (cur & ~MASK_FILE[FILE_H]) << 1;
break;
case RD_WEST:
cur = (cur & ~MASK_FILE[FILE_A]) >> 1;
break;
case RD_NE:
cur = (cur & ~MASK_FILE[FILE_H]) << 9;
break;
case RD_NW:
cur = (cur & ~MASK_FILE[FILE_A]) << 7;
break;
case RD_SE:
cur = (cur & ~MASK_FILE[FILE_H]) >> 7;
break;
case RD_SW:
cur = (cur & ~MASK_FILE[FILE_A]) >> 9;
break;
}
if (!cur)
break;
accum |= cur;
}
r[dir][sq] = accum;
}
}
return r;
}();

#ifdef __BMI2__
/// @brief Software fallback for the PEXT instruction.
/// @details Used during constant evaluation when BMI2 is unavailable.
/// @param val The value to compress.
/// @param mask The bit mask.
/**
* @brief Extracts bits from a value according to a mask and compacts them.
*
* For each set bit position in `mask`, extracts the corresponding bit from `val`
* and places it into the result at consecutive positions, starting from bit 0.
*
* @param val The value to extract bits from.
* @param mask A mask indicating which bit positions in `val` to extract.
* @return A compacted bitboard containing only the extracted bits.
*/
constexpr uint64_t software_pext_u64(uint64_t val, uint64_t mask) {
uint64_t result = 0;
uint64_t bit_position = 0;

for (uint64_t bit = 1; bit != 0; bit <<= 1) {
if (mask & bit) {
if (val & bit) {
result |= 1ULL << bit_position;
}
++bit_position;
}
}
return result;
}

/// @brief Magic structure for PEXT-based magic bitboards (BMI2 path).
struct Magic {
Bitboard mask; ///< Relevant occupancy mask.
int index; ///< Starting index into the attack table.
/**
* @brief Extracts relevant occupancy bits for magic bitboard indexing.
*
* @param b Occupancy bitboard.
* @return Compressed index into the magic attack table.
*/
constexpr Bitboard operator()(Bitboard b) const {
if (is_constant_evaluated()) {
return software_pext_u64(b, mask);
} else {
return _pext_u64(b, mask);
}
}
};
#else
/// @brief Magic structure for classical (multiply-and-shift) magic bitboards.
struct Magic {
Bitboard mask; ///< Relevant occupancy mask.
Bitboard magic; ///< Magic multiplier.
size_t index; ///< Starting index into the attack table.
Bitboard shift; ///< Right-shift amount.
/**
* @brief Converts an occupancy pattern to an attack table index.
*
* @return Index for accessing the precomputed attack bitboard.
*/
constexpr Bitboard operator()(Bitboard b) const { return (((b & mask)) * magic) >> shift; }
};
#endif

#ifndef GENERATE_AT_RUNTIME
#define _POSSIBLY_CONSTEXPR constexpr
#else
Expand Down Expand Up @@ -153,6 +268,12 @@ _POSSIBLY_CONSTEXPR std::array<uint64_t, 64> BishopMagics = {
/// @tparam IsBishop true for bishop, false for rook.
/// @return Pair of (magic table, attack table).
template <auto AttackFunc, size_t TableSize, bool IsBishop>
/**
* @brief Generates magic bitboard tables for fast attack computation.
*
* @return A pair containing the Magic entry table (64 entries, one per square) and the corresponding precomputed attack
* bitboards table.
*/
_POSSIBLY_CONSTEXPR std::pair<std::array<Magic, 64>, std::array<Bitboard, TableSize>> generate_magic_table() {
std::array<Magic, 64> table{};
std::array<Bitboard, TableSize> attacks{};
Expand All @@ -165,7 +286,6 @@ _POSSIBLY_CONSTEXPR std::pair<std::array<Magic, 64>, std::array<Bitboard, TableS

Bitboard mask = AttackFunc(static_cast<Square>(sq), 0) & ~edges;
int bits = popcount(mask);
int shift = 64 - bits;
Bitboard magic = 0;
if constexpr (IsBishop)
magic = BishopMagics[sq];
Expand All @@ -176,7 +296,7 @@ _POSSIBLY_CONSTEXPR std::pair<std::array<Magic, 64>, std::array<Bitboard, TableS
entry.mask = mask;
#ifndef __BMI2__
entry.magic = magic;
entry.shift = shift;
entry.shift = 64 - bits;
#endif
entry.index = offset;

Expand All @@ -203,14 +323,27 @@ _POSSIBLY_CONSTEXPR std::pair<std::array<Magic, 64>, std::array<Bitboard, 0x1900
_POSSIBLY_CONSTEXPR std::array<Magic, 64> RookTable = rookData.first;
_POSSIBLY_CONSTEXPR std::array<Bitboard, 0x19000> RookAttacks = rookData.second;

/// @brief Look up bishop attacks from the precomputed magic table.
/**
* @brief Returns the attack bitboard for a bishop on the given square.
*
* @param sq The square where the bishop is located.
* @param occupied The occupied squares that block the bishop's attack paths.
* @return Bitboard with bits set for each square the bishop attacks.
*/
[[nodiscard]] Bitboard bishop(Square sq, Bitboard occupied) {
return BishopAttacks[BishopTable[(int)sq].index + BishopTable[(int)sq](occupied)];
const auto &entry = BishopTable[(int)sq];
return BishopAttacks[entry.index + entry(occupied)];
}

/// @brief Look up rook attacks from the precomputed magic table.
/**
* @brief Look up rook attacks from the precomputed magic table.
* @param sq The square where the rook is located.
* @param occupied A bitboard representing occupied squares.
* @return A bitboard of squares the rook can attack.
*/
[[nodiscard]] Bitboard rook(Square sq, Bitboard occupied) {
return RookAttacks[RookTable[(int)sq].index + RookTable[(int)sq](occupied)];
const auto &entry = RookTable[(int)sq];
return RookAttacks[entry.index + entry(occupied)];
}
} // namespace chess::attacks
namespace chess::movegen {
Expand Down
59 changes: 13 additions & 46 deletions attacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,51 +114,6 @@ constexpr Bitboard MASK_FILE[8] = {
0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808,
0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080,
};

#ifdef __BMI2__
/// @brief Software fallback for the PEXT instruction.
/// @details Used during constant evaluation when BMI2 is unavailable.
/// @param val The value to compress.
/// @param mask The bit mask.
/// @return Compressed bits.
constexpr uint64_t software_pext_u64(uint64_t val, uint64_t mask) {
uint64_t result = 0;
uint64_t bit_position = 0;

for (uint64_t bit = 1; bit != 0; bit <<= 1) {
if (mask & bit) {
if (val & bit) {
result |= 1ULL << bit_position;
}
++bit_position;
}
}
return result;
}

/// @brief Magic structure for PEXT-based magic bitboards (BMI2 path).
struct Magic {
Bitboard mask; ///< Relevant occupancy mask.
int index; ///< Starting index into the attack table.
constexpr Bitboard operator()(Bitboard b) const {
if (is_constant_evaluated()) {
return software_pext_u64(b, mask);
} else {
return _pext_u64(b, mask);
}
}
};
#else
/// @brief Magic structure for classical (multiply-and-shift) magic bitboards.
struct Magic {
Bitboard mask; ///< Relevant occupancy mask.
Bitboard magic; ///< Magic multiplier.
size_t index; ///< Starting index into the attack table.
Bitboard shift; ///< Right-shift amount.
constexpr Bitboard operator()(Bitboard b) const { return (((b & mask)) * magic) >> shift; }
};
#endif

} // namespace chess::attacks
namespace chess::attacks {

Expand Down Expand Up @@ -302,7 +257,13 @@ template <Color c> [[nodiscard]] constexpr Bitboard pawn(const Bitboard pawns) {
/// @param sq Square.
/// @param occupied Occupancy bitboard.
/// @return Bitboard of squares attacked.
template <PieceType pt> [[nodiscard]] inline Bitboard slider(Square sq, Bitboard occupied) {
template <PieceType pt> /**
* Computes attack squares for a slider piece.
* @param sq Square the piece occupies.
* @param occupied Squares currently occupied on the board.
* @return Bitboard of squares attacked by the piece.
*/
[[nodiscard]] inline Bitboard slider(Square sq, Bitboard occupied) {
static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK || pt == PieceType::QUEEN, "PieceType must be a slider!");

if constexpr (pt == PieceType::BISHOP)
Expand All @@ -313,4 +274,10 @@ template <PieceType pt> [[nodiscard]] inline Bitboard slider(Square sq, Bitboard
return queen(sq, occupied);
}

// Ray direction indices for precomputed ray bitboards
enum RayDir : int { RD_NORTH = 0, RD_SOUTH = 1, RD_EAST = 2, RD_WEST = 3, RD_NE = 4, RD_NW = 5, RD_SE = 6, RD_SW = 7 };

/// @brief Precomputed rays from each square in 8 directions.
extern const std::array<std::array<Bitboard, 64>, 8> RAYS;

} // namespace chess::attacks
Loading