Skip to content

Commit 0014efe

Browse files
authored
Implementing check mask (#69)
* added Zeros() and Ones() static constructors for Bitboards as well as new overloads for test clear and set functions * added Square methods to test easily for identical rank, file and diagonals * added docstrings for attackers functions * between squares utils and tests * more tests for attackers * save * fixed linting and tests * fixed undefined behavior on step = 0
1 parent a12c4a7 commit 0014efe

File tree

14 files changed

+1419
-22
lines changed

14 files changed

+1419
-22
lines changed

include/bitbishop/bitboard.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ class Bitboard {
5252
/** @brief Returns the raw 64-bit value of the bitboard. */
5353
[[nodiscard]] constexpr uint64_t value() const { return m_bb; }
5454

55+
/** @brief Returns a Bitboard instance with all bits set to zero. */
56+
static constexpr Bitboard Zeros() noexcept { return {0ULL}; }
57+
58+
/** @brief Returns a Bitbaord instance with all bits set to one. */
59+
static constexpr Bitboard Ones() noexcept { return {~0ULL}; }
60+
5561
/**
5662
* @brief Sets a bit (places a piece) on a given square.
5763
*
@@ -70,6 +76,7 @@ class Bitboard {
7076
*/
7177
constexpr void set(Square square) { m_bb |= (1ULL << square.value()); }
7278
constexpr void set(Square::Value square) { m_bb |= (1ULL << square); }
79+
constexpr void set(std::uint8_t square) { m_bb |= (1ULL << square); }
7380

7481
/**
7582
* @brief Clears a bit (removes a piece) on a given square.
@@ -89,6 +96,7 @@ class Bitboard {
8996
*/
9097
constexpr void clear(Square square) { m_bb &= ~(1ULL << square.value()); }
9198
constexpr void clear(Square::Value square) { m_bb &= ~(1ULL << square); }
99+
constexpr void clear(std::uint8_t square) { m_bb &= ~(1ULL << square); }
92100

93101
/**
94102
* @brief Checks if a square is occupied.
@@ -108,6 +116,7 @@ class Bitboard {
108116
*/
109117
[[nodiscard]] constexpr bool test(Square square) const { return ((m_bb >> square.value()) & 1ULL) != 0ULL; }
110118
[[nodiscard]] constexpr bool test(Square::Value square) const { return ((m_bb >> square) & 1ULL) != 0ULL; }
119+
[[nodiscard]] constexpr bool test(std::uint8_t square) const { return ((m_bb >> square) & 1ULL) != 0ULL; }
111120

112121
/** @brief Clears the whole bitboard (all bits = 0). */
113122
constexpr void reset() { m_bb = 0ULL; }
@@ -167,6 +176,15 @@ class Bitboard {
167176
*/
168177
[[nodiscard]] constexpr bool any() const noexcept { return m_bb != 0ULL; }
169178

179+
/**
180+
* @brief Tells if the bitboard is empty / has no bit set.
181+
*
182+
* @return True if no bit is set on that bitboard.
183+
*
184+
* @note Equivalent to '!any()' or 'count() == 0'
185+
*/
186+
[[nodiscard]] constexpr bool empty() const noexcept { return m_bb == 0ULL; }
187+
170188
/**
171189
* @brief Removes and returns the least significant set bit (LSB) from the bitboard.
172190
*

include/bitbishop/board.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ class Board {
6363
*/
6464
Board(const std::string& fen);
6565

66+
[[nodiscard]] static Board Empty() noexcept { return {"8/8/8/8/8/8/8/8 w KQkq - 0 1"}; }
67+
6668
/**
6769
* @brief Retrieves the piece on a given square.
6870
* @param square The square to query.

include/bitbishop/lookups/attackers.hpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010
#include <bitbishop/lookups/rook_rays.hpp>
1111
#include <bitbishop/square.hpp>
1212

13+
/**
14+
* @brief Computes the geometric attackers to a target square for a given color.
15+
*
16+
* Returns a bitboard containing all squares from which a piece of the given
17+
* color could attack the target square, assuming an empty board for sliding
18+
* pieces.
19+
*
20+
* This function performs a purely geometric attack computation:
21+
* - King, knight, and pawn attacks are exact.
22+
* - Rook, bishop, and queen attacks are based on precomputed rays and do not
23+
* account for blockers.
24+
*
25+
* The resulting bitboard therefore represents *potential* attackers and must
26+
* be filtered against board occupancy to determine actual attacks.
27+
*
28+
* This function is intended for use in:
29+
* - check detection
30+
* - pin detection
31+
* - attack-based legality filtering
32+
*
33+
* @param target The square being attacked.
34+
* @param color The color of the attacking side.
35+
* @return A bitboard of squares that could attack @p target.
36+
*
37+
* @note This function does not consider board occupancy.
38+
* @note The returned bitboard may include squares that are blocked in the
39+
* current position.
40+
*/
1341
constexpr Bitboard attackers_to(Square target, Color color) {
1442
using namespace Lookups;
1543
const int square_index = target.value();
@@ -31,15 +59,40 @@ constexpr Bitboard attackers_to(Square target, Color color) {
3159
return attackers;
3260
}
3361

62+
/**
63+
* @brief Precomputed lookup table of geometric attackers for all squares and colors.
64+
*
65+
* For each color and target square, this table contains a bitboard of all
66+
* squares from which a piece of that color could potentially attack the target
67+
* square, ignoring board occupancy.
68+
*
69+
* The table is indexed as:
70+
* @code
71+
* ATTACKERS_TO[color_index][square_index]
72+
* @endcode
73+
*
74+
* This table is computed entirely at compile time and serves as a fast,
75+
* constant-time replacement for repeated calls to attackers_to().
76+
*
77+
* Typical use cases include:
78+
* - fast king-in-check detection
79+
* - identifying candidate checking pieces
80+
* - pin and x-ray attack analysis
81+
*
82+
* @note Sliding piece attacks in this table are unblocked rays and must be
83+
* masked with board occupancy when determining actual attacks.
84+
*/
3485
constexpr std::array<std::array<Bitboard, Const::BOARD_SIZE>, ColorUtil::size()> ATTACKERS_TO = []() constexpr {
3586
using namespace Const;
3687

3788
std::array<std::array<Bitboard, BOARD_SIZE>, ColorUtil::size()> table{};
89+
3890
for (Color col : ColorUtil::all()) {
3991
const std::size_t coli = ColorUtil::to_index(col);
4092
for (int sq = 0; sq < BOARD_SIZE; ++sq) {
4193
table[coli][sq] = Bitboard(attackers_to(Square(sq, std::in_place), col));
4294
}
4395
}
96+
4497
return table;
4598
}();
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include <array>
2+
#include <bitbishop/bitboard.hpp>
3+
#include <bitbishop/constants.hpp>
4+
5+
namespace Lookups {
6+
7+
/**
8+
* @brief Computes the directional step between two aligned squares.
9+
*
10+
* Returns the signed square-index increment required to move from the
11+
* source square toward the destination square along a shared rank, file,
12+
* or diagonal.
13+
*
14+
* The returned value corresponds to the underlying square indexing:
15+
* - +-1 for horizontal movement (same rank)
16+
* - +-8 for vertical movement (same file)
17+
* - +-9 for NE–SW diagonal movement
18+
* - +-7 for NW–SE diagonal movement
19+
* - 0 if source and destination square are identical
20+
* - 0 if source and destination squares are not aligned
21+
*
22+
* This function assumes that the two squares are aligned. Its primary use
23+
* is internal traversal of rays when building geometric lookup tables such
24+
* as BETWEEN.
25+
*
26+
* @param from The starting square.
27+
* @param to The target square.
28+
* @return The signed step value to advance one square toward @p to.
29+
*/
30+
constexpr int direction(Square from, Square to) {
31+
if (from == to) {
32+
return 0;
33+
}
34+
if (from.same_rank(to)) {
35+
return (to.file() > from.file()) ? +1 : -1;
36+
}
37+
if (from.same_file(to)) {
38+
return (to.rank() > from.rank()) ? +Const::BOARD_WIDTH : -Const::BOARD_WIDTH;
39+
}
40+
if (from.same_ne_sw_diag(to)) {
41+
return (to.rank() > from.rank()) ? +(Const::BOARD_WIDTH + 1) : -(Const::BOARD_WIDTH + 1);
42+
}
43+
if (from.same_nw_se_diag(to)) {
44+
return (to.rank() > from.rank()) ? +(Const::BOARD_WIDTH - 1) : -(Const::BOARD_WIDTH - 1);
45+
}
46+
47+
return 0;
48+
}
49+
50+
/**
51+
* @brief Computes the bitboard of squares strictly between two aligned squares.
52+
*
53+
* Returns a bitboard containing all squares located strictly between the
54+
* source and destination squares, excluding both endpoints.
55+
*
56+
* If the squares are not aligned along the same rank, file, or diagonal,
57+
* or if both squares are identical, an empty bitboard is returned.
58+
*
59+
* This function is purely geometric and does not depend on board occupancy.
60+
* It is intended for use in compile-time construction of lookup tables and
61+
* in runtime legality checks such as pins and check interposition.
62+
*
63+
* @param from The starting square.
64+
* @param to The target square.
65+
* @return A bitboard of squares strictly between @p from and @p to.
66+
*/
67+
constexpr Bitboard ray_between(Square from, Square to) {
68+
if (from == to) {
69+
return Bitboard::Zeros();
70+
}
71+
72+
if (!(from.same_file(to) || from.same_rank(to) || from.same_diag(to))) {
73+
return Bitboard::Zeros();
74+
}
75+
76+
// Defensive check: if step is 0, squares aren't truly aligned
77+
const int step = direction(from, to);
78+
if (step == 0) {
79+
return Bitboard::Zeros();
80+
}
81+
82+
Bitboard ray = Bitboard::Zeros();
83+
for (std::uint8_t square = from.value() + step; square != to.value(); square += step) {
84+
ray.set(square);
85+
}
86+
87+
return ray;
88+
}
89+
90+
/**
91+
* @brief Precomputed lookup table of squares lying strictly between any two squares.
92+
*
93+
* For each pair of squares (from, to), this table contains a bitboard of all
94+
* squares strictly between them along a shared rank, file, or diagonal.
95+
*
96+
* If the squares are not aligned, the corresponding entry is an empty bitboard.
97+
*
98+
* This table is computed entirely at compile time and is used extensively
99+
* in move generation, including:
100+
* - pin detection
101+
* - check resolution
102+
* - slider attack filtering
103+
*
104+
* The table is indexed as BETWEEN[from][to].
105+
*/
106+
constexpr std::array<std::array<Bitboard, Const::BOARD_SIZE>, Const::BOARD_SIZE> BETWEEN = []() constexpr {
107+
using namespace Const;
108+
109+
std::array<std::array<Bitboard, BOARD_SIZE>, BOARD_SIZE> table{};
110+
111+
for (int from = 0; from < BOARD_SIZE; ++from) {
112+
for (int to = 0; to < BOARD_SIZE; ++to) {
113+
table[from][to] = ray_between(Square(from, std::in_place), Square(to, std::in_place));
114+
}
115+
}
116+
117+
return table;
118+
}();
119+
120+
} // namespace Lookups
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#include <bitbishop/bitboard.hpp>
2+
#include <bitbishop/board.hpp>
3+
#include <bitbishop/square.hpp>
4+
5+
Bitboard compute_check_mask(Square king_sq, const Bitboard& checkers, const Board& board);

include/bitbishop/square.hpp

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,70 @@ class Square {
151151
return static_cast<int>(m_value) / BOARD_WIDTH;
152152
}
153153

154+
/**
155+
* @brief Tells if two squares lays on the same file.
156+
* @return True if two squares lays on the same file, false otherwise.
157+
*/
158+
[[nodiscard]] constexpr bool same_file(const Square& other) const { return this->file() == other.file(); }
159+
160+
/**
161+
* @brief Tells if two squares lays on the same rank.
162+
* @return True if two squares lays on the same rank, false otherwise.
163+
*/
164+
[[nodiscard]] constexpr bool same_rank(const Square& other) const { return this->rank() == other.rank(); }
165+
166+
/**
167+
* @brief Checks whether this square lies on the same NE–SW diagonal as another square.
168+
*
169+
* Two squares are aligned on a NE–SW diagonal if the difference between their
170+
* file and rank coordinates is identical. Along such a diagonal, (file − rank)
171+
* remains constant.
172+
*
173+
* This diagonal orientation corresponds to bishop movement in the NE–SW direction
174+
* and is commonly used when computing diagonal attacks, pins, and interposition
175+
* rays.
176+
*
177+
* @param other The square to compare against.
178+
* @return true if both squares share the same NE–SW diagonal, false otherwise.
179+
*/
180+
[[nodiscard]] constexpr bool same_ne_sw_diag(const Square& other) const {
181+
return (this->file() - this->rank()) == (other.file() - other.rank());
182+
}
183+
184+
/**
185+
* @brief Checks whether this square lies on the same NW–SE diagonal as another square.
186+
*
187+
* Two squares are aligned on a NW–SE diagonal if the sum of their file and rank
188+
* coordinates is identical. Along such a diagonal, (file + rank) remains constant.
189+
*
190+
* This diagonal orientation corresponds to bishop movement in the NW–SE direction
191+
* and is commonly used when computing diagonal attacks, pins, and interposition
192+
* rays.
193+
*
194+
* @param other The square to compare against.
195+
* @return true if both squares share the same NW–SE diagonal, false otherwise.
196+
*/
197+
[[nodiscard]] constexpr bool same_nw_se_diag(const Square& other) const {
198+
return (this->file() + this->rank()) == (other.file() + other.rank());
199+
}
200+
201+
/**
202+
* @brief Checks whether this square lies on the same diagonal as another square.
203+
*
204+
* Two squares are on the same diagonal if they share either:
205+
* - the same NE–SW diagonal, or
206+
* - the same NW–SE diagonal.
207+
*
208+
* This geometric property is used to determine bishop and queen alignment,
209+
* detect pins, compute interposition rays, and build diagonal attack lookups.
210+
*
211+
* @param other The square to compare against.
212+
* @return true if both squares are aligned on a common diagonal, false otherwise.
213+
*/
214+
[[nodiscard]] constexpr bool same_diag(const Square& other) const {
215+
return same_ne_sw_diag(other) || same_nw_se_diag(other);
216+
}
217+
154218
/**
155219
* @brief Convert the square to algebraic notation.
156220
* @return Lowercase string like "a1", "e4", "h8".
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <bitbishop/lookups/between_squares.hpp>
2+
#include <bitbishop/movegen/check_mask.hpp>
3+
4+
Bitboard compute_check_mask(Square king_sq, const Bitboard& checkers, const Board& board) {
5+
if (!checkers) {
6+
return Bitboard::Ones();
7+
}
8+
9+
if (checkers.count() > 1) {
10+
return Bitboard::Zeros();
11+
}
12+
13+
Square checker_sq = checkers.lsb().value();
14+
15+
const Bitboard knights_and_pawns =
16+
board.knights(Color::WHITE) | board.knights(Color::BLACK) | board.pawns(Color::WHITE) | board.pawns(Color::BLACK);
17+
const bool is_knight_or_pawn = knights_and_pawns.test(checker_sq);
18+
if (is_knight_or_pawn) {
19+
return checkers;
20+
}
21+
22+
return Lookups::ray_between(checker_sq, king_sq) | checkers;
23+
}

tests/bitbishop/bitboard/test_bb_constructors.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,25 @@ TEST(BitboardTest, CopyConstructorIsConstexpr) {
7171
static_assert(cp.value() == 0x00000000000000FFULL, "Copy constructor must be constexpr");
7272
EXPECT_EQ(cp.value(), 0x00000000000000FFULL);
7373
}
74+
75+
/**
76+
* @test Static Bitboard::Zeros() constructor.
77+
* @brief Verifies that the constructor can be evaluated at compile time.
78+
*/
79+
TEST(BitboardTest, ZerosStaticConstructor) {
80+
constexpr Bitboard bb = Bitboard::Zeros();
81+
82+
static_assert(bb.value() == 0ULL, "Bitboard::Zeros() static constructor must be constexpr");
83+
EXPECT_EQ(bb.value(), 0ULL);
84+
}
85+
86+
/**
87+
* @test Static Bitboard::Ones() constructor.
88+
* @brief Verifies that the constructor can be evaluated at compile time.
89+
*/
90+
TEST(BitboardTest, OnesStaticConstructor) {
91+
constexpr Bitboard bb = Bitboard::Ones();
92+
93+
static_assert(bb.value() == (~0ULL), "Bitboard::Ones() static constructor must be constexpr");
94+
EXPECT_EQ(bb.value(), (~0ULL));
95+
}

0 commit comments

Comments
 (0)