diff --git a/CMakeLists.txt b/CMakeLists.txt index 85b9facf..585ee652 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,8 @@ function(do_test name) add_test(${name} ${name}) endfunction() +do_test(test_see) +do_test(test_see_prototype) do_test(test_is_legal) do_test(test_repetition) do_test(test_static_vector) diff --git a/src/board.hpp b/src/board.hpp index 4d6d7f3d..04d5ce7e 100644 --- a/src/board.hpp +++ b/src/board.hpp @@ -35,13 +35,17 @@ struct Place { return {}; } - static constexpr u8 COLOR_MASK = 0x10; + static constexpr i32 PTYPE_SHIFT = 5; + static constexpr u8 PTYPE_MASK = 0xE0; + static constexpr i32 COLOR_SHIFT = 4; + static constexpr u8 COLOR_MASK = 0x10; + static constexpr u8 ID_MASK = 0x0F; constexpr Place() = default; constexpr Place(Color color, PieceType pt, PieceId id) { - raw = - static_cast((static_cast(color) << 4) | (static_cast(pt) << 5) | id.raw); + raw = static_cast((static_cast(color) << COLOR_SHIFT) + | (static_cast(pt) << PTYPE_SHIFT) | id.raw); } [[nodiscard]] constexpr bool is_empty() const { @@ -51,10 +55,10 @@ struct Place { return static_cast((raw & COLOR_MASK) != 0); } [[nodiscard]] constexpr PieceType ptype() const { - return static_cast((raw >> 5) & 0x7); + return static_cast((raw & PTYPE_MASK) >> PTYPE_SHIFT); } [[nodiscard]] constexpr PieceId id() const { - return PieceId{static_cast(raw & 0xF)}; + return PieceId{static_cast(raw & ID_MASK)}; } [[nodiscard]] constexpr char to_char() const { diff --git a/src/common.hpp b/src/common.hpp index a5396080..3e60081d 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -29,6 +29,10 @@ constexpr Color invert(Color color) { return static_cast(static_cast(color) ^ 1); } +constexpr Color operator~(Color color) { + return invert(color); +} + enum class PieceType : u8 { None, Pawn, diff --git a/src/geometry.hpp b/src/geometry.hpp index 04777ada..25306987 100644 --- a/src/geometry.hpp +++ b/src/geometry.hpp @@ -50,6 +50,12 @@ inline v512 superpiece_attacks(v512 ray_places, v512 ray_valid) { ray_valid); } +inline u64 closest(u64 occupied) { + u64 o = occupied | 0x8181818181818181; + u64 x = o ^ (o - 0x0303030303030303); + return x & occupied; +} + inline v512 attackers_from_rays(v512 ray_places) { constexpr u8 K = 1 << 0; constexpr u8 WP = 1 << 1; diff --git a/src/move.hpp b/src/move.hpp index 8da5811a..00dbac59 100644 --- a/src/move.hpp +++ b/src/move.hpp @@ -68,7 +68,11 @@ struct Move { } [[nodiscard]] constexpr bool is_castle() const { - return raw & static_cast(MoveFlags::Castle); + return flags() == MoveFlags::Castle; + } + + [[nodiscard]] constexpr bool is_en_passant() const { + return flags() == MoveFlags::EnPassant; } [[nodiscard]] constexpr std::optional promo() const { diff --git a/src/search.cpp b/src/search.cpp index a7de9ce3..121841e4 100644 --- a/src/search.cpp +++ b/src/search.cpp @@ -3,6 +3,7 @@ #include "common.hpp" #include "movegen.hpp" #include "movepick.hpp" +#include "see.hpp" #include "tm.hpp" #include "tuned.hpp" #include "uci.hpp" @@ -383,6 +384,11 @@ Value Worker::quiesce(Position& pos, Stack* ss, Value alpha, Value beta, i32 ply // Iterate over the move list for (Move m = moves.next(); m != Move::none(); m = moves.next()) { + // QS SEE Pruning + if (best_value > -VALUE_WIN && !SEE::see(pos, m, tuned::quiesce_see_threshold)) { + continue; + } + // Do move Position pos_after = pos.move(m); moves_searched++; diff --git a/src/see.hpp b/src/see.hpp new file mode 100644 index 00000000..f41642e1 --- /dev/null +++ b/src/see.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "common.hpp" +#include "geometry.hpp" +#include "move.hpp" +#include "position.hpp" +#include "util/types.hpp" +#include "util/vec.hpp" +#include +#include +#include +#include + +namespace Clockwork::SEE { + +inline Value value(PieceType ptype) { + constexpr std::array TABLE{{0, 100, 300, 300, 500, 900, 10000}}; + return TABLE[static_cast(ptype)]; +} + +inline Value gain(const Position& pos, Move move) { + if (move.is_castle()) { + return 0; + } + if (move.is_en_passant()) { + return value(PieceType::Pawn); + } + + Value score = value(pos.board()[move.to()].ptype()); + if (move.is_promotion()) { + score += value(*move.promo()) - value(PieceType::Pawn); + } + return score; +} + +inline bool see(const Position& pos, Move move, Value threshold) { + Square sq = move.to(); + Color stm = pos.active_color(); + + Value score = gain(pos, move) - threshold; + if (score < 0) { + return false; + } + + PieceType next = move.promo().value_or(pos.board()[move.from()].ptype()); + score -= value(next); + stm = invert(stm); + + if (score >= 0) { + return true; + } + + // Extract all possible attackers to our position + auto [ray_coords, ray_valid] = geometry::superpiece_rays(sq); + v512 ray_places = v512::permute8(ray_coords, pos.board().to_vec()); + v512 ray_attackers = ray_places & geometry::attackers_from_rays(ray_places); + v512 ptypes = ray_attackers & ray_valid & v512::broadcast8(Place::PTYPE_MASK); + + // Bitrays (not bitboards) + u64 color = v512::test8(ray_places, v512::broadcast8(Place::COLOR_MASK)); + u64 occupied = v512::test8(ray_places, ray_valid); + u64 attackers = v512::test8(ray_attackers, ray_valid); + + // Remove already moved piece + occupied ^= v512::eq8(ray_coords, v512::broadcast8(move.from().raw)); + if (move.is_en_passant()) { + occupied &= pos.active_color() == Color::Black ? 0xFFFFFFFFFFFFFFFD : 0xFFFFFFFDFFFFFFFF; + } + + // Extract bitrays for each piece type + std::array ptype_bits{ + 0, // None + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Pawn) << Place::PTYPE_SHIFT)), + 0x0101010101010101, // Knight + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Bishop) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Rook) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Queen) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::King) << Place::PTYPE_SHIFT)), + 0, // Invalid + }; + v512 ptype_vec{ptype_bits}; + + auto current_attackers = [&]() { + return geometry::closest(occupied) & (stm == Color::Black ? color : ~color) & attackers; + }; + + while (u64 current = current_attackers()) { + i32 next = std::countr_zero((ptype_vec & v512::broadcast64(current)).nonzero64()); + auto ptype = static_cast(next); + u64 br = ptype_bits[next] & current; + occupied ^= lowest_bit(br); + + score = -score - 1 - value(ptype); + stm = invert(stm); + + if (score >= 0) { + if (ptype == PieceType::King && current_attackers() != 0) { + // We'd be in check if we actually did that. + stm = invert(stm); + } + break; + } + } + + return stm != pos.active_color(); +} + +} diff --git a/src/tuned.hpp b/src/tuned.hpp index 2c1bad27..c272cad2 100644 --- a/src/tuned.hpp +++ b/src/tuned.hpp @@ -7,16 +7,19 @@ namespace Clockwork::tuned { -#define CLOCKWORK_TUNABLES(TUNE, NO_TUNE) \ - \ - /* RFP Values */ \ - TUNE(rfp_margin, 80, 40, 160, 4, 0.002) \ - NO_TUNE(rfp_depth, 6, 4, 10, .5, 0.002) \ - \ - /* NMP Values */ \ - NO_TUNE(nmp_depth, 3, 1, 10, .5, 0.002) \ - NO_TUNE(nmp_base_r, 3, 1, 10, .5, 0.002) \ - \ +#define CLOCKWORK_TUNABLES(TUNE, NO_TUNE) \ + \ + /* RFP Values */ \ + TUNE(rfp_margin, 80, 40, 160, 4, 0.002) \ + NO_TUNE(rfp_depth, 6, 4, 10, .5, 0.002) \ + \ + /* NMP Values */ \ + NO_TUNE(nmp_depth, 3, 1, 10, .5, 0.002) \ + NO_TUNE(nmp_base_r, 3, 1, 10, .5, 0.002) \ + \ + /* SEE Values */ \ + TUNE(quiesce_see_threshold, 0, -1000, 100, 20, 0.002) \ + \ /* End of Tunables */ #define DEFINE_VARIABLE(NAME, DEFAULT, ...) inline i32 NAME = DEFAULT; diff --git a/src/util/bit.hpp b/src/util/bit.hpp index bb2686d6..009daabd 100644 --- a/src/util/bit.hpp +++ b/src/util/bit.hpp @@ -9,6 +9,11 @@ template return x & (x - 1); } +template +[[nodiscard]] constexpr T lowest_bit(T x) { + return x & -x; +} + [[nodiscard]] constexpr i32 sign(i32 x) { if (x == 0) { return 0; diff --git a/src/util/vec/avx2.hpp b/src/util/vec/avx2.hpp index f103f12d..8bdda201 100644 --- a/src/util/vec/avx2.hpp +++ b/src/util/vec/avx2.hpp @@ -8,6 +8,9 @@ namespace Clockwork { +forceinline u8 concat8(u8 a, u8 b) { + return a | (b << 4); +} forceinline u32 concat32(u16 a, u16 b) { return static_cast(a) | (static_cast(b) << 16); } @@ -248,6 +251,10 @@ struct v256 { static_cast(~_mm256_movemask_epi8(_mm256_cmpeq_epi16(a.raw, b.raw))), 0xAAAAAAAA)); } + static forceinline u8 eq64(v256 a, v256 b) { + return static_cast(_mm256_movemask_pd((__m256d)_mm256_cmpeq_epi64(a.raw, b.raw))); + } + template [[nodiscard]] forceinline v128 extract128() const { return {_mm256_extracti128_si256(raw, offset)}; @@ -293,6 +300,9 @@ struct v512 { forceinline explicit v512(std::array src) : raw(std::bit_cast>(src)) { } + forceinline explicit v512(std::array src) : + raw(std::bit_cast>(src)) { + } static forceinline v512 zero() { return v512{v256::zero(), v256::zero()}; @@ -406,6 +416,10 @@ struct v512 { return static_cast(_pext_u64(~x, 0xAAAAAAAAAAAAAAAA)); } + static forceinline u8 neq64(v512 a, v512 b) { + return ~concat8(v256::eq64(a.raw[0], b.raw[0]), v256::eq64(a.raw[1], b.raw[1])); + } + static forceinline u64 testn8(v512 a, v512 b) { return (a & b).zero8(); } @@ -422,6 +436,9 @@ struct v512 { [[nodiscard]] forceinline u32 nonzero16() const { return neq16(*this, zero()); } + [[nodiscard]] forceinline u64 nonzero64() const { + return neq64(*this, zero()); + } friend forceinline v512 operator&(v512 a, v512 b) { return v512{a.raw[0] & b.raw[0], a.raw[1] & b.raw[1]}; diff --git a/tests/test_see.cpp b/tests/test_see.cpp new file mode 100644 index 00000000..1ffbc988 --- /dev/null +++ b/tests/test_see.cpp @@ -0,0 +1,92 @@ +#include "move.hpp" +#include "position.hpp" +#include "see.hpp" +#include "test.hpp" +#include +#include + +using namespace Clockwork; + +void test_see(std::string_view pos_str, std::string_view move_str, i32 value) { + Position pos = Position::parse(pos_str).value(); + Move move = Move::parse(move_str, pos).value(); + + std::cout << pos << " | " << move << " | " << value << std::endl; + + REQUIRE(SEE::see(pos, move, value + 1) == false); + REQUIRE(SEE::see(pos, move, value - 1) == true); +} + +int main() { + test_see("6k1/1pp4p/p1pb4/6q1/3P1pRr/2P4P/PP1Br1P1/5RKN w - - 0 1", "f1f4", -100); + test_see("5rk1/1pp2q1p/p1pb4/8/3P1NP1/2P5/1P1BQ1P1/5RK1 b - - 0 1", "d6f4", 0); + test_see("4R3/2r3p1/5bk1/1p1r3p/p2PR1P1/P1BK1P2/1P6/8 b - - 0 1", "h5g4", 0); + test_see("4R3/2r3p1/5bk1/1p1r1p1p/p2PR1P1/P1BK1P2/1P6/8 b - - 0 1", "h5g4", 0); + test_see("4r1k1/5pp1/nbp4p/1p2p2q/1P2P1b1/1BP2N1P/1B2QPPK/3R4 b - - 0 1", "g4f3", 0); + test_see("2r1r1k1/pp1bppbp/3p1np1/q3P3/2P2P2/1P2B3/P1N1B1PP/2RQ1RK1 b - - 0 1", "d6e5", 100); + test_see("7r/5qpk/p1Qp1b1p/3r3n/BB3p2/5p2/P1P2P2/4RK1R w - - 0 1", "e1e8", 0); + test_see("6rr/6pk/p1Qp1b1p/2n5/1B3p2/5p2/P1P2P2/4RK1R w - - 0 1", "e1e8", -500); + test_see("7r/5qpk/2Qp1b1p/1N1r3n/BB3p2/5p2/P1P2P2/4RK1R w - - 0 1", "e1e8", -500); + test_see("6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - 0 1", "f7f8q", 200); + test_see("6RR/4bP2/8/8/5r2/3K4/5p2/4k3 w - - 0 1", "f7f8n", 200); + test_see("7R/5P2/8/8/6r1/3K4/5p2/4k3 w - - 0 1", "f7f8q", 800); + test_see("7R/5P2/8/8/6r1/3K4/5p2/4k3 w - - 0 1", "f7f8b", 200); + test_see("7R/4bP2/8/8/1q6/3K4/5p2/4k3 w - - 0 1", "f7f8r", -100); + test_see("8/4kp2/2npp3/1Nn5/1p2PQP1/7q/1PP1B3/4KR1r b - - 0 1", "h1f1", 0); + test_see("8/4kp2/2npp3/1Nn5/1p2P1P1/7q/1PP1B3/4KR1r b - - 0 1", "h1f1", 0); + test_see("2r2r1k/6bp/p7/2q2p1Q/3PpP2/1B6/P5PP/2RR3K b - - 0 1", "c5c1", 100); + test_see("r2qk1nr/pp2ppbp/2b3p1/2p1p3/8/2N2N2/PPPP1PPP/R1BQR1K1 w kq - 0 1", "f3e5", 100); + test_see("6r1/4kq2/b2p1p2/p1pPb3/p1P2B1Q/2P4P/2B1R1P1/6K1 w - - 0 1", "f4e5", 0); + test_see("3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R4B/PQ3P1P/3R2K1 w - h6 0 1", "g5h6", 0); + test_see("3q2nk/pb1r1p2/np6/3P2Pp/2p1P3/2R1B2B/PQ3P1P/3R2K1 w - h6 0 1", "g5h6", 100); + test_see("2r4r/1P4pk/p2p1b1p/7n/BB3p2/2R2p2/P1P2P2/4RK2 w - - 0 1", "c3c8", 500); + // test_see("2r5/1P4pk/p2p1b1p/5b1n/BB3p2/2R2p2/P1P2P2/4RK2 w - - 0 1", "c3c8", 500); // Q promo + test_see("2r4k/2r4p/p7/2b2p1b/4pP2/1BR5/P1R3PP/2Q4K w - - 0 1", "c3c5", 300); + test_see("8/pp6/2pkp3/4bp2/2R3b1/2P5/PP4B1/1K6 w - - 0 1", "g2c6", -200); + test_see("4q3/1p1pr1k1/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - 0 1", "e6e4", -400); + test_see("4q3/1p1pr1kb/1B2rp2/6p1/p3PP2/P3R1P1/1P2R1K1/4Q3 b - - 0 1", "h7e4", 100); + test_see("3r3k/3r4/2n1n3/8/3p4/2PR4/1B1Q4/3R3K w - - 0 1", "d3d4", -100); + test_see("1k1r4/1ppn3p/p4b2/4n3/8/P2N2P1/1PP1R1BP/2K1Q3 w - - 0 1", "d3e5", 100); + test_see("1k1r3q/1ppn3p/p4b2/4p3/8/P2N2P1/1PP1R1BP/2K1Q3 w - - 0 1", "d3e5", -200); + test_see("rnb2b1r/ppp2kpp/5n2/4P3/q2P3B/5R2/PPP2PPP/RN1QKB2 w Q - 0 1", "h4f6", 100); + test_see("r2q1rk1/2p1bppp/p2p1n2/1p2P3/4P1b1/1nP1BN2/PP3PPP/RN1QR1K1 b - - 0 1", "g4f3", 0); + test_see("r1bqkb1r/2pp1ppp/p1n5/1p2p3/3Pn3/1B3N2/PPP2PPP/RNBQ1RK1 b kq - 0 1", "c6d4", 0); + test_see("r1bq1r2/pp1ppkbp/4N1p1/n3P1B1/8/2N5/PPP2PPP/R2QK2R w KQ - 0 1", "e6g7", 0); + test_see("r1bq1r2/pp1ppkbp/4N1pB/n3P3/8/2N5/PPP2PPP/R2QK2R w KQ - 0 1", "e6g7", 300); + test_see("rnq1k2r/1b3ppp/p2bpn2/1p1p4/3N4/1BN1P3/PPP2PPP/R1BQR1K1 b kq - 0 1", "d6h2", -200); + test_see("rn2k2r/1bq2ppp/p2bpn2/1p1p4/3N4/1BN1P3/PPP2PPP/R1BQR1K1 b kq - 0 1", "d6h2", 100); + test_see("r2qkbn1/ppp1pp1p/3p1rp1/3Pn3/4P1b1/2N2N2/PPP2PPP/R1BQKB1R b KQq - 0 1", "g4f3", 100); + test_see("rnbq1rk1/pppp1ppp/4pn2/8/1bPP4/P1N5/1PQ1PPPP/R1B1KBNR b KQ - 0 1", "b4c3", 0); + test_see("r4rk1/3nppbp/bq1p1np1/2pP4/8/2N2NPP/PP2PPB1/R1BQR1K1 b - - 0 1", "b6b2", -800); + test_see("r4rk1/1q1nppbp/b2p1np1/2pP4/8/2N2NPP/PP2PPB1/R1BQR1K1 b - - 0 1", "f6d5", -200); + test_see("1r3r2/5p2/4p2p/2k1n1P1/2PN1nP1/1P3P2/8/2KR1B1R b - - 0 1", "b8b3", -400); + test_see("1r3r2/5p2/4p2p/4n1P1/kPPN1nP1/5P2/8/2KR1B1R b - - 0 1", "b8b4", 100); + test_see("2r2rk1/5pp1/pp5p/q2p4/P3n3/1Q3NP1/1P2PP1P/2RR2K1 b - - 0 1", "c8c1", 0); + test_see("1r3r1k/p4pp1/2p1p2p/qpQP3P/2P5/3R4/PP3PP1/1K1R4 b - - 0 1", "a5a2", -800); + test_see("1r5k/p4pp1/2p1p2p/qpQP3P/2P2P2/1P1R4/P4rP1/1K1R4 b - - 0 1", "a5a2", 100); + test_see("r2q1rk1/1b2bppp/p2p1n2/1ppNp3/3nP3/P2P1N1P/BPP2PP1/R1BQR1K1 w - - 0 1", "d5e7", 0); + test_see("rnbqrbn1/pp3ppp/3p4/2p2k2/4p3/3B1K2/PPP2PPP/RNB1Q1NR w - - 0 1", "d3e4", 100); + test_see("rnb1k2r/p3p1pp/1p3p1b/7n/1N2N3/3P1PB1/PPP1P1PP/R2QKB1R w KQkq - 0 1", "e4d6", -200); + test_see("r1b1k2r/p4npp/1pp2p1b/7n/1N2N3/3P1PB1/PPP1P1PP/R2QKB1R w KQkq - 0 1", "e4d6", 0); + test_see("2r1k2r/pb4pp/5p1b/2KB3n/4N3/2NP1PB1/PPP1P1PP/R2Q3R w k - 0 1", "d5c6", -300); + test_see("2r1k2r/pb4pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w k - 0 1", "d5c6", 0); + test_see("2r1k3/pbr3pp/5p1b/2KB3n/1N2N3/3P1PB1/PPP1P1PP/R2Q3R w - - 0 1", "d5c6", -300); + test_see("5k2/p2P2pp/8/1pb5/1Nn1P1n1/6Q1/PPP4P/R3K1NR w KQ - 0 1", "d7d8q", 800); + test_see("r4k2/p2P2pp/8/1pb5/1Nn1P1n1/6Q1/PPP4P/R3K1NR w KQ - 0 1", "d7d8q", -100); + test_see("5k2/p2P2pp/1b6/1p6/1Nn1P1n1/8/PPP4P/R2QK1NR w KQ - 0 1", "d7d8q", 200); + test_see("4kbnr/p1P1pppp/b7/4q3/7n/8/PP1PPPPP/RNBQKBNR w KQk - 0 1", "c7c8q", -100); + test_see("4kbnr/p1P1pppp/b7/4q3/7n/8/PPQPPPPP/RNB1KBNR w KQk - 0 1", "c7c8q", 200); + test_see("4kbnr/p1P1pppp/b7/4q3/7n/8/PPQPPPPP/RNB1KBNR w KQk - 0 1", "c7c8q", 200); + test_see("4kbnr/p1P4p/b1q5/5pP1/4n3/5Q2/PP1PPP1P/RNB1KBNR w KQk f6 0 1", "g5f6", 0); + test_see("4kbnr/p1P4p/b1q5/5pP1/4n3/5Q2/PP1PPP1P/RNB1KBNR w KQk f6 0 1", "g5f6", 0); + test_see("4kbnr/p1P4p/b1q5/5pP1/4n2Q/8/PP1PPP1P/RNB1KBNR w KQk f6 0 1", "g5f6", 0); + test_see("1n2kb1r/p1P4p/2qb4/5pP1/4n2Q/8/PP1PPP1P/RNB1KBNR w KQk - 0 1", "c7b8q", 200); + test_see("rnbqk2r/pp3ppp/2p1pn2/3p4/3P4/N1P1BN2/PPB1PPPb/R2Q1RK1 w kq - 0 1", "g1h2", 300); + test_see("3N4/2K5/2n5/1k6/8/8/8/8 b - - 0 1", "c6d8", 0); + test_see("3n3r/2P5/8/1k6/8/8/3Q4/4K3 w - - 0 1", "c7d8q", 700); + test_see("r2n3r/2P1P3/4N3/1k6/8/8/8/4K3 w - - 0 1", "e6d8", 300); + test_see("8/8/8/1k6/6b1/4N3/2p3K1/3n4 w - - 0 1", "e3d1", 0); + test_see("8/8/1k6/8/8/2N1N3/4p1K1/3n4 w - - 0 1", "c3d1", 100); + test_see("r1bqk1nr/pppp1ppp/2n5/1B2p3/1b2P3/5N2/PPPP1PPP/RNBQK2R w KQkq - 0 1", "e1g1", 0); + return 0; +} diff --git a/tests/test_see_prototype.cpp b/tests/test_see_prototype.cpp new file mode 100644 index 00000000..68ff2df9 --- /dev/null +++ b/tests/test_see_prototype.cpp @@ -0,0 +1,115 @@ +#include "board.hpp" +#include "geometry.hpp" +#include "position.hpp" +#include "square.hpp" +#include "test.hpp" +#include "util/types.hpp" +#include +#include +#include +#include + +using namespace Clockwork; + +template +void print8(T x) { + std::cout << std::hex; + auto y = std::bit_cast>(x); + for (int i = 0; i < sizeof(T); i++) { + if (i != 0 && (i % 8) == 0) { + std::cout << "| "; + } + std::cout << std::setw(2) << std::setfill('0') << (int)y[i] << ' '; + } + std::cout << std::endl; +} + +int main() { + Position pos = Position::parse("5rk1/5pp1/2r4p/5b2/2R5/6Q1/R1P1qPP1/5NK1 b - - 0 1").value(); + Move move = Move::parse("f5c2", pos).value(); + Square focus = move.to(); + + // Extract all possible attackers to our position + + auto [ray_coords, ray_valid] = geometry::superpiece_rays(focus); + v512 ray_places = v512::permute8(ray_coords, pos.board().to_vec()); + v512 ray_attackers = ray_places & geometry::attackers_from_rays(ray_places); + v512 ptypes = ray_attackers & ray_valid & v512::broadcast8(Place::PTYPE_MASK); + + print8(ray_coords); + print8(ray_valid); + print8(ray_places); + print8(ray_attackers); + print8(ptypes); + + u64 color = v512::test8(ray_places, v512::broadcast8(Place::COLOR_MASK)); + u64 occupied = v512::test8(ray_places, ray_valid); + u64 attackers = v512::test8(ray_attackers, ray_valid); + + std::cout << std::setw(16) << std::setfill('0') << occupied << std::endl; + std::cout << std::setw(16) << std::setfill('0') << attackers << std::endl; + + std::array color_bits{{ + ~color, + color, + }}; + + std::cout << std::setw(16) << std::setfill('0') << color_bits[0] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << color_bits[1] << std::endl; + + // Extract bitrays for each piece type + alignas(64) std::array ptype_bits{ + 0, // None + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Pawn) << Place::PTYPE_SHIFT)), + 0x0101010101010101, // Knight + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Bishop) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Rook) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::Queen) << Place::PTYPE_SHIFT)), + v512::eq8(ptypes, v512::broadcast8(static_cast(PieceType::King) << Place::PTYPE_SHIFT)), + 0, // Invalid + }; + v512 ptype_vec{ptype_bits}; + + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[0] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[1] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[2] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[3] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[4] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[5] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[6] << std::endl; + std::cout << std::setw(16) << std::setfill('0') << ptype_bits[7] << std::endl; + + Color stm = pos.active_color(); + + while (true) { + u64 current = geometry::closest(occupied) & color_bits[static_cast(stm)]; + if ((current & attackers) == 0) { + break; + } + + print8(ptype_vec & v512::broadcast64(current)); + std::cout << std::setw(2) << std::setfill('0') + << (ptype_vec & v512::broadcast64(current)).nonzero64() << std::endl; + i32 next = std::countr_zero((ptype_vec & v512::broadcast64(current)).nonzero64()); + u64 br = ptype_bits[next] & current; + + std::cout << "current: " << std::setw(16) << std::setfill('0') << current + << std::endl; + std::cout << "occupied: " << std::setw(16) << std::setfill('0') << occupied + << std::endl; + std::cout << "next: " << next << std::endl; + std::cout << "br: " << std::setw(16) << std::setfill('0') << br << std::endl; + std::cout << "lowest_bit(br): " << std::setw(16) << std::setfill('0') << lowest_bit(br) + << std::endl; + + occupied ^= lowest_bit(br); + + PieceType ptype = static_cast(next); + + std::cout << color_char(stm) << ": " << piece_char(ptype) << std::endl; + + stm = ~stm; + } + + return 0; +}