diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index 98e1f29..5be825a 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -61,11 +61,7 @@ jobs: run: cmake --build ${{ steps.vars.outputs.dir }} --config ${{ matrix.build_type }} - name: Test - working-directory: ${{ steps.vars.outputs.dir }} - shell: bash - run: | - if [[ "${{ matrix.os }}" == "windows-latest" ]]; then - ctest --build-config ${{ matrix.build_type }} --verbose - else - ctest --verbose - fi + working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest --build-config ${{ matrix.build_type }} --verbose diff --git a/.gitignore b/.gitignore index 3f3a5f0..e76920e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ build/ !.gitignore CMakePresets.json CMakeSettings.json -*.s +*.s \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index a2f5ab2..856e7cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # --- Compiler tuning --- -set(ASAN_ENABLED OFF CACHE BOOL "Whether to enable ASan or not") +set(SAN_ENABLED OFF CACHE BOOL "Whether to enable ASan or not") if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") # clang-cl: forward GCC/Clang style constexpr flags via /clang: @@ -24,24 +24,12 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") -march=native -mtune=native -ftemplate-backtrace-limit=0 ) - if (ASAN_ENABLED) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) - endif() endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-fconstexpr-ops-limit=2000000000 -fconstexpr-depth=1024 -march=native -mtune=native -ftemplate-backtrace-limit=0) - if (ASAN_ENABLED) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) - endif() elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(ARCH_FLAG "/arch:AVX2" CACHE STRING "MSVC architecture flag (/arch:SSE2, /arch:AVX, /arch:AVX2, /arch:AVX512)") add_compile_options(/constexpr:steps2000000000 /constexpr:depth1024 ${ARCH_FLAG}) - if (ASAN_ENABLED) - add_compile_options(/fsanitize=address) - add_link_options(/fsanitize=address) - endif() endif() add_compile_definitions(GENERATE_AT_RUNTIME) diff --git a/bitboard.h b/bitboard.h index 6178c93..a324f4d 100644 --- a/bitboard.h +++ b/bitboard.h @@ -4,7 +4,7 @@ #if defined(_MSC_VER) #include #endif - +#include namespace chess { // ------------------------------- // constexpr fallbacks @@ -43,7 +43,7 @@ constexpr int msb_constexpr(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) [[gnu::const]] #endif -inline constexpr int popcount(Bitboard x) noexcept { +__FORCEINLINE constexpr int popcount(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) if (!is_constant_evaluated()) return __builtin_popcountll(x); @@ -57,7 +57,7 @@ inline constexpr int popcount(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) [[gnu::const]] #endif -inline constexpr int lsb(Bitboard x) noexcept { +__FORCEINLINE constexpr int lsb(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) if (!is_constant_evaluated()) return __builtin_ctzll(x); @@ -74,7 +74,7 @@ inline constexpr int lsb(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) [[gnu::const]] #endif -inline constexpr int msb(Bitboard x) noexcept { +__FORCEINLINE constexpr int msb(Bitboard x) noexcept { #if defined(__GNUG__) || defined(__clang__) if (!is_constant_evaluated()) return 63 - __builtin_clzll(x); @@ -91,13 +91,17 @@ inline constexpr int msb(Bitboard x) noexcept { // ------------------------------- // destructive variants // ------------------------------- -inline int pop_lsb(Bitboard &b) noexcept { +__FORCEINLINE int pop_lsb(Bitboard &b) noexcept { int c = lsb(b); +#ifndef __BMI2__ b &= b - 1; +#else + b = _blsr_u64(b); +#endif return c; } -inline int pop_msb(Bitboard &b) noexcept { +__FORCEINLINE int pop_msb(Bitboard &b) noexcept { int c = msb(b); b &= ~(1ULL << c); return c; diff --git a/examples/perft_with_tt.cpp b/examples/perft_with_tt.cpp index 78fdc47..bf2c44b 100644 --- a/examples/perft_with_tt.cpp +++ b/examples/perft_with_tt.cpp @@ -113,18 +113,17 @@ template void benchmar double avgTime = totalTime / N_RUNS; double avgMnps = (nodes / avgTime) / 1'000'000.0; - std::cout << "Average: " << avgTime << " s, " << avgMnps << " Mnps\n"; + std::cout << "Average: " << avgTime << " s, " << avgMnps << " Mnps (nodes="<<(nodes/N_RUNS)<<")\n"; } int main() { - _Position pos("8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 1"); + _Position pos; #ifdef TT_ENABLED tt.resize(1 << 28); #endif - benchmark<3, true, ContiguousMappingPiece>(pos); - + benchmark<7, true, EnginePiece>(pos); // Movelist moves; // pos.template legals(moves); // for (const Move &m : moves) { diff --git a/position.cpp b/position.cpp index ddeb55f..a5b6e8d 100644 --- a/position.cpp +++ b/position.cpp @@ -22,16 +22,16 @@ template struct alignas(64) SplatTable { } }; -inline constexpr SplatTable<> SPLAT_TABLE{}; -template inline constexpr SplatTable SPLAT_PAWN_TABLE{}; +__FORCEINLINE constexpr SplatTable<> SPLAT_TABLE{}; +template __FORCEINLINE constexpr SplatTable SPLAT_PAWN_TABLE{}; // AVX-512 (32 lanes of uint16_t) -inline static Move *write_moves(Move *moveList, uint32_t mask, __m512i vector) { +__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); } -inline static Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { +__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) @@ -41,143 +41,15 @@ inline static Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { return moveList; } -template inline static Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { +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; } -// temp AVX2 solution, but apperantly it runs slower and fails the tests -// #elif defined(__AVX2__) -// template struct alignas(64) SplatTable { -// alignas(32) 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(); -// } -// } -//}; -// -// inline constexpr SplatTable<> SPLAT_TABLE{}; -// template inline constexpr SplatTable SPLAT_PAWN_TABLE{}; -// -// constexpr std::array, 256> build_shuffle_lut() { -// std::array, 256> lut{}; -// -// for (int m = 0; m < 256; ++m) { -// int pos = 0; -// for (int i = 0; i < 8; ++i) { -// if (m & (1 << i)) { -// lut[m][pos++] = 2 * i; -// lut[m][pos++] = 2 * i + 1; -// } -// } -// // fill remaining with 0x80 -// while (pos < 16) -// lut[m][pos++] = 0x80; -// } -// -// return lut; -//} -// -// constexpr auto SHUFFLE_LUT_ARRAY = build_shuffle_lut(); -// alignas(16) static __m128i _pshufb_compress_lut[256]; -// -// struct ShuffleLutInitializer { -// ShuffleLutInitializer() { -// for (int i = 0; i < 256; ++i) { -// std::memcpy(&_pshufb_compress_lut[i], SHUFFLE_LUT_ARRAY[i].data(), 16); -// } -// } -//}; -// -//// Guaranteed to run before main() -// static ShuffleLutInitializer _shuffle_lut_init; -// -//// Compress 16Ă—int16_t lanes from v according to mask, store contiguously, return #written -// static inline int compressstore_epi16_avx2(int16_t* dst, __m256i v, uint16_t mask) { -// __m128i lo = _mm256_castsi256_si128(v); -// __m128i hi = _mm256_extracti128_si256(v, 1); -// -// uint8_t mask_lo = mask & 0xFF; -// uint8_t mask_hi = (mask >> 8) & 0xFF; -// -// __m128i shuf_lo = _pshufb_compress_lut[mask_lo]; -// __m128i shuf_hi = _pshufb_compress_lut[mask_hi]; -// -// __m128i cmp_lo = _mm_shuffle_epi8(lo, shuf_lo); -// __m128i cmp_hi = _mm_shuffle_epi8(hi, shuf_hi); -// -// int count_lo = popcount(mask_lo); -// int count_hi = popcount(mask_hi); -// -// _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), cmp_lo); -// _mm_storeu_si128(reinterpret_cast<__m128i*>(dst + count_lo), cmp_hi); -// -// return count_lo + count_hi; -// } -// -//// Same logical behavior as your AVX-512 write_moves() -// inline Move* write_moves(Move* moveList, uint32_t mask, __m256i vector) { -// int n = compressstore_epi16_avx2(reinterpret_cast(moveList), vector, -// static_cast(mask)); -// return moveList + n; -// } -// inline Move *write_moves(Move *moveList, uint32_t mask, __m256i lo_vec, __m256i hi_vec) { -// uint16_t mask_lo = static_cast(mask & 0xFFFF); -// uint16_t mask_hi = static_cast(mask >> 16); -// -// moveList = write_moves(moveList, mask_lo, lo_vec); -// moveList = write_moves(moveList, mask_hi, hi_vec); -// -// return moveList; -// } -// -//// ----------------- splat_moves AVX2 ----------------- -// inline Move *splat_moves(Move *moveList, uint16_t from, uint64_t to_bb) { -// const uint16_t *base = SPLAT_TABLE.data.data(); -// -// // load 4 blocks: 0..15, 16..31, 32..47, 48..63 -// __m256i t0 = _mm256_loadu_si256(reinterpret_cast(base + 0)); -// __m256i t1 = _mm256_loadu_si256(reinterpret_cast(base + 16)); -// __m256i t2 = _mm256_loadu_si256(reinterpret_cast(base + 32)); -// __m256i t3 = _mm256_loadu_si256(reinterpret_cast(base + 48)); -// -// __m256i fromVec = _mm256_set1_epi16(from); -// -// // lower 32-bit bitboard -// moveList = write_moves(moveList, static_cast(to_bb >> 0), _mm256_or_si256(t0, fromVec), _mm256_or_si256(t1, fromVec)); -// -// // upper 32-bit bitboard -// moveList = write_moves(moveList, static_cast(to_bb >> 32), _mm256_or_si256(t2, fromVec), _mm256_or_si256(t3, fromVec)); -// -// return moveList; -// } -// -//// ----------------- splat_pawn_moves AVX2 ----------------- -// template inline Move *splat_pawn_moves(Move *moveList, uint64_t to_bb) { -// const uint16_t *base = SPLAT_PAWN_TABLE.data.data(); -// -// __m256i t0 = _mm256_loadu_si256(reinterpret_cast(base + 0)); -// __m256i t1 = _mm256_loadu_si256(reinterpret_cast(base + 16)); -// __m256i t2 = _mm256_loadu_si256(reinterpret_cast(base + 32)); -// __m256i t3 = _mm256_loadu_si256(reinterpret_cast(base + 48)); -// -// // lower 32-bit bitboard -// moveList = write_moves(moveList, static_cast(to_bb >> 0), t0, t1); -// -// // upper 32-bit bitboard -// moveList = write_moves(moveList, static_cast(to_bb >> 32), t2, t3); -// -// return moveList; -// } - #else -template inline static Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { +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) @@ -191,7 +63,7 @@ template inline static Move *splat_pawn_moves(Move *moveList, return moveList; } -inline static Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { +__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; @@ -243,6 +115,7 @@ template template void _Position2) throw std::invalid_argument("Too many ep candidates??? i think there's only 2?"); while (candidates) { Square from = static_cast(pop_lsb(candidates)); @@ -264,164 +137,92 @@ template template void _Position template void _Position::genPawnSingleMoves(Movelist &moves) const { - constexpr Direction UP = pawn_push(c); - // constexpr Bitboard PROMO_RANK = (c == WHITE) ? attacks::MASK_RANK[6] : attacks::MASK_RANK[1]; - constexpr Bitboard PROMO_NEXT = (c == WHITE) ? attacks::MASK_RANK[7] : attacks::MASK_RANK[0]; - constexpr Bitboard NOT_FILE_A = 0xfefefefefefefefeULL; - constexpr Bitboard NOT_FILE_H = 0x7f7f7f7f7f7f7f7fULL; - - const Bitboard pawns = pieces(); - const Bitboard enemy_occ = occ(~c); - const Bitboard all_occ = occ(); - const Bitboard bishopPin = this->_bishop_pin; - const Bitboard rookPin = this->_rook_pin; - const Bitboard check_mask = this->_check_mask; - const Bitboard pinned = pawns & _pin_mask; - Move *moveList; // no chance - - // ---------- 1. Single forward pushes ---------- - - if constexpr (!capturesOnly) { - const Bitboard unpinned = pawns &~_pin_mask; - Bitboard one_push = (c == WHITE ? unpinned << 8 : unpinned >> 8) & ~all_occ; - one_push &= check_mask; - Bitboard _pinned = pinned & ~bishopPin; - Bitboard pinned_push = (c == WHITE ? _pinned << 8 : _pinned >> 8) & ~all_occ; - pinned_push &= rookPin & check_mask; - Bitboard push_targets = one_push | pinned_push; - Bitboard promo_targets = push_targets & PROMO_NEXT; - Bitboard non_promo_targets = push_targets & ~promo_targets; // NAND - while (promo_targets) { - Square to = Square(pop_lsb(promo_targets)); - Square from = Square(to - UP); - - moves.push_back(Move::make(from, to, KNIGHT)); - moves.push_back(Move::make(from, to, BISHOP)); - moves.push_back(Move::make(from, to, ROOK)); - moves.push_back(Move::make(from, to, QUEEN)); - } - // while (non_promo_targets) { - // Square to = Square(pop_lsb(non_promo_targets)); - // Square from = Square(to - UP); - // moves.push_back(Move(from, to)); - // } - moveList = moves.data() + moves.size(); - Move *moveEnd = _chess::splat_pawn_moves(moveList, non_promo_targets); - moves.size_ += std::distance(moveList, moveEnd); - moveList = moveEnd; - } - // ---------- 2. Left captures ---------- - Bitboard left = (pawns & NOT_FILE_A); - Bitboard left_tgt = (c == WHITE ? (left << 7) : (left >> 9)) & enemy_occ & check_mask; - Bitboard promo_left_targets = left_tgt & PROMO_NEXT; - Bitboard non_promo_left_targets = left_tgt & ~promo_left_targets; // NAND - - const Bitboard pinnedSet = pinned; - while (promo_left_targets) { - Square to = Square(pop_lsb(promo_left_targets)); - Square from = Square(to - ((c == WHITE) ? 7 : -9)); - Bitboard from_bb = 1ULL << from; - Bitboard move_bb = from_bb | (1ULL << to); - - Bitboard pinMatch = ((move_bb & bishopPin) == move_bb) | ((move_bb & rookPin) == move_bb); - bool legal = !((pinnedSet >> from) & 1ULL) || pinMatch; - if (legal) { - moves.push_back(Move::make(from, to, KNIGHT)); - moves.push_back(Move::make(from, to, BISHOP)); - moves.push_back(Move::make(from, to, ROOK)); - moves.push_back(Move::make(from, to, QUEEN)); - } - } + 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; { - // while (non_promo_left_targets) { - // Square to = Square(pop_lsb(non_promo_left_targets)); - // Square from = Square(to - ((c == WHITE) ? 7 : -9)); - // Bitboard from_bb = 1ULL << from; - // Bitboard move_bb = from_bb | (1ULL << to); - - // // skip if pinned but capture not along pin ray - // if (!((pinned & from_bb) && !((move_bb & current_state._bishop_pin) == move_bb || (move_bb & current_state._rook_pin) == move_bb))) - // moves.push_back(Move(from, to)); - // } - // } - Bitboard legal_sources = 0; - Bitboard targets = non_promo_left_targets; - - constexpr int shift = (c == WHITE) ? 7 : -9; // pawn left capture delta - - while (targets) { - int to = pop_lsb(targets); - int from = to - shift; - Bitboard to_bb = (1ULL << to); - Bitboard move_bb = (1ULL << from) | to_bb; - - // Determine legality using bitwise logic instead of branch - Bitboard pinMatch = ((move_bb & bishopPin) == move_bb) | ((move_bb & rookPin) == move_bb); - bool legal = !((pinnedSet >> from) & 1ULL) || pinMatch; - - // branchless combine - legal_sources |= legal ? to_bb : 0; + 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; + } } - // generate pawn moves - Move *moveList = moves.data() + moves.size(); - constexpr Direction dir = (c == WHITE) ? NORTH_WEST : SOUTH_WEST; - Move *moveEnd = _chess::splat_pawn_moves(moveList, legal_sources); - moves.size_ += moveEnd - moveList; - } - // ---------- 3. Right captures ---------- - Bitboard right = (pawns & NOT_FILE_H); - Bitboard right_tgt = (c == WHITE ? (right << 9) : (right >> 7)) & enemy_occ & check_mask; - Bitboard promo_right_targets = right_tgt & PROMO_NEXT; - Bitboard non_promo_right_targets = right_tgt & ~promo_right_targets; // NAND - - while (promo_right_targets) { - Square to = Square(pop_lsb(promo_right_targets)); - Square from = Square(to - ((c == WHITE) ? 9 : -7)); - Bitboard from_bb = 1ULL << from; - Bitboard move_bb = from_bb | (1ULL << to); - - Bitboard pinMatch = ((move_bb & bishopPin) == move_bb) | ((move_bb & rookPin) == move_bb); - bool legal = !((pinnedSet >> from) & 1ULL) || pinMatch; - if (legal) { - moves.push_back(Move::make(from, to, KNIGHT)); - moves.push_back(Move::make(from, to, BISHOP)); - moves.push_back(Move::make(from, to, ROOK)); - moves.push_back(Move::make(from, to, QUEEN)); + 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 (non_promo_right_targets) { - // Square to = Square(pop_lsb(non_promo_right_targets)); - // Square from = Square(to - ((c == WHITE) ? 9 : -7)); - // Bitboard from_bb = 1ULL << from; - // Bitboard move_bb = from_bb | (1ULL << to); - - // if (!((pinned & from_bb) && !((move_bb & current_state._bishop_pin) == move_bb || (move_bb & current_state._rook_pin) == move_bb))) - // moves.push_back(Move(from, to)); - // } - Bitboard legal_sources = 0; - while (non_promo_right_targets) { - Square to = Square(pop_lsb(non_promo_right_targets)); - Square from = Square(to - ((c == WHITE) ? 9 : -7)); - Bitboard from_bb = 1ULL << from, to_bb = 1ULL << to; - Bitboard move_bb = from_bb | to_bb; - - // skip if pinned but capture not along pin ray - Bitboard pinMatch = ((move_bb & bishopPin) == move_bb) | ((move_bb & rookPin) == move_bb); - bool legal = !((pinnedSet >> from) & 1ULL) || pinMatch; - - // branchless combine - legal_sources |= legal ? to_bb : 0; + + 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; } - moveList = moves.data() + moves.size(); - constexpr Direction dir = c == WHITE ? NORTH_EAST : SOUTH_EAST; - Move *moveEnd = _chess::splat_pawn_moves(moveList, legal_sources); - moves.size_ += std::distance(moveList, moveEnd); - moveList = moveEnd; } -} + 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::genPawnDoubleMoves(Movelist &moves) const { constexpr Bitboard RANK_2 = (c == WHITE) ? attacks::MASK_RANK[1] : attacks::MASK_RANK[6]; constexpr Direction UP = pawn_push(c); @@ -493,43 +294,35 @@ template template voi out.size_ += popcount(moves); // clang-format off if constexpr (!capturesOnly) { - const auto &st = current_state; - if (checkers()) - return; + if (checkers()) return; if constexpr (c == WHITE) { // Kingside - if ((st.castlingRights & WHITE_OO) - && piece_on(SQ_F1) == PieceC::NO_PIECE - && piece_on(SQ_G1) == PieceC::NO_PIECE + if ((current_state.castlingRights & WHITE_OO) + && !(occ() & ((1ULL << SQ_F1) | (1ULL << SQ_G1))) && !(enemyAttacks & ((1ULL << SQ_F1) | (1ULL << SQ_G1)))) { out.push_back(Move::make(SQ_E1, SQ_H1)); } // Queenside - if ((st.castlingRights & WHITE_OOO) - && piece_on(SQ_D1) == PieceC::NO_PIECE - && piece_on(SQ_C1) == PieceC::NO_PIECE - && piece_on(SQ_B1) == PieceC::NO_PIECE - && !(enemyAttacks & ((1ULL << SQ_D1) | (1ULL << SQ_C1)))) + if ((current_state.castlingRights & WHITE_OOO) + && !(occ() & ((1ULL << SQ_B1) | (1ULL << SQ_C1) | (1ULL << SQ_D1))) + && !(enemyAttacks & ((1ULL << SQ_C1) | (1ULL << SQ_D1)))) { out.push_back(Move::make(SQ_E1, SQ_A1)); } - } else { + } else { // BLACK // Kingside - if ((st.castlingRights & BLACK_OO) - && piece_on(SQ_F8) == PieceC::NO_PIECE - && piece_on(SQ_G8) == PieceC::NO_PIECE + if ((current_state.castlingRights & BLACK_OO) + && !(occ() & ((1ULL << SQ_F8) | (1ULL << SQ_G8))) && !(enemyAttacks & ((1ULL << SQ_F8) | (1ULL << SQ_G8)))) { out.push_back(Move::make(SQ_E8, SQ_H8)); } // Queenside - if ((st.castlingRights & BLACK_OOO) - && piece_on(SQ_D8) == PieceC::NO_PIECE - && piece_on(SQ_C8) == PieceC::NO_PIECE - && piece_on(SQ_B8) == PieceC::NO_PIECE - && !(enemyAttacks & ((1ULL << SQ_D8) | (1ULL << SQ_C8)))) + if ((current_state.castlingRights & BLACK_OOO) + && !(occ() & ((1ULL << SQ_B8) | (1ULL << SQ_C8) | (1ULL << SQ_D8))) + && !(enemyAttacks & ((1ULL << SQ_C8) | (1ULL << SQ_D8)))) { out.push_back(Move::make(SQ_E8, SQ_A8)); } @@ -655,7 +448,7 @@ template template void _Position template inline auto _Position::undoMove() -> std::conditional_t &, void> { +template template auto _Position::undoMove() -> std::conditional_t &, void> { // Save only the move (we'll restore the full state next) const Move move = current_state.mv; @@ -709,7 +502,7 @@ template template inline auto _Posit (p[BISHOP] & sq_bb) ? BISHOP : (p[ROOK] & sq_bb) ? ROOK : (p[QUEEN] & sq_bb) ? QUEEN : - (p[KING] & sq_bb) ? KING : PAWN; // fallback, though should never happen + (p[KING] & sq_bb) ? KING : NO_PIECE_TYPE; // fallback, though should never happen // clang-format on pieces_list[sq] = make_piece(pt, pc); } @@ -1231,17 +1024,6 @@ template bool _Position::is_insufficien return false; } -template inline bool _Position::has_repeated() const { - auto idx = history.size() - 1; - int end = std::min(rule50_count(), current_state.pliesFromNull); - while (end-- >= 4) { - if (history[idx].repetition) - return true; - - idx--; - } - return false; -} template CastlingRights _Position::clean_castling_rights() const { constexpr Bitboard cr_WOO = 1ULL << SQ_H1; constexpr Bitboard cr_WOOO = 1ULL << SQ_A1; @@ -1317,7 +1099,6 @@ template void _Position::refresh_attacks(); \ template uint64_t _Position::zobrist() const; \ template Move _Position::parse_uci(std::string) const; \ template Move _Position::push_uci(std::string); \ -template bool _Position::has_repeated() const; \ template bool _Position::is_valid() const; \ template bool _Position::is_valid() const; \ template bool _Position::is_insufficient_material() const; diff --git a/position.h b/position.h index 8007164..281558d 100644 --- a/position.h +++ b/position.h @@ -12,7 +12,6 @@ #if defined(_MSC_VER) #include #endif - namespace chess { namespace _chess { inline void *aligned_alloc(size_t alignment, size_t size) { @@ -173,7 +172,7 @@ template inline void legals(Movelist &out) const { + template __FORCEINLINE void legals(Movelist &out) const { if constexpr (type == MoveGenType::ALL) { // Simple cases genEP(out); @@ -203,7 +202,7 @@ template inline void legals(Movelist &out) const { + template __FORCEINLINE void legals(Movelist &out) const { const Color stm = sideToMove(); // Cache it ASSUME(stm == WHITE || stm == BLACK); // Now clearly no side effects @@ -222,7 +221,7 @@ template void doMove(const Move &move); template auto undoMove() -> std::conditional_t &, void>; - inline void doNullMove() { + __FORCEINLINE void doNullMove() { history.push_back(current_state); current_state.turn = ~current_state.turn; current_state.hash ^= zobrist::RandomTurn; @@ -231,14 +230,14 @@ template inline Bitboard pieces(Color c) const { + __FORCEINLINE Bitboard pieces() const { return occ(); } + template __FORCEINLINE Bitboard pieces(Color c) const { ASSUME(c == WHITE || c == BLACK); if constexpr (pt == PIECE_TYPE_NB || pt == ALL_PIECES) return occ(c); return current_state.pieces[pt] & current_state.occ[c]; } - template inline Bitboard pieces(PieceType pt) const { + template __FORCEINLINE Bitboard pieces(PieceType pt) const { ASSUME(c == WHITE || c == BLACK); if (pt == PIECE_TYPE_NB || pt == ALL_PIECES) return occ(c); @@ -250,7 +249,7 @@ template && ...)>> [[nodiscard]] inline Bitboard pieces(PTypes... ptypes) const { return (current_state.pieces[ptypes] | ...); } + template && ...)>> [[nodiscard]] __FORCEINLINE Bitboard pieces(PTypes... ptypes) const { return (current_state.pieces[ptypes] | ...); } - template && ...)>> [[nodiscard]] inline Bitboard pieces(Color c, PTypes... ptypes) const { return (pieces(ptypes, c) | ...); } + template && ...)>> [[nodiscard]] __FORCEINLINE Bitboard pieces(Color c, PTypes... ptypes) const { return (pieces(ptypes, c) | ...); } /** * @brief Returns the origin squares of pieces of a given color attacking a target square @@ -289,7 +288,7 @@ template (color); // using the fact that if we can attack PieceType from square, they can attack us back @@ -308,7 +307,7 @@ template inline void placePiece(Square sq, Color c) { + template __FORCEINLINE void placePiece(Square sq, Color c) { if constexpr (pt != NO_PIECE_TYPE) { Bitboard v = 1ULL << sq; current_state.pieces[pt] |= v; @@ -345,7 +344,7 @@ template inline void removePiece(Square sq, Color c) { + template __FORCEINLINE void removePiece(Square sq, Color c) { if constexpr (pt != NO_PIECE_TYPE) { Bitboard v = ~(1ULL << sq); current_state.pieces[pt] &= v; @@ -357,7 +356,7 @@ template inline Square square(Color c) const { return Square(lsb(pieces(c))); } - inline Square kingSq(Color c) const { return current_state.kings[c]; } - inline Bitboard checkers() const { return _checkers; } - inline Bitboard pin_mask() const { return _pin_mask; } + __FORCEINLINE Color sideToMove() const { return current_state.turn; } + __FORCEINLINE uint64_t hash() const { return current_state.hash; } + __FORCEINLINE uint64_t key() const { return current_state.hash; } + __FORCEINLINE Color side_to_move() const { return current_state.turn; } + __FORCEINLINE Square ep_square() const { return current_state.enPassant; } + template __FORCEINLINE Square square(Color c) const { return Square(lsb(pieces(c))); } + __FORCEINLINE Square kingSq(Color c) const { return current_state.kings[c]; } + __FORCEINLINE Bitboard checkers() const { return _checkers; } + __FORCEINLINE Bitboard pin_mask() const { return _pin_mask; } _Position(std::string fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); - inline bool isCapture(Move mv) const { return mv.type_of() == EN_PASSANT || (mv.type_of() != CASTLING && piece_on(mv.to_sq()) != PieceC::NO_PIECE); } - inline bool is_capture(Move mv) const { return isCapture(mv); } - inline bool is_zeroing(Move mv) const { return isCapture(mv) || type_of(at(mv.from_sq())) == PAWN; } + __FORCEINLINE bool isCapture(Move mv) const { return mv.type_of() == EN_PASSANT || (mv.type_of() != CASTLING && piece_on(mv.to_sq()) != PieceC::NO_PIECE); } + __FORCEINLINE bool is_capture(Move mv) const { return isCapture(mv); } + __FORCEINLINE bool is_zeroing(Move mv) const { return isCapture(mv) || type_of(at(mv.from_sq())) == PAWN; } std::string fen() const; - inline uint8_t halfmoveClock() const { return current_state.halfMoveClock; } - inline uint16_t fullmoveNumber() const { return current_state.fullMoveNumber; } - inline uint8_t rule50_count() const { return current_state.halfMoveClock; } - inline CastlingRights castlingRights(Color c) const { return current_state.castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING); } - inline CastlingRights castlingRights() const { return current_state.castlingRights; } - inline bool is_castling(Move mv) const { return (mv.type_of() & CASTLING) != 0; } - inline const HistoryEntry &state() const { return current_state; } + __FORCEINLINE uint8_t halfmoveClock() const { return current_state.halfMoveClock; } + __FORCEINLINE uint16_t fullmoveNumber() const { return current_state.fullMoveNumber; } + __FORCEINLINE uint8_t rule50_count() const { return current_state.halfMoveClock; } + __FORCEINLINE CastlingRights castlingRights(Color c) const { return current_state.castlingRights & (c == WHITE ? WHITE_CASTLING : BLACK_CASTLING); } + __FORCEINLINE CastlingRights castlingRights() const { return current_state.castlingRights; } + __FORCEINLINE bool is_castling(Move mv) const { return (mv.type_of() & CASTLING) != 0; } + __FORCEINLINE const HistoryEntry &state() const { return current_state; } uint64_t zobrist() const; - inline PieceC piece_at(Square sq) const { return piece_on(sq); } - inline PieceC at(Square sq) const { return piece_at(sq); } - inline Square enpassantSq() const { return ep_square(); } + __FORCEINLINE PieceC piece_at(Square sq) const { return piece_on(sq); } + __FORCEINLINE PieceC at(Square sq) const { return piece_at(sq); } + __FORCEINLINE Square enpassantSq() const { return ep_square(); } CastlingRights clean_castling_rights() const; void setFEN(const std::string &str); - inline void set_fen(const std::string &str) { setFEN(str); } + __FORCEINLINE void set_fen(const std::string &str) { setFEN(str); } Move parse_uci(std::string) const; Move push_uci(std::string); Square _valid_ep_square() const; - template inline int count() const { return popcount(pieces(pt)); } - template inline int count() const { return popcount(pieces()); } - template inline int count(Color c) const { return popcount(pieces(c)); } - inline int count(PieceType pt, Color c) const { return popcount(pieces(pt, c)); } - inline int ply() const { return 2 * (current_state.fullMoveNumber - 1) + (sideToMove() == BLACK); } + template __FORCEINLINE int count() const { return popcount(pieces(pt)); } + template __FORCEINLINE int count() const { return popcount(pieces()); } + template __FORCEINLINE int count(Color c) const { return popcount(pieces(c)); } + __FORCEINLINE int count(PieceType pt, Color c) const { return popcount(pieces(pt, c)); } + __FORCEINLINE int ply() const { return 2 * (current_state.fullMoveNumber - 1) + (sideToMove() == BLACK); } bool is_insufficient_material(Color c) const; - inline bool isInsufficientMaterial(Color c) const { return is_insufficient_material(c); } - inline bool hasInsufficientMaterial(Color c) const { return is_insufficient_material(c); } - inline bool has_insufficient_material(Color c) const { return is_insufficient_material(c); } - inline bool is_insufficient_material() const {return has_insufficient_material(WHITE) && has_insufficient_material(BLACK); } - inline bool inCheck() const { return checkers() != 0; } - inline bool is_check() const { return checkers() != 0; } - inline bool has_castling_rights(Color c) const { return castlingRights(c) != 0; } - inline bool has_kingside_castling_rights(Color c) const { return (castlingRights(c) & KING_SIDE) != 0; } - inline bool has_queenside_castling_rights(Color c) const { return (castlingRights(c) & QUEEN_SIDE) != 0; } + __FORCEINLINE bool isInsufficientMaterial(Color c) const { return is_insufficient_material(c); } + __FORCEINLINE bool hasInsufficientMaterial(Color c) const { return is_insufficient_material(c); } + __FORCEINLINE bool has_insufficient_material(Color c) const { return is_insufficient_material(c); } + __FORCEINLINE bool is_insufficient_material() const { return has_insufficient_material(WHITE) && has_insufficient_material(BLACK); } + __FORCEINLINE bool inCheck() const { return checkers() != 0; } + __FORCEINLINE bool is_check() const { return checkers() != 0; } + __FORCEINLINE bool has_castling_rights(Color c) const { return castlingRights(c) != 0; } + __FORCEINLINE bool has_kingside_castling_rights(Color c) const { return (castlingRights(c) & KING_SIDE) != 0; } + __FORCEINLINE bool has_queenside_castling_rights(Color c) const { return (castlingRights(c) & QUEEN_SIDE) != 0; } // Return true if a position repeats once earlier but strictly // after the root, or repeats twice before or at the root. - inline bool is_repetition(int ply) const { return current_state.repetition && current_state.repetition < ply; } + __FORCEINLINE bool is_repetition(int ply) const { return current_state.repetition && current_state.repetition < ply; } // Test if it's draw of 75 move rule (that forces everyone to draw). Excludes checkmates, of course! - inline bool is_draw(int ply) const { return rule50_count() > 99 || is_repetition(ply); } + __FORCEINLINE bool is_draw(int ply) const { return rule50_count() > 99 || is_repetition(ply); } // Tests whether there has been at least one repetition // of positions since the last capture or pawn move. - inline bool has_repeated() const; - inline bool _is_halfmoves(int n) { return rule50_count() >= n; } - inline bool is_seventyfive_moves(int n) { return _is_halfmoves(150); } - inline bool is_fifty_moves(int n) { return _is_halfmoves(150); } - inline bool is_fivefold_repetition() { return is_repetition(5); } - inline bool is_attacked_by(Color color, Square sq, Bitboard occupied = 0) const { + __FORCEINLINE bool has_repeated() const { + auto idx = history.size() - 1; + int end = std::min(rule50_count(), current_state.pliesFromNull); + while (end-- >= 4) { + if (history[idx].repetition) + return true; + + idx--; + } + return false; + } + __FORCEINLINE bool _is_halfmoves(int n) { return rule50_count() >= n; } + __FORCEINLINE bool is_seventyfive_moves(int n) { return _is_halfmoves(150); } + __FORCEINLINE bool is_fifty_moves(int n) { return _is_halfmoves(150); } + __FORCEINLINE bool is_fivefold_repetition() { return is_repetition(5); } + __FORCEINLINE bool is_attacked_by(Color color, Square sq, Bitboard occupied = 0) const { Bitboard occ_bb = occupied ? occupied : this->occ(); return attackers_mask(color, sq, occ_bb) != 0; } - inline bool was_into_check() const { + __FORCEINLINE bool was_into_check() const { bool atk = false; Bitboard bb = pieces(~sideToMove()); while (!atk && bb) { @@ -479,7 +488,7 @@ template (color); // using the fact that if we can attack PieceType from square, they can attack us back @@ -491,7 +500,7 @@ template (pop_lsb(path)), occupied); @@ -501,7 +510,7 @@ template bool is_valid() const; private: - template [[nodiscard]] inline Bitboard pinMask(Color c, Square sq) const { + template [[nodiscard]] __FORCEINLINE Bitboard pinMask(Color c, Square sq) const { static_assert(pt == BISHOP || pt == ROOK, "Only bishop or rook allowed!"); Bitboard occ_opp = occ(~sideToMove()); Bitboard occ_us = occ(sideToMove()); @@ -531,18 +540,18 @@ template state) { + __FORCEINLINE _Position(HistoryEntry state) { // compatible! current_state = state; refresh_attacks(); } - inline _Position(const _Position &other) + __FORCEINLINE _Position(const _Position &other) : current_state(other.current_state), history(other.history) // calls HeapAllocatedValueList's copy constructor { refresh_attacks(); } - template || std::is_same_v) && !std::is_same_v, int> = 0> [[deprecated("Incompatible piece method used, you shouldn't call this")]] inline _Position(HistoryEntry state) { + template || std::is_same_v) && !std::is_same_v, int> = 0> [[deprecated("Incompatible piece method used, you shouldn't call this")]] __FORCEINLINE _Position(HistoryEntry state) { // incompatible! current_state = HistoryEntry(); current_state.turn = state.turn; diff --git a/tests.cpp b/tests.cpp index 70b2fd2..bd28690 100644 --- a/tests.cpp +++ b/tests.cpp @@ -308,8 +308,9 @@ TEST_CASE("Move making and unmaking") { TEST_CASE("Perft pawn-only startpos") { std::string fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; Position position(fen); - REQUIRE(perft(position, 6) == 11515584); + REQUIRE(perft(position, 1) == 16); REQUIRE(perft(position, 5) == 815968); + REQUIRE(perft(position, 6) == 11515584); } TEST_CASE("King walk empty board wking e4 bking f8 depth 6") { std::string fen = "5k2/8/8/8/3K4/8/8/8 w - - 0 1"; diff --git a/types.h b/types.h index ec80383..3579972 100644 --- a/types.h +++ b/types.h @@ -37,6 +37,13 @@ } while (0) #endif +#ifdef _MSC_VER +#define __FORCEINLINE inline __forceinline +#elif defined(__GNUC__) || defined(__clang__) +#define __FORCEINLINE inline __attribute__((always_inline)) +#else +#define __FORCEINLINE inline +#endif constexpr bool is_constant_evaluated() { #if __cpp_if_consteval >= 202106L if consteval { @@ -169,15 +176,9 @@ template size_t enum_idx() { return -1; } // clang-format on -constexpr PieceType piece_of(PolyglotPiece p) { - return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast(static_cast(p) / 2 + 1); -} -constexpr PieceType piece_of(EnginePiece p) { - return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast((static_cast(p) - 1) % 8 + 1); -} -constexpr PieceType piece_of(ContiguousMappingPiece p) { - return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast(static_cast(p) % 6+1); -} +constexpr PieceType piece_of(PolyglotPiece p) { return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast(static_cast(p) / 2 + 1); } +constexpr PieceType piece_of(EnginePiece p) { return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast((static_cast(p) - 1) % 8 + 1); } +constexpr PieceType piece_of(ContiguousMappingPiece p) { return p == decltype(p)::NO_PIECE ? NO_PIECE_TYPE : static_cast(static_cast(p) % 6 + 1); } constexpr PieceType type_of(PolyglotPiece p) { return piece_of(p); } constexpr PieceType type_of(EnginePiece p) { return piece_of(p); } constexpr PieceType type_of(ContiguousMappingPiece p) { return piece_of(p); }