diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..aae67d9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +./** text=auto eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1ae7e05..f91faa5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,3 +26,7 @@ Test log from the tester. **Hardware/Software info** List about the OS, hardware, compiler and tester. + +**Specifically, if the issue is reported by logic analysis, provide solution if applicable.** + +solution to the issue if applicable. If not, please provide the logic analysis data and your interpretation of it. \ No newline at end of file diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml index 82b3350..2985327 100644 --- a/.github/workflows/clang-format.yml +++ b/.github/workflows/clang-format.yml @@ -3,7 +3,9 @@ name: clang-format on: pull_request: branches: [ "main" ] - + push: + branches-ignore: + - main jobs: format: if: github.actor != 'github-actions[bot]' @@ -14,6 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + ref: ${{ github.head_ref }} token: ${{ secrets.GITHUB_TOKEN }} - name: Install clang-format @@ -30,4 +33,4 @@ jobs: git config user.name "GitHub Actions" git commit -am "Apply clang-format" git push - fi \ No newline at end of file + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f80f75c --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,73 @@ +name: test + +on: + pull_request: + branches: [ "main" ] + +jobs: + build: + if: ${{ !endsWith(github.event.pull_request.title, 'no functional change') }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest] + build_type: [Debug, Release] + c_compiler: [gcc, clang, cl] + + include: + # Windows + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + - os: windows-latest + c_compiler: clang + cpp_compiler: clang++ + - os: windows-latest + c_compiler: gcc + cpp_compiler: g++ + + # Linux + - os: ubuntu-latest + c_compiler: gcc + cpp_compiler: g++ + - os: ubuntu-latest + c_compiler: clang + cpp_compiler: clang++ + + exclude: + - os: ubuntu-latest + c_compiler: cl + + steps: + - uses: actions/checkout@v4 + + - name: Set build dir + id: vars + shell: bash + run: echo "dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + + - name: Configure CMake + shell: bash + run: | + cmake -B "${{ steps.vars.outputs.dir }}" \ + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DSANITIZERS="address,undefined" \ + -DDART_TESTING_TIMEOUT=0 \ + -S "${{ github.workspace }}" + + - name: Build + 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 -j 4 --timeout 0 + else + ctest --verbose -j 4 --timeout 0 + fi diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/try_compile.yml similarity index 69% rename from .github/workflows/cmake-multi-platform.yml rename to .github/workflows/try_compile.yml index 0402087..730a31e 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/try_compile.yml @@ -1,18 +1,17 @@ -name: CMake on multiple platforms +name: Compilation on: - pull_request: - branches: [ "main" ] + pull_request: jobs: build: runs-on: ${{ matrix.os }} strategy: - fail-fast: false + fail-fast: true matrix: os: [ubuntu-latest, windows-latest] - build_type: [Debug, Release] + build_type: [Debug] c_compiler: [gcc, clang, cl] include: @@ -52,10 +51,6 @@ jobs: run: | CXX_FLAGS="" LINK_FLAGS="" - if [[ "${{ matrix.os }}" == "ubuntu-latest" && "${{ matrix.build_type }}" == "Debug" ]]; then - CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -g" - LINK_FLAGS="-fsanitize=address,undefined" - fi cmake -B "${{ steps.vars.outputs.dir }}" \ -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} \ @@ -67,13 +62,3 @@ jobs: - name: Build 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 -j 4 - else - ctest --verbose -j 4 - fi diff --git a/.gitignore b/.gitignore index 6ba49a4..27f2772 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,22 @@ -out/ -build/ -.vs/ +# Ignore everything +* + +# Allow source and essential files !*.cpp !*.h +!CMakeLists.txt !README.md !LICENSE -!CMakeLists.txt !.gitignore !.gitattributes -CMakePresets.json -CMakeSettings.json -*.s -.vscode -.cache -deps/* -!deps/ -* \ No newline at end of file +!.github/** +!.github/ +# Ignore build/editor junk +build/ +out/ +.vs/ +.vscode/ +.cache/ +cmake-*/ +# Ignore asm outputs +*.s \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f0e51e3..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "deps/doctest"] - path = deps/doctest - url = https://github.com/doctest/doctest.git diff --git a/CMakeLists.txt b/CMakeLists.txt index d5124e9..6236634 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(chesslib LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -option(ENABLE_COVERAGE "Enable coverage reporting" OFF) + # --- Compiler tuning --- if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") @@ -32,10 +32,6 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") add_compile_options(/constexpr:steps2000000000 /constexpr:depth1024 ${ARCH_FLAG}) endif() -if(ENABLE_COVERAGE) - set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) - set(CMAKE_INTERPROCEDURAL_OPTIMIZATION OFF) -endif() add_compile_definitions(GENERATE_AT_RUNTIME) if(CMAKE_BUILD_TYPE MATCHES "Debug") add_compile_definitions(_DEBUG) @@ -48,125 +44,32 @@ set(SOURCES add_library(chesslib STATIC ${SOURCES}) target_include_directories(chesslib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) # --- Enable CTest integration --- -enable_testing() -include(FetchContent) -FetchContent_Declare( - doctest - GIT_REPOSITORY https://github.com/doctest/doctest.git - GIT_TAG v2.4.12 -) -FetchContent_MakeAvailable(doctest) -# --- Test executable --- -add_executable(test_chess - tests.cpp -) -add_executable(NonImportantTests - non_core_tests.cpp -) -target_link_libraries(test_chess PRIVATE chesslib) -target_include_directories(test_chess PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${doctest_SOURCE_DIR}) -target_link_libraries(NonImportantTests PRIVATE chesslib) -target_include_directories(NonImportantTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${doctest_SOURCE_DIR}) -add_test(NAME test_core COMMAND test_chess) -add_test(NAME api_tests COMMAND NonImportantTests) -if(ENABLE_COVERAGE) - - if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - foreach(t chesslib test_chess NonImportantTests) - target_compile_options(${t} PRIVATE - --coverage - -O0 - ) - target_link_options(${t} PRIVATE - --coverage - ) - endforeach() - - find_program(GCOVR gcovr REQUIRED) - - add_custom_target(coverage - COMMAND ${CMAKE_COMMAND} -E make_directory coverage - COMMAND ${CMAKE_CTEST_COMMAND} - COMMAND ${GCOVR} - -r ${CMAKE_SOURCE_DIR} - --html - --html-details - -o coverage/index.html - --exclude-directories tests - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Generating GCC coverage report" - ) - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - foreach(t chesslib test_chess NonImportantTests) - target_compile_options(${t} PRIVATE - -fprofile-instr-generate - -fcoverage-mapping - -O0 - ) - target_link_options(${t} PRIVATE - -fprofile-instr-generate - ) - endforeach() - - if(WIN32) - - find_program(LLVM_PROFDATA llvm-profdata REQUIRED) - find_program(LLVM_COV llvm-cov REQUIRED) - - add_custom_target(coverage - COMMAND ${CMAKE_COMMAND} -E make_directory coverage - - COMMAND ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=coverage/coverage.profraw - CTEST_PARALLEL_LEVEL=1 - ${CMAKE_CTEST_COMMAND} - - COMMAND ${LLVM_PROFDATA} merge - -sparse - coverage/coverage.profraw - -o coverage/coverage.profdata - - COMMAND ${LLVM_COV} show - $ - $ - -instr-profile=coverage/coverage.profdata - -format=html - -output-dir=coverage - -ignore-filename-regex="tests|doctest" - -Xdemangler=llvm-cxxfilt - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Generating Clang coverage report (Windows)" - ) - else() - - find_program(LLVM_PROFDATA llvm-profdata REQUIRED) - find_program(LLVM_COV llvm-cov REQUIRED) - - add_custom_target(coverage - COMMAND ${CMAKE_COMMAND} -E make_directory coverage - - COMMAND ${CMAKE_COMMAND} -E env - LLVM_PROFILE_FILE=coverage/%m.profraw - ${CMAKE_CTEST_COMMAND} - - COMMAND ${LLVM_PROFDATA} merge - -sparse - coverage - -o coverage/coverage.profdata - - COMMAND ${LLVM_COV} show - $ - $ - -instr-profile=coverage/coverage.profdata - -format=html - -output-dir=coverage - -ignore-filename-regex="tests|doctest" - -Xdemangler=c++filt - - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMENT "Generating Clang coverage report" - ) +include(CTest) + +if(BUILD_TESTING) + include(FetchContent) + FetchContent_Declare( + doctest + GIT_REPOSITORY https://github.com/doctest/doctest.git + GIT_TAG v2.4.12 + ) + FetchContent_MakeAvailable(doctest) + + # --- Test executable --- + add_executable(test_chess tests.cpp) + add_executable(NonImportantTests non_core_tests.cpp) + + target_link_libraries(test_chess PRIVATE chesslib doctest::doctest) + target_link_libraries(NonImportantTests PRIVATE chesslib doctest::doctest) + + add_test(NAME test_core COMMAND test_chess) + add_test(NAME api_tests COMMAND NonImportantTests) + if (UNIX AND CMAKE_BUILD_TYPE MATCHES "Debug") + set(SANITIZERS "" CACHE STRING "sanitizers such as undefined,address") + + if (NOT "${SANITIZERS}" STREQUAL "") + add_compile_options(-fsanitize=${SANITIZERS} -fno-omit-frame-pointer) + add_link_options(-fsanitize=${SANITIZERS}) endif() endif() diff --git a/attacks.h b/attacks.h index de5e176..5027d84 100644 --- a/attacks.h +++ b/attacks.h @@ -144,9 +144,7 @@ extern const std::array BishopAttacks; * @param b * @return */ -template [[nodiscard]] static constexpr Bitboard shift(const Bitboard b) { - ASSUME(direction == NORTH || direction == EAST || direction == SOUTH || direction == WEST || direction == NORTH_EAST || - direction == SOUTH_EAST || direction == SOUTH_WEST || direction == NORTH_WEST); +[[nodiscard]] static constexpr Bitboard shift(const Bitboard b, Direction direction) { switch (direction) { case Direction::NORTH: return b << 8; @@ -166,8 +164,16 @@ template [[nodiscard]] static constexpr Bitboard shift(con return (b & ~MASK_FILE[7]) >> 7; default: UNREACHABLE(); + return 0; } } +/** + * @brief Shifts a bitboard in a given direction + * @tparam direction + * @param b + * @return + */ +template [[nodiscard]] static constexpr Bitboard shift(const Bitboard b) { return shift(b, direction); } /** * @brief diff --git a/fwd_decl.h b/fwd_decl.h index 7fbf593..87e64e2 100644 --- a/fwd_decl.h +++ b/fwd_decl.h @@ -25,4 +25,7 @@ using Movelist = ValueList; enum class PolyglotPiece : uint8_t; enum class EnginePiece : uint8_t; enum class ContiguousMappingPiece : uint8_t; -} // namespace chess \ No newline at end of file +using Position = _Position; +using Board = Position; + +} // namespace chess diff --git a/movegen.cpp b/movegen.cpp index 666d6b1..5bdfcc5 100644 --- a/movegen.cpp +++ b/movegen.cpp @@ -20,13 +20,13 @@ template struct alignas(64) SplatTable { 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) { +static inline 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) { +inline 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) @@ -37,7 +37,7 @@ Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { return moveList; } -template Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { +template inline 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)); @@ -45,9 +45,9 @@ template Move *splat_pawn_moves(Move *moveList, Bitboard to_b return moveList; } #else -template Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { +template inline Move *splat_pawn_moves(Move *moveList, Bitboard to_bb) { while (to_bb) { - Square to = (Square)pop_lsb(to_bb); + auto to = static_cast(pop_lsb(to_bb)); #if defined(_DEBUG) || !defined(NDEBUG) Square from = to - offset; assert(from >= 0 && from < 64); // sanity check @@ -59,9 +59,9 @@ template Move *splat_pawn_moves(Move *moveList, Bitboard to_b return moveList; } -Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { +inline Move *splat_moves(Move *moveList, Square from, Bitboard to_bb) { while (to_bb) - *moveList++ = Move(from, (Square)pop_lsb(to_bb)); + *moveList++ = Move(from, static_cast(pop_lsb(to_bb))); return moveList; } #endif @@ -79,7 +79,7 @@ template void movegen::genEP(const _Position &pos if (!candidates) return; - const Square ep_pawn_sq = static_cast(ep_sq - pawn_push(c)); + const Square ep_pawn_sq = ep_sq - pawn_push(c); const Bitboard ep_mask = (1ULL << ep_pawn_sq) | (1ULL << ep_sq); // ASSUME(popcount(candidates) <= 32); @@ -131,14 +131,9 @@ 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); @@ -237,7 +232,7 @@ void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboar const Bitboard myOcc = pos.occ(c); // Remove king from board when computing enemy attacks - const Bitboard occWithoutKing = occAll ^ (1ULL << kingSq); + const Bitboard occWithoutKing = occAll ^ 1ULL << kingSq; Bitboard enemyAttacks = 0ULL; // Sliding pieces @@ -278,12 +273,12 @@ void movegen::genKingMoves(const _Position &pos, Movelist &out, Bitboar Bitboard OOO_SAFE = between(kingSq, castling_king_square(c, false)); Square rookKing = pos.getCastlingMetadata(c).rook_start_ks, rookQueen = pos.getCastlingMetadata(c).rook_start_qs; - if ((pos.castlingRights() & kingRights) && - !((occupancy & OO_EMPTY) || (enemy_attacks & OO_SAFE) || (_pin_mask & (1ULL << rookKing)))) { + if (pos.castlingRights() & kingRights && + !(occupancy & OO_EMPTY || enemy_attacks & OO_SAFE || _pin_mask & 1ULL << rookKing)) { out.push_back(Move::make(kingSq, rookKing)); } - if ((pos.castlingRights() & queenRights) && - !((occupancy & OOO_EMPTY) || (enemy_attacks & OOO_SAFE) || (_pin_mask & (1ULL << rookQueen)))) { + if (pos.castlingRights() & queenRights && + !(occupancy & OOO_EMPTY || enemy_attacks & OOO_SAFE || _pin_mask & 1ULL << rookQueen)) { out.push_back(Move::make(kingSq, rookQueen)); } } @@ -312,9 +307,9 @@ void movegen::genSlidingMoves( // Bitboard blockers = occ() ^ from_bb; // remove piece temporarily auto func = attacks::queen; - if constexpr (pt == PieceType::BISHOP) + if constexpr (pt == BISHOP) func = attacks::bishop; - else if constexpr (pt == PieceType::ROOK) + else if constexpr (pt == ROOK) func = attacks::rook; func = rook_hit ? attacks::rook : bishop_hit ? attacks::bishop : func; Bitboard filtered_pin = pin_mask & filter_list; @@ -325,16 +320,6 @@ void movegen::genSlidingMoves( 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 &); \ diff --git a/movegen.h b/movegen.h index 6e4eb2b..df2eb41 100644 --- a/movegen.h +++ b/movegen.h @@ -1,10 +1,6 @@ #pragma once #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::_chess namespace chess::movegen { template void genEP(const _Position &, Movelist &); @@ -17,5 +13,8 @@ template void genKingMoves(cons template void genSlidingMoves(const _Position &, Movelist &, Bitboard, Bitboard, Bitboard); extern std::array, 65> SQUARES_BETWEEN_BB; +/* + * [(file(sq1), rank(sq1)), (file(sq2), rank(sq2))] -> bitboard of squares between sq1 and sq2, excluding sq1 and sq2 + */ [[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 6a5cf7f..36e1d27 100644 --- a/moves_io.cpp +++ b/moves_io.cpp @@ -34,24 +34,24 @@ std::string moveToUci(Move mv, bool chess960) { move += squareToString(mv.from_sq()); // To square, special: castlings switch (mv.type_of()) { - case CASTLING: - switch (mv.to_sq()) { - case SQ_H1: - move += "g1"; // White kingside castling - break; - case SQ_A1: - move += "c1"; // white queenside castling - break; - case SQ_H8: - move += "g8"; // black kingside castling - break; - case SQ_A8: - move += "c8"; // black queenside castling - break; - default: - if (chess960) - move += squareToString(mv.to_sq()); - else { + case CASTLING: { + if (chess960) + move += squareToString(mv.to_sq()); + else { + switch (mv.to_sq()) { + case SQ_H1: + move += "g1"; // White kingside castling + break; + case SQ_A1: + move += "c1"; // white queenside castling + break; + case SQ_H8: + move += "g8"; // black kingside castling + break; + case SQ_A8: + move += "c8"; // black queenside castling + break; + default: #if defined(_DEBUG) || !defined(NDEBUG) assert(false && "this isn't chess960"); #else @@ -60,7 +60,7 @@ std::string moveToUci(Move mv, bool chess960) { #endif } } - break; + } break; case PROMOTION: move += squareToString(mv.to_sq()); move += PieceTypeChar[mv.promotion_type()]; @@ -84,36 +84,44 @@ template Move uciToMove(const _Position &pos, std THROW_IF_EXCEPTIONS_ON(IllegalMoveException("source !in [a1, h8], target !in [a1, h8]")); return Move::NO_MOVE; } + auto move = (uci.length() == 4) ? Move::make(source, target) : Move::NO_MOVE; auto pt = piece_of(pos.at(source)); if (pt == NO_PIECE_TYPE) { THROW_IF_EXCEPTIONS_ON(IllegalMoveException("source need to be a existing piece, got nothing")); return Move::NO_MOVE; } - if (!pos.chess960() && pt == KING && square_distance(target, source) == 2) { + // castling in chess960 + if (pos.chess960() && pt == PieceType::KING && pos.template at(target) == PieceType::ROOK && + pos.template at(target) == pos.sideToMove()) { + move = Move::make(source, target); + } + + // convert to king captures rook + // in chess960 the move should be sent as king captures rook already! + else if (!pos.chess960() && pt == PieceType::KING && square_distance(target, source) == 2) { target = make_sq(target > source ? File::FILE_H : File::FILE_A, rank_of(source)); - return Move::make(source, target); + move = Move::make(source, target); } // en passant - if (pt == PAWN && target == pos.enpassantSq()) { - return Move::make(source, target); + else if (pt == PAWN && target == pos.enpassantSq()) { + move = Move::make(source, target); } // promotion - if (pt == PAWN && uci.length() == 5 && (rank_of(target) == (pos.sideToMove() == WHITE ? RANK_8 : RANK_1))) { + else if (pt == PAWN && uci.length() == 5 && (rank_of(target) == (pos.sideToMove() == WHITE ? RANK_8 : RANK_1))) { auto promotion = parse_pt(uci[4]); if (promotion == NO_PIECE_TYPE || promotion == KING || promotion == PAWN) { #if defined(_DEBUG) || !defined(NDEBUG) - assert(false && "promotions: NRBQ"); + assert(false && "promotions: [NRBQ]"); #else - THROW_IF_EXCEPTIONS_ON(IllegalMoveException("promotions: NRBQ")); + THROW_IF_EXCEPTIONS_ON(IllegalMoveException("promotions: [NRBQ]")); #endif return Move::NO_MOVE; } - return Move::make(source, target, promotion); + move = Move::make(source, target, promotion); } - auto move = (uci.length() == 4) ? Move::make(source, target) : Move::NO_MOVE; Movelist moves; pos.legals(moves); auto it = std::find(moves.begin(), moves.end(), move); @@ -354,9 +362,8 @@ template Move parseSan(const _Position &pos, std: } } template std::string moveToSan(const _Position &pos, Move move, bool long_, bool suffix) { - const char FILE_NAMES[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; + constexpr char FILE_NAMES[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; - const char RANK_NAMES[] = { '1', '2', '3', '4', '5', '6', '7', '8' }; constexpr char PieceTypeChar[] = " pnbrqk"; // Null move. (or none) if (!move.is_ok()) { @@ -377,7 +384,7 @@ template std::string moveToSan(const _Position &p } } if (piece_type == NO_PIECE_TYPE) { - THROW_IF_EXCEPTIONS_ON(IllegalMoveException("san() and lan() expect move to be pseudo-legal or null, but got " + + THROW_IF_EXCEPTIONS_ON(IllegalMoveException("moveToSan() expect move to be pseudo-legal or null, but got " + moveToUci(move) + " in " + pos.fen())); return ""; } @@ -406,6 +413,7 @@ template std::string moveToSan(const _Position &p // Disambiguate only if there are other candidates that can move to the same square. if (others) { + const char RANK_NAMES[] = { '1', '2', '3', '4', '5', '6', '7', '8' }; bool need_file = false, need_rank = false; for (Square sq = SQ_A1; sq < SQ_NONE; ++sq) { if (others & (1ULL << sq)) { @@ -446,7 +454,7 @@ template std::string moveToSan(const _Position &p return san; _Position p = pos; p.doMove(move); - bool _check = p.is_check(); + const bool _check = p.is_check(); Movelist moves; p.legals(moves); // Checkmate: no legal moves and in check; Stalemate: no legal moves and not in check diff --git a/moves_io.h b/moves_io.h index e0b2435..5dccb74 100644 --- a/moves_io.h +++ b/moves_io.h @@ -1,13 +1,10 @@ #pragma once +#include "fwd_decl.h" #include #include #include #include -namespace chess { -class Move; -enum Square : int8_t; -template class _Position; -namespace uci { +namespace chess::uci { std::string moveToUci(Move move, bool chess960 = false); std::string squareToString(Square sq); @@ -32,5 +29,4 @@ template Move parseSan(const _Position &pos, std::string_view uci, bool remove_illegals = false); template std::string moveToSan(const _Position &pos, Move move, bool long_ = false, bool suffix = true); -} // namespace uci -} // namespace chess +} // namespace chess::uci diff --git a/non_core_tests.cpp b/non_core_tests.cpp index 68f4669..b9536ac 100644 --- a/non_core_tests.cpp +++ b/non_core_tests.cpp @@ -843,12 +843,12 @@ TEST_SUITE("SAN Parser") { Move m = Move::make(Square::SQ_C4, Square::SQ_E5); - // CHECK(uci::moveToSan(b, m) == "Nxe5"); + CHECK(uci::moveToSan(b, m) == "Nxe5"); REQUIRE(uci::parseSan(b, "Nxe5") == m); } TEST_CASE("Parse No Move") { - Position b = Position(); + Position b; REQUIRE(uci::parseSan(b, "") == Move::NO_MOVE); } @@ -891,6 +891,13 @@ TEST_SUITE("SAN Parser") { REQUIRE(uci::parseSan(b, "O-O+") == m); } } + +TEST_SUITE("misc tests") { + TEST_CASE("FEN reconstruction") { + Position pos(Position::START_CHESS960_FEN, true); + REQUIRE(pos.fen() == Position::START_CHESS960_FEN); + } +} int main(int argc, char **argv) { doctest::Context ctx; ctx.setOption("success", true); diff --git a/position.cpp b/position.cpp index eccf198..c8bc9ec 100644 --- a/position.cpp +++ b/position.cpp @@ -1,8 +1,12 @@ #include "position.h" #include "movegen.h" #include "moves_io.h" -#include "position.h" +#include "printers.h" +#include "zobrist.h" +#include +#include #include +#include #ifndef GENERATE_AT_RUNTIME #define _POSSIBLY_CONSTEXPR constexpr #else @@ -41,6 +45,7 @@ template template void _Position template void _Position template void _Position template void _Position= 4) { - auto *stp = &history[history.size_ - 1]; + auto *stp = &history[history.size() - 1]; stp -= 1; for (int i = 4; i <= end; i += 2) { stp -= 2; if (stp->hash == hash()) { - current_state.repetition = stp->repetition ? -i : i; - break; + current_state.repetition++; } } } @@ -196,6 +201,7 @@ template void _Position::setFEN(const s current_state = HistoryEntry(); history.clear(); _chess960 = chess960; + std::fill(std::begin(pieces_list), std::end(pieces_list), PieceC::NO_PIECE); std::istringstream ss(str); std::string board_fen, active_color, castling, enpassant; int halfmove = 0, fullmove = 1; @@ -276,7 +282,6 @@ template void _Position::setFEN(const s } } - // Final assertions after parsing INVALID_ARG_IF(file_count != 8, "Last rank must have 8 squares"); INVALID_ARG_IF(rank_count != 7, "FEN must contain exactly 8 ranks"); } @@ -293,64 +298,125 @@ template void _Position::setFEN(const s // 3. Castling rights current_state.castlingRights = NO_CASTLING; - for (char c : castling) { - switch (c) { - case 'K': - INVALID_ARG_IF(chess960, "Chess960 needs specific file"); - current_state.castlingRights |= WHITE_OO; - current_state.castlingMetadata[WHITE].king_start = kingSq(WHITE); - current_state.castlingMetadata[WHITE].rook_start_ks = SQ_H1; - break; - case 'Q': - INVALID_ARG_IF(chess960, "Chess960 needs specific file"); - current_state.castlingRights |= WHITE_OOO; - current_state.castlingMetadata[WHITE].king_start = kingSq(WHITE); - current_state.castlingMetadata[WHITE].rook_start_qs = SQ_A1; - break; - case 'k': - INVALID_ARG_IF(chess960, "Chess960 needs specific file"); - current_state.castlingRights |= BLACK_OO; - current_state.castlingMetadata[BLACK].king_start = kingSq(BLACK); - current_state.castlingMetadata[BLACK].rook_start_ks = SQ_H8; - break; - case 'q': - INVALID_ARG_IF(chess960, "Chess960 needs specific file"); - current_state.castlingRights |= BLACK_OOO; - current_state.castlingMetadata[BLACK].king_start = kingSq(BLACK); - current_state.castlingMetadata[BLACK].rook_start_qs = SQ_A8; - break; - // some optional chess960? maybe not. - case '-': - break; - default: { - INVALID_ARG_IF(!chess960, "Invalid castling right for non-Chess960"); - if (c >= 'A' && c <= 'H') // white-castling - { - const auto king_start = kingSq(WHITE); - const auto rook_start = make_sq(RANK_1, static_cast(c - 'A')); - - CastlingRights cr = (file_of(rook_start) > file_of(king_start) ? WHITE_OO : WHITE_OOO); - current_state.castlingRights |= cr; - current_state.castlingMetadata[WHITE].king_start = kingSq(WHITE); - if (cr & KING_SIDE) - current_state.castlingMetadata[WHITE].rook_start_ks = rook_start; - else - current_state.castlingMetadata[WHITE].rook_start_qs = rook_start; - } else if (c >= 'a' && c <= 'h') // white-castling - { - const auto king_start = kingSq(BLACK); - const auto rook_start = make_sq(RANK_8, static_cast(c - 'a')); - - CastlingRights cr = (file_of(rook_start) > file_of(king_start) ? BLACK_OO : BLACK_OOO); - current_state.castlingRights |= cr; - current_state.castlingMetadata[BLACK].king_start = king_start; - if (cr & KING_SIDE) - current_state.castlingMetadata[BLACK].rook_start_ks = rook_start; - else - current_state.castlingMetadata[BLACK].rook_start_qs = rook_start; - } - break; - } + if (castling != "-") { + for (Color color : { WHITE, BLACK }) { + auto findKing = [&]() -> Square { + auto it = std::find_if(std::begin(pieces_list), std::end(pieces_list), [&](PieceC p) { + return p == make_piece(KING, color); + }); + INVALID_ARG_IF(it == std::end(pieces_list), "No king found for castling"); + return static_cast(it - pieces_list); + }; + + auto findRookQS = [&](Square king_sq, Color color) -> Square { + Rank r = rank_of(king_sq); + for (int f = file_of(king_sq) - 1; f >= FILE_A; --f) { + Square sq = make_sq(r, static_cast(f)); + PieceC p = pieces_list[sq]; + if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color) + return sq; + } + return SQ_NONE; + }; + + auto findRookKS = [&](Square king_sq, Color color) -> Square { + Rank r = rank_of(king_sq); + for (int f = file_of(king_sq) + 1; f <= FILE_H; ++f) { + Square sq = make_sq(r, static_cast(f)); + PieceC p = pieces_list[sq]; + if (p != PieceC::NO_PIECE && type_of(p) == ROOK && color_of(p) == color) + return sq; + } + return SQ_NONE; + }; + auto apply = [&](char c) { + Square king_sq = findKing(); + if (king_sq == SQ_NONE) + return; + + Square rook_ks = findRookKS(king_sq, color); + Square rook_qs = findRookQS(king_sq, color); + + auto setKS = [&](Square rook_sq) { + INVALID_ARG_IF(rook_sq == SQ_NONE, "kingside rook not found"); + INVALID_ARG_IF(rank_of(king_sq) != rank_of(rook_sq), "kingside rook not on same rank"); + + if (color == WHITE) { + current_state.castlingRights |= WHITE_OO; + current_state.castlingMetadata[WHITE].king_start = king_sq; + current_state.castlingMetadata[WHITE].rook_start_ks = rook_sq; + } else { + current_state.castlingRights |= BLACK_OO; + current_state.castlingMetadata[BLACK].king_start = king_sq; + current_state.castlingMetadata[BLACK].rook_start_ks = rook_sq; + } + }; + + auto setQS = [&](Square rook_sq) { + INVALID_ARG_IF(rook_sq == SQ_NONE, "queenside rook not found"); + INVALID_ARG_IF(rank_of(king_sq) != rank_of(rook_sq), "queenside rook not on same rank"); + + if (color == WHITE) { + current_state.castlingRights |= WHITE_OOO; + current_state.castlingMetadata[WHITE].king_start = king_sq; + current_state.castlingMetadata[WHITE].rook_start_qs = rook_sq; + } else { + current_state.castlingRights |= BLACK_OOO; + current_state.castlingMetadata[BLACK].king_start = king_sq; + current_state.castlingMetadata[BLACK].rook_start_qs = rook_sq; + } + }; + + if (c == 'K' && color == WHITE) { + if (rook_ks == SQ_NONE) + return; + if (rank_of(king_sq) != rank_of(rook_ks)) + return; + setKS(rook_ks); + } else if (c == 'Q' && color == WHITE) { + if (rook_qs == SQ_NONE) + return; + if (rank_of(king_sq) != rank_of(rook_qs)) + return; + setQS(rook_qs); + } else if (c == 'k' && color == BLACK) { + if (rook_ks == SQ_NONE) + return; + if (rank_of(king_sq) != rank_of(rook_ks)) + return; + setKS(rook_ks); + } else if (c == 'q' && color == BLACK) { + if (rook_qs == SQ_NONE) + return; + if (rank_of(king_sq) != rank_of(rook_qs)) + return; + setQS(rook_qs); + } + + else if (c >= 'A' && c <= 'H' && color == WHITE) { + File f = static_cast(c - 'A'); + Square rook_sq = make_sq(RANK_1, f); + + PieceC p = pieces_list[rook_sq]; + INVALID_ARG_IF(p == PieceC::NO_PIECE || type_of(p) != ROOK || color_of(p) != WHITE, + "Invalid white Chess960 rook"); + + (f > file_of(king_sq)) ? setKS(rook_sq) : setQS(rook_sq); + } else if (c >= 'a' && c <= 'h' && color == BLACK) { + File f = static_cast(c - 'a'); + Square rook_sq = make_sq(RANK_8, f); + + PieceC p = pieces_list[rook_sq]; + INVALID_ARG_IF(p == PieceC::NO_PIECE || type_of(p) != ROOK || color_of(p) != BLACK, + "Invalid black Chess960 rook"); + + (f > file_of(king_sq)) ? setKS(rook_sq) : setQS(rook_sq); + } + + // ignore '-' + }; + + std::for_each(castling.begin(), castling.end(), apply); } } current_state.hash ^= zobrist::RandomCastle[current_state.castlingRights]; @@ -422,14 +488,14 @@ template std::string _Position::fen() c emptyCount++; } else { if (emptyCount > 0) { - ss << std::to_string(emptyCount); + ss << emptyCount; emptyCount = 0; } ss << piece; } } if (emptyCount > 0) - ss << std::to_string(emptyCount); + ss << emptyCount; if (rank > 0) ss << '/'; } @@ -530,8 +596,7 @@ template template bool _Position(PAWN, ~stm)) + if (piece_at(ep_sq - pawn_push(stm)) != make_piece(PAWN, ~stm)) return false; if (!(attacks::pawn(~stm, ep_sq) & pieces(stm))) @@ -540,35 +605,39 @@ template template bool _Position 2) return false; + if (_valid_ep_square() != ep_square()) + return false; + if (clean_castling_rights() != castlingRights()) + return false; return true; } -template CheckType _Position::givesCheck(Move m) const { - const static auto getSniper = [](const _Position *p, Square ksq, Bitboard oc) { +template CheckType _Position::givesCheck(Move move) const { + const static auto getSniper = [](const _Position *p, const Square ksq, Bitboard oc) { const auto us_occ = p->us(p->sideToMove()); const auto bishop = attacks::bishop(ksq, oc) & p->pieces(PieceType::BISHOP, PieceType::QUEEN) & us_occ; const auto rook = attacks::rook(ksq, oc) & p->pieces(PieceType::ROOK, PieceType::QUEEN) & us_occ; return (bishop | rook); }; - assert(color_of(at(m.from())) == sideToMove()); + assert(color_of(at(move.from())) == sideToMove()); - const Square from = m.from(); - const Square to = m.to(); + const Square from = move.from(); + const Square to = move.to(); const Square ksq = kingSq(~sideToMove()); const Bitboard toBB = 1ULL << (to); const PieceType pt = piece_of(at(from)); Bitboard fromKing = 0ull; - if (pt == PieceType::PAWN) { + if (pt == PAWN) { fromKing = attacks::pawn(~side_to_move(), ksq); - } else if (pt == PieceType::KNIGHT) { + } else if (pt == KNIGHT) { fromKing = attacks::knight(ksq); - } else if (pt == PieceType::BISHOP) { + } else if (pt == BISHOP) { fromKing = attacks::bishop(ksq, occ()); - } else if (pt == PieceType::ROOK) { + } else if (pt == ROOK) { fromKing = attacks::rook(ksq, occ()); - } else if (pt == PieceType::QUEEN) { + } else if (pt == QUEEN) { fromKing = attacks::queen(ksq, occ()); } @@ -579,22 +648,20 @@ template CheckType _Position::givesChec const Bitboard fromBB = 1ULL << (from); const Bitboard oc = occ() ^ fromBB; - Bitboard sniper = getSniper(this, ksq, oc); - - if (sniper) { - Square sq = (Square)pop_lsb(sniper); - return (!(movegen::between(ksq, sq) & toBB) || m.typeOf() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK - : CheckType::NO_CHECK; + if (Bitboard sniper = getSniper(this, ksq, oc)) { + const auto sq = static_cast(pop_lsb(sniper)); + return (!(movegen::between(ksq, sq) & toBB) || move.typeOf() == Move::CASTLING) ? CheckType::DISCOVERY_CHECK + : CheckType::NO_CHECK; } - switch (m.typeOf()) { + switch (move.typeOf()) { case Move::NORMAL: return CheckType::NO_CHECK; case Move::PROMOTION: { Bitboard attacks = 0ull; - switch (m.promotionType()) { + switch (move.promotionType()) { case KNIGHT: attacks = attacks::knight(to); break; @@ -620,7 +687,7 @@ template CheckType _Position::givesChec } case Move::CASTLING: { - Square rookTo = relative_square(side_to_move(), to > from ? SQ_F1 : SQ_D1); + const Square rookTo = relative_square(side_to_move(), to > from ? SQ_F1 : SQ_D1); return (attacks::rook(ksq, occ()) & (1ULL << rookTo)) ? CheckType::DISCOVERY_CHECK : CheckType::NO_CHECK; } } @@ -629,7 +696,7 @@ template CheckType _Position::givesChec return CheckType::NO_CHECK; // Prevent a compiler warning } template void _Position::refresh_attacks() { - Color c = sideToMove(); + const Color c = sideToMove(); Square king_sq = kingSq(c); _bishop_pin = pinMask(c, king_sq); @@ -638,17 +705,14 @@ template void _Position::refresh_attack _checkers = attackers(~c, king_sq); - int num_checks = popcount(_checkers); - - switch (num_checks) { + switch (popcount(_checkers)) { case 0: _check_mask = ~0ULL; // no checks, full mask break; case 1: { - Square sq = static_cast(lsb(_checkers)); - Bitboard mask = (1ULL << sq) | movegen::between(king_sq, sq); - _check_mask = mask; + auto sq = static_cast(lsb(_checkers)); + _check_mask = 1ULL << sq | movegen::between(king_sq, sq); break; } @@ -660,20 +724,20 @@ template void _Position::refresh_attack template uint64_t _Position::zobrist() const { uint64_t hash = 0; for (int sq = 0; sq < 64; ++sq) { - auto p = piece_on((Square)sq); - hash ^= (p == PieceC::NO_PIECE) ? 0 : zobrist::RandomPiece[enum_idx()][(int)p][sq]; + auto p = piece_on(static_cast(sq)); + hash ^= (p == PieceC::NO_PIECE) ? 0 : zobrist::RandomPiece[enum_idx()][static_cast(p)][sq]; } hash ^= (current_state.turn == WHITE) ? zobrist::RandomTurn : 0; hash ^= zobrist::RandomCastle[current_state.castlingRights]; auto ep_sq = current_state.enPassant; if (ep_sq == SQ_NONE) return hash; - if (ep_sq != SQ_NONE) { - File f = file_of(ep_sq); + { + const File f = file_of(ep_sq); Bitboard ep_mask = (1ULL << ep_sq); // Shift to the rank where the opposing pawn sits - Color stm = sideToMove(); + const Color stm = sideToMove(); // Color them = ~stm; ep_mask = (stm == WHITE) ? (ep_mask >> 8) : (ep_mask << 8); @@ -687,19 +751,20 @@ template uint64_t _Position::zobrist() } template Move _Position::parse_uci(std::string uci) const { - return chess::uci::uciToMove(*this, uci); + return uci::uciToMove(*this, uci); } template Move _Position::push_uci(std::string uci) { - auto mv = parse_uci(uci); + const auto mv = parse_uci(std::move(uci)); doMove(mv); return mv; } template Square _Position::_valid_ep_square() const { if (ep_square() == SQ_NONE) return SQ_NONE; - Rank ep_rank = sideToMove() == WHITE ? RANK_6 : RANK_3; - Bitboard mask = 1ULL << ep_square(); + Rank ep_rank; + ep_rank = sideToMove() == WHITE ? RANK_6 : RANK_3; + const Bitboard mask = 1ULL << ep_square(); Bitboard pawn_mask = mask << 8; Bitboard org_pawn_mask = mask >> 8; if (sideToMove() == BLACK) @@ -727,35 +792,32 @@ template bool _Position::is_insufficien // only bishop + knight, can't mate if (count == 3) { - if (pieces(PieceType::BISHOP, Color::WHITE) || pieces(PieceType::BISHOP, Color::BLACK)) + if (pieces(BISHOP, WHITE) || pieces(BISHOP, BLACK)) return true; - if (pieces(PieceType::KNIGHT, Color::WHITE) || pieces(PieceType::KNIGHT, Color::BLACK)) + if (pieces(KNIGHT, WHITE) || pieces(KNIGHT, BLACK)) return true; } // same-colored bishops, can't mate if (count == 4) { // bishops on same color (one per side) - if (pieces(PieceType::BISHOP, Color::WHITE) && pieces(PieceType::BISHOP, Color::BLACK)) { - Square w = (Square)lsb(pieces(PieceType::BISHOP, Color::WHITE)); - Square b = (Square)lsb(pieces(PieceType::BISHOP, Color::BLACK)); - if (((9 * (w ^ b)) & 8) == 0) + if (pieces(BISHOP, WHITE) && pieces(BISHOP, BLACK)) { + if (auto w = static_cast(lsb(pieces(BISHOP, WHITE))), b = static_cast(lsb(pieces(BISHOP, BLACK))); + ((9 * (w ^ b)) & 8) == 0) return true; } // one side with two bishops on same color - auto white_bishops = pieces(PieceType::BISHOP, Color::WHITE); - auto black_bishops = pieces(PieceType::BISHOP, Color::BLACK); + auto white_bishops = pieces(BISHOP, WHITE); + auto black_bishops = pieces(BISHOP, BLACK); if (popcount(white_bishops) == 2) { - Square b1 = (Square)lsb(white_bishops); - Square b2 = (Square)msb(white_bishops); - if (((9 * (b1 ^ b2)) & 8) == 0) + if (auto b1 = static_cast(lsb(white_bishops)), b2 = static_cast(msb(white_bishops)); + ((9 * (b1 ^ b2)) & 8) == 0) return true; } else if (popcount(black_bishops) == 2) { - Square b1 = (Square)lsb(black_bishops); - Square b2 = (Square)msb(black_bishops); - if (((9 * (b1 ^ b2)) & 8) == 0) + if (auto b1 = static_cast(lsb(black_bishops)), b2 = static_cast(msb(black_bishops)); + ((9 * (b1 ^ b2)) & 8) == 0) return true; } } @@ -763,12 +825,10 @@ template bool _Position::is_insufficien return false; } template CastlingRights _Position::clean_castling_rights() const { - constexpr Bitboard cr_WOO = 1ULL << SQ_H1; - constexpr Bitboard cr_WOOO = 1ULL << SQ_A1; - constexpr Bitboard cr_BOO = 1ULL << SQ_H8; - constexpr Bitboard cr_BOOO = 1ULL << SQ_A8; - if (history.size()) - return castlingRights(); + const Bitboard cr_BOO = current_state.castlingMetadata[BLACK].rook_start_ks; + const Bitboard cr_BOOO = current_state.castlingMetadata[BLACK].rook_start_qs; + const Bitboard cr_WOO = current_state.castlingMetadata[WHITE].rook_start_ks; + const Bitboard cr_WOOO = current_state.castlingMetadata[WHITE].rook_start_qs; Bitboard castling = 0; // mappings castling |= (castlingRights() & WHITE_OO) ? cr_WOO : 0; @@ -783,9 +843,9 @@ template CastlingRights _Position::clea white_castling &= (cr_WOO | cr_WOOO); black_castling &= (cr_BOO | cr_BOOO); // king exists in e1/e8 depending on color - if (!(occ(WHITE) & pieces(KING) & (1ULL << SQ_E1))) + if (!(occ(WHITE) & pieces(KING) & (1ULL << current_state.castlingMetadata[WHITE].king_start))) white_castling = 0; - if (!(occ(BLACK) & pieces(KING) & (1ULL << SQ_E8))) + if (!(occ(BLACK) & pieces(KING) & (1ULL << current_state.castlingMetadata[BLACK].king_start))) black_castling = 0; castling = white_castling | black_castling; // Re-map diff --git a/position.h b/position.h index 841d8a2..482fd33 100644 --- a/position.h +++ b/position.h @@ -2,14 +2,11 @@ #include "attacks.h" #include "bitboard.h" #include "movegen.h" -#include "printers.h" #include "types.h" #include "zobrist.h" -#include -#include +#include #include -#include -#include +#include namespace chess { template struct alignas(64) HistoryEntry { @@ -38,97 +35,6 @@ template struct alignas(64) HistoryEntry { // implementation-specific implementations goes here }; -template class HeapAllocatedValueList { - private: - constexpr static int ALIGNMENT = 64; - - public: - using size_type = std::size_t; - inline HeapAllocatedValueList() { - values_ = reinterpret_cast(::operator new(MaxSize * sizeof(T), std::align_val_t{ ALIGNMENT })); - assert(values_); - } - inline ~HeapAllocatedValueList() { ::operator delete(values_, std::align_val_t{ ALIGNMENT }); } - - HeapAllocatedValueList(const HeapAllocatedValueList &other) : size_(other.size_) { - if (values_) - ::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) { - ::operator delete(values_, std::align_val_t{ ALIGNMENT }); - // Allocate new memory and copy - 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); - - // Free old memory - free(values_); - - // Assign new data - values_ = new_values; - size_ = other.size_; - } - return *this; - } - - HeapAllocatedValueList(HeapAllocatedValueList &&other) noexcept : size_(other.size_) { - ::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) { - ::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_); - } - return *this; - } - inline size_type size() const { return size_; } - - inline void push_back(const T &value) { - assert(size_ < MaxSize); - - values_[size_++] = value; - } - inline void clear() { size_ = 0; } - inline const T &pop() { - assert(size_ > 0); - return values_[--size_]; // always safe due to mask - } - - inline void pop_back() { size_ -= (size_ > 0) ? 1 : 0; } - - inline const T &front() const { return (size_ > 0) ? values_[0] : T{}; } - - inline T &operator[](int index) { - assert(index < MaxSize); // relax the conditions, it's BRANCHLESS so forgive - size_type i = static_cast(index); - return values_[i]; - } - - inline T &operator[](int index) const { - assert(index < MaxSize); // relax the conditions, it's BRANCHLESS so forgive - size_type i = static_cast(index); - return values_[i]; - } - inline const T *begin() const { return values_; } - inline T *data() { return values_; } - inline const T *end() const { return values_ + size_; } - size_type size_ = 0; - - private: - T *values_ = nullptr; -}; - enum class CheckType { NO_CHECK, DIRECT_CHECK, DISCOVERY_CHECK }; enum class MoveGenType : uint16_t { NONE = 0, @@ -163,12 +69,12 @@ template current_state; // Move history stack - HeapAllocatedValueList, 6144> history; - Bitboard _rook_pin; - Bitboard _bishop_pin; - Bitboard _checkers; - Bitboard _check_mask; - Bitboard _pin_mask; + std::vector> history; + Bitboard _rook_pin{}; + Bitboard _bishop_pin{}; + Bitboard _checkers{}; + Bitboard _check_mask{}; + Bitboard _pin_mask{}; PieceC pieces_list[SQUARE_NB + 1] = { PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, PieceC::NO_PIECE, @@ -182,13 +88,15 @@ template inline void legals(Movelist &out) const { + template void legals(Movelist &out) const { - constexpr uint16_t raw = static_cast(type); + constexpr auto raw = static_cast(type); constexpr uint16_t pieceBits = raw & static_cast(MoveGenType::PIECE_MASK); constexpr uint16_t modeBits = raw & (static_cast(MoveGenType::CAPTURE) | static_cast(MoveGenType::QUIET)); @@ -243,10 +151,7 @@ template inline void legals(Movelist &out) const { - const Color stm = sideToMove(); // Cache it - ASSUME(stm == WHITE || stm == BLACK); // Now clearly no side effects - - switch (stm) { + switch (sideToMove()) { case WHITE: legals(out); return; @@ -260,7 +165,7 @@ template void doMove(const Move &move); template inline auto undoMove() -> std::conditional_t &, void> { - assert(history.size_ > 0 && "undoMove called with empty history"); + assert(!history.empty() && "undoMove called with empty history"); // Restore previous state from history assert(current_state.mv.is_ok() && "Corrupted history entry"); @@ -268,10 +173,12 @@ template inline Bitboard pieces(Color c) const { - ASSUME(c == WHITE || c == BLACK); + [[nodiscard]] inline Bitboard pieces() const { return occ(); } + template [[nodiscard]] inline Bitboard pieces(Color c) const { + assert(c != COLOR_NB); 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 { - ASSUME(c == WHITE || c == BLACK); + template [[nodiscard]] inline Bitboard pieces(PieceType pt) const { + static_assert(c != COLOR_NB); if (pt == PIECE_TYPE_NB || pt == ALL_PIECES) return occ(c); return current_state.pieces[pt] & current_state.occ[c]; } - template inline Bitboard pieces() const { - ASSUME(c == WHITE || c == BLACK); + template [[nodiscard]] inline Bitboard pieces() const { + static_assert(c != COLOR_NB); if constexpr (pt == PIECE_TYPE_NB || pt == ALL_PIECES) return occ(c); return current_state.pieces[pt] & current_state.occ[c]; } - inline Bitboard pieces(PieceType pt, Color c) const { - ASSUME(c == WHITE || c == BLACK); + [[nodiscard]] inline Bitboard pieces(PieceType pt, Color c) const { + assert(c != COLOR_NB); // still branchless switch (pt) { case PIECE_TYPE_NB: @@ -313,8 +220,8 @@ template (pt)) { case PIECE_TYPE_NB: case ALL_PIECES: return occ(); @@ -443,11 +350,11 @@ 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; } - inline _Position(std::string fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", bool chess960 = false) { + [[nodiscard]] inline Bitboard us(Color c) const { return occ(c); } + [[nodiscard]] inline Color sideToMove() const { return current_state.turn; } + [[nodiscard]] inline uint64_t hash() const { return current_state.hash; } + [[nodiscard]] inline uint64_t key() const { return current_state.hash; } + [[nodiscard]] inline Color side_to_move() const { return current_state.turn; } + [[nodiscard]] inline Square ep_square() const { return current_state.enPassant; } + template [[nodiscard]] inline Square square(Color c) const { + return static_cast(lsb(pieces(c))); + } + [[nodiscard]] inline Square kingSq(Color c) const { return current_state.kings[c]; } + [[nodiscard]] inline Bitboard checkers() const { return _checkers; } + [[nodiscard]] inline Bitboard pin_mask() const { return _pin_mask; } + inline _Position(std::string fen = START_FEN, bool chess960 = false) { + history.reserve(6144); setFEN(fen, chess960); } - inline bool isCapture(Move mv) const { + [[nodiscard]] 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; } - 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 { + [[nodiscard]] inline bool is_capture(Move mv) const { return isCapture(mv); } + [[nodiscard]] inline bool is_zeroing(Move mv) const { return isCapture(mv) || at(mv.from_sq()) == PAWN; } + [[nodiscard]] std::string fen() const; + [[nodiscard]] inline uint8_t halfmoveClock() const { return current_state.halfMoveClock; } + [[nodiscard]] inline uint16_t fullmoveNumber() const { return current_state.fullMoveNumber; } + [[nodiscard]] inline uint8_t rule50_count() const { return current_state.halfMoveClock; } + [[nodiscard]] 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; } + [[nodiscard]] inline CastlingRights castlingRights() const { return current_state.castlingRights; } + [[nodiscard]] inline bool is_castling(Move mv) const { return mv.type_of() == CASTLING; } inline const HistoryEntry &state() const { return current_state; } uint64_t zobrist() const; inline PieceC piece_at(Square sq) const { return piece_on(sq); } - template inline PieceC at(Square sq) const { + template inline T at(Square sq) const { assert(chess::is_valid(sq)); if constexpr (std::is_same_v) return piece_of(piece_at(sq)); + else if constexpr (std::is_same_v) + return color_of(piece_at(sq)); else return piece_at(sq); } inline Square enpassantSq() const { return ep_square(); } CastlingRights clean_castling_rights() const; void setFEN(const std::string &str, bool chess960 = false); - inline void set_fen(const std::string &str, bool chess960) { setFEN(str, chess960); } + inline void set_fen(const std::string &str, bool chess960 = false) { setFEN(str, chess960); } + inline void setFen(const std::string &str, bool chess960 = false) { setFEN(str, chess960); } Move parse_uci(std::string) const; Move push_uci(std::string); Square _valid_ep_square() const; @@ -535,15 +448,15 @@ template = ply; } + // Test if it's draw of 75 move rule (that forces everyone to draw). It doesn't consider checkmates. inline 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. @@ -629,8 +542,8 @@ template [[nodiscard]] inline 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()); + Bitboard occ_opp = occ(~c); + Bitboard occ_us = occ(c); Bitboard opp_sliders; opp_sliders = (pieces(~c) | pieces(QUEEN, ~c)) & occ_opp; @@ -641,7 +554,7 @@ template #include namespace chess { -// disclaimer: please don't pass Chess960 moves +// disclaimer: please don't pass Chess960 moves for move functions, use uci::uciToMove std::ostream &operator<<(std::ostream &os, const Move mv); std::ostream &operator<<(std::ostream &os, const Color c); std::ostream &operator<<(std::ostream &os, const CastlingRights cr); diff --git a/repetition.h b/repetition.h deleted file mode 100644 index 479a59c..0000000 --- a/repetition.h +++ /dev/null @@ -1,5 +0,0 @@ -#include "types.h" -namespace chess { -extern const std::array cuckoo; -extern const std::array cuckooMove; -} // namespace chess \ No newline at end of file diff --git a/tests.cpp b/tests.cpp index 906b7c3..3c60bf0 100644 --- a/tests.cpp +++ b/tests.cpp @@ -229,6 +229,17 @@ void check_perfts(const std::vector> &entries) { auto end_time = high_resolution_clock::now(); elapsed += duration(end_time - start_time).count(); nodes += entry.info.nodes; + if (entry.info.nodes < 5e6) { + _Position pos2 = pos; + REQUIRE(pos.fen() == pos2.fen()); + auto start_time = high_resolution_clock::now(); + REQUIRE(perft(pos2, entry.info.depth) == entry.info.nodes); + auto end_time = high_resolution_clock::now(); + elapsed += duration(end_time - start_time).count(); + nodes += entry.info.nodes; + } else { + std::cerr << "\n(skipped copying test)\n"; + } } { _Position pos(entry.input, chess960); @@ -237,6 +248,17 @@ void check_perfts(const std::vector> &entries) { auto end_time = high_resolution_clock::now(); elapsed += duration(end_time - start_time).count(); nodes += entry.info.nodes; + if (entry.info.nodes < 5e6) { + _Position pos2 = pos; + REQUIRE(pos.fen() == pos2.fen()); + auto start_time = high_resolution_clock::now(); + REQUIRE(perft(pos2, entry.info.depth) == entry.info.nodes); + auto end_time = high_resolution_clock::now(); + elapsed += duration(end_time - start_time).count(); + nodes += entry.info.nodes; + } else { + std::cerr << "\n(skipped copying test)\n"; + } } { _Position pos(entry.input, chess960); @@ -245,6 +267,17 @@ void check_perfts(const std::vector> &entries) { auto end_time = high_resolution_clock::now(); elapsed += duration(end_time - start_time).count(); nodes += entry.info.nodes; + if (entry.info.nodes < 5e6) { + _Position pos2 = pos; + REQUIRE(pos.fen() == pos2.fen()); + auto start_time = high_resolution_clock::now(); + REQUIRE(perft(pos2, entry.info.depth) == entry.info.nodes); + auto end_time = high_resolution_clock::now(); + elapsed += duration(end_time - start_time).count(); + nodes += entry.info.nodes; + } else { + std::cerr << "\n(skipped copying test)\n"; + } } } double mnps = (nodes / elapsed) / 1'000'000.0; @@ -331,7 +364,66 @@ TEST_CASE("Move making pin update") { REQUIRE(pin_mask == 0x8040201000000ULL); } } -TEST_CASE("Perfts") { + +TEST_CASE("Experienced bugs in this repo") { + { + Position p("rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3"); + REQUIRE(p.zobrist() == p.hash()); + REQUIRE(p.hash() == 11114434025341711937ULL); + } + { + Position p("rnbqkbnr/pppp1ppp/8/3PpP2/8/8/PPP2PPP/RNBQKBNR w KQkq e6 0 2"); + REQUIRE(p.zobrist() == p.hash()); + REQUIRE(p.hash() == 11926398512926811756ULL); + } + { + Position p("r4rk1/2p1qpp1/p1np1n1p/1pb1p1B1/P1B1P1b1/1PNP1N2/2P1QPPP/R4RK1 w - - 0 12"); + REQUIRE(p.zobrist() == p.hash()); + REQUIRE(p.hash() == 221975837765752100ULL); + } + { + Position p("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3"); + REQUIRE(p.zobrist() == p.hash()); + p.push_uci("e5d6"); + REQUIRE(p.zobrist() == p.hash()); + } + { + Position p("rnbqkbnr/ppppp1pp/8/4Pp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2"); + REQUIRE(p.zobrist() == p.hash()); + p.push_uci("d7d5"); + REQUIRE(p.zobrist() == p.hash()); + } + { + Position p; + p.doMove(Move(SQ_A2, SQ_A3)); + p.doMove(Move(SQ_B8, SQ_A6)); + p.doMove(Move(SQ_F2, SQ_F3)); + p.doMove(Move(SQ_F7, SQ_F5)); + REQUIRE(p.zobrist() == p.hash()); + REQUIRE(p.hash() == 4177524090105507023); + } + { + Position p; + REQUIRE(p.getCastlingPath(WHITE, true) == 0x60); + REQUIRE(p.getCastlingPath(WHITE, false) == 0xe); + REQUIRE(p.getCastlingPath(BLACK, true) == 0x6000000000000000ULL); + REQUIRE(p.getCastlingPath(BLACK, false) == 0xe00000000000000ULL); + } + { + Position p; + p.setFEN("1nbqkbnr/1ppppppp/r7/8/4P3/8/PPPP1PPP/RNBQK1NR w KQk - 0 3"); + REQUIRE(p.fen() == "1nbqkbnr/1ppppppp/r7/8/4P3/8/PPPP1PPP/RNBQK1NR w KQk - 0 3"); + } +} +TEST_CASE("Captures only?") { + std::vector> tests = { + { "rn1qkbnr/ppp1pppp/3p4/6B1/6b1/3P4/PPP1PPPP/RN1QKBNR w KQkq - 2 3", 17, 6732 }, + { "rn1qkbnr/ppp1pppp/3p4/6B1/6b1/3P4/PPP1PPPP/RN1QKBNR w KQkq - 2 3", 30, 360 }, + { "2b5/2p5/8/8/5B1k/q7/2K5/8 w - - 0 1", 1, 1 } + }; + check_perfts(tests); +} +TEST_CASE("Perfts" * doctest::timeout(36000)) { std::vector> tests = { { "5k2/8/8/8/3K4/8/8/8 w - - 0 1", 1, 8 }, { "5k2/8/8/8/3K4/8/8/8 w - - 0 1", 3, 310 }, @@ -1253,60 +1345,7 @@ TEST_CASE("Perfts") { }; check_perfts(tests); } -TEST_CASE("Experienced bugs in this repo") { - { - Position p("rnbqkbnr/pp1p1ppp/8/2pPp3/8/8/PPP1PPPP/RNBQKBNR w KQkq e6 0 3"); - REQUIRE(p.zobrist() == p.hash()); - REQUIRE(p.hash() == 11114434025341711937ULL); - } - { - Position p("rnbqkbnr/pppp1ppp/8/3PpP2/8/8/PPP2PPP/RNBQKBNR w KQkq e6 0 2"); - REQUIRE(p.zobrist() == p.hash()); - REQUIRE(p.hash() == 11926398512926811756ULL); - } - { - Position p("r4rk1/2p1qpp1/p1np1n1p/1pb1p1B1/P1B1P1b1/1PNP1N2/2P1QPPP/R4RK1 w - - 0 12"); - REQUIRE(p.zobrist() == p.hash()); - REQUIRE(p.hash() == 221975837765752100ULL); - } - { - Position p("rnbqkbnr/ppp1p1pp/8/3pPp2/8/8/PPPP1PPP/RNBQKBNR w KQkq d6 0 3"); - REQUIRE(p.zobrist() == p.hash()); - p.push_uci("e5d6"); - REQUIRE(p.zobrist() == p.hash()); - } - { - Position p("rnbqkbnr/ppppp1pp/8/4Pp2/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2"); - REQUIRE(p.zobrist() == p.hash()); - p.push_uci("d7d5"); - REQUIRE(p.zobrist() == p.hash()); - } - { - Position p; - p.doMove(Move(SQ_A2, SQ_A3)); - p.doMove(Move(SQ_B8, SQ_A6)); - p.doMove(Move(SQ_F2, SQ_F3)); - p.doMove(Move(SQ_F7, SQ_F5)); - REQUIRE(p.zobrist() == p.hash()); - REQUIRE(p.hash() == 4177524090105507023); - } - { - Position p; - REQUIRE(p.getCastlingPath(WHITE, true) == 0x60); - REQUIRE(p.getCastlingPath(WHITE, false) == 0xe); - REQUIRE(p.getCastlingPath(BLACK, true) == 0x6000000000000000ULL); - REQUIRE(p.getCastlingPath(BLACK, false) == 0xe00000000000000ULL); - } -} -TEST_CASE("Captures only?") { - std::vector> tests = { - { "rn1qkbnr/ppp1pppp/3p4/6B1/6b1/3P4/PPP1PPPP/RN1QKBNR w KQkq - 2 3", 17, 6732 }, - { "rn1qkbnr/ppp1pppp/3p4/6B1/6b1/3P4/PPP1PPPP/RN1QKBNR w KQkq - 2 3", 30, 360 }, - { "2b5/2p5/8/8/5B1k/q7/2K5/8 w - - 0 1", 1, 1 } - }; - check_perfts(tests); -} -TEST_CASE("Chess960") { +TEST_CASE("Chess960" * doctest::timeout(36000)) { std::vector> tests = { { "bqnb1rkr/pp3ppp/3ppn2/2p5/5P2/P2P4/NPP1P1PP/BQ1BNRKR w HFhf - 2 9", 1, 21 }, { "bqnb1rkr/pp3ppp/3ppn2/2p5/5P2/P2P4/NPP1P1PP/BQ1BNRKR w HFhf - 2 9", 2, 528 }, @@ -7075,5 +7114,6 @@ int main(int argc, char **argv) { doctest::Context ctx; ctx.setOption("success", true); ctx.setOption("no-breaks", true); + ctx.setOption("abort-after", 1); return ctx.run(); } diff --git a/types.h b/types.h index e5e2ee1..f6177f8 100644 --- a/types.h +++ b/types.h @@ -45,16 +45,13 @@ constexpr bool is_constant_evaluated() { // both MSVC (non-comformant __cplusplus) and by-default _MSVC_LANG and other compiles with // conformant __cplusplus #elif __cplusplus >= 202002L || _MSVC_LANG >= 202002L - if (std::is_constant_evaluated()) - return true; + return std::is_constant_evaluated(); #elif defined(__GNUC__) // defined for both GCC and clang - if (__builtin_is_constant_evaluated()) - return true; + return __builtin_is_constant_evaluated(); #elif _MSC_VER >= 1925 - if (__builtin_is_constant_evaluated()) - return true; + return __builtin_is_constant_evaluated(); #else -#error "NAWH we don't think we can detect compile time in this compiler"; +#warning "NAWH we don't think we can detect compile time in this compiler"; #endif return false; } @@ -79,28 +76,30 @@ enum Square : int8_t { enum File : int8_t { FILE_A, FILE_B, FILE_C, FILE_D, FILE_E, FILE_F, FILE_G, FILE_H, FILE_NB }; enum Rank : int8_t { RANK_1, RANK_2, RANK_3, RANK_4, RANK_5, RANK_6, RANK_7, RANK_8, RANK_NB }; -constexpr bool is_valid(const Rank r, const File f) { return 0 <= r && r <= 7 && 0 <= f && f <= 7; } // unsigned already fix signedness +constexpr Square square_mirror(const Square sq){return static_cast(static_cast(sq)^56);} +constexpr Square flip_sq(const Square sq){return square_mirror(sq);} +constexpr bool is_valid(const Rank r, const File f) { return 0 <= r && r <= 7 && 0 <= f && f <= 7; } constexpr bool is_valid(const Square s) { return 0 <= s && s < 64; } -constexpr File file_of(Square s) { - ASSUME(0 <= s && s < 64); - return File(s & 7); +constexpr File file_of(const Square s) { + assert(0 <= s && s < 64); + return static_cast(s & 7); } -constexpr Rank rank_of(Square s) { - ASSUME(0 <= s && s < 64); - return Rank(s >> 3); +constexpr Rank rank_of(const Square s) { + assert(0 <= s && s < 64); + return static_cast(s >> 3); } -constexpr Square make_sq(Rank r, File f) { - ASSUME(0 <= r && r <= 7 && 0 <= f && f <= 7); +constexpr Square make_sq(const Rank r, const File f) { + assert(0 <= r && r <= 7 && 0 <= f && f <= 7); return static_cast(static_cast(r * 8 + f)); } -constexpr Square make_sq(File f, Rank r) { - ASSUME(0 <= r && r <= 7 && 0 <= f && f <= 7); +constexpr Square make_sq(const File f, const Rank r) { + assert(0 <= r && r <= 7 && 0 <= f && f <= 7); return static_cast(static_cast(r * 8 + f)); } enum Color : uint8_t { WHITE = 0, BLACK = 1, COLOR_NB = 2 }; // Toggle color -constexpr Color operator~(Color c) { return Color(c ^ BLACK); } +constexpr Color operator~(const Color c) { return static_cast(c ^ BLACK); } enum PieceType : std::int8_t { NO_PIECE_TYPE = 0, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, PIECE_TYPE_NB = 8 }; enum Direction : int8_t { NORTH = 8, @@ -115,11 +114,11 @@ enum Direction : int8_t { DIR_NONE = 0 }; // clang-format on -inline constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -inline constexpr Square castling_rook_square(Color c, bool is_king_side) { +inline constexpr Square relative_square(const Color c, const Square s) { return static_cast(s ^ (c * 56)); } +inline constexpr Square castling_rook_square(const Color c, const bool is_king_side) { return relative_square(c, is_king_side ? SQ_F1 : Square::SQ_D1); } -inline constexpr Square castling_king_square(Color c, bool is_king_side) { +inline constexpr Square castling_king_square(const Color c, const bool is_king_side) { return relative_square(c, is_king_side ? SQ_G1 : Square::SQ_C1); }