From 784916726ac6fa242400f7f3c223ec4236cf33e2 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Sun, 23 Nov 2025 09:53:12 +0700 Subject: [PATCH 1/4] intial commit (and later commits not merged for impl-specifics) --- position.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/position.h b/position.h index 9f6f01f..1571ca2 100644 --- a/position.h +++ b/position.h @@ -149,7 +149,7 @@ template class HeapAllocatedValueList { }; enum class MoveGenType : uint8_t { ALL, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, CAPTURE }; -template || std::is_same_v || std::is_same_v>> class _Position { +template ::value>> class _Position { private: HistoryEntry current_state; From 12b9985bb3fd607d3e74336c054cb336448639d2 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Sun, 23 Nov 2025 10:05:00 +0700 Subject: [PATCH 2/4] implemented material-only key calculation --- position.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/position.h b/position.h index 1571ca2..01684f1 100644 --- a/position.h +++ b/position.h @@ -524,6 +524,8 @@ template bool is_valid() const; private: From 0880858a257c97a961276b0ac82736696a3da903 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Mon, 8 Dec 2025 12:10:31 +0700 Subject: [PATCH 3/4] modernizes movegen (refactor to namespace `chess::movegen`) --- .clang-format | 2 +- .github/workflows/clang-format.yml | 6 + .gitignore | 3 +- CMakeLists.txt | 2 +- attacks.cpp | 36 +-- attacks.h | 7 +- fwd_decl.h | 3 + movegen.cpp | 471 +++++++++++++++++++++++++++++ movegen.h | 17 ++ moves_io.cpp | 15 +- position.cpp | 367 +++------------------- position.h | 44 +-- printers.cpp | 8 +- tests.cpp | 57 +++- 14 files changed, 613 insertions(+), 425 deletions(-) create mode 100644 .github/workflows/clang-format.yml create mode 100644 movegen.cpp create mode 100644 movegen.h diff --git a/.clang-format b/.clang-format index a2cbe27..5de674c 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,7 @@ BasedOnStyle: LLVM IndentWidth: 4 ContinuationIndentWidth: 4 -ColumnLimit: 1000 +ColumnLimit: 128 AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false BinPackParameters: false diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..12d8034 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,6 @@ +name: clang-format check + +uses: jidicula/clang-format-action@v4.16.0 +on: + pull_request: + branches: [ "main" ] \ No newline at end of file diff --git a/.gitignore b/.gitignore index e76920e..1a01982 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ build/ !.gitignore CMakePresets.json CMakeSettings.json -*.s \ No newline at end of file +*.s +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c1f0fd6..4355ccb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ endif() set(SOURCES position.cpp attacks.cpp "zobrist.cpp" - "moves_io.cpp" "printers.cpp" "repetition.h" "fwd_decl.h") + "moves_io.cpp" "printers.cpp" "movegen.cpp") add_library(chesslib STATIC ${SOURCES}) target_include_directories(chesslib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # --- Enable CTest integration --- diff --git a/attacks.cpp b/attacks.cpp index c0cc63a..7bf56ae 100644 --- a/attacks.cpp +++ b/attacks.cpp @@ -116,7 +116,8 @@ _POSSIBLY_CONSTEXPR std::array BishopMagics = { }; // clang-format on -template _POSSIBLY_CONSTEXPR std::pair, std::array> generate_magic_table() { +template +_POSSIBLY_CONSTEXPR std::pair, std::array> generate_magic_table() { std::array table{}; std::array attacks{}; @@ -124,7 +125,8 @@ template _POSSIBLY_CONSTEXPR for (Square sq = SQ_A1; sq < SQ_NONE; ++sq) { Bitboard occ = 0; - Bitboard edges = ((attacks::MASK_RANK[0] | attacks::MASK_RANK[7]) & ~attacks::MASK_RANK[rank_of(sq)]) | ((attacks::MASK_FILE[0] | attacks::MASK_FILE[7]) & ~attacks::MASK_FILE[file_of(sq)]); + Bitboard edges = ((attacks::MASK_RANK[0] | attacks::MASK_RANK[7]) & ~attacks::MASK_RANK[rank_of(sq)]) | + ((attacks::MASK_FILE[0] | attacks::MASK_FILE[7]) & ~attacks::MASK_FILE[file_of(sq)]); Bitboard mask = AttackFunc(static_cast(sq), 0) & ~edges; int bits = popcount(mask); @@ -156,33 +158,13 @@ template _POSSIBLY_CONSTEXPR return std::pair{ table, attacks }; } -_POSSIBLY_CONSTEXPR std::pair, std::array> bishopData = generate_magic_table<_chess::_HyperbolaBishopAttacks, 0x1480, true>(); +_POSSIBLY_CONSTEXPR std::pair, std::array> bishopData = + generate_magic_table<_chess::_HyperbolaBishopAttacks, 0x1480, true>(); _POSSIBLY_CONSTEXPR std::array BishopTable = bishopData.first; _POSSIBLY_CONSTEXPR std::array BishopAttacks = bishopData.second; -_POSSIBLY_CONSTEXPR std::pair, std::array> rookData = generate_magic_table<_chess::_HyperbolaRookAttacks, 0x19000, false>(); +_POSSIBLY_CONSTEXPR std::pair, std::array> rookData = + generate_magic_table<_chess::_HyperbolaRookAttacks, 0x19000, false>(); _POSSIBLY_CONSTEXPR std::array RookTable = rookData.first; _POSSIBLY_CONSTEXPR std::array RookAttacks = rookData.second; -} // namespace chess::attacks - -namespace chess::movegen { -inline static _POSSIBLY_CONSTEXPR Bitboard att(PieceType pt, Square sq, Bitboard occ) { return (pt == BISHOP) ? _chess::_HyperbolaBishopAttacks(sq, occ) : _chess ::_HyperbolaRookAttacks(sq, occ); } - -inline static _POSSIBLY_CONSTEXPR std::array, 64> generate_between() { - std::array, 64> squares_between_bb{}; - - for (int sq1 = 0; sq1 < 64; ++sq1) { - for (PieceType pt : { BISHOP, ROOK }) { - for (int sq2 = 0; sq2 < 64; ++sq2) { - if (att(pt, Square(sq1), 0) & (1ULL << sq2)) { - squares_between_bb[sq1][sq2] = att(pt, Square(sq1), 1ULL << (sq2)) & att(pt, Square(sq2), 1ULL << (sq1)); - } - squares_between_bb[sq1][sq2] |= 1ULL << (sq2); - } - } - } - - return squares_between_bb; -} -_POSSIBLY_CONSTEXPR std::array, 64> SQUARES_BETWEEN_BB = generate_between(); -} // namespace chess::movegen \ No newline at end of file +} // namespace chess::attacks \ No newline at end of file diff --git a/attacks.h b/attacks.h index e0cc449..d762d62 100644 --- a/attacks.h +++ b/attacks.h @@ -283,9 +283,4 @@ template [[nodiscard]] constexpr Bitboard slider(Square sq, Bitbo return queen(sq, occupied); } -} // namespace chess::attacks - -namespace chess::movegen { -extern const std::array, 64> SQUARES_BETWEEN_BB; -[[nodiscard]] inline Bitboard between(Square sq1, Square sq2) noexcept { return SQUARES_BETWEEN_BB[sq1][sq2]; } -} // namespace chess::movegen +} // namespace chess::attacks \ No newline at end of file diff --git a/fwd_decl.h b/fwd_decl.h index 2dec6b9..d858c89 100644 --- a/fwd_decl.h +++ b/fwd_decl.h @@ -15,9 +15,12 @@ enum MoveType : uint16_t; enum File : int8_t; enum Rank : int8_t; class Move; +enum class MoveGenType : uint8_t; template class _Position; using Bitboard=uint64_t; using Key = uint64_t; +template class ValueList; +using Movelist = ValueList; // bonus: define the piece enums here enum class PolyglotPiece : uint8_t; enum class EnginePiece : uint8_t; diff --git a/movegen.cpp b/movegen.cpp new file mode 100644 index 0000000..53cf242 --- /dev/null +++ b/movegen.cpp @@ -0,0 +1,471 @@ +#include "movegen.h" +#include "position.h" + +namespace chess { + +namespace _chess { +#if defined(__AVX512BW__) +template struct alignas(64) SplatTable { + std::array data; + constexpr int clamp64(int x) { return (x < 0) ? 0 : (x > 63 ? 63 : x); } + + constexpr SplatTable() : data{} { + for (int i = 0; i < 64; ++i) { + int from = clamp64(i - Offset); + data[i] = Move((Square)from, (Square)i).raw(); + } + } +}; + +constexpr SplatTable<> SPLAT_TABLE{}; +template constexpr SplatTable SPLAT_PAWN_TABLE{}; +// AVX-512 (32 lanes of uint16_t) +static Move *write_moves(Move *moveList, uint32_t mask, __m512i vector) { + // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 + _mm512_storeu_si512(reinterpret_cast<__m512i *>(moveList), _mm512_maskz_compress_epi16(mask, vector)); + return moveList + popcount(mask); +} + +Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { + const auto *table = reinterpret_cast(SPLAT_TABLE.data.data()); + __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw()); + // two 32-lane blocks (0..31, 32..63) + moveList = write_moves(moveList, static_cast(to_bb >> 0), _mm512_or_si512(_mm512_load_si512(table + 0), fromVec)); + moveList = + write_moves(moveList, static_cast(to_bb >> 32), _mm512_or_si512(_mm512_load_si512(table + 1), fromVec)); + + return moveList; +} + +template Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { + const auto *table = reinterpret_cast(SPLAT_PAWN_TABLE.data.data()); + moveList = write_moves(moveList, static_cast(to_bb >> 0), _mm512_load_si512(table + 0)); + moveList = write_moves(moveList, static_cast(to_bb >> 32), _mm512_load_si512(table + 1)); + + return moveList; +} +#else +template Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { + while (to_bb) { + Square to = (Square)pop_lsb(to_bb); +#if defined(_DEBUG) || !defined(NDEBUG) + Square from = to - offset; + assert(from >= 0 && from < 64); // sanity check + *moveList++ = Move(from, to); +#else + *moveList++ = Move(to - offset, to); +#endif + } + return moveList; +} + +Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { + while (to_bb) + *moveList++ = Move(from, (Square)pop_lsb(to_bb)); + return moveList; +} +#endif +} // namespace _chess +} // namespace chess +namespace chess { +template void movegen::genEP(const _Position &pos, Movelist &mv) { + + const Square king_sq = pos.kingSq(c); + const Square ep_sq = pos.ep_square(); + if (ep_sq == SQ_NONE) + return; + + Bitboard candidates = attacks::pawn(~c, ep_sq) & pos.template pieces(); + if (!candidates) + return; + + const Square ep_pawn_sq = static_cast(ep_sq - pawn_push(c)); + const Bitboard ep_mask = (1ULL << ep_pawn_sq) | (1ULL << ep_sq); + // ASSUME(popcount(candidates) <= 32); + + 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; + + // attackers check + Bitboard atks = 0; + atks |= attacks::bishop(king_sq, occ_temp) & (pos.template pieces() | pos.template pieces()); + atks |= attacks::rook(king_sq, occ_temp) & (pos.template pieces() | pos.template pieces()); + atks &= pos.occ(~c); + if (!atks) { + mv.push_back(Move::make(from, ep_sq)); + } + } +} +template +void movegen::genPawnDoubleMoves(const _Position &pos, Movelist &moves, Bitboard pin_mask, Bitboard check_mask) { + constexpr Bitboard RANK_2 = (c == WHITE) ? attacks::MASK_RANK[1] : attacks::MASK_RANK[6]; + constexpr Direction UP = pawn_push(c); + + Bitboard all_occ = pos.occ(); + Bitboard pawns = pos.template pieces() & RANK_2; + + // Split pin types + Bitboard pin_file = pin_mask & attacks::MASK_FILE[file_of(pos.kingSq(c))]; + + Bitboard unpinned = pawns & ~pin_mask; + Bitboard file_pinned = pawns & pin_file; + + // First step must be empty + Bitboard step1_unpinned = attacks::shift(unpinned) & ~all_occ; + Bitboard step1_pinned = attacks::shift(file_pinned) & pin_file & ~all_occ; + + // Second step must also be empty + Bitboard step2_unpinned = attacks::shift(step1_unpinned) & ~all_occ; + Bitboard step2_pinned = attacks::shift(step1_pinned) & pin_file & ~all_occ; + + Bitboard destinations = (step2_unpinned | step2_pinned) & check_mask; + + _chess::splat_pawn_moves<2 * UP>(moves.data() + moves.size_, destinations); + moves.size_ += popcount(destinations); +} +template +void movegen::genPawnSingleMoves( + const _Position &pos, Movelist &moves, Bitboard _rook_pin, Bitboard _bishop_pin, Bitboard _check_mask) { + constexpr auto UP = relative_direction(c, NORTH); + constexpr auto DOWN = relative_direction(c, SOUTH); + constexpr auto DOWN_LEFT = relative_direction(c, SOUTH_WEST); + constexpr auto DOWN_RIGHT = relative_direction(c, SOUTH_EAST); + constexpr auto UP_LEFT = relative_direction(c, NORTH_WEST); + constexpr auto UP_RIGHT = relative_direction(c, NORTH_EAST); + constexpr auto RANK_B_PROMO = attacks::MASK_RANK[relative_rank(c, RANK_7)]; + constexpr auto RANK_PROMO = attacks::MASK_RANK[relative_rank(c, RANK_8)]; + constexpr auto DOUBLE_PUSH_RANK = attacks::MASK_RANK[relative_rank(c, RANK_3)]; + + const auto pawns = pos.template pieces(); + const auto occ_opp = pos.occ(~c); + const auto occ_all = pos.occ(); + // These pawns can maybe take Left or Right + const Bitboard pawns_lr = pawns & ~_rook_pin; + const Bitboard unpinned_pawns_lr = pawns_lr & ~_bishop_pin; + const Bitboard pinned_pawns_lr = pawns_lr & _bishop_pin; + + auto l_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & _bishop_pin); + auto r_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & _bishop_pin); + // Prune moves that don't capture a piece and are not on the checkmask. + l_pawns &= occ_opp & _check_mask; + r_pawns &= occ_opp & _check_mask; + + // These pawns can walk Forward + const auto pawns_hv = pawns & ~_bishop_pin; + + const auto pawns_pinned_hv = pawns_hv & _rook_pin; + const auto pawns_unpinned_hv = pawns_hv & ~_rook_pin; + + // Prune moves that are blocked by a piece + const auto single_push_unpinned = attacks::shift(pawns_unpinned_hv) & ~occ_all; + const auto single_push_pinned = attacks::shift(pawns_pinned_hv) & _rook_pin & ~occ_all; + + // Prune moves that are not on the checkmask. + Bitboard single_push = (single_push_unpinned | single_push_pinned) & _check_mask; + { + Bitboard promo_left = l_pawns & RANK_PROMO; + Bitboard promo_right = r_pawns & RANK_PROMO; + Bitboard promo_push = single_push & RANK_PROMO; + + if constexpr (!capturesOnly) { + while (promo_push) { + Square to = static_cast(pop_lsb(promo_push)); + Square from = static_cast(to - UP); + moves[moves.size_] = Move::make(from, to, KNIGHT); + moves[moves.size_ + 1] = Move::make(from, to, BISHOP); + moves[moves.size_ + 2] = Move::make(from, to, ROOK); + moves[moves.size_ + 3] = Move::make(from, to, QUEEN); + moves.size_ += 4; + } + } + + while (promo_left) { + Square to = static_cast(pop_lsb(promo_left)); + Square from = static_cast(to - UP_LEFT); // correct + moves[moves.size_] = Move::make(from, to, KNIGHT); + moves[moves.size_ + 1] = Move::make(from, to, BISHOP); + moves[moves.size_ + 2] = Move::make(from, to, ROOK); + moves[moves.size_ + 3] = Move::make(from, to, QUEEN); + moves.size_ += 4; + } + + while (promo_right) { + Square to = static_cast(pop_lsb(promo_right)); + Square from = static_cast(to - UP_RIGHT); // correct + moves[moves.size_] = Move::make(from, to, KNIGHT); + moves[moves.size_ + 1] = Move::make(from, to, BISHOP); + moves[moves.size_ + 2] = Move::make(from, to, ROOK); + moves[moves.size_ + 3] = Move::make(from, to, QUEEN); + moves.size_ += 4; + } + } + + single_push &= ~RANK_PROMO; + l_pawns &= ~RANK_PROMO; + r_pawns &= ~RANK_PROMO; + if constexpr (!capturesOnly) { + _chess::splat_pawn_moves(moves.data() + moves.size_, single_push); + moves.size_ += popcount(single_push); + } + _chess::splat_pawn_moves(moves.data() + moves.size_, l_pawns); + moves.size_ += popcount(l_pawns); + _chess::splat_pawn_moves(moves.data() + moves.size_, r_pawns); + moves.size_ += popcount(r_pawns); +} +template +void movegen::genKnightMoves(const _Position &pos, Movelist &list, Bitboard _pin_mask, Bitboard _check_mask) { + Bitboard knights = pos.template pieces() & ~_pin_mask; // yes, unconditionally. + while (knights) { + Square x = static_cast(pop_lsb(knights)); + Bitboard moves = attacks::knight(x) & ~pos.occ(c); + moves &= _check_mask; + if constexpr (capturesOnly) + moves &= pos.occ(~c); + _chess::splat_moves(list.data() + list.size(), x, moves); + list.size_ += popcount(moves); + } +} +template void movegen::genKingMoves(const _Position &pos, Movelist &out) { + constexpr Color them = ~c; + const Square kingSq = pos.kingSq(c); + const Bitboard occAll = pos.occ(); + const Bitboard myOcc = pos.occ(c); + + // Remove king from board when computing enemy attacks + const Bitboard occWithoutKing = occAll ^ (1ULL << kingSq); + Bitboard enemyAttacks = 0ULL; + + // Sliding pieces + Bitboard bLike = pos.template pieces() | pos.template pieces(); + while (bLike) + enemyAttacks |= attacks::bishop(static_cast(pop_lsb(bLike)), occWithoutKing); + + Bitboard rLike = pos.template pieces() | pos.template pieces(); + while (rLike) + enemyAttacks |= attacks::rook(static_cast(pop_lsb(rLike)), occWithoutKing); + + // Knights + enemyAttacks |= attacks::knight(pos.template pieces()); + + // Pawns + enemyAttacks |= attacks::pawn(pos.template pieces()); + + // Enemy king (adjacent control squares) + enemyAttacks |= attacks::king(pos.kingSq(them)); + + // Candidate king moves = legal squares not attacked by enemy + Bitboard moves = attacks::king(kingSq) & ~myOcc & ~enemyAttacks; + if constexpr (capturesOnly) + moves &= pos.occ(~c); + _chess::splat_moves(out.data() + out.size(), kingSq, moves); + out.size_ += popcount(moves); + if constexpr (!capturesOnly) { + if (pos.checkers()) + return; + + // Precompute all castling masks + constexpr Bitboard WHITE_OO_EMPTY = (1ULL << SQ_F1) | (1ULL << SQ_G1); + constexpr Bitboard WHITE_OO_SAFE = (1ULL << SQ_F1) | (1ULL << SQ_G1); + constexpr Bitboard WHITE_OOO_EMPTY = (1ULL << SQ_B1) | (1ULL << SQ_C1) | (1ULL << SQ_D1); + constexpr Bitboard WHITE_OOO_SAFE = (1ULL << SQ_C1) | (1ULL << SQ_D1); + + constexpr Bitboard BLACK_OO_EMPTY = (1ULL << SQ_F8) | (1ULL << SQ_G8); + constexpr Bitboard BLACK_OO_SAFE = (1ULL << SQ_F8) | (1ULL << SQ_G8); + constexpr Bitboard BLACK_OOO_EMPTY = (1ULL << SQ_B8) | (1ULL << SQ_C8) | (1ULL << SQ_D8); + constexpr Bitboard BLACK_OOO_SAFE = (1ULL << SQ_C8) | (1ULL << SQ_D8); + + Bitboard occupancy = pos.occ(); + Bitboard enemy_attacks = enemyAttacks; + + if constexpr (c == WHITE) { + if ((pos.castlingRights() & WHITE_OO) && !(occupancy & WHITE_OO_EMPTY) && !(enemy_attacks & WHITE_OO_SAFE)) { + out.push_back(Move::make(SQ_E1, SQ_H1)); + } + if ((pos.castlingRights() & WHITE_OOO) && !(occupancy & WHITE_OOO_EMPTY) && !(enemy_attacks & WHITE_OOO_SAFE)) { + out.push_back(Move::make(SQ_E1, SQ_A1)); + } + } else { + if ((pos.castlingRights() & BLACK_OO) && !(occupancy & BLACK_OO_EMPTY) && !(enemy_attacks & BLACK_OO_SAFE)) { + out.push_back(Move::make(SQ_E8, SQ_H8)); + } + if ((pos.castlingRights() & BLACK_OOO) && !(occupancy & BLACK_OOO_EMPTY) && !(enemy_attacks & BLACK_OOO_SAFE)) { + out.push_back(Move::make(SQ_E8, SQ_A8)); + } + } + } +} +template +void movegen::genSlidingMoves( + const _Position &pos, Movelist &moves, Bitboard _rook_pin, Bitboard _bishop_pin, Bitboard _check_mask) { + static_assert(pt == BISHOP || pt == ROOK || pt == QUEEN, "Sliding pieces only."); + Bitboard sliders = pos.template pieces(); + Bitboard occ_all = pos.occ(); + // Square king_sq = current_state.kings[c]; + Bitboard rook_pinners = _rook_pin; // bitboard of enemy rooks/queens pinning + Bitboard bishop_pinners = _bishop_pin; + if constexpr (pt == BISHOP) + sliders &= ~rook_pinners; + if constexpr (pt == ROOK) + sliders &= ~bishop_pinners; + Bitboard filter_list = ~pos.occ(c) & _check_mask; + while (sliders) { + Square from = static_cast(pop_lsb(sliders)); + Bitboard from_bb = 1ULL << from; + + Bitboard rook_hit = rook_pinners & from_bb; + Bitboard bishop_hit = bishop_pinners & from_bb; + Bitboard pin_mask = rook_hit ? rook_pinners : bishop_hit ? bishop_pinners : ~0ULL; + + // Bitboard blockers = occ() ^ from_bb; // remove piece temporarily + auto func = attacks::queen; + if constexpr (pt == PieceType::BISHOP) + func = attacks::bishop; + else if constexpr (pt == PieceType::ROOK) + func = attacks::rook; + func = rook_hit ? attacks::rook : bishop_hit ? attacks::bishop : func; + Bitboard filtered_pin = pin_mask & filter_list; + Bitboard targets = func(from, occ_all) & filtered_pin; + if constexpr (capturesOnly) + targets &= pos.occ(~c); + _chess::splat_moves(moves.data() + moves.size_, from, targets); + moves.size_ += popcount(targets); + } +} +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves<(Direction)16>(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves<(Direction)-16>(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +template Move *chess::_chess::splat_pawn_moves(Move *, Bitboard); +#define INSTANTIATE(PieceC) \ + template void chess::movegen::genEP(const _Position &, Movelist &); \ + template void chess::movegen::genEP(const _Position &, Movelist &); \ + template void chess::movegen::genPawnDoubleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genPawnDoubleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genPawnSingleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genPawnSingleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genPawnSingleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genPawnSingleMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genSlidingMoves(const _Position &, \ + Movelist &, \ + Bitboard, \ + Bitboard, \ + Bitboard); \ + template void chess::movegen::genKnightMoves(const _Position&, Movelist&, Bitboard, Bitboard); \ + template void chess::movegen::genKnightMoves(const _Position&, Movelist&, Bitboard, Bitboard); \ + template void chess::movegen::genKnightMoves(const _Position&, Movelist&, Bitboard, Bitboard); \ + template void chess::movegen::genKnightMoves(const _Position&, Movelist&, Bitboard, Bitboard); \ + template void chess::movegen::genKingMoves(const _Position&, Movelist&); \ + template void chess::movegen::genKingMoves(const _Position&, Movelist&); \ + template void chess::movegen::genKingMoves(const _Position&, Movelist&); \ + template void chess::movegen::genKingMoves(const _Position&, Movelist&); +INSTANTIATE(EnginePiece) +INSTANTIATE(PolyglotPiece) +INSTANTIATE(ContiguousMappingPiece) +inline static Bitboard att(PieceType pt, Square sq, Bitboard occ) { + return (pt == BISHOP) ? attacks::bishop(sq, occ) : attacks::rook(sq, occ); +} + +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 }) { + for (int sq2 = 0; sq2 < 64; ++sq2) { + if (att(pt, Square(sq1), 0) & (1ULL << sq2)) { + squares_between_bb[sq1][sq2] = att(pt, Square(sq1), 1ULL << (sq2)) & att(pt, Square(sq2), 1ULL << (sq1)); + } + squares_between_bb[sq1][sq2] |= 1ULL << (sq2); + } + } + } + + return squares_between_bb; +} +std::array, 64> movegen::SQUARES_BETWEEN_BB = generate_between(); +} // namespace chess \ No newline at end of file diff --git a/movegen.h b/movegen.h new file mode 100644 index 0000000..3e90222 --- /dev/null +++ b/movegen.h @@ -0,0 +1,17 @@ +#include "fwd_decl.h" +#include +namespace chess::_chess{ + template extern Move *splat_pawn_moves(Move *moveList, Bitboard to_bb); + extern Move *splat_moves(Move *moveList, Square from, Bitboard to_bb); +} +namespace chess::movegen { + + template void genEP(const _Position&, Movelist&); + template void genPawnDoubleMoves(const _Position&, Movelist &, Bitboard, Bitboard); + template void genPawnSingleMoves(const _Position&, Movelist &, Bitboard, Bitboard, Bitboard); + template void genKnightMoves(const _Position&, Movelist &, Bitboard, Bitboard); + template void genKingMoves(const _Position&, Movelist &); + template void genSlidingMoves(const _Position&, Movelist &, Bitboard, Bitboard, Bitboard); + extern std::array, 64> SQUARES_BETWEEN_BB; + [[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 49acb17..649828f 100644 --- a/moves_io.cpp +++ b/moves_io.cpp @@ -7,7 +7,12 @@ namespace chess { namespace uci { 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", "b3", "c3", "d3", "e3", "f3", "g3", "h3", "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", "none" }; + constexpr std::string_view fileChars[65] = { + "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", "a3", + "b3", "c3", "d3", "e3", "f3", "g3", "h3", "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", "a5", "b5", + "c5", "d5", "e5", "f5", "g5", "h5", "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", "a7", "b7", "c7", + "d7", "e7", "f7", "g7", "h7", "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", "none" + }; return std::string{ fileChars[sq] }; } std::string moveToUci(const Move &mv) { @@ -57,7 +62,9 @@ std::string moveToUci(const Move &mv) { } return move; } -template Move uciToMove(const _Position &pos, std::string uci) { return uciToMove(pos, std::string_view(uci)); } +template Move uciToMove(const _Position &pos, std::string uci) { + return uciToMove(pos, std::string_view(uci)); +} template Move uciToMove(const _Position &pos, std::string_view uci) { if (uci.length() < 4) { #ifdef __EXCEPTIONS @@ -117,8 +124,8 @@ template Move uciToMove(const _Position &pos, std #endif return move; } -#define INSTANTITATE(PieceC) \ - template Move uciToMove(const _Position &, std::string_view); \ +#define INSTANTITATE(PieceC) \ + template Move uciToMove(const _Position &, std::string_view); \ template Move uciToMove(const _Position &, std::string); INSTANTITATE(PolyglotPiece) INSTANTITATE(EnginePiece) diff --git a/position.cpp b/position.cpp index ed4aeea..28fe48c 100644 --- a/position.cpp +++ b/position.cpp @@ -1,4 +1,5 @@ #include "position.h" +#include "movegen.h" #include "moves_io.h" #include "position.h" #include @@ -8,68 +9,6 @@ #define _POSSIBLY_CONSTEXPR const #endif namespace chess { -namespace _chess { -#if defined(__AVX512BW__) -template struct alignas(64) SplatTable { - std::array data; - constexpr int clamp64(int x) { return (x < 0) ? 0 : (x > 63 ? 63 : x); } - - constexpr SplatTable() : data{} { - for (int i = 0; i < 64; ++i) { - int from = clamp64(i - Offset); - data[i] = Move((Square)from, (Square)i).raw(); - } - } -}; - -__FORCEINLINE constexpr SplatTable<> SPLAT_TABLE{}; -template __FORCEINLINE constexpr SplatTable SPLAT_PAWN_TABLE{}; -// AVX-512 (32 lanes of uint16_t) -__FORCEINLINE static Move *write_moves(Move *moveList, uint32_t mask, __m512i vector) { - // Avoid _mm512_mask_compressstoreu_epi16() as it's 256 uOps on Zen4 - _mm512_storeu_si512(reinterpret_cast<__m512i *>(moveList), _mm512_maskz_compress_epi16(mask, vector)); - return moveList + popcount(mask); -} - -__FORCEINLINE static Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { - const auto *table = reinterpret_cast(SPLAT_TABLE.data.data()); - __m512i fromVec = _mm512_set1_epi16(Move(from, SQUARE_ZERO).raw()); - // two 32-lane blocks (0..31, 32..63) - moveList = write_moves(moveList, static_cast(to_bb >> 0), _mm512_or_si512(_mm512_load_si512(table + 0), fromVec)); - moveList = write_moves(moveList, static_cast(to_bb >> 32), _mm512_or_si512(_mm512_load_si512(table + 1), fromVec)); - - return moveList; -} - -template __FORCEINLINE static Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { - const auto *table = reinterpret_cast(SPLAT_PAWN_TABLE.data.data()); - moveList = write_moves(moveList, static_cast(to_bb >> 0), _mm512_load_si512(table + 0)); - moveList = write_moves(moveList, static_cast(to_bb >> 32), _mm512_load_si512(table + 1)); - - return moveList; -} -#else -template __FORCEINLINE static Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { - while (to_bb) { - Square to = (Square)pop_lsb(to_bb); -#if defined(_DEBUG) || !defined(NDEBUG) - Square from = to - offset; - assert(from >= 0 && from < 64); // sanity check - *moveList++ = Move(from, to); -#else - *moveList++ = Move(to - offset, to); -#endif - } - return moveList; -} - -__FORCEINLINE static Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { - while (to_bb) - *moveList++ = Move(from, (Square)pop_lsb(to_bb)); - return moveList; -} -#endif -} // namespace _chess static _POSSIBLY_CONSTEXPR CastlingRights make_clear_mask(Color c, PieceType pt, Square sq) { if (pt == KING) { if (c == WHITE && sq == SQ_E1) @@ -90,7 +29,7 @@ static _POSSIBLY_CONSTEXPR CastlingRights make_clear_mask(Color c, PieceType pt, } static _POSSIBLY_CONSTEXPR auto make_castle_masks() { - std::array, PIECE_TYPE_NB>, COLOR_NB+1> tbl{}; + std::array, PIECE_TYPE_NB>, COLOR_NB + 1> tbl{}; for (int c = 0; c <= COLOR_NB; ++c) for (int pt = 0; pt < PIECE_TYPE_NB; ++pt) @@ -102,226 +41,20 @@ static _POSSIBLY_CONSTEXPR auto make_castle_masks() { static _POSSIBLY_CONSTEXPR auto clearCastleMask = make_castle_masks(); template template void _Position::genEP(Movelist &ep_moves) const { - const Square king_sq = kingSq(c); - const Square ep_sq = ep_square(); - if (ep_sq == SQ_NONE) - return; - - Bitboard candidates = attacks::pawn(~c, ep_sq) & pieces(); - if (!candidates) - return; - - const Square ep_pawn_sq = static_cast(ep_sq - pawn_push(c)); - const Bitboard ep_mask = (1ULL << ep_pawn_sq) | (1ULL << ep_sq); - // ASSUME(popcount(candidates) <= 32); - - while (candidates) { - Square from = static_cast(pop_lsb(candidates)); - - // Remove the EP pawn and this attacker from occupancy - Bitboard occ_temp = occ(); - occ_temp ^= (1ULL << from) | ep_mask; - - // inline attackers check - Bitboard atks = 0; - atks |= attacks::bishop(king_sq, occ_temp) & (pieces() | pieces()); - atks |= attacks::rook(king_sq, occ_temp) & (pieces() | pieces()); - atks &= occ(~c); - if (!atks) { - ep_moves.push_back(Move::make(from, ep_sq)); - } - } + chess::movegen::genEP(*this, ep_moves); } -template template void _Position::genPawnSingleMoves(Movelist &moves) const { - constexpr auto UP = relative_direction(c, NORTH); - constexpr auto DOWN = relative_direction(c, SOUTH); - constexpr auto DOWN_LEFT = relative_direction(c, SOUTH_WEST); - constexpr auto DOWN_RIGHT = relative_direction(c, SOUTH_EAST); - constexpr auto UP_LEFT = relative_direction(c, NORTH_WEST); - constexpr auto UP_RIGHT = relative_direction(c, NORTH_EAST); - constexpr auto RANK_B_PROMO = attacks::MASK_RANK[relative_rank(c, RANK_7)]; - constexpr auto RANK_PROMO = attacks::MASK_RANK[relative_rank(c, RANK_8)]; - constexpr auto DOUBLE_PUSH_RANK = attacks::MASK_RANK[relative_rank(c, RANK_3)]; - - const auto pawns = pieces(); - const auto occ_opp=occ(~c); - const auto occ_all=occ(); - // These pawns can maybe take Left or Right - const Bitboard pawns_lr = pawns & ~_rook_pin; - const Bitboard unpinned_pawns_lr = pawns_lr & ~_bishop_pin; - const Bitboard pinned_pawns_lr = pawns_lr & _bishop_pin; - - auto l_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & _bishop_pin); - auto r_pawns = attacks::shift(unpinned_pawns_lr) | (attacks::shift(pinned_pawns_lr) & _bishop_pin); - // Prune moves that don't capture a piece and are not on the checkmask. - l_pawns &= occ_opp & _check_mask; - r_pawns &= occ_opp & _check_mask; - - // These pawns can walk Forward - const auto pawns_hv = pawns & ~_bishop_pin; - - const auto pawns_pinned_hv = pawns_hv & _rook_pin; - const auto pawns_unpinned_hv = pawns_hv & ~_rook_pin; - - // Prune moves that are blocked by a piece - const auto single_push_unpinned = attacks::shift(pawns_unpinned_hv) & ~occ_all; - const auto single_push_pinned = attacks::shift(pawns_pinned_hv) & _rook_pin & ~occ_all; - - // Prune moves that are not on the checkmask. - Bitboard single_push = (single_push_unpinned | single_push_pinned) & _check_mask; - { - Bitboard promo_left = l_pawns & RANK_PROMO; - Bitboard promo_right = r_pawns & RANK_PROMO; - Bitboard promo_push = single_push & RANK_PROMO; - - if constexpr (!capturesOnly){ - while (promo_push){ - Square to = static_cast(pop_lsb(promo_push)); - Square from = static_cast(to - UP); - moves[moves.size_]=Move::make(from, to, KNIGHT); - moves[moves.size_+1]=Move::make(from, to, BISHOP); - moves[moves.size_+2]=Move::make(from, to, ROOK); - moves[moves.size_+3]=Move::make(from, to, QUEEN); - moves.size_+=4; - } - } - - while (promo_left){ - Square to = static_cast(pop_lsb(promo_left)); - Square from = static_cast(to - UP_LEFT); // correct - moves[moves.size_]=Move::make(from, to, KNIGHT); - moves[moves.size_+1]=Move::make(from, to, BISHOP); - moves[moves.size_+2]=Move::make(from, to, ROOK); - moves[moves.size_+3]=Move::make(from, to, QUEEN); - moves.size_+=4; - } - - while (promo_right){ - Square to = static_cast(pop_lsb(promo_right)); - Square from = static_cast(to - UP_RIGHT); // correct - moves[moves.size_]=Move::make(from, to, KNIGHT); - moves[moves.size_+1]=Move::make(from, to, BISHOP); - moves[moves.size_+2]=Move::make(from, to, ROOK); - moves[moves.size_+3]=Move::make(from, to, QUEEN); - moves.size_+=4; - } - } - - single_push &= ~RANK_PROMO; - l_pawns &= ~RANK_PROMO; - r_pawns &= ~RANK_PROMO; - if constexpr (!capturesOnly){ - _chess::splat_pawn_moves(moves.data() + moves.size_, single_push); - moves.size_ += popcount(single_push); - } - _chess::splat_pawn_moves(moves.data() + moves.size_, l_pawns); - moves.size_ += popcount(l_pawns); - _chess::splat_pawn_moves(moves.data() + moves.size_, r_pawns); - moves.size_ += popcount(r_pawns); +template +template +void _Position::genPawnSingleMoves(Movelist &moves) const { + chess::movegen::genPawnSingleMoves(*this, moves, _rook_pin, _bishop_pin, _check_mask); } template template void _Position::genPawnDoubleMoves(Movelist &moves) const { - constexpr Bitboard RANK_2 = (c == WHITE) ? attacks::MASK_RANK[1] : attacks::MASK_RANK[6]; - constexpr Direction UP = pawn_push(c); - - Bitboard all_occ = occ(); - Bitboard pawns = pieces() & RANK_2; - - // Split pin types - const Bitboard pin_mask = this->_pin_mask; - Bitboard pin_file = pin_mask & attacks::MASK_FILE[file_of(kingSq(c))]; - - Bitboard unpinned = pawns & ~pin_mask; - Bitboard file_pinned = pawns & pin_file; - - // First step must be empty - Bitboard step1_unpinned = attacks::shift(unpinned) & ~all_occ; - Bitboard step1_pinned = attacks::shift(file_pinned) & pin_file & ~all_occ; - - // Second step must also be empty - Bitboard step2_unpinned = attacks::shift(step1_unpinned) & ~all_occ; - Bitboard step2_pinned = attacks::shift(step1_pinned) & pin_file & ~all_occ; - - Bitboard destinations = (step2_unpinned | step2_pinned) & this->_check_mask; - - //// Source pawns = only from RANK_2 (already masked above) - // Bitboard sources = (c == WHITE) ? (destinations >> 16) : (destinations << 16); - - // while (sources) { - // Square from = static_cast(pop_lsb(sources)); - // Square to = from + 2 * UP; - // moves.push_back(Move(from, to)); - // } - _chess::splat_pawn_moves<2 * UP>(moves.data() + moves.size_, destinations); - moves.size_ += popcount(destinations); + chess::movegen::genPawnDoubleMoves(*this, moves, _pin_mask, _check_mask); } -template template void _Position::genKingMoves(Movelist &out) const { - constexpr Color them = ~c; - const Square kingSq = this->kingSq(c); - const Bitboard occAll = occ(); - const Bitboard myOcc = occ(c); - - // Remove king from board when computing enemy attacks - const Bitboard occWithoutKing = occAll ^ (1ULL << kingSq); - Bitboard enemyAttacks = 0ULL; - - // Sliding pieces - Bitboard bLike = pieces() | pieces(); - while (bLike) - enemyAttacks |= attacks::bishop(static_cast(pop_lsb(bLike)), occWithoutKing); - - Bitboard rLike = pieces() | pieces(); - while (rLike) - enemyAttacks |= attacks::rook(static_cast(pop_lsb(rLike)), occWithoutKing); - - // Knights - enemyAttacks |= attacks::knight(pieces()); - - // Pawns - enemyAttacks |= attacks::pawn(pieces()); - - // Enemy king (adjacent control squares) - enemyAttacks |= attacks::king(this->kingSq(them)); - - // Candidate king moves = legal squares not attacked by enemy - Bitboard moves = attacks::king(kingSq) & ~myOcc & ~enemyAttacks; - if constexpr (capturesOnly) - moves &= occ(~c); - _chess::splat_moves(out.data() + out.size(), kingSq, moves); - out.size_ += popcount(moves); - if constexpr (!capturesOnly) { - if (checkers()) - return; - - // Precompute all castling masks - constexpr Bitboard WHITE_OO_EMPTY = (1ULL << SQ_F1) | (1ULL << SQ_G1); - constexpr Bitboard WHITE_OO_SAFE = (1ULL << SQ_F1) | (1ULL << SQ_G1); - constexpr Bitboard WHITE_OOO_EMPTY = (1ULL << SQ_B1) | (1ULL << SQ_C1) | (1ULL << SQ_D1); - constexpr Bitboard WHITE_OOO_SAFE = (1ULL << SQ_C1) | (1ULL << SQ_D1); - - constexpr Bitboard BLACK_OO_EMPTY = (1ULL << SQ_F8) | (1ULL << SQ_G8); - constexpr Bitboard BLACK_OO_SAFE = (1ULL << SQ_F8) | (1ULL << SQ_G8); - constexpr Bitboard BLACK_OOO_EMPTY = (1ULL << SQ_B8) | (1ULL << SQ_C8) | (1ULL << SQ_D8); - constexpr Bitboard BLACK_OOO_SAFE = (1ULL << SQ_C8) | (1ULL << SQ_D8); - - Bitboard occupancy = occ(); - Bitboard enemy_attacks = enemyAttacks; - - if constexpr (c == WHITE) { - if ((current_state.castlingRights & WHITE_OO) && !(occupancy & WHITE_OO_EMPTY) && !(enemy_attacks & WHITE_OO_SAFE)) { - out.push_back(Move::make(SQ_E1, SQ_H1)); - } - if ((current_state.castlingRights & WHITE_OOO) && !(occupancy & WHITE_OOO_EMPTY) && !(enemy_attacks & WHITE_OOO_SAFE)) { - out.push_back(Move::make(SQ_E1, SQ_A1)); - } - } else { - if ((current_state.castlingRights & BLACK_OO) && !(occupancy & BLACK_OO_EMPTY) && !(enemy_attacks & BLACK_OO_SAFE)) { - out.push_back(Move::make(SQ_E8, SQ_H8)); - } - if ((current_state.castlingRights & BLACK_OOO) && !(occupancy & BLACK_OOO_EMPTY) && !(enemy_attacks & BLACK_OOO_SAFE)) { - out.push_back(Move::make(SQ_E8, SQ_A8)); - } - } - } +template +template +void _Position::genKingMoves(Movelist &out) const { + chess::movegen::genKingMoves(*this, out); } template template void _Position::doMove(const Move &move) { assert(move.is_ok() && "doMove called with invalid move"); @@ -336,7 +69,8 @@ template template void _Position template void _Position(rook_dest, us); placePiece(king_dest, us); current_state.incr_sqs[0] = from_sq; @@ -434,7 +169,8 @@ template template void _Position void _Position::setFEN(const s } } - if (enpassant != "-" && enpassant.length() == 2 && enpassant[0] >= 'a' && enpassant[0] <= 'h' && enpassant[1] >= '1' && enpassant[1] <= '8') { + if (enpassant != "-" && enpassant.length() == 2 && enpassant[0] >= 'a' && enpassant[0] <= 'h' && enpassant[1] >= '1' && + enpassant[1] <= '8') { File f = static_cast(enpassant[0] - 'a'); Rank r = static_cast(enpassant[1] - '1'); Square ep_sq = make_sq(r, f); @@ -763,7 +500,8 @@ template template bool _Position(); // Base set: 1 queen, 2 rooks, 2 bishops, 2 knights - int promotions = std::max(0, (queens - 1)) + std::max(0, (rooks - 2)) + std::max(0, (bishops - 2)) + std::max(0, (knights - 2)); + int promotions = + std::max(0, (queens - 1)) + std::max(0, (rooks - 2)) + std::max(0, (bishops - 2)) + std::max(0, (knights - 2)); if (pawns + promotions > 8) return false; // impossible @@ -776,7 +514,8 @@ template template bool _Position(); // Base set: 1 queen, 2 rooks, 2 bishops, 2 knights - int promotions = std::max(0, (queens - 1)) + std::max(0, (rooks - 2)) + std::max(0, (bishops - 2)) + std::max(0, (knights - 2)); + int promotions = + std::max(0, (queens - 1)) + std::max(0, (rooks - 2)) + std::max(0, (bishops - 2)) + std::max(0, (knights - 2)); if (pawns + promotions > 8) return false; // impossible @@ -856,58 +595,22 @@ template uint64_t _Position::zobrist() return hash; } -template template void _Position::genKnightMoves(Movelist &list) const { - Bitboard knights = pieces() & ~_pin_mask; // yes, unconditionally. - const Bitboard check_mask = _check_mask; - while (knights) { - Square x = static_cast(pop_lsb(knights)); - Bitboard moves = attacks::knight(x) & ~occ(c); - moves &= check_mask; - if constexpr (capturesOnly) - moves &= occ(~c); - _chess::splat_moves(list.data() + list.size(), x, moves); - list.size_ += popcount(moves); - } +template +template +void _Position::genKnightMoves(Movelist &list) const { + chess::movegen::genKnightMoves(*this, list, _pin_mask, _check_mask); } -template template void _Position::genSlidingMoves(Movelist &moves) const { - static_assert(pt == BISHOP || pt == ROOK || pt == QUEEN, "Sliding pieces only."); - Bitboard sliders = pieces(); - Bitboard occ_all = occ(); - // Square king_sq = current_state.kings[c]; - Bitboard rook_pinners = _rook_pin; // bitboard of enemy rooks/queens pinning - Bitboard bishop_pinners = _bishop_pin; - if constexpr (pt == BISHOP) - sliders &= ~rook_pinners; - if constexpr (pt == ROOK) - sliders &= ~bishop_pinners; - Bitboard filter_list = ~occ(c) & _check_mask; - while (sliders) { - Square from = static_cast(pop_lsb(sliders)); - Bitboard from_bb = 1ULL << from; - - Bitboard rook_hit = rook_pinners & from_bb; - Bitboard bishop_hit = bishop_pinners & from_bb; - Bitboard pin_mask = rook_hit ? rook_pinners : bishop_hit ? bishop_pinners : ~0ULL; - - // Bitboard blockers = occ() ^ from_bb; // remove piece temporarily - auto func = attacks::queen; - if constexpr (pt == PieceType::BISHOP) - func = attacks::bishop; - else if constexpr (pt == PieceType::ROOK) - func = attacks::rook; - func = rook_hit ? attacks::rook : bishop_hit ? attacks::bishop : func; - Bitboard filtered_pin = pin_mask & filter_list; - Bitboard targets = func(from, occ_all) & filtered_pin; - if constexpr (capturesOnly) - targets &= occ(~c); - _chess::splat_moves(moves.data() + moves.size_, from, targets); - moves.size_ += popcount(targets); - } +template +template +void _Position::genSlidingMoves(Movelist &moves) const { + chess::movegen::genSlidingMoves(*this, moves, _rook_pin, _bishop_pin, _check_mask); } template _Position::_Position(std::string fen) { setFEN(fen); } -template Move _Position::parse_uci(std::string uci) const { return chess::uci::uciToMove(*this, uci); } +template Move _Position::parse_uci(std::string uci) const { + return chess::uci::uciToMove(*this, uci); +} template Move _Position::push_uci(std::string uci) { auto mv = parse_uci(uci); diff --git a/position.h b/position.h index 01684f1..c5b0939 100644 --- a/position.h +++ b/position.h @@ -9,32 +9,10 @@ #include #include #include -#if defined(_MSC_VER) +#if defined(WIN32) #include #endif namespace chess { -namespace _chess { -inline void *aligned_alloc(size_t alignment, size_t size) { -#if defined(_MSC_VER) - return _aligned_malloc(size, alignment); -#elif defined(__APPLE__) || defined(__linux__) - void *ptr = nullptr; - if (posix_memalign(&ptr, alignment, size) != 0) - return nullptr; - return ptr; -#else - return std::aligned_alloc(alignment, size); -#endif -} - -inline void aligned_free(void *ptr) { -#if defined(_MSC_VER) - _aligned_free(ptr); -#else - free(ptr); -#endif -} -} // namespace _chess template struct alignas(64) HistoryEntry { // Bitboards for each piece type (white and black) @@ -65,23 +43,23 @@ template class HeapAllocatedValueList { public: using size_type = std::size_t; inline HeapAllocatedValueList() { - values_ = reinterpret_cast(_chess::aligned_alloc(ALIGNMENT, MaxSize * sizeof(T))); + values_ = reinterpret_cast(::operator new(MaxSize*sizeof(T), std::align_val_t{ALIGNMENT})); assert(values_); } - inline ~HeapAllocatedValueList() { _chess::aligned_free(values_); } + inline ~HeapAllocatedValueList() { ::operator delete(values_, std::align_val_t{ALIGNMENT}); } HeapAllocatedValueList(const HeapAllocatedValueList &other) : size_(other.size_) { - _chess::aligned_free(values_); - values_ = reinterpret_cast(_chess::aligned_alloc(ALIGNMENT, MaxSize * sizeof(T))); + ::operator delete(values_, std::align_val_t{ALIGNMENT}); + values_ = reinterpret_cast(::operator new(MaxSize*sizeof(T), std::align_val_t{ALIGNMENT})); assert(values_); std::copy(other.values_, other.values_ + size_, values_); } HeapAllocatedValueList &operator=(const HeapAllocatedValueList &other) { if (this != &other) { - _chess::aligned_free(values_); + ::operator delete(values_, std::align_val_t{ALIGNMENT}); // Allocate new memory and copy - T *new_values = reinterpret_cast(_chess::aligned_alloc(ALIGNMENT, MaxSize * sizeof(T))); + T *new_values = reinterpret_cast(::operator new(MaxSize*sizeof(T), std::align_val_t{ALIGNMENT})); assert(new_values); std::copy(other.values_, other.values_ + other.size_, new_values); @@ -96,16 +74,16 @@ template class HeapAllocatedValueList { } HeapAllocatedValueList(HeapAllocatedValueList &&other) noexcept : size_(other.size_) { - _chess::aligned_free(values_); - values_ = reinterpret_cast(_chess::aligned_alloc(ALIGNMENT, MaxSize * sizeof(T))); + ::operator delete(values_, std::align_val_t{ALIGNMENT}); + values_ = reinterpret_cast(::operator new(MaxSize*sizeof(T), std::align_val_t{ALIGNMENT})); assert(values_); std::copy(other.values_, other.values_ + size_, values_); } HeapAllocatedValueList &operator=(HeapAllocatedValueList &&other) noexcept { if (this != &other) { - _chess::aligned_free(values_); - values_ = reinterpret_cast(_chess::aligned_alloc(ALIGNMENT, MaxSize * sizeof(T))); + ::operator delete(values_, std::align_val_t{ALIGNMENT}); + values_ = reinterpret_cast(::operator new(MaxSize*sizeof(T), std::align_val_t{ALIGNMENT})); assert(values_); std::copy(other.values_, other.values_ + size_, values_); } diff --git a/printers.cpp b/printers.cpp index 2f90ef6..6c091d6 100644 --- a/printers.cpp +++ b/printers.cpp @@ -34,7 +34,8 @@ template std::ostream &operator<<(std::ostream &os, os << " a b c d e f g h\n"; // Ensure key is printed in hex, but restores after this function - os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << pos.key() << '\n'; + os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase << std::setfill('0') << std::setw(16) << pos.key() + << '\n'; return os; } @@ -80,13 +81,12 @@ std::ostream &operator<<(std::ostream &os, const Square &sq) { os << "SQ_" << str_toupper(chess::uci::squareToString(sq)); return os; } -template std::ostream & operator<<(std::ostream &os, PieceC p) { +template std::ostream &operator<<(std::ostream &os, PieceC p) { constexpr std::string_view mapping = " PNBRQK..pnbrqk."; return os << mapping[static_cast(make_piece(piece_of(p), color_of(p)))]; } -#define INSTANTITATE(PieceC) \ -template std::ostream &operator<<(std::ostream &, const _Position &); +#define INSTANTITATE(PieceC) template std::ostream &operator<<(std::ostream &, const _Position &); INSTANTITATE(EnginePiece) INSTANTITATE(PolyglotPiece) diff --git a/tests.cpp b/tests.cpp index 1027162..139ca21 100644 --- a/tests.cpp +++ b/tests.cpp @@ -80,19 +80,31 @@ static_assert(make_piece(ROOK, WHITE) == PolyglotPiece::WROOK, "m static_assert(make_piece(QUEEN, WHITE) == PolyglotPiece::WQUEEN, "make_piece QUEEN,WHITE -> WQUEEN"); static_assert(make_piece(KING, WHITE) == PolyglotPiece::WKING, "make_piece KING,WHITE -> WKING"); -static_assert(make_piece(PAWN, BLACK) == ContiguousMappingPiece::BPAWN, "make_piece PAWN,BLACK -> BPAWN"); -static_assert(make_piece(KNIGHT, BLACK) == ContiguousMappingPiece::BKNIGHT, "make_piece KNIGHT,BLACK -> BKNIGHT"); -static_assert(make_piece(BISHOP, BLACK) == ContiguousMappingPiece::BBISHOP, "make_piece BISHOP,BLACK -> BBISHOP"); -static_assert(make_piece(ROOK, BLACK) == ContiguousMappingPiece::BROOK, "make_piece ROOK,BLACK -> BROOK"); -static_assert(make_piece(QUEEN, BLACK) == ContiguousMappingPiece::BQUEEN, "make_piece QUEEN,BLACK -> BQUEEN"); -static_assert(make_piece(KING, BLACK) == ContiguousMappingPiece::BKING, "make_piece KING,BLACK -> BKING"); +static_assert(make_piece(PAWN, BLACK) == ContiguousMappingPiece::BPAWN, + "make_piece PAWN,BLACK -> BPAWN"); +static_assert(make_piece(KNIGHT, BLACK) == ContiguousMappingPiece::BKNIGHT, + "make_piece KNIGHT,BLACK -> BKNIGHT"); +static_assert(make_piece(BISHOP, BLACK) == ContiguousMappingPiece::BBISHOP, + "make_piece BISHOP,BLACK -> BBISHOP"); +static_assert(make_piece(ROOK, BLACK) == ContiguousMappingPiece::BROOK, + "make_piece ROOK,BLACK -> BROOK"); +static_assert(make_piece(QUEEN, BLACK) == ContiguousMappingPiece::BQUEEN, + "make_piece QUEEN,BLACK -> BQUEEN"); +static_assert(make_piece(KING, BLACK) == ContiguousMappingPiece::BKING, + "make_piece KING,BLACK -> BKING"); -static_assert(make_piece(PAWN, WHITE) == ContiguousMappingPiece::WPAWN, "make_piece PAWN,WHITE -> WPAWN"); -static_assert(make_piece(KNIGHT, WHITE) == ContiguousMappingPiece::WKNIGHT, "make_piece KNIGHT,WHITE -> WKNIGHT"); -static_assert(make_piece(BISHOP, WHITE) == ContiguousMappingPiece::WBISHOP, "make_piece BISHOP,WHITE -> WBISHOP"); -static_assert(make_piece(ROOK, WHITE) == ContiguousMappingPiece::WROOK, "make_piece ROOK,WHITE -> WROOK"); -static_assert(make_piece(QUEEN, WHITE) == ContiguousMappingPiece::WQUEEN, "make_piece QUEEN,WHITE -> WQUEEN"); -static_assert(make_piece(KING, WHITE) == ContiguousMappingPiece::WKING, "make_piece KING,WHITE -> WKING"); +static_assert(make_piece(PAWN, WHITE) == ContiguousMappingPiece::WPAWN, + "make_piece PAWN,WHITE -> WPAWN"); +static_assert(make_piece(KNIGHT, WHITE) == ContiguousMappingPiece::WKNIGHT, + "make_piece KNIGHT,WHITE -> WKNIGHT"); +static_assert(make_piece(BISHOP, WHITE) == ContiguousMappingPiece::WBISHOP, + "make_piece BISHOP,WHITE -> WBISHOP"); +static_assert(make_piece(ROOK, WHITE) == ContiguousMappingPiece::WROOK, + "make_piece ROOK,WHITE -> WROOK"); +static_assert(make_piece(QUEEN, WHITE) == ContiguousMappingPiece::WQUEEN, + "make_piece QUEEN,WHITE -> WQUEEN"); +static_assert(make_piece(KING, WHITE) == ContiguousMappingPiece::WKING, + "make_piece KING,WHITE -> WKING"); // --------- Round-trip consistency ---------- static_assert(piece_of(make_piece(PAWN, WHITE)) == PAWN, "Round-trip piece PAWN,WHITE"); static_assert(piece_of(make_piece(KNIGHT, WHITE)) == KNIGHT, "Round-trip piece KNIGHT,WHITE"); @@ -220,7 +232,8 @@ template uint64_t perft(_Po return total; } } -template void check_perfts(const std::vector> &entries) { +template +void check_perfts(const std::vector> &entries) { uint64_t nodes = 0; double elapsed = 0; using namespace std::chrono; @@ -521,7 +534,8 @@ TEST_CASE("Minor position test perft") { { "K1k5/8/P7/8/8/8/8/8 w - - 0 1", 6, 2217 }, { "8/k1P5/8/1K6/8/8/8/8 w - - 0 1", 7, 567584 }, { "8/8/2k5/5q2/5n2/8/5K2/8 b - - 0 1", 4, 23527 }, - // https://github.com/SebLague/Chess-Coding-Adventure/blob/Chess-V2-Unity/Assets/Scripts/Testing/Perft/Suites/Suite%20Full.txt converted to [fen, depth, nodes], deduped tests + // https://github.com/SebLague/Chess-Coding-Adventure/blob/Chess-V2-Unity/Assets/Scripts/Testing/Perft/Suites/Suite%20Full.txt + // converted to [fen, depth, nodes], deduped tests { "2b1b3/1r1P4/3K3p/1p6/2p5/6k1/1P3p2/4B3 w - - 0 42", 5, 5617302 }, #if IS_RELEASE == 1 { "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -", 6, 11030083 }, @@ -1710,7 +1724,9 @@ TEST_CASE("was_into_check") { check_was_into_check(tests); check_was_into_check(tests); } -TEST_CASE("Zobrist mapping?") { REQUIRE(zobrist::RandomPiece[enum_idx()][(int)PolyglotPiece::BPAWN][0] == 0x9D39247E33776D41); } +TEST_CASE("Zobrist mapping?") { + REQUIRE(zobrist::RandomPiece[enum_idx()][(int)PolyglotPiece::BPAWN][0] == 0x9D39247E33776D41); +} struct repetitions_t { std::string FEN; std::vector moves; @@ -1732,7 +1748,16 @@ void check_repetitions(std::vector> &tests) { } TEST_CASE("is_repetition") { std::vector> tests = { - { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", { Move(SQ_B1, SQ_C3), Move(SQ_B8, SQ_C6), Move(SQ_C3, SQ_B1), Move(SQ_C6, SQ_B8), Move(SQ_G1, SQ_F3), Move(SQ_G8, SQ_F6), Move(SQ_F3, SQ_G1), Move(SQ_F6, SQ_G8) }, 3, true } + { "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", + { Move(SQ_B1, SQ_C3), + Move(SQ_B8, SQ_C6), + Move(SQ_C3, SQ_B1), + Move(SQ_C6, SQ_B8), + Move(SQ_G1, SQ_F3), + Move(SQ_G8, SQ_F6), + Move(SQ_F3, SQ_G1), + Move(SQ_F6, SQ_G8) }, + 3, true } }; check_repetitions(tests); } From 076866ee65ede959457b8558b2ea09a29a29cb20 Mon Sep 17 00:00:00 2001 From: winapiadmin Date: Mon, 8 Dec 2025 12:22:21 +0700 Subject: [PATCH 4/4] fix compile errors --- movegen.h | 1 + position.h | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/movegen.h b/movegen.h index 3e90222..f3568fb 100644 --- a/movegen.h +++ b/movegen.h @@ -1,3 +1,4 @@ +#pragma once #include "fwd_decl.h" #include namespace chess::_chess{ diff --git a/position.h b/position.h index c5b0939..cd6a363 100644 --- a/position.h +++ b/position.h @@ -9,9 +9,7 @@ #include #include #include -#if defined(WIN32) -#include -#endif +#include "movegen.h" namespace chess { template struct alignas(64) HistoryEntry {