Skip to content

Commit 25f6674

Browse files
authored
Implementing pin detection (#71)
* added a new bitboard constructor from a square * implemented and tested pins functions * renamed internal lib to Bitbishop * updated clang tidy rules to support 'us' color * fixed linting
1 parent 0014efe commit 25f6674

File tree

12 files changed

+1351
-7
lines changed

12 files changed

+1351
-7
lines changed

.clang-tidy

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ WarningsAsErrors: "*"
99

1010
CheckOptions:
1111
- key: readability-identifier-length.IgnoredVariableNames
12-
value: "^(sq|to|from|bb|A[1-8]|B[1-8]|C[1-8]|D[1-8]|E[1-8]|F[1-8]|G[1-8]|H[1-8])$"
12+
value: "^(sq|to|from|bb|us|A[1-8]|B[1-8]|C[1-8]|D[1-8]|E[1-8]|F[1-8]|G[1-8]|H[1-8])$"
1313
- key: readability-identifier-length.IgnoredParameterNames
14-
value: "^(sq|to|from|bb)$"
14+
value: "^(sq|to|from|bb|us)$"
1515
- key: readability-identifier-length.IgnoredLoopCounterNames
1616
value: "^(r|f)$"

include/bitbishop/bitboard.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ class Bitboard {
4949
/** @brief Constructs a bitboard from another bitboard by copy. */
5050
constexpr Bitboard(const Bitboard& bitboard) : m_bb(bitboard.value()) {}
5151

52+
/** @brief Constructs a bitboard with the given square being the only bit set to one. */
53+
constexpr Bitboard(Square square) : m_bb(0ULL) { set(square); }
54+
5255
/** @brief Returns the raw 64-bit value of the bitboard. */
5356
[[nodiscard]] constexpr uint64_t value() const { return m_bb; }
5457

include/bitbishop/movegen/pins.hpp

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#include <bitbishop/bitboard.hpp>
2+
#include <bitbishop/board.hpp>
3+
#include <bitbishop/lookups/between_squares.hpp>
4+
#include <bitbishop/lookups/bishop_rays.hpp>
5+
#include <bitbishop/lookups/rook_rays.hpp>
6+
#include <bitbishop/square.hpp>
7+
8+
/**
9+
* @brief Enumeration of sliding ray types used for pin detection.
10+
*
11+
* Distinguishes between rook-like and bishop-like rays,
12+
* determining which enemy sliders can produce a pin.
13+
*/
14+
enum class RayType : std::uint8_t {
15+
ROOK, ///< Orthogonal rays (rook / queen)
16+
BISHOP ///< Diagonal rays (bishop / queen)
17+
};
18+
19+
/**
20+
* @brief Enumeration of ray directions from the king square.
21+
*
22+
* Used to determine ray orientation and correct blocker ordering
23+
* (LSB vs MSB) when scanning occupied squares.
24+
*/
25+
enum class RayDir : std::uint8_t {
26+
N,
27+
S,
28+
E,
29+
W, ///< Orthogonal directions
30+
NE,
31+
NW,
32+
SE,
33+
SW ///< Diagonal directions
34+
};
35+
36+
/**
37+
* @brief Describes a single ray used for pin detection.
38+
*
39+
* A PinRay represents one directional ray starting from the king square,
40+
* along with the information required to:
41+
* - determine blocker ordering
42+
* - validate enemy sliding pieces that can create pins
43+
*/
44+
struct PinRay {
45+
Bitboard ray; ///< Precomputed ray bitboard from the king square
46+
RayType type; ///< Type of sliding piece relevant for this ray
47+
RayDir dir; ///< Direction of the ray
48+
49+
/**
50+
* @brief Returns the first blocker along this ray.
51+
*
52+
* The function selects either the least-significant or most-significant
53+
* bit depending on the ray direction.
54+
*
55+
* @param bb Bitboard of occupied squares along the ray
56+
* @return Square of the closest blocker to the king
57+
*/
58+
[[nodiscard]] Square first_blocker(const Bitboard& bb) const {
59+
switch (dir) {
60+
case RayDir::N:
61+
case RayDir::NE:
62+
case RayDir::E:
63+
case RayDir::NW:
64+
return bb.lsb().value();
65+
default:
66+
return bb.msb().value();
67+
}
68+
}
69+
70+
/**
71+
* @brief Checks whether a piece matches the required enemy slider for this ray.
72+
*
73+
* For rook rays, accepts enemy rooks or queens.
74+
* For bishop rays, accepts enemy bishops or queens.
75+
*
76+
* @param piece Bitboard with a single enemy piece
77+
* @param board Current board state
78+
* @param them Enemy color
79+
* @return true if the piece can create a pin along this ray
80+
*/
81+
[[nodiscard]] bool matches_slider(const Bitboard& piece, const Board& board, Color them) const {
82+
return (type == RayType::BISHOP) ? (piece & (board.bishops(them) | board.queens(them)))
83+
: (piece & (board.rooks(them) | board.queens(them)));
84+
}
85+
};
86+
87+
/**
88+
* @brief Result structure for pin computation.
89+
*
90+
* Contains:
91+
* - a bitboard of all pinned friendly pieces
92+
* - a per-square pin ray mask restricting legal movement
93+
*/
94+
struct PinResult {
95+
Bitboard pinned = Bitboard::Zeros();
96+
std::array<Bitboard, Const::BOARD_SIZE> pin_ray{};
97+
};
98+
99+
/**
100+
* @brief Scans a single ray from the king to detect a possible pin.
101+
*
102+
* The function detects the classical pin pattern:
103+
* king → friendly piece → enemy sliding piece
104+
*
105+
* If such a configuration is found:
106+
* - the friendly piece is marked as pinned
107+
* - its legal movement is restricted to the pin ray
108+
*
109+
* @param king_sq Square of the friendly king
110+
* @param ray_info Ray descriptor (direction, type, bitboard)
111+
* @param board Current board position
112+
* @param us Friendly color
113+
* @param result Accumulated pin result (modified in place)
114+
*/
115+
void scan_pin_ray(Square king_sq, const PinRay& ray_info, const Board& board, Color us, PinResult& result);
116+
117+
/**
118+
* @brief Computes all pinned pieces for the given side.
119+
*
120+
* Pins are detected by scanning all rook and bishop rays
121+
* originating from the king square.
122+
*
123+
* A piece is considered pinned if moving it would expose
124+
* the king to a sliding attack.
125+
*
126+
* @param king_sq Square of the friendly king
127+
* @param board Current board position
128+
* @param us Friendly color
129+
* @return PinResult containing pinned pieces and their pin rays
130+
*/
131+
PinResult compute_pins(Square king_sq, const Board& board, Color us);

main/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
message(STATUS "> Found main directory")
22

33
add_executable(SandBox sandbox.cpp)
4-
target_link_libraries(SandBox PRIVATE ChessEngineLib spdlog::spdlog)
4+
target_link_libraries(SandBox PRIVATE Bitbishop spdlog::spdlog)
55

66
add_executable(UCIChessEngine uci_loop.cpp)
7-
target_link_libraries(UCIChessEngine PRIVATE ChessEngineLib spdlog::spdlog)
7+
target_link_libraries(UCIChessEngine PRIVATE Bitbishop spdlog::spdlog)
88

99
set_property(
1010
TARGET

src/bitbishop/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ message(STATUS "> Found src/bitbishop directory")
44
option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
55

66
# Define the library target name and source folder
7-
set(target_name ChessEngineLib)
7+
set(target_name Bitbishop)
88
set(target_folder_name bitbishop)
99

1010
# Search and list all .cpp files in the source folder
@@ -54,7 +54,7 @@ target_include_directories(${target_name}
5454
)
5555

5656
# Define the folder in the IDE where the target will appear
57-
set_property(TARGET ${target_name} PROPERTY FOLDER ChessEngineLib)
57+
set_property(TARGET ${target_name} PROPERTY FOLDER Bitbishop)
5858

5959
# Library packaging section
6060

src/bitbishop/movegen/pins.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
#include <bitbishop/movegen/pins.hpp>
3+
4+
void scan_pin_ray(Square king_sq, const PinRay& ray_info, const Board& board, Color us, PinResult& result) {
5+
const Bitboard occupied = board.occupied();
6+
const Bitboard own = board.friendly(us);
7+
const Color them = ColorUtil::opposite(us);
8+
9+
Bitboard blockers = ray_info.ray & occupied;
10+
if (!blockers) {
11+
return;
12+
}
13+
14+
Square first_blocker_sq = ray_info.first_blocker(blockers);
15+
Bitboard first_blocker_bb(first_blocker_sq);
16+
if (!(first_blocker_bb & own)) {
17+
return;
18+
}
19+
20+
Bitboard beyond = ray_info.ray & ~(Lookups::BETWEEN[king_sq.value()][first_blocker_sq.value()] | first_blocker_bb);
21+
Bitboard second_blockers_bb = beyond & occupied;
22+
if (!second_blockers_bb) {
23+
return;
24+
}
25+
26+
Square second_blocker_sq = ray_info.first_blocker(second_blockers_bb);
27+
Bitboard second_blocker_bb(second_blocker_sq);
28+
if (!ray_info.matches_slider(second_blocker_bb, board, them)) {
29+
return;
30+
}
31+
32+
result.pinned |= first_blocker_bb;
33+
result.pin_ray[first_blocker_sq.value()] =
34+
Lookups::BETWEEN[king_sq.value()][second_blocker_sq.value()] | second_blocker_bb;
35+
}
36+
37+
PinResult compute_pins(Square king_sq, const Board& board, Color us) {
38+
PinResult result;
39+
40+
const std::array<PinRay, 8> ray_infos = {{
41+
PinRay{.ray = Lookups::ROOK_NORTH_RAYS[king_sq.value()], .type = RayType::ROOK, .dir = RayDir::N},
42+
PinRay{.ray = Lookups::ROOK_SOUTH_RAYS[king_sq.value()], .type = RayType::ROOK, .dir = RayDir::S},
43+
PinRay{.ray = Lookups::ROOK_EAST_RAYS[king_sq.value()], .type = RayType::ROOK, .dir = RayDir::E},
44+
PinRay{.ray = Lookups::ROOK_WEST_RAYS[king_sq.value()], .type = RayType::ROOK, .dir = RayDir::W},
45+
PinRay{.ray = Lookups::BISHOP_NORTHEAST_RAYS[king_sq.value()], .type = RayType::BISHOP, .dir = RayDir::NE},
46+
PinRay{.ray = Lookups::BISHOP_NORTHWEST_RAYS[king_sq.value()], .type = RayType::BISHOP, .dir = RayDir::NW},
47+
PinRay{.ray = Lookups::BISHOP_SOUTHEAST_RAYS[king_sq.value()], .type = RayType::BISHOP, .dir = RayDir::SE},
48+
PinRay{.ray = Lookups::BISHOP_SOUTHWEST_RAYS[king_sq.value()], .type = RayType::BISHOP, .dir = RayDir::SW},
49+
}};
50+
51+
for (const auto& ray_info : ray_infos) {
52+
scan_pin_ray(king_sq, ray_info, board, us, result);
53+
}
54+
55+
return result;
56+
}

tests/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ foreach(TEST_FILE ${TESTS_SOURCES})
88
get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE)
99
add_executable(${TEST_NAME} ${TEST_FILE} ${TESTS_HEADERS})
1010
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME})
11-
target_link_libraries(${TEST_NAME} PRIVATE ChessEngineLib GTest::gtest GTest::gtest_main)
11+
target_link_libraries(${TEST_NAME} PRIVATE Bitbishop GTest::gtest GTest::gtest_main)
1212
target_include_directories(${TEST_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
1313
set_property(TARGET ${TEST_NAME} PROPERTY FOLDER Tests)
1414
endforeach(TEST_FILE)

tests/bitbishop/bitboard/test_bb_constructors.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,15 @@ TEST(BitboardTest, OnesStaticConstructor) {
9393
static_assert(bb.value() == (~0ULL), "Bitboard::Ones() static constructor must be constexpr");
9494
EXPECT_EQ(bb.value(), (~0ULL));
9595
}
96+
97+
/**
98+
* @test Bitboard(Square) constructor.
99+
* @brief Verifies that the constructor can be evaluated at compile time.
100+
*/
101+
TEST(BitboardConstructorsTest, SquareConstructorIsConstexpr) {
102+
constexpr Bitboard bb = Bitboard(Squares::A4);
103+
104+
static_assert(bb.test(Squares::A4) == true, "Bitboard(Square) must be constexpr");
105+
EXPECT_TRUE(bb.test(Squares::A4));
106+
EXPECT_EQ(bb.count(), 1);
107+
}

0 commit comments

Comments
 (0)