From 3b022252e12310b112b6d18487c588a191cd6fda Mon Sep 17 00:00:00 2001
From: winapiadmin <138602885+winapiadmin@users.noreply.github.com>
Date: Sun, 24 May 2026 18:19:27 +0700
Subject: [PATCH 01/35] cleanup comments
---
.github/workflows/test.yml | 2 +-
attacks.cpp | 53 +++--
attacks.h | 351 +++++++++++++++----------------
bitboard.h | 38 +++-
fwd_decl.h | 61 +++++-
movegen.cpp | 25 ++-
movegen.h | 30 ++-
moves_io.cpp | 25 ++-
moves_io.h | 55 ++++-
position.cpp | 232 +++++++++++---------
position.h | 421 +++++++++++++++++++++++--------------
printers.cpp | 21 +-
printers.h | 24 ++-
types.h | 270 ++++++++++++++++--------
zobrist.cpp | 7 +
zobrist.h | 15 +-
16 files changed, 1043 insertions(+), 587 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 8a2ceca..729aefa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,6 +11,7 @@ on:
jobs:
format:
+ if: github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
@@ -30,7 +31,6 @@ 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
build:
diff --git a/attacks.cpp b/attacks.cpp
index ee5a025..a7abe64 100644
--- a/attacks.cpp
+++ b/attacks.cpp
@@ -16,12 +16,17 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+
+/// @file attacks.cpp
+/// @brief Magic-bitboard generation, hyperbola-quintessence helpers, and between-square table.
+
#include "attacks.h"
namespace chess::_chess {
// [INTERNAL]
-// Reverse bits horizontally in 64-bit integer
+/// @brief Reverse bits horizontally in a 64-bit integer.
+/// @details Used by the hyperbola quintessence algorithm.
static constexpr Bitboard reverse(Bitboard b) {
b = (b & 0x5555555555555555ULL) << 1 | ((b >> 1) & 0x5555555555555555ULL);
b = (b & 0x3333333333333333ULL) << 2 | ((b >> 2) & 0x3333333333333333ULL);
@@ -32,6 +37,11 @@ static constexpr Bitboard reverse(Bitboard b) {
return b;
}
+/// @brief Hyperbola quintessence attack computation for a single line.
+/// @param sliderBB Bitboard with the slider's square set.
+/// @param occ Occupancy bitboard.
+/// @param mask Line mask (rank, file, or diagonal).
+/// @return Attacks along the masked line.
static constexpr Bitboard hyp_quint(Bitboard sliderBB, Bitboard occ, Bitboard mask) {
Bitboard occ_masked = occ & mask;
Bitboard left = occ_masked - 2 * sliderBB;
@@ -39,7 +49,7 @@ static constexpr Bitboard hyp_quint(Bitboard sliderBB, Bitboard occ, Bitboard ma
return (left ^ reverse(right)) & mask;
}
-// For Bishop: Mask for diagonal and anti-diagonal
+/// @brief Compute the diagonal mask through a square.
static constexpr Bitboard diag_mask(Square sq) {
int r = rank_of(sq);
int f = file_of(sq);
@@ -53,6 +63,7 @@ static constexpr Bitboard diag_mask(Square sq) {
}
return mask;
}
+/// @brief Compute the anti-diagonal mask through a square.
static constexpr Bitboard antidiag_mask(Square sq) {
int r = rank_of(sq);
int f = file_of(sq);
@@ -68,7 +79,7 @@ static constexpr Bitboard antidiag_mask(Square sq) {
return mask;
}
-// Hyperbola Quintessence for Bishop
+/// @brief Bishop attacks via hyperbola quintessence.
static constexpr Bitboard _HyperbolaBishopAttacks(Square sq, Bitboard occ) {
Bitboard slider = 1ULL << sq;
Bitboard d_mask = diag_mask(sq);
@@ -76,12 +87,13 @@ static constexpr Bitboard _HyperbolaBishopAttacks(Square sq, Bitboard occ) {
return hyp_quint(slider, occ, d_mask) | hyp_quint(slider, occ, ad_mask);
}
-// For Rook: Rank and File Masks
+/// @brief Rank mask for a square.
static constexpr Bitboard rank_mask(Square sq) { return attacks::MASK_RANK[rank_of(sq)]; }
+/// @brief File mask for a square.
static constexpr Bitboard file_mask(Square sq) { return attacks::MASK_FILE[file_of(sq)]; }
-// Hyperbola Quintessence for Rook
+/// @brief Rook attacks via hyperbola quintessence.
static constexpr Bitboard _HyperbolaRookAttacks(Square sq, Bitboard occ) {
Bitboard slider = 1ULL << sq;
Bitboard r_mask = rank_mask(sq);
@@ -134,6 +146,12 @@ _POSSIBLY_CONSTEXPR std::array BishopMagics = {
};
// clang-format on
+
+/// @brief Generate magic-bitboard lookup tables.
+/// @tparam AttackFunc The hyperbola attack function to use.
+/// @tparam TableSize Total number of attack entries.
+/// @tparam IsBishop true for bishop, false for rook.
+/// @return Pair of (magic table, attack table).
template
_POSSIBLY_CONSTEXPR std::pair, std::array> generate_magic_table() {
std::array table{};
@@ -184,34 +202,27 @@ _POSSIBLY_CONSTEXPR std::pair, std::array();
_POSSIBLY_CONSTEXPR std::array RookTable = rookData.first;
_POSSIBLY_CONSTEXPR std::array RookAttacks = rookData.second;
-/**
- * @brief Returns the bishop attacks for a given square
- * @param sq
- * @param occupied
- * @return
- */
+
+/// @brief Look up bishop attacks from the precomputed magic table.
[[nodiscard]] Bitboard bishop(Square sq, Bitboard occupied) {
return BishopAttacks[BishopTable[(int)sq].index + BishopTable[(int)sq](occupied)];
}
-/**
- * @brief Returns the rook attacks for a given square
- * @param sq
- * @param occupied
- * @return
- */
+/// @brief Look up rook attacks from the precomputed magic table.
[[nodiscard]] Bitboard rook(Square sq, Bitboard occupied) {
return RookAttacks[RookTable[(int)sq].index + RookTable[(int)sq](occupied)];
}
-
} // namespace chess::attacks
namespace chess::movegen {
+
+/// @brief Hyperbola attack for bishop or rook (used for between-table generation).
inline static Bitboard att(PieceType pt, Square sq, Bitboard occ) {
return (pt == BISHOP) ? chess::_chess::_HyperbolaBishopAttacks(sq, occ) : chess::_chess::_HyperbolaRookAttacks(sq, occ);
}
-inline static std::array, SQ_NONE + 1> generate_between() {
- std::array, SQ_NONE + 1> squares_between_bb{};
+/// @brief Generate the between-square table at program startup.
+inline static std::array, 64> generate_between() {
+ std::array, 64> squares_between_bb{};
for (int sq1 = 0; sq1 < 64; ++sq1) {
for (PieceType pt : { BISHOP, ROOK }) {
@@ -226,5 +237,5 @@ inline static std::array, SQ_NONE + 1> generat
return squares_between_bb;
}
-std::array, SQ_NONE + 1> SQUARES_BETWEEN_BB = generate_between();
+std::array, 64> SQUARES_BETWEEN_BB = generate_between();
} // namespace chess::movegen
diff --git a/attacks.h b/attacks.h
index c3e1654..0d36bf6 100644
--- a/attacks.h
+++ b/attacks.h
@@ -24,95 +24,112 @@
#include
#include
#include
+
+/// @file attacks.h
+/// @brief Precomputed attack tables and magic-bitboard lookup functions.
+
namespace chess::attacks {
-// clang-format off
- // pre-calculated lookup table for pawn attacks
- constexpr Bitboard PawnAttacks[2][64] = {
- // white pawn attacks
- { 0x200, 0x500, 0xa00, 0x1400,
- 0x2800, 0x5000, 0xa000, 0x4000,
- 0x20000, 0x50000, 0xa0000, 0x140000,
- 0x280000, 0x500000, 0xa00000, 0x400000,
- 0x2000000, 0x5000000, 0xa000000, 0x14000000,
- 0x28000000, 0x50000000, 0xa0000000, 0x40000000,
- 0x200000000, 0x500000000, 0xa00000000, 0x1400000000,
- 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000,
- 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000,
- 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000,
- 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000,
- 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000,
- 0x200000000000000, 0x500000000000000, 0xa00000000000000, 0x1400000000000000,
- 0x2800000000000000, 0x5000000000000000, 0xa000000000000000, 0x4000000000000000,
- 0x0, 0x0, 0x0, 0x0,
- 0x0, 0x0, 0x0, 0x0 },
- // black pawn attacks
- { 0x0, 0x0, 0x0, 0x0,
- 0x0, 0x0, 0x0, 0x0,
- 0x2, 0x5, 0xa, 0x14,
- 0x28, 0x50, 0xa0, 0x40,
- 0x200, 0x500, 0xa00, 0x1400,
- 0x2800, 0x5000, 0xa000, 0x4000,
- 0x20000, 0x50000, 0xa0000, 0x140000,
- 0x280000, 0x500000, 0xa00000, 0x400000,
- 0x2000000, 0x5000000, 0xa000000, 0x14000000,
- 0x28000000, 0x50000000, 0xa0000000, 0x40000000,
- 0x200000000, 0x500000000, 0xa00000000, 0x1400000000,
- 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000,
- 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000,
- 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000,
- 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000,
- 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000
- }
- };
+/// @brief Precomputed pawn-attack bitboards.
+/// @details Indexed as PawnAttacks[color][square].
+constexpr Bitboard PawnAttacks[2][64] = {
+ // clang-format off
+ // white pawn attacks
+ { 0x200, 0x500, 0xa00, 0x1400,
+ 0x2800, 0x5000, 0xa000, 0x4000,
+ 0x20000, 0x50000, 0xa0000, 0x140000,
+ 0x280000, 0x500000, 0xa00000, 0x400000,
+ 0x2000000, 0x5000000, 0xa000000, 0x14000000,
+ 0x28000000, 0x50000000, 0xa0000000, 0x40000000,
+ 0x200000000, 0x500000000, 0xa00000000, 0x1400000000,
+ 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000,
+ 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000,
+ 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000,
+ 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000,
+ 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000,
+ 0x200000000000000, 0x500000000000000, 0xa00000000000000, 0x1400000000000000,
+ 0x2800000000000000, 0x5000000000000000, 0xa000000000000000, 0x4000000000000000,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0 },
+
+ // black pawn attacks
+ { 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0,
+ 0x2, 0x5, 0xa, 0x14,
+ 0x28, 0x50, 0xa0, 0x40,
+ 0x200, 0x500, 0xa00, 0x1400,
+ 0x2800, 0x5000, 0xa000, 0x4000,
+ 0x20000, 0x50000, 0xa0000, 0x140000,
+ 0x280000, 0x500000, 0xa00000, 0x400000,
+ 0x2000000, 0x5000000, 0xa000000, 0x14000000,
+ 0x28000000, 0x50000000, 0xa0000000, 0x40000000,
+ 0x200000000, 0x500000000, 0xa00000000, 0x1400000000,
+ 0x2800000000, 0x5000000000, 0xa000000000, 0x4000000000,
+ 0x20000000000, 0x50000000000, 0xa0000000000, 0x140000000000,
+ 0x280000000000, 0x500000000000, 0xa00000000000, 0x400000000000,
+ 0x2000000000000, 0x5000000000000, 0xa000000000000, 0x14000000000000,
+ 0x28000000000000, 0x50000000000000, 0xa0000000000000, 0x40000000000000
+ }
+ // clang-format on
+};
- // pre-calculated lookup table for knight attacks
- constexpr Bitboard KnightAttacks[64] = {
- 0x0000000000020400, 0x0000000000050800, 0x00000000000A1100, 0x0000000000142200,
- 0x0000000000284400, 0x0000000000508800, 0x0000000000A01000, 0x0000000000402000,
- 0x0000000002040004, 0x0000000005080008, 0x000000000A110011, 0x0000000014220022,
- 0x0000000028440044, 0x0000000050880088, 0x00000000A0100010, 0x0000000040200020,
- 0x0000000204000402, 0x0000000508000805, 0x0000000A1100110A, 0x0000001422002214,
- 0x0000002844004428, 0x0000005088008850, 0x000000A0100010A0, 0x0000004020002040,
- 0x0000020400040200, 0x0000050800080500, 0x00000A1100110A00, 0x0000142200221400,
- 0x0000284400442800, 0x0000508800885000, 0x0000A0100010A000, 0x0000402000204000,
- 0x0002040004020000, 0x0005080008050000, 0x000A1100110A0000, 0x0014220022140000,
- 0x0028440044280000, 0x0050880088500000, 0x00A0100010A00000, 0x0040200020400000,
- 0x0204000402000000, 0x0508000805000000, 0x0A1100110A000000, 0x1422002214000000,
- 0x2844004428000000, 0x5088008850000000, 0xA0100010A0000000, 0x4020002040000000,
- 0x0400040200000000, 0x0800080500000000, 0x1100110A00000000, 0x2200221400000000,
- 0x4400442800000000, 0x8800885000000000, 0x100010A000000000, 0x2000204000000000,
- 0x0004020000000000, 0x0008050000000000, 0x00110A0000000000, 0x0022140000000000,
- 0x0044280000000000, 0x0088500000000000, 0x0010A00000000000, 0x0020400000000000};
+/// @brief Precomputed knight-attack bitboards.
+/// @details Indexed by square.
+constexpr Bitboard KnightAttacks[64] = {
+ 0x0000000000020400, 0x0000000000050800, 0x00000000000A1100, 0x0000000000142200,
+ 0x0000000000284400, 0x0000000000508800, 0x0000000000A01000, 0x0000000000402000,
+ 0x0000000002040004, 0x0000000005080008, 0x000000000A110011, 0x0000000014220022,
+ 0x0000000028440044, 0x0000000050880088, 0x00000000A0100010, 0x0000000040200020,
+ 0x0000000204000402, 0x0000000508000805, 0x0000000A1100110A, 0x0000001422002214,
+ 0x0000002844004428, 0x0000005088008850, 0x000000A0100010A0, 0x0000004020002040,
+ 0x0000020400040200, 0x0000050800080500, 0x00000A1100110A00, 0x0000142200221400,
+ 0x0000284400442800, 0x0000508800885000, 0x0000A0100010A000, 0x0000402000204000,
+ 0x0002040004020000, 0x0005080008050000, 0x000A1100110A0000, 0x0014220022140000,
+ 0x0028440044280000, 0x0050880088500000, 0x00A0100010A00000, 0x0040200020400000,
+ 0x0204000402000000, 0x0508000805000000, 0x0A1100110A000000, 0x1422002214000000,
+ 0x2844004428000000, 0x5088008850000000, 0xA0100010A0000000, 0x4020002040000000,
+ 0x0400040200000000, 0x0800080500000000, 0x1100110A00000000, 0x2200221400000000,
+ 0x4400442800000000, 0x8800885000000000, 0x100010A000000000, 0x2000204000000000,
+ 0x0004020000000000, 0x0008050000000000, 0x00110A0000000000, 0x0022140000000000,
+ 0x0044280000000000, 0x0088500000000000, 0x0010A00000000000, 0x0020400000000000};
- // pre-calculated lookup table for king attacks
- constexpr Bitboard KingAttacks[64] = {
- 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14,
- 0x0000000000003828, 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040,
- 0x0000000000030203, 0x0000000000070507, 0x00000000000E0A0E, 0x00000000001C141C,
- 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, 0x0000000000C040C0,
- 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00,
- 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000,
- 0x0000000302030000, 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000,
- 0x0000003828380000, 0x0000007050700000, 0x000000E0A0E00000, 0x000000C040C00000,
- 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, 0x00001C141C000000,
- 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000,
- 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000,
- 0x0038283800000000, 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000,
- 0x0302030000000000, 0x0705070000000000, 0x0E0A0E0000000000, 0x1C141C0000000000,
- 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, 0xC040C00000000000,
- 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000,
- 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000};
- constexpr Bitboard MASK_RANK[8] = {
- 0xff, 0xff00, 0xff0000, 0xff000000,
- 0xff00000000, 0xff0000000000, 0xff000000000000, 0xff00000000000000};
+/// @brief Precomputed king-attack bitboards.
+/// @details Indexed by square.
+constexpr Bitboard KingAttacks[64] = {
+ 0x0000000000000302, 0x0000000000000705, 0x0000000000000E0A, 0x0000000000001C14,
+ 0x0000000000003828, 0x0000000000007050, 0x000000000000E0A0, 0x000000000000C040,
+ 0x0000000000030203, 0x0000000000070507, 0x00000000000E0A0E, 0x00000000001C141C,
+ 0x0000000000382838, 0x0000000000705070, 0x0000000000E0A0E0, 0x0000000000C040C0,
+ 0x0000000003020300, 0x0000000007050700, 0x000000000E0A0E00, 0x000000001C141C00,
+ 0x0000000038283800, 0x0000000070507000, 0x00000000E0A0E000, 0x00000000C040C000,
+ 0x0000000302030000, 0x0000000705070000, 0x0000000E0A0E0000, 0x0000001C141C0000,
+ 0x0000003828380000, 0x0000007050700000, 0x000000E0A0E00000, 0x000000C040C00000,
+ 0x0000030203000000, 0x0000070507000000, 0x00000E0A0E000000, 0x00001C141C000000,
+ 0x0000382838000000, 0x0000705070000000, 0x0000E0A0E0000000, 0x0000C040C0000000,
+ 0x0003020300000000, 0x0007050700000000, 0x000E0A0E00000000, 0x001C141C00000000,
+ 0x0038283800000000, 0x0070507000000000, 0x00E0A0E000000000, 0x00C040C000000000,
+ 0x0302030000000000, 0x0705070000000000, 0x0E0A0E0000000000, 0x1C141C0000000000,
+ 0x3828380000000000, 0x7050700000000000, 0xE0A0E00000000000, 0xC040C00000000000,
+ 0x0203000000000000, 0x0507000000000000, 0x0A0E000000000000, 0x141C000000000000,
+ 0x2838000000000000, 0x5070000000000000, 0xA0E0000000000000, 0x40C0000000000000};
+
+/// @brief Per-rank mask (rank 0-7).
+constexpr Bitboard MASK_RANK[8] = {
+ 0xff, 0xff00, 0xff0000, 0xff000000,
+ 0xff00000000, 0xff0000000000, 0xff000000000000, 0xff00000000000000};
+
+/// @brief Per-file mask (file 0-7).
+constexpr Bitboard MASK_FILE[8] = {
+ 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808,
+ 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080,
+};
- constexpr Bitboard MASK_FILE[8] = {
- 0x101010101010101, 0x202020202020202, 0x404040404040404, 0x808080808080808,
- 0x1010101010101010, 0x2020202020202020, 0x4040404040404040, 0x8080808080808080,
- };
-// clang-format on
#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;
@@ -127,9 +144,11 @@ constexpr uint64_t software_pext_u64(uint64_t val, uint64_t mask) {
}
return result;
}
+
+/// @brief Magic structure for PEXT-based magic bitboards (BMI2 path).
struct Magic {
- Bitboard mask;
- int index;
+ 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);
@@ -139,23 +158,23 @@ struct Magic {
}
};
#else
+/// @brief Magic structure for classical (multiply-and-shift) magic bitboards.
struct Magic {
- Bitboard mask;
- Bitboard magic;
- size_t index;
- Bitboard shift;
+ 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 {
-/**
- * @brief Shifts a bitboard in a given direction
- * @tparam direction
- * @param b
- * @return
- */
+
+/// @brief Shift a bitboard in the given direction.
+/// @param b The bitboard.
+/// @param direction Direction to shift.
+/// @return Shifted bitboard.
[[nodiscard]] static constexpr Bitboard shift(const Bitboard b, Direction direction) {
switch (direction) {
case Direction::NORTH:
@@ -176,31 +195,24 @@ namespace chess::attacks {
return (b & ~MASK_FILE[7]) >> 7;
case DOUBLE_NORTH:
return b << 16;
-
case DOUBLE_SOUTH:
return b >> 16;
-
case DOUBLE_EAST:
return (b & ~MASK_FILE[7] & ~(MASK_FILE[7] >> 1)) << 2;
-
case DOUBLE_WEST:
return (b & ~MASK_FILE[0] & ~(MASK_FILE[0] << 1)) >> 2;
-
case DOUBLE_NORTH_EAST: {
Bitboard t = (b & ~MASK_FILE[7]) << 9;
return (t & ~MASK_FILE[7]) << 9;
}
-
case DOUBLE_NORTH_WEST: {
Bitboard t = (b & ~MASK_FILE[0]) << 7;
return (t & ~MASK_FILE[0]) << 7;
}
-
case DOUBLE_SOUTH_EAST: {
Bitboard t = (b & ~MASK_FILE[7]) >> 7;
return (t & ~MASK_FILE[7]) >> 7;
}
-
case DOUBLE_SOUTH_WEST: {
Bitboard t = (b & ~MASK_FILE[0]) >> 9;
return (t & ~MASK_FILE[0]) >> 9;
@@ -212,117 +224,94 @@ namespace chess::attacks {
return 0;
}
}
-/**
- * @brief Shifts a bitboard in a given direction
- * @tparam direction
- * @param b
- * @return
- */
+
+/// @brief Template wrapper for shift in a compile-time-known direction.
template [[nodiscard]] static constexpr Bitboard shift(const Bitboard b) { return shift(b, direction); }
-/**
- * @brief
- * @tparam c
- * @param pawns
- * @return
- */
+/// @brief Generate left-side pawn attacks for the given colour.
+/// @tparam c Colour.
+/// @param pawns Bitboard of pawns.
+/// @return Bitboard of left-capture target squares.
template [[nodiscard]] constexpr Bitboard pawnLeftAttacks(const Bitboard pawns) {
ASSUME(c == WHITE || c == BLACK);
return c == WHITE ? (pawns << 7) & ~MASK_FILE[7] : (pawns >> 7) & ~MASK_FILE[0];
}
-/**
- * @brief Generate the right side pawn attacks.
- * @tparam c
- * @param pawns
- * @return
- */
+/// @brief Generate right-side pawn attacks for the given colour.
+/// @tparam c Colour.
+/// @param pawns Bitboard of pawns.
+/// @return Bitboard of right-capture target squares.
template [[nodiscard]] constexpr Bitboard pawnRightAttacks(const Bitboard pawns) {
ASSUME(c == WHITE || c == BLACK);
return c == WHITE ? (pawns << 9) & ~MASK_FILE[0] : (pawns >> 9) & ~MASK_FILE[7];
}
-/**
- * @brief Generate the right side pawn attacks.
- * @tparam c
- * @param pawns
- * @return
- */
+/// @brief Generate all pawn attacks from a bitboard of pawns.
+/// @tparam c Colour.
+/// @param pawns Bitboard of pawns.
+/// @return Bitboard of all squares attacked by the pawns.
template [[nodiscard]] constexpr Bitboard pawn(const Bitboard pawns) {
ASSUME(c == WHITE || c == BLACK);
if constexpr (c == WHITE) {
- return ((pawns & ~MASK_FILE[FILE_A]) << 7) | // left captures
- ((pawns & ~MASK_FILE[FILE_H]) << 9); // right captures
+ return ((pawns & ~MASK_FILE[FILE_A]) << 7) |
+ ((pawns & ~MASK_FILE[FILE_H]) << 9);
} else {
return ((pawns & ~MASK_FILE[FILE_H]) >> 7) | ((pawns & ~MASK_FILE[FILE_A]) >> 9);
}
}
-/**
- * @brief Returns the pawn attacks for a given color and square
- * @param c
- * @param sq
- * @return
- */
+/// @brief Look up pawn attacks for a single square.
+/// @param c Colour.
+/// @param sq Square.
+/// @return Bitboard of squares attacked.
[[nodiscard]] constexpr Bitboard pawn(Color c, Square sq) { return PawnAttacks[(int)c][(int)sq]; }
-/**
- * @brief Returns the knight attacks for a given square
- * @param sq
- * @return
- */
+/// @brief Look up knight attacks for a single square.
+/// @param sq Square.
+/// @return Bitboard of squares attacked.
[[nodiscard]] constexpr Bitboard knight(Square sq) { return KnightAttacks[(int)sq]; }
-/**
- * @brief Returns the knight attacks for given knights
- * @param sq
- * @return
- */
+
+/// @brief Compute knight attacks for a bitboard of knights.
+/// @param knights Bitboard of knights.
+/// @return Bitboard of all squares attacked.
[[nodiscard]] constexpr Bitboard knight(Bitboard knights) {
- Bitboard l1 = (knights >> 1) & 0x7f7f7f7f7f7f7f7fULL; // shift left by 1, mask out file A
- Bitboard l2 = (knights >> 2) & 0x3f3f3f3f3f3f3f3fULL; // shift left by 2, mask out files A+B
- Bitboard r1 = (knights << 1) & 0xfefefefefefefefeULL; // shift right by 1, mask out file H
- Bitboard r2 = (knights << 2) & 0xfcfcfcfcfcfcfcfcULL; // shift right by 2, mask out files G+H
- Bitboard h1 = l1 | r1; // 1-square horizontal shifts
- Bitboard h2 = l2 | r2; // 2-square horizontal shifts
- return (h1 << 16) | (h1 >> 16) | (h2 << 8) | (h2 >> 8); // vertical shifts: +2,+1,-2,-1
+ Bitboard l1 = (knights >> 1) & 0x7f7f7f7f7f7f7f7fULL;
+ Bitboard l2 = (knights >> 2) & 0x3f3f3f3f3f3f3f3fULL;
+ Bitboard r1 = (knights << 1) & 0xfefefefefefefefeULL;
+ Bitboard r2 = (knights << 2) & 0xfcfcfcfcfcfcfcfcULL;
+ Bitboard h1 = l1 | r1;
+ Bitboard h2 = l2 | r2;
+ return (h1 << 16) | (h1 >> 16) | (h2 << 8) | (h2 >> 8);
}
-/**
- * @brief Returns the bishop attacks for a given square
- * @param sq
- * @param occupied
- * @return
- */
+
+/// @brief Look up bishop attacks via magic bitboard (defined in attacks.cpp).
+/// @param sq Bishop square.
+/// @param occupied Occupancy bitboard.
+/// @return Bitboard of squares attacked.
[[nodiscard]] Bitboard bishop(Square sq, Bitboard occupied);
-/**
- * @brief Returns the rook attacks for a given square
- * @param sq
- * @param occupied
- * @return
- */
+/// @brief Look up rook attacks via magic bitboard (defined in attacks.cpp).
+/// @param sq Rook square.
+/// @param occupied Occupancy bitboard.
+/// @return Bitboard of squares attacked.
[[nodiscard]] Bitboard rook(Square sq, Bitboard occupied);
-/**
- * @brief Returns the queen attacks for a given square
- * @param sq
- * @param occupied
- * @return
- */
+
+/// @brief Compute queen attacks (bishop | rook).
+/// @param sq Queen square.
+/// @param occupied Occupancy bitboard.
+/// @return Bitboard of squares attacked.
[[nodiscard]] inline Bitboard queen(Square sq, Bitboard occupied) { return bishop(sq, occupied) | rook(sq, occupied); }
-/**
- * @brief Returns the king attacks for a given square
- * @param sq
- * @return
- */
+/// @brief Look up king attacks for a single square.
+/// @param sq Square.
+/// @return Bitboard of squares attacked.
[[nodiscard]] constexpr Bitboard king(Square sq) { return KingAttacks[(int)sq]; }
-/**
- * @brief Returns the slider attacks for a given square
- * @param sq
- * @param occupied
- * @tparam pt
- * @return
- */
+/// @brief Template dispatcher for slider attacks (bishop / rook / queen).
+/// @tparam pt Piece type (must be a slider).
+/// @param sq Square.
+/// @param occupied Occupancy bitboard.
+/// @return Bitboard of squares attacked.
template [[nodiscard]] inline Bitboard slider(Square sq, Bitboard occupied) {
static_assert(pt == PieceType::BISHOP || pt == PieceType::ROOK || pt == PieceType::QUEEN, "PieceType must be a slider!");
diff --git a/bitboard.h b/bitboard.h
index 0aa7eae..7a6002b 100644
--- a/bitboard.h
+++ b/bitboard.h
@@ -23,10 +23,15 @@
#include
#endif
#include
+
+/// @file bitboard.h
+/// @brief Bitboard utility functions (popcount, LSB, MSB, etc.).
+
namespace chess {
-// -------------------------------
-// constexpr fallbacks
-// -------------------------------
+
+/// @brief constexpr fallback for population count.
+/// @param x Input bitboard.
+/// @return Number of set bits.
constexpr int popcount_constexpr(Bitboard x) noexcept {
int count = 0;
while (x) {
@@ -36,6 +41,9 @@ constexpr int popcount_constexpr(Bitboard x) noexcept {
return count;
}
+/// @brief constexpr fallback for least-significant bit index.
+/// @param x Input bitboard.
+/// @return Index of the lowest set bit (0-based).
constexpr int lsb_constexpr(Bitboard x) noexcept {
int pos = 0;
while ((x & 1) == 0) {
@@ -45,6 +53,9 @@ constexpr int lsb_constexpr(Bitboard x) noexcept {
return pos;
}
+/// @brief constexpr fallback for most-significant bit index.
+/// @param x Input bitboard.
+/// @return Index of the highest set bit (0-based).
constexpr int msb_constexpr(Bitboard x) noexcept {
int pos = 63;
Bitboard mask = 1ULL << 63;
@@ -55,9 +66,9 @@ constexpr int msb_constexpr(Bitboard x) noexcept {
return pos;
}
-// -------------------------------
-// runtime + constexpr aware
-// -------------------------------
+/// @brief Population count (uses hardware POPCNT when available).
+/// @param x Input bitboard.
+/// @return Number of set bits.
#if defined(__GNUG__) || defined(__clang__)
[[gnu::const]]
#endif
@@ -72,6 +83,9 @@ inline constexpr int popcount(Bitboard x) noexcept {
return popcount_constexpr(x);
}
+/// @brief Least-significant bit index (uses hardware BSF when available).
+/// @param x Input bitboard (must be non-zero).
+/// @return Index of the lowest set bit.
#if defined(__GNUG__) || defined(__clang__)
[[gnu::const]]
#endif
@@ -89,6 +103,9 @@ inline constexpr int lsb(Bitboard x) noexcept {
return lsb_constexpr(x);
}
+/// @brief Most-significant bit index (uses hardware BSR when available).
+/// @param x Input bitboard (must be non-zero).
+/// @return Index of the highest set bit.
#if defined(__GNUG__) || defined(__clang__)
[[gnu::const]]
#endif
@@ -106,9 +123,9 @@ inline constexpr int msb(Bitboard x) noexcept {
return msb_constexpr(x);
}
-// -------------------------------
-// destructive variants
-// -------------------------------
+/// @brief Extract and pop the least-significant bit (destructive).
+/// @param b Bitboard reference; modified in place.
+/// @return Index of the lowest set bit before removal.
inline int pop_lsb(Bitboard &b) noexcept {
int c = lsb(b);
#ifndef __BMI2__
@@ -119,6 +136,9 @@ inline int pop_lsb(Bitboard &b) noexcept {
return c;
}
+/// @brief Extract and pop the most-significant bit (destructive).
+/// @param b Bitboard reference; modified in place.
+/// @return Index of the highest set bit before removal.
inline int pop_msb(Bitboard &b) noexcept {
int c = msb(b);
b &= ~(1ULL << c);
diff --git a/fwd_decl.h b/fwd_decl.h
index 9f822ca..69ab337 100644
--- a/fwd_decl.h
+++ b/fwd_decl.h
@@ -19,31 +19,90 @@
#pragma once
#include
#include
+
+/// @file fwd_decl.h
+/// @brief Forward declarations for all major chess types.
+
namespace chess {
+
+/// @enum Color
+/// @brief Side to move or piece color.
enum Color : uint8_t;
+
+/// @enum PieceType
+/// @brief Basic piece type without color information.
enum PieceType : std::int8_t;
+/// @brief Trait to detect piece-enum types (PolyglotPiece, EnginePiece, ContiguousMappingPiece).
template struct is_piece_enum : std::false_type {};
template struct is_piece_enum> : std::true_type {};
+
+/// @enum CastlingRights
+/// @brief Bitmask of available castling rights.
enum CastlingRights : int8_t;
+/// @enum Square
+/// @brief Board square index (0-63, A1-H8).
enum Square : int8_t;
+/// @enum Direction
+/// @brief Compass direction offsets for board traversal.
enum Direction : int8_t;
+/// @enum MoveType
+/// @brief Move-type flags (normal, promotion, en-passant, castling).
enum MoveType : uint16_t;
+/// @enum File
+/// @brief File index (0-7, A-H).
enum File : int8_t;
+/// @enum Rank
+/// @brief Rank index (0-7, 1-8).
enum Rank : int8_t;
+
+/// @class Move
+/// @brief Compact 16-bit move representation.
class Move;
+
+/// @enum MoveGenType
+/// @brief Move-generation filter flags for piece type and move kind.
enum class MoveGenType : uint16_t;
+
+/// @class _Position
+/// @brief Templated chess position class parameterised by the piece enum.
template class _Position;
+
+/// @typedef Bitboard
+/// @brief 64-bit bitboard representing a set of squares.
using Bitboard = uint64_t;
+
+/// @typedef Key
+/// @brief 64-bit Zobrist hash key.
using Key = uint64_t;
+
+/// @class ValueList
+/// @brief Stack-allocated fixed-capacity container.
template class ValueList;
+
+/// @typedef Movelist
+/// @brief Fixed-capacity list of up to 256 moves.
using Movelist = ValueList;
-// bonus: define the piece enums here
+
+/// @enum PolyglotPiece
+/// @brief Piece encoding used by Polyglot opening books.
enum class PolyglotPiece : uint8_t;
+
+/// @enum EnginePiece
+/// @brief Default engine piece encoding (8 values per colour).
enum class EnginePiece : uint8_t;
+
+/// @enum ContiguousMappingPiece
+/// @brief Compact piece encoding (0-5 white, 6-11 black).
enum class ContiguousMappingPiece : uint8_t;
+
+/// @typedef Position
+/// @brief Default chess position type (uses EnginePiece).
using Position = _Position;
+
+/// @typedef Board
+/// @brief Alias for Position.
using Board = Position;
} // namespace chess
diff --git a/movegen.cpp b/movegen.cpp
index 60f5155..8e218cf 100644
--- a/movegen.cpp
+++ b/movegen.cpp
@@ -22,6 +22,10 @@
// movegen references
// License: https://github.com/Disservin/chess-library/blob/master/LICENSE
+
+/// @file movegen.cpp
+/// @brief Move generator: AVX-512 accelerated splatting and per-piece-type move generation.
+
#include "movegen.h"
#include "position.h"
@@ -126,7 +130,7 @@ inline Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) {
namespace chess {
template void movegen::genEP(const _Position &pos, Movelist &mv) {
- const Square king_sq = pos.kingSq(c);
+ const Square king_sq = pos.king_sq(c);
const Square ep_sq = pos.ep_square();
if (ep_sq == SQ_NONE)
return;
@@ -139,12 +143,12 @@ template void movegen::genEP(const _Position &pos
const Bitboard ep_mask = (1ULL << ep_pawn_sq) | (1ULL << ep_sq);
// ASSUME(popcount(candidates) <= 32);
+ Bitboard occ_all = pos.occ();
while (candidates) {
Square from = static_cast(pop_lsb(candidates));
// Remove the EP pawn and this attacker from occupancy
- Bitboard occ_temp = pos.occ();
- occ_temp ^= (1ULL << from) | ep_mask;
+ Bitboard occ_temp = occ_all ^ ((1ULL << from) | ep_mask);
// attackers check
Bitboard atks = 0;
@@ -165,7 +169,7 @@ void movegen::genPawnDoubleMoves(const _Position &pos, Movelist &moves,
Bitboard pawns = pos.template pieces() & RANK_2;
// Split pin types
- Bitboard pin_file = pin_mask & attacks::MASK_FILE[file_of(pos.kingSq(c))];
+ Bitboard pin_file = pin_mask & attacks::MASK_FILE[file_of(pos.king_sq(c))];
Bitboard unpinned = pawns & ~pin_mask;
Bitboard file_pinned = pawns & pin_file;
@@ -283,7 +287,7 @@ void movegen::genKnightMoves(const _Position &pos, Movelist &list, Bitb
template
void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboard _pin_mask) {
constexpr Color them = ~c;
- const Square kingSq = pos.kingSq(c);
+ const Square kingSq = pos.king_sq(c);
const Bitboard occAll = pos.occ();
const Bitboard myOcc = pos.occ(c);
@@ -307,7 +311,7 @@ void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboar
enemyAttacks |= attacks::pawn(pos.template pieces());
// Enemy king (adjacent control squares)
- enemyAttacks |= attacks::king(pos.kingSq(them));
+ enemyAttacks |= attacks::king(pos.king_sq(them));
Bitboard moves = attacks::king(kingSq) & ~myOcc & ~enemyAttacks;
if constexpr (capturesOnly)
@@ -322,11 +326,11 @@ void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboar
Bitboard enemy_attacks = enemyAttacks;
constexpr CastlingRights kingRights = KING_SIDE & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING),
queenRights = QUEEN_SIDE & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
- Bitboard OO_EMPTY = pos.getCastlingPath(c, true);
+ Bitboard OO_EMPTY = pos.get_castling_path(c, true);
Bitboard OO_SAFE = between(kingSq, castling_king_square(c, true));
- Bitboard OOO_EMPTY = pos.getCastlingPath(c, false);
+ Bitboard OOO_EMPTY = pos.get_castling_path(c, false);
Bitboard OOO_SAFE = between(kingSq, castling_king_square(c, false));
- Square rookKing = pos.getCastlingMetadata(c).rook_start_ks, rookQueen = pos.getCastlingMetadata(c).rook_start_qs;
+ Square rookKing = pos.get_castling_metadata(c).rook_start_ks, rookQueen = pos.get_castling_metadata(c).rook_start_qs;
if (pos.castlingRights() & kingRights &&
!(occupancy & OO_EMPTY || enemy_attacks & OO_SAFE || _pin_mask & 1ULL << rookKing)) {
@@ -350,6 +354,7 @@ void movegen::genSlidingMoves(
sliders &= ~rook_pinners;
if constexpr (pt == ROOK)
sliders &= ~bishop_pinners;
+ Bitboard occ_opp = pos.occ(~c);
Bitboard filter_list = ~pos.occ(c) & _check_mask;
while (sliders) {
Square from = static_cast(pop_lsb(sliders));
@@ -368,7 +373,7 @@ void movegen::genSlidingMoves(
Bitboard filtered_pin = pin_mask & filter_list;
Bitboard targets = func(from, occ_all) & filtered_pin;
if constexpr (capturesOnly)
- targets &= pos.occ(~c);
+ targets &= occ_opp;
_chess::splat_moves(moves.data() + moves.size_, from, targets);
moves.size_ += popcount(targets);
}
diff --git a/movegen.h b/movegen.h
index bb6aea0..948bffc 100644
--- a/movegen.h
+++ b/movegen.h
@@ -19,20 +19,42 @@
#pragma once
#include "fwd_decl.h"
#include
+
+/// @file movegen.h
+/// @brief Move-generation declarations and between-square table.
+
namespace chess::movegen {
+/// @brief Generate en-passant captures for the given colour.
template void genEP(const _Position &, Movelist &);
+
+/// @brief Generate double-pawn pushes (from the starting rank).
template void genPawnDoubleMoves(const _Position &, Movelist &, Bitboard, Bitboard);
+
+/// @brief Generate single-pawn moves (pushes and captures).
template
void genPawnSingleMoves(const _Position &, Movelist &, Bitboard, Bitboard, Bitboard);
+
+/// @brief Generate knight moves.
template
void genKnightMoves(const _Position &, Movelist &, Bitboard, Bitboard);
+
+/// @brief Generate king moves.
template void genKingMoves(const _Position &, Movelist &, Bitboard);
+
+/// @brief Generate sliding-piece moves (bishop, rook, queen).
template
void genSlidingMoves(const _Position &, Movelist &, Bitboard, Bitboard, Bitboard);
-extern std::array, 65> SQUARES_BETWEEN_BB;
-/*
- * [(file(sq1), rank(sq1)), (file(sq2), rank(sq2))] -> bitboard of squares between sq1 and sq2, excluding sq1 and sq2
- */
+
+/// @brief Precomputed between-square bitboards.
+/// @details squares_between_bb[sq1][sq2] contains a bitboard of all squares
+/// strictly between sq1 and sq2 (excluding both endpoints).
+extern std::array, 64> SQUARES_BETWEEN_BB;
+
+/// @brief Look up the squares between two squares.
+/// @param sq1 First square.
+/// @param sq2 Second square.
+/// @return Bitboard of squares between sq1 and sq2, excluding sq1 and sq2.
+/// Returns 1ULL << sq2 if the squares are not on the same line.
[[nodiscard]] inline Bitboard between(Square sq1, Square sq2) noexcept { return SQUARES_BETWEEN_BB[sq1][sq2]; }
} // namespace chess::movegen
diff --git a/moves_io.cpp b/moves_io.cpp
index f794881..3e04dc0 100644
--- a/moves_io.cpp
+++ b/moves_io.cpp
@@ -19,6 +19,10 @@
// UCI moves parsing
// License: https://github.com/Disservin/chess-library/blob/master/LICENSE
+
+/// @file moves_io.cpp
+/// @brief UCI move parsing and conversion (moveToUci, uciToMove).
+
#include "moves_io.h"
#include "position.h"
#include "types.h"
@@ -31,6 +35,7 @@
#endif
namespace chess {
namespace uci {
+/// @brief Convert a Square to algebraic notation string (e.g. 0→"a1").
std::string squareToString(Square sq) {
constexpr std::string_view fileChars[65] = {
"a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", "a3",
@@ -40,6 +45,7 @@ std::string squareToString(Square sq) {
};
return std::string{ fileChars[sq] };
}
+/// @brief Convert a Move to UCI string representation.
std::string moveToUci(Move mv, bool chess960) {
if (!mv.is_ok()) {
// null move
@@ -90,6 +96,7 @@ std::string moveToUci(Move mv, bool chess960) {
}
return move;
}
+/// @brief Convert a UCI string (e.g. "e2e4") to a Move object.
template Move uciToMove(const _Position &pos, std::string_view uci) {
if (uci.length() < 4) {
THROW_IF_EXCEPTIONS_ON(IllegalMoveException("example: a2a4 or d7d8q"));
@@ -111,7 +118,7 @@ template Move uciToMove(const _Position &pos, std
}
// castling in chess960
if (pos.chess960() && pt == PieceType::KING && pos.template at(target) == PieceType::ROOK &&
- pos.template at(target) == pos.sideToMove()) {
+ pos.template at(target) == pos.side_to_move()) {
move = Move::make(source, target);
}
@@ -122,12 +129,12 @@ template Move uciToMove(const _Position &pos, std
move = Move::make(source, target);
}
// en passant
- else if (pt == PAWN && target == pos.enpassantSq()) {
+ else if (pt == PAWN && target == pos.ep_square()) {
move = Move::make(source, target);
}
// promotion
- else if (pt == PAWN && uci.length() == 5 && (rank_of(target) == (pos.sideToMove() == WHITE ? RANK_8 : RANK_1))) {
+ else if (pt == PAWN && uci.length() == 5 && (rank_of(target) == (pos.side_to_move() == WHITE ? RANK_8 : RANK_1))) {
auto promotion = parse_pt(uci[4]);
if (promotion == NO_PIECE_TYPE || promotion == KING || promotion == PAWN) {
@@ -152,6 +159,7 @@ template Move uciToMove(const _Position &pos, std
#endif
return move;
}
+/// @brief Parse a SAN (Standard Algebraic Notation) move string.
template Move parseSan(const _Position &pos, std::string_view raw_san, bool remove_illegals) {
auto do_parse = [&](std::string_view input_san) -> Move {
if (input_san.empty())
@@ -164,8 +172,8 @@ template Move parseSan(const _Position &pos, std:
// 1) Castling shortcuts
if (san == "O-O" || san == "0-0" || san == "O-O+" || san == "0-0+" || san == "O-O#" || san == "0-0#") {
- const auto from = pos.kingSq(pos.side_to_move());
- const auto to = pos.getCastlingMetadata(pos.sideToMove()).rook_start_ks;
+ const auto from = pos.king_sq(pos.side_to_move());
+ const auto to = pos.get_castling_metadata(pos.side_to_move()).rook_start_ks;
Move km = chess::Move::make(from, to);
if (std::find(moves.begin(), moves.end(), km) != moves.end())
@@ -174,8 +182,8 @@ template Move parseSan(const _Position &pos, std:
return Move::none();
}
if (san == "O-O-O" || san == "0-0-0" || san == "O-O-O+" || san == "0-0-0+" || san == "O-O-O#" || san == "0-0-0#") {
- const auto from = pos.kingSq(pos.side_to_move());
- const auto to = pos.getCastlingMetadata(pos.sideToMove()).rook_start_qs;
+ const auto from = pos.king_sq(pos.side_to_move());
+ const auto to = pos.get_castling_metadata(pos.side_to_move()).rook_start_qs;
Move qm = chess::Move::make(from, to);
if (std::find(moves.begin(), moves.end(), qm) != moves.end())
@@ -381,6 +389,7 @@ template Move parseSan(const _Position &pos, std:
return do_parse(raw_san);
}
}
+/// @brief Convert a Move to SAN or LAN (Long Algebraic Notation) string.
template std::string moveToSan(const _Position &pos, Move move, bool long_, bool suffix) {
constexpr char FILE_NAMES[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
@@ -473,7 +482,7 @@ template std::string moveToSan(const _Position &p
if (!suffix)
return san;
_Position p = pos;
- p.doMove(move);
+ p.do_move(move);
const bool _check = p.is_check();
Movelist moves;
p.legals(moves);
diff --git a/moves_io.h b/moves_io.h
index 2a00591..5ae6e67 100644
--- a/moves_io.h
+++ b/moves_io.h
@@ -18,14 +18,29 @@
*/
#pragma once
#include "fwd_decl.h"
+#include "types.h"
#include
#include
#include
#include
+
+/// @file moves_io.h
+/// @brief UCI and SAN move conversion functions.
+
namespace chess::uci {
+
+/// @brief Convert a Move to UCI coordinate string (e.g. "e2e4", "e7e8q").
+/// @param move The move.
+/// @param chess960 Whether to use Chess960 castling encoding.
+/// @return UCI string.
std::string moveToUci(Move move, bool chess960 = false);
+/// @brief Convert a Square to algebraic notation (e.g. "e4").
+/// @param sq The square.
+/// @return Two-character string.
std::string squareToString(Square sq);
+
+/// @brief Exception thrown when a SAN string represents an illegal move.
class IllegalMoveException : public std::exception {
public:
IllegalMoveException(const std::string &message) : message_(message) {}
@@ -34,6 +49,8 @@ class IllegalMoveException : public std::exception {
private:
std::string message_;
};
+
+/// @brief Exception thrown when a SAN string is ambiguous.
class AmbiguousMoveException : public std::exception {
public:
AmbiguousMoveException(const std::string &message) : message_(message) {}
@@ -42,9 +59,45 @@ class AmbiguousMoveException : public std::exception {
private:
std::string message_;
};
+
+/// @brief Parse a UCI string into a Move for the given position.
+/// @tparam T Piece enum type.
+/// @tparam P Position tag.
+/// @param pos The position.
+/// @param uci UCI string (e.g. "e2e4").
+/// @return The parsed Move.
template Move uciToMove(const _Position &pos, std::string_view uci);
+
+/// @brief Parse a SAN string into a Move for the given position.
+/// @tparam T Piece enum type.
+/// @tparam P Position tag.
+/// @param pos The position.
+/// @param san SAN string (e.g. "Nf3", "O-O").
+/// @param remove_illegals If true, return Move::NO_MOVE instead of throwing.
+/// @return The parsed Move.
+template
+Move parseSan(const _Position &pos, std::string_view san, bool remove_illegals = false);
+
+/// @brief Alias for parseSan.
template
-Move parseSan(const _Position &pos, std::string_view uci, bool remove_illegals = false);
+Move parse_san(const _Position &pos, std::string_view san, bool remove_illegals = false) {
+ return parseSan(pos, san, remove_illegals);
+}
+
+/// @brief Convert a Move to SAN string for the given position.
+/// @tparam T Piece enum type.
+/// @tparam P Position tag.
+/// @param pos The position.
+/// @param move The move.
+/// @param long_ Use long algebraic notation.
+/// @param suffix Include check/mate suffix (+/#).
+/// @return SAN string.
template
std::string moveToSan(const _Position &pos, Move move, bool long_ = false, bool suffix = true);
+
+/// @brief Alias for moveToSan.
+template
+std::string move_to_san(const _Position &pos, Move move, bool long_ = false, bool suffix = true) {
+ return moveToSan(pos, move, long_, suffix);
+}
} // namespace chess::uci
diff --git a/position.cpp b/position.cpp
index c1636a5..40bd23e 100644
--- a/position.cpp
+++ b/position.cpp
@@ -16,6 +16,10 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
+
+/// @file position.cpp
+/// @brief Position implementation: doMove, undoMove, setFEN, FEN export, and validation.
+
#include "position.h"
#include "movegen.h"
#include "moves_io.h"
@@ -38,6 +42,36 @@
#endif
namespace chess {
+namespace {
+
+/// @brief Precomputed mask for the pawn that would deliver an en-passant capture.
+/// @details ep_pawn_mask[sq] has the capturing-pawn square set for the given EP target square.
+constexpr Bitboard ep_pawn_mask_for(Square sq) {
+ const Rank r = rank_of(sq);
+ if (r != RANK_3 && r != RANK_6) return 0;
+
+ Bitboard m = 1ULL << static_cast(sq);
+ if (r == RANK_6)
+ m >>= 8; // WHITE stm → shift down to rank 5
+ else
+ m <<= 8; // BLACK stm → shift up to rank 4
+ return ((m << 1) & ~attacks::MASK_FILE[0]) | ((m >> 1) & ~attacks::MASK_FILE[7]);
+}
+
+constexpr std::array make_ep_pawn_masks() {
+ std::array table{};
+ for (int i = 0; i < 64; ++i)
+ table[i] = ep_pawn_mask_for(static_cast(i));
+ return table;
+}
+
+constexpr auto ep_pawn_masks = make_ep_pawn_masks();
+
+} // namespace
+
+/// @brief Apply a move to the board, updating all internal state.
+/// @tparam Strict If true, asserts/checks for invalid moves.
+/// @param move The move to execute.
template template void _Position::doMove(const Move &move) {
assert(move.is_ok() && "doMove called with invalid move");
Square from_sq = move.from_sq(), to_sq = move.to_sq();
@@ -48,8 +82,12 @@ template template void _Position template void _Position(from_sq, us);
removePiece(rook_start, us);
@@ -140,71 +178,66 @@ template template void _Position> 8) : (ep_mask << 8);
-
- // Keep adjacent files only.
- ep_mask = ((ep_mask << 1) & ~attacks::MASK_FILE[0]) | ((ep_mask >> 1) & ~attacks::MASK_FILE[7]);
-
- // Include key if their pawns can attack it.
- state().epIncluded = (ep_mask & pieces(stm)) != 0;
+ state().epIncluded = (ep_pawn_masks[state().enPassant] & pieces(~us)) != 0;
state().hash ^= state().epIncluded ? zobrist::RandomEP[f] : 0;
}
}
{
CastlingRights clear_mask = NO_CASTLING;
// Moving piece
- if (moving_piecetype == KING && from_sq == state().castlingMetadata[us].king_start) {
+ if (moving_piecetype == KING && from_sq == castling_meta_[us].king_start) {
clear_mask |= (us == WHITE ? WHITE_CASTLING : BLACK_CASTLING);
} else if (moving_piecetype == ROOK) {
- if (from_sq == state().castlingMetadata[us].rook_start_ks) {
+ if (from_sq == castling_meta_[us].rook_start_ks) {
clear_mask |= (us == WHITE ? WHITE_OO : BLACK_OO);
- } else if (from_sq == state().castlingMetadata[us].rook_start_qs) {
+ } else if (from_sq == castling_meta_[us].rook_start_qs) {
clear_mask |= (us == WHITE ? WHITE_OOO : BLACK_OOO);
}
}
// Captured piece
if (target_piecetype == ROOK) {
- if (to_sq == state().castlingMetadata[target_color].rook_start_ks)
+ if (to_sq == castling_meta_[target_color].rook_start_ks)
clear_mask |= (target_color == WHITE ? WHITE_OO : BLACK_OO);
- else if (to_sq == state().castlingMetadata[target_color].rook_start_qs)
+ else if (to_sq == castling_meta_[target_color].rook_start_qs)
clear_mask |= (target_color == WHITE ? WHITE_OOO : BLACK_OOO);
}
- CastlingRights prev = state().castlingRights;
- state().castlingRights &= ~clear_mask;
- state().hash ^= zobrist::RandomCastle[prev] ^ zobrist::RandomCastle[state().castlingRights];
+ if (clear_mask) {
+ CastlingRights prev = state().castlingRights;
+ state().castlingRights &= ~clear_mask;
+ state().hash ^= zobrist::RandomCastle[prev] ^ zobrist::RandomCastle[state().castlingRights];
+ }
}
state().turn = ~state().turn;
// Update halfmoves, fullmoves and stm
state().fullMoveNumber += (state().turn == WHITE);
- state().halfMoveClock = (is_capture || moving_piecetype == PAWN) ? 0 : (state().halfMoveClock + 1);
+ state().halfMoveClock = (is_capt || moving_piecetype == PAWN) ? 0 : (state().halfMoveClock + 1);
state().pliesFromNull++;
state().hash ^= zobrist::RandomTurn;
+ rep_hashes_.push_back(state().hash);
refresh_attacks();
// DO NOT MIX REPETITIONS
if constexpr (Strict) {
- // Calculate the repetition info. It is the ply distance from the previous
- // occurrence of the same position, negative in the 3-fold case, or zero
- // if the position was not repeated.
state().repetition = 0;
int end = std::min(rule50_count(), state().pliesFromNull);
if (end >= 4) {
+ Key cur_hash = hash();
for (int i = 4; i <= end; i += 2) {
- if (history[history.size() - 1 - i].hash == hash()) {
+ if (rep_hashes_[rep_hashes_.size() - 1 - i] == cur_hash)
state().repetition++;
- }
}
}
}
}
+/// @brief Set the position from a FEN string.
+/// @param str FEN string.
+/// @param chess960 Whether to interpret castling notation as Chess960.
+/// @param mode FEN parsing strictness mode.
template
void _Position::setFEN(const std::string &str, bool chess960, FENParsingMode mode) {
history.clear();
+ rep_hashes_.clear();
history.push_back(HistoryEntry());
_chess960 = chess960;
std::fill(std::begin(pieces_list), std::end(pieces_list), PieceC::NO_PIECE);
@@ -314,23 +347,23 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
return static_cast(it - pieces_list);
};
- auto findRookQS = [&](Square king_sq, Color color) -> Square {
+ auto findRookQS = [&](Square king_sq, Color color_) -> Square {
Rank r = rank_of(king_sq);
for (int f = file_of(king_sq) - 1; f >= FILE_A; --f) {
Square sq = make_sq(r, static_cast(f));
PieceC p = pieces_list[sq];
- if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color)
+ if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color_)
return sq;
}
return SQ_NONE;
};
- auto findRookKS = [&](Square king_sq, Color color) -> Square {
+ auto findRookKS = [&](Square king_sq, Color color_) -> Square {
Rank r = rank_of(king_sq);
for (int f = file_of(king_sq) + 1; f <= FILE_H; ++f) {
Square sq = make_sq(r, static_cast(f));
PieceC p = pieces_list[sq];
- if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color)
+ if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color_)
return sq;
}
return SQ_NONE;
@@ -351,12 +384,12 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
if (color == WHITE) {
state().castlingRights |= WHITE_OO;
- state().castlingMetadata[WHITE].king_start = king_sq;
- state().castlingMetadata[WHITE].rook_start_ks = rook_sq;
+ castling_meta_[WHITE].king_start = king_sq;
+ castling_meta_[WHITE].rook_start_ks = rook_sq;
} else {
state().castlingRights |= BLACK_OO;
- state().castlingMetadata[BLACK].king_start = king_sq;
- state().castlingMetadata[BLACK].rook_start_ks = rook_sq;
+ castling_meta_[BLACK].king_start = king_sq;
+ castling_meta_[BLACK].rook_start_ks = rook_sq;
}
};
@@ -366,12 +399,12 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
if (color == WHITE) {
state().castlingRights |= WHITE_OOO;
- state().castlingMetadata[WHITE].king_start = king_sq;
- state().castlingMetadata[WHITE].rook_start_qs = rook_sq;
+ castling_meta_[WHITE].king_start = king_sq;
+ castling_meta_[WHITE].rook_start_qs = rook_sq;
} else {
state().castlingRights |= BLACK_OOO;
- state().castlingMetadata[BLACK].king_start = king_sq;
- state().castlingMetadata[BLACK].rook_start_qs = rook_sq;
+ castling_meta_[BLACK].king_start = king_sq;
+ castling_meta_[BLACK].rook_start_qs = rook_sq;
}
};
@@ -438,21 +471,21 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
for (Color c : { WHITE, BLACK }) {
// king
if (castlingRights() & (c & KING_SIDE)) {
- const auto king_from = state().castlingMetadata[c].king_start;
- const auto rook_from = make_sq(file_of(state().castlingMetadata[c].rook_start_ks), rank_of(king_from));
+ const auto king_from = castling_meta_[c].king_start;
+ const auto rook_from = make_sq(file_of(castling_meta_[c].rook_start_ks), rank_of(king_from));
const auto king_to = castling_king_square(c, true);
const auto rook_to = castling_rook_square(c, true);
- state().castlingMetadata[c].castling_paths[true] =
+ castling_meta_[c].castling_paths[true] =
(movegen::between(rook_from, rook_to) | movegen::between(king_from, king_to)) &
~((1ULL << king_from) | (1ULL << rook_from));
}
// queen
if (castlingRights() & (c & QUEEN_SIDE)) {
- const auto king_from = state().castlingMetadata[c].king_start;
- const auto rook_from = make_sq(file_of(state().castlingMetadata[c].rook_start_qs), rank_of(king_from));
+ const auto king_from = castling_meta_[c].king_start;
+ const auto rook_from = make_sq(file_of(castling_meta_[c].rook_start_qs), rank_of(king_from));
const auto king_to = castling_king_square(c, false);
const auto rook_to = castling_rook_square(c, false);
- state().castlingMetadata[c].castling_paths[false] =
+ castling_meta_[c].castling_paths[false] =
(movegen::between(rook_from, rook_to) | movegen::between(king_from, king_to)) &
~((1ULL << king_from) | (1ULL << rook_from));
}
@@ -464,13 +497,7 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
Rank r = static_cast(enpassant[1] - '1');
Square ep_sq = make_sq(r, f);
state().enPassant = ep_sq;
- Bitboard ep_mask = 1ULL << ep_sq;
- if (sideToMove() == WHITE) {
- ep_mask >>= 8;
- } else
- ep_mask <<= 8;
- ep_mask = ((ep_mask << 1) & ~attacks::MASK_FILE[0]) | ((ep_mask >> 1) & ~attacks::MASK_FILE[7]);
- if (ep_mask & pieces(sideToMove())) {
+ if (ep_pawn_masks[ep_sq] & pieces(side_to_move())) {
state().hash ^= zobrist::RandomEP[f];
state().epIncluded = true;
}
@@ -486,8 +513,11 @@ void _Position::setFEN(const std::string &str, bool chess960, FENPars
state().fullMoveNumber = fullmove;
refresh_attacks();
state().repetition = state().pliesFromNull = 0;
+ rep_hashes_.push_back(state().hash);
}
+/// @brief Export the position as a FEN string.
+/// @param xfen If true, use X-FEN castling notation (supports Chess960).
template std::string _Position::fen(bool xfen) const {
std::ostringstream ss;
@@ -515,28 +545,28 @@ template std::string _Position::fen(boo
}
// 2) Side to move
- ss << ' ' << (sideToMove() == WHITE ? 'w' : 'b');
+ ss << ' ' << (side_to_move() == WHITE ? 'w' : 'b');
// 3) Castling availability
ss << ' ';
std::string castlingStr;
if (chess960()) {
if (castlingRights() & WHITE_OO)
- castlingStr += (xfen && state().castlingMetadata[WHITE].rook_start_ks == SQ_H1)
+ castlingStr += (xfen && castling_meta_[WHITE].rook_start_ks == SQ_H1)
? 'K'
- : static_cast('A' + file_of(state().castlingMetadata[WHITE].rook_start_ks));
+ : static_cast('A' + file_of(castling_meta_[WHITE].rook_start_ks));
if (castlingRights() & WHITE_OOO)
- castlingStr += (xfen && state().castlingMetadata[WHITE].rook_start_qs == SQ_A1)
+ castlingStr += (xfen && castling_meta_[WHITE].rook_start_qs == SQ_A1)
? 'Q'
- : static_cast('A' + file_of(state().castlingMetadata[WHITE].rook_start_qs));
+ : static_cast('A' + file_of(castling_meta_[WHITE].rook_start_qs));
if (castlingRights() & BLACK_OO)
- castlingStr += (xfen && state().castlingMetadata[BLACK].rook_start_ks == SQ_H8)
+ castlingStr += (xfen && castling_meta_[BLACK].rook_start_ks == SQ_H8)
? 'k'
- : static_cast('a' + file_of(state().castlingMetadata[BLACK].rook_start_ks));
+ : static_cast('a' + file_of(castling_meta_[BLACK].rook_start_ks));
if (castlingRights() & BLACK_OOO)
- castlingStr += (xfen && state().castlingMetadata[BLACK].rook_start_qs == SQ_A8)
+ castlingStr += (xfen && castling_meta_[BLACK].rook_start_qs == SQ_A8)
? 'q'
- : static_cast('a' + file_of(state().castlingMetadata[BLACK].rook_start_qs));
+ : static_cast('a' + file_of(castling_meta_[BLACK].rook_start_qs));
} else {
if (castlingRights() & WHITE_OO)
castlingStr += 'K';
@@ -555,29 +585,31 @@ template std::string _Position::fen(boo
ss << (ep == SQ_NONE ? "-" : uci::squareToString(ep));
// 5) Halfmove clock
- ss << ' ' << (int)halfmoveClock();
+ ss << ' ' << (int)rule50_count();
// 6) Fullmove number
- ss << ' ' << (int)fullmoveNumber();
+ ss << ' ' << (int)fullmove_number();
return ss.str();
}
+/// @brief Validate the current position for internal consistency.
+/// @tparam Strict If true, also check for non-pawn material and legal castling rights.
template template bool _Position::is_valid() const {
if (count() != 1)
return false;
if (count() != 1)
return false;
- Color stm = sideToMove();
+ Color stm = side_to_move();
// stm checking
- bool whiteInCheck = isAttacked(kingSq(WHITE), BLACK);
- bool blackInCheck = isAttacked(kingSq(BLACK), WHITE);
+ bool whiteInCheck = is_attacked(king_sq(WHITE), BLACK);
+ bool blackInCheck = is_attacked(king_sq(BLACK), WHITE);
// Both kings cannot be in check simultaneously
if (whiteInCheck && blackInCheck)
return false;
// The side to move cannot have its king currently in check from itself (nonsense)
- if (isAttacked(kingSq(~stm), stm))
+ if (is_attacked(king_sq(~stm), stm))
return false;
if (piece_on(SQ_A1) != PieceC::WROOK && (castlingRights() & WHITE_OOO) != 0)
return false;
@@ -633,19 +665,20 @@ template template bool _Position CheckType _Position::givesCheck(Move move) const {
const static auto getSniper = [](const _Position *p, const Square ksq, Bitboard oc) {
- const auto us_occ = p->us(p->sideToMove());
+ const auto us_occ = p->us(p->side_to_move());
const auto bishop = attacks::bishop(ksq, oc) & p->pieces(PieceType::BISHOP, PieceType::QUEEN) & us_occ;
const auto rook = attacks::rook(ksq, oc) & p->pieces(PieceType::ROOK, PieceType::QUEEN) & us_occ;
return (bishop | rook);
};
- assert(color_of(at(move.from())) == sideToMove());
+ assert(color_of(at(move.from())) == side_to_move());
const Square from = move.from();
const Square to = move.to();
- const Square ksq = kingSq(~sideToMove());
+ const Square ksq = king_sq(~side_to_move());
const Bitboard toBB = 1ULL << (to);
const PieceType pt = piece_of(at(from));
@@ -672,18 +705,18 @@ template CheckType _Position::givesChec
if (Bitboard sniper = getSniper(this, ksq, oc)) {
const auto sq = static_cast(pop_lsb(sniper));
- return (!(movegen::between(ksq, sq) & toBB) || move.typeOf() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK
+ return (!(movegen::between(ksq, sq) & toBB) || move.type_of() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK
: CheckType::NO_CHECK;
}
- switch (move.typeOf()) {
+ switch (move.type_of()) {
case Move::NORMAL:
return CheckType::NO_CHECK;
case Move::PROMOTION: {
Bitboard attacks = 0ull;
- switch (move.promotionType()) {
+ switch (move.promotion_type()) {
case KNIGHT:
attacks = attacks::knight(to);
break;
@@ -700,7 +733,7 @@ template CheckType _Position::givesChec
break;
}
- return (attacks & pieces(PieceType::KING, ~sideToMove())) ? CheckType::DIRECT_CHECK : CheckType::NO_CHECK;
+ return (attacks & pieces(PieceType::KING, ~side_to_move())) ? CheckType::DIRECT_CHECK : CheckType::NO_CHECK;
}
case Move::ENPASSANT: {
@@ -717,15 +750,16 @@ template CheckType _Position::givesChec
assert(false);
return CheckType::NO_CHECK; // Prevent a compiler warning
}
+/// @brief Recompute cached attack info (checkers, check mask, pins).
+/// @details Called after setFEN and undoMove (when not restoring from history).
template void _Position::refresh_attacks() {
- const Color c = sideToMove();
+ const Color c = side_to_move();
- Square king_sq = kingSq(c);
- _bishop_pin = pinMask(c, king_sq);
- _rook_pin = pinMask(c, king_sq);
- _pin_mask = _bishop_pin | _rook_pin;
+ Square king_square = king_sq(c);
+ pinMasks(c, king_square, _rook_pin, _bishop_pin);
+ _pin_mask = _rook_pin | _bishop_pin;
- _checkers = attackers(~c, king_sq);
+ _checkers = attackers(~c, king_square);
switch (popcount(_checkers)) {
case 0:
@@ -734,7 +768,7 @@ template void _Position::refresh_attack
case 1: {
auto sq = static_cast(lsb(_checkers));
- _check_mask = 1ULL << sq | movegen::between(king_sq, sq);
+ _check_mask = 1ULL << sq | movegen::between(king_square, sq);
break;
}
@@ -743,6 +777,7 @@ template void _Position::refresh_attack
break;
}
}
+/// @brief Compute Zobrist hash for the current position.
template uint64_t _Position::zobrist() const {
uint64_t hash = 0;
for (int sq = 0; sq < 64; ++sq) {
@@ -757,45 +792,42 @@ template uint64_t _Position::zobrist()
{
const File f = file_of(ep_sq);
Bitboard ep_mask = (1ULL << ep_sq);
-
- // Shift to the rank where the opposing pawn sits
- const Color stm = sideToMove();
- // Color them = ~stm;
+ const Color stm = side_to_move();
ep_mask = (stm == WHITE) ? (ep_mask >> 8) : (ep_mask << 8);
-
- // Pawns on adjacent files only
ep_mask = ((ep_mask << 1) & ~attacks::MASK_FILE[0]) | ((ep_mask >> 1) & ~attacks::MASK_FILE[7]);
-
if (ep_mask & pieces(stm))
hash ^= zobrist::RandomEP[f];
}
return hash;
}
+/// @brief Parse a UCI move string into a Move object.
template Move _Position::parse_uci(std::string uci) const {
return uci::uciToMove(*this, uci);
}
+/// @brief Parse and immediately push (execute) a UCI move string.
template Move _Position::push_uci(std::string uci) {
const auto mv = parse_uci(std::move(uci));
- doMove(mv);
+ do_move(mv);
return mv;
}
+/// @brief Compute the valid en-passant target square, or SQ_NONE if none.
template Square _Position::_valid_ep_square() const {
if (ep_square() == SQ_NONE)
return SQ_NONE;
Rank ep_rank;
- ep_rank = sideToMove() == WHITE ? RANK_6 : RANK_3;
+ ep_rank = side_to_move() == WHITE ? RANK_6 : RANK_3;
const Bitboard mask = 1ULL << ep_square();
Bitboard pawn_mask = mask << 8;
Bitboard org_pawn_mask = mask >> 8;
- if (sideToMove() == BLACK)
+ if (side_to_move() == BLACK)
std::swap(pawn_mask, org_pawn_mask);
// rank 3 or rank 6, depending on color
if (rank_of(ep_square()) != ep_rank)
return SQ_NONE;
// a pawn in 2 ranks behind
- if (!(pieces(PAWN) & occ(~sideToMove()) & pawn_mask))
+ if (!(pieces(PAWN) & occ(~side_to_move()) & pawn_mask))
return SQ_NONE;
// ep_sq must be empty
if (occ() & mask)
@@ -805,6 +837,7 @@ template Square _Position::_valid_ep_sq
return SQ_NONE;
return ep_square();
}
+/// @brief Check if a given color has insufficient mating material.
template bool _Position::is_insufficient_material(Color c) const {
const auto count = popcount(occ());
@@ -846,11 +879,12 @@ template bool _Position::is_insufficien
return false;
}
+/// @brief Compute the set of castling rights that are physically valid on the board.
template CastlingRights _Position::clean_castling_rights() const {
- const Bitboard cr_BOO = state().castlingMetadata[BLACK].rook_start_ks;
- const Bitboard cr_BOOO = state().castlingMetadata[BLACK].rook_start_qs;
- const Bitboard cr_WOO = state().castlingMetadata[WHITE].rook_start_ks;
- const Bitboard cr_WOOO = state().castlingMetadata[WHITE].rook_start_qs;
+ const Bitboard cr_BOO = castling_meta_[BLACK].rook_start_ks;
+ const Bitboard cr_BOOO = castling_meta_[BLACK].rook_start_qs;
+ const Bitboard cr_WOO = castling_meta_[WHITE].rook_start_ks;
+ const Bitboard cr_WOOO = castling_meta_[WHITE].rook_start_qs;
Bitboard castling = 0;
// mappings
castling |= (castlingRights() & WHITE_OO) ? cr_WOO : 0;
@@ -865,9 +899,9 @@ template CastlingRights _Position::clea
white_castling &= (cr_WOO | cr_WOOO);
black_castling &= (cr_BOO | cr_BOOO);
// king exists in e1/e8 depending on color
- if (!(occ(WHITE) & pieces(KING) & (1ULL << state().castlingMetadata[WHITE].king_start)))
+ if (!(occ(WHITE) & pieces(KING) & (1ULL << castling_meta_[WHITE].king_start)))
white_castling = 0;
- if (!(occ(BLACK) & pieces(KING) & (1ULL << state().castlingMetadata[BLACK].king_start)))
+ if (!(occ(BLACK) & pieces(KING) & (1ULL << castling_meta_[BLACK].king_start)))
black_castling = 0;
castling = white_castling | black_castling;
// Re-map
@@ -891,7 +925,7 @@ template Move _Position::push_uci(std::string); \
template bool _Position::is_valid() const; \
template bool _Position::is_valid() const; \
template CheckType _Position::givesCheck(Move) const; \
-template bool _Position::is_insufficient_material() const;
+template bool _Position::is_insufficient_material(Color c) const;
// clang-format off
INSTANTIATE(PolyglotPiece)
INSTANTIATE(EnginePiece)
diff --git a/position.h b/position.h
index c879391..f6783ec 100644
--- a/position.h
+++ b/position.h
@@ -25,42 +25,53 @@
#include
#include
#include
+
+/// @file position.h
+/// @brief Chess position representation, move execution, and game-state queries.
+
namespace chess {
+/// @struct HistoryEntry
+/// @brief Saved position state for undo operations.
+/// @tparam Piece Piece-enum type.
template struct alignas(64) HistoryEntry {
- // Bitboards for each piece type (white and black)
- Bitboard pieces[7];
- Bitboard occ[COLOR_NB];
- Color turn; // true if white to move
- Move mv;
- Key hash;
- uint8_t halfMoveClock; // Half-move clock for 50/75-move rule
- uint16_t fullMoveNumber; // Full-move number (starts at 1)
+ Bitboard pieces[7]; ///< Bitboards per piece type.
+ Bitboard occ[COLOR_NB]; ///< Occupancy per colour.
+ Color turn; ///< Side to move.
+ Move mv; ///< The move that led to this position.
+ Key hash; ///< Zobrist hash.
+ uint8_t halfMoveClock; ///< Half-move clock for 50/75-move rule.
+ uint16_t fullMoveNumber; ///< Full-move number (starts at 1).
bool epIncluded;
- int8_t repetition = 0;
+ int8_t repetition; ///< Repetition counter from this position.
uint8_t pliesFromNull = 0;
- Square enPassant = SQ_NONE; // En passant target square
+ Square enPassant = SQ_NONE; ///< En-passant target square.
Square kings[COLOR_NB] = { SQ_NONE };
- CastlingRights castlingRights; // Castling rights bitmask
+ CastlingRights castlingRights; ///< Castling rights bitmask.
Square incr_sqs[4] = { SQ_NONE, SQ_NONE, SQ_NONE, SQ_NONE };
Piece incr_pc[4] = { Piece::NO_PIECE, Piece::NO_PIECE, Piece::NO_PIECE, Piece::NO_PIECE };
- struct {
- Square king_start = SQ_NONE;
- Square rook_start_ks = SQ_NONE;
- Square rook_start_qs = SQ_NONE;
- std::array castling_paths;
- } castlingMetadata[2];
- // implementation-specific implementations goes here
+ /// @name Cached attack data (saved to avoid recomputation on undo)
+ /// @{
+ Bitboard saved_rook_pin{};
+ Bitboard saved_bishop_pin{};
+ Bitboard saved_checkers{};
+ Bitboard saved_check_mask{};
+ /// @}
};
+/// @enum CheckType
+/// @brief Classification of check on a move.
enum class CheckType { NO_CHECK, DIRECT_CHECK, DISCOVERY_CHECK };
+/// @enum FENParsingMode
+/// @brief FEN parsing mode for castling rights.
enum FENParsingMode { MODE_XFEN, MODE_SMK, MODE_AUTO };
+/// @enum MoveGenType
+/// @brief Flags controlling which pieces and move types are generated.
enum class MoveGenType : uint16_t {
NONE = 0,
- // piece selectors
PAWN = 1 << 1,
KNIGHT = 1 << 2,
BISHOP = 1 << 3,
@@ -70,7 +81,6 @@ enum class MoveGenType : uint16_t {
PIECE_MASK = PAWN | KNIGHT | BISHOP | ROOK | QUEEN | KING,
- // move-type selectors
CAPTURE = 1 << 7,
QUIET = 1 << 8,
@@ -84,13 +94,17 @@ template constexpr MoveGenType operator&(MoveGenType a, M
template constexpr MoveGenType operator|(MoveGenType a, MoveGenType b) {
using U = std::underlying_type_t;
- return static_cast(static_cast(a) |
- static_cast(b)); // NOLINT(clang-analyzer-optin.core.EnumCastOutOfRange)
+ return static_cast(static_cast(a) | static_cast(b));
}
+
+/// @class _Position
+/// @brief Templated chess position.
+/// @tparam PieceC Piece-enum type (EnginePiece, PolyglotPiece, or ContiguousMappingPiece).
+/// @tparam (unused) Position tag parameter.
template ::value>> class _Position {
private:
- // Move history stack
std::vector> history;
+ std::vector rep_hashes_;
Bitboard _rook_pin{};
Bitboard _bishop_pin{};
Bitboard _checkers{};
@@ -107,40 +121,44 @@ template castling_paths{};
+ } castling_meta_[2]{};
+
public:
static inline constexpr auto START_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
static inline constexpr auto START_CHESS960_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w HAha - 0 1";
- // Legal move generation functions
- template void legals(Movelist &out) const {
+ /// @brief Generate legal moves filtered by type.
+ /// @tparam type Bitmask of MoveGenType flags.
+ /// @tparam c Colour to move.
+ /// @param out Output move list.
+ template void legals(Movelist &out) const {
constexpr auto raw = static_cast(type);
constexpr uint16_t pieceBits = raw & static_cast(MoveGenType::PIECE_MASK);
constexpr uint16_t modeBits =
raw & (static_cast(MoveGenType::CAPTURE) | static_cast(MoveGenType::QUIET));
- // ----------------------------------------
- // Resolve default piece selection
- // ----------------------------------------
constexpr uint16_t effectivePieces =
pieceBits ? pieceBits
: (raw == static_cast(MoveGenType::NONE) ? 0 : static_cast(MoveGenType::PIECE_MASK));
- // ----------------------------------------
- // Resolve default mode selection
- // ----------------------------------------
constexpr bool includeCaps = modeBits == 0 || (modeBits & static_cast(MoveGenType::CAPTURE));
constexpr bool includeQuiet = modeBits == 0 || (modeBits & static_cast(MoveGenType::QUIET));
constexpr bool captureOnly = includeCaps && !includeQuiet;
- // Early-out for NONE
if constexpr (effectivePieces == 0 && modeBits != 0)
return;
- // Now your existing piece dispatch logic stays the same:
if constexpr (effectivePieces & static_cast(MoveGenType::PAWN)) {
movegen::genPawnSingleMoves(*this, out, _rook_pin, _bishop_pin, _check_mask);
if constexpr (includeQuiet)
@@ -148,31 +166,26 @@ template (*this, out);
}
-
if constexpr (effectivePieces & static_cast(MoveGenType::KNIGHT)) {
movegen::genKnightMoves(*this, out, _pin_mask, _check_mask);
}
-
if constexpr (effectivePieces & static_cast(MoveGenType::KING)) {
movegen::genKingMoves(*this, out, _pin_mask);
}
-
if constexpr (effectivePieces & static_cast(MoveGenType::BISHOP)) {
movegen::genSlidingMoves(*this, out, _rook_pin, _bishop_pin, _check_mask);
}
-
if constexpr (effectivePieces & static_cast(MoveGenType::ROOK)) {
movegen::genSlidingMoves(*this, out, _rook_pin, _bishop_pin, _check_mask);
}
-
if constexpr (effectivePieces & static_cast(MoveGenType::QUEEN)) {
movegen::genSlidingMoves(*this, out, _rook_pin, _bishop_pin, _check_mask);
}
}
- // Legal move generation functions
+ /// @brief Generate legal moves (runtime colour dispatch).
template inline void legals(Movelist &out) const {
- switch (sideToMove()) {
+ switch (side_to_move()) {
case WHITE:
legals(out);
return;
@@ -184,26 +197,44 @@ template