From 2fa62916c30f53b878f8cf1ac3b6e6d75794e6f8 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 18:44:30 -0600 Subject: [PATCH 1/5] feat: initial commit of ICCF notation functions --- libchess/include/libchess/notation/ICCF.hpp | 57 ++++++ libchess/src/notation/ICCF.cpp | 201 ++++++++++++++++++++ libchess/src/notation/UCI.cpp | 4 +- tests/unit/libchess/notation/ICCF.cpp | 16 ++ 4 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 libchess/include/libchess/notation/ICCF.hpp create mode 100644 libchess/src/notation/ICCF.cpp create mode 100644 tests/unit/libchess/notation/ICCF.cpp diff --git a/libchess/include/libchess/notation/ICCF.hpp b/libchess/include/libchess/notation/ICCF.hpp new file mode 100644 index 00000000..78dd01f4 --- /dev/null +++ b/libchess/include/libchess/notation/ICCF.hpp @@ -0,0 +1,57 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +/** @file + This file provides functions for converting Move objects to and from + ICCF-format numeric notation. + + @ingroup notation + */ + +#pragma once + +#include +#include +#include +#include + +namespace chess::game { +struct Position; +} // namespace chess::game + +namespace chess::notation { + +using game::Position; +using moves::Move; + +/** Returns the ICCF-format algebraic notation for the given Move object. + + @ingroup notation + @see from_iccf() + */ +[[nodiscard]] auto to_iccf(Move move) -> std::string; + +/** Parses the ICCF-format algebraic notation string into a Move object. + The current position is used to determine the type of the moved piece. + + If the input string cannot be parsed correctly, returns an explanatory error string. + + @ingroup notation + @see to_iccf() + */ +[[nodiscard]] auto from_iccf( + const Position& position, std::string_view text) + -> std::expected; + +} // namespace chess::notation diff --git a/libchess/src/notation/ICCF.cpp b/libchess/src/notation/ICCF.cpp new file mode 100644 index 00000000..b092e657 --- /dev/null +++ b/libchess/src/notation/ICCF.cpp @@ -0,0 +1,201 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace chess::notation { + +namespace { + using board::Square; + using std::string; + using PieceType = pieces::Type; + + [[nodiscard, gnu::const]] auto digit_to_char(std::integral auto digit) -> char + { + assert(digit >= 0 and digit <= 9); + + return '0' + digit; + } + + [[nodiscard, gnu::const]] auto to_iccf_string(const Square square) + -> std::array + { + return { + digit_to_char(std::to_underlying(square.file) + 1), + digit_to_char(std::to_underlying(square.rank) + 1) + }; + } + + [[nodiscard, gnu::const]] auto to_iccf_char(const PieceType piece) -> char + { + switch (piece) { + case PieceType::Queen : return '1'; + case PieceType::Rook : return '2'; + case PieceType::Bishop: return '3'; + case PieceType::Knight: return '4'; + default: + std::unreachable(); + return '0'; + } + } +} // namespace + +auto to_iccf(Move move) -> string +{ + return move.promoted_type() + .transform([move](const PieceType promotedType) { + return std::format( + "{}{}{}", + to_iccf_string(move.from()), + to_iccf_string(move.to()), + to_iccf_char(promotedType)); + }) + .or_else([move] { + return std::make_optional( + std::format( + "{}{}", + to_iccf_string(move.from()), + to_iccf_string(move.to()))); + }) + .value(); +} + +namespace { + using board::BitboardIndex; + + [[nodiscard, gnu::const]] auto digit_from_char(const char input) + -> std::expected + { + switch (input) { + case '0': return BitboardIndex { 0 }; + case '1': return BitboardIndex { 1 }; + case '2': return BitboardIndex { 2 }; + case '3': return BitboardIndex { 3 }; + case '4': return BitboardIndex { 4 }; + case '5': return BitboardIndex { 5 }; + case '6': return BitboardIndex { 6 }; + case '7': return BitboardIndex { 7 }; + case '8': return BitboardIndex { 8 }; + case '9': return BitboardIndex { 9 }; + default : return std::unexpected { + std::format("Cannot parse digit from input: {}", input) + }; + } + } + + [[nodiscard, gnu::const]] auto parse_square(const std::string_view input) + -> std::expected + { + if (input.size() != 2uz) + return std::unexpected { + std::format("Cannot parse Square from string: {}", input) + }; + + return digit_from_char(input.front()) + .and_then([input](const BitboardIndex fileNum) { + return digit_from_char(input.back()) + .and_then([fileNum](const BitboardIndex rankNum) { + return std::expected { + Square { + .file = static_cast(fileNum - 1), + .rank = static_cast(rankNum - 1) } + }; + }); + }); + } + + [[nodiscard, gnu::const]] auto parse_piece_type(const char input) + -> std::expected + { + switch (input) { + case '1': return PieceType::Queen; + case '2': return PieceType::Rook; + case '3': return PieceType::Bishop; + case '4': return PieceType::Knight; + default : return std::unexpected { + std::format("Cannot parse promoted type from input: {}", input) + }; + } + } +} // namespace + +using MoveOrError = std::expected; + +auto from_iccf( + const Position& position, std::string_view text) + -> MoveOrError +{ + text = util::strings::trim(text); + + if (text.length() < 4uz) + return std::unexpected { + "Expected at least 4 characters for ICCF notation" + }; + + return parse_square(text.substr(0uz, 2uz)) + .and_then([&text, &position](const Square fromSq) { + text = text.substr(2uz); + + return parse_square(text.substr(0uz, 2uz)) + .and_then([&text, &position, fromSq](const Square toSq) -> MoveOrError { + text = text.substr(2uz); + + return position.our_pieces() + .get_piece_on(fromSq) + .transform([text, fromSq, toSq, side = position.sideToMove](const PieceType movedType) -> MoveOrError { + if (text.empty()) { + // non-promotion + + if (movedType == PieceType::King and file_distance(fromSq, toSq) > 1) { + if (toSq.is_kingside()) + return moves::castle_kingside(side); + + return moves::castle_queenside(side); + } + + return Move { fromSq, toSq, movedType }; + } + + // promotion + return parse_piece_type(text.front()) + .transform([fromSq, toSq](const PieceType promotedType) { + return Move { fromSq, toSq, PieceType::Pawn, promotedType }; + }); + }) + .value_or( + std::unexpected { + std::format( + "No piece for color {} can move from square {}", + magic_enum::enum_name(position.sideToMove), fromSq) }); + }); + }); +} + +} // namespace chess::notation diff --git a/libchess/src/notation/UCI.cpp b/libchess/src/notation/UCI.cpp index ef29efe2..11c10af6 100644 --- a/libchess/src/notation/UCI.cpp +++ b/libchess/src/notation/UCI.cpp @@ -86,8 +86,8 @@ auto from_uci( // promotion return pieces::from_string(text) - .transform([from, dest, movedType](const PieceType promotedType) { - return Move { from, dest, movedType, promotedType }; + .transform([from, dest](const PieceType promotedType) { + return Move { from, dest, PieceType::Pawn, promotedType }; }) .transform_error([](const string_view parseError) { return std::format( diff --git a/tests/unit/libchess/notation/ICCF.cpp b/tests/unit/libchess/notation/ICCF.cpp new file mode 100644 index 00000000..f2d6ce43 --- /dev/null +++ b/tests/unit/libchess/notation/ICCF.cpp @@ -0,0 +1,16 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +#include +#include From ff9ae304f3ec21d6d65c2fcb59dde6895222092c Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 23:17:10 -0600 Subject: [PATCH 2/5] refactor: chrono utility header --- libbenbot/src/search/Callbacks.cpp | 10 +++-- libchess/include/libchess/util/Chrono.hpp | 55 +++++++++++++++++++++++ libchess/src/uci/Printing.cpp | 3 +- 3 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 libchess/include/libchess/util/Chrono.hpp diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 7dd4bf4b..7d380da3 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -15,6 +15,7 @@ #include #include #include // IWYU pragma: keep - for std::abs() +#include #include #include #include @@ -23,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -57,7 +59,7 @@ namespace { using std::string; - enum class Alignment { + enum class Alignment : std::uint_least8_t { Left, Right, Center @@ -105,14 +107,14 @@ namespace { std::cout << get_column_text(text); } - template + template [[nodiscard]] auto get_duration_string( const milliseconds duration) -> std::optional { static constexpr auto msPerUnit = duration_cast(Duration { 1uz }); if (duration >= msPerUnit) { - using FractionalDuration = std::chrono::duration; + using FractionalDuration = chess::util::FractionalDuration; return std::format( "{:.2%Q %q}", @@ -195,7 +197,7 @@ namespace { void print_score( const Score& score) { - enum class ScoreType { + enum class ScoreType : std::uint_least8_t { Winning, Losing, Equal diff --git a/libchess/include/libchess/util/Chrono.hpp b/libchess/include/libchess/util/Chrono.hpp new file mode 100644 index 00000000..f2f6dba1 --- /dev/null +++ b/libchess/include/libchess/util/Chrono.hpp @@ -0,0 +1,55 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +/** @file + This file provides some utilities for working with the ``std::chrono`` library. + @ingroup util + */ + +#pragma once + +#include +#include + +namespace chess::util { + +namespace detail { + template + inline constexpr bool IsChronoDuration = false; + + template + inline constexpr bool IsChronoDuration> = true; +} // namespace detail + +/** This concept matches any specialization of ``std::chrono::duration``. + + @ingroup util + */ +template +concept ChronoDuration = detail::IsChronoDuration; + +/** This typedef allows converting a chrono duration to one with the same period, + but with a floating-point tick type. + + Example usage: + @code{.cpp} + using PartialSeconds = FractionalDuration; + + PartialSeconds secs { 1.5f }; + @endcode + */ +template +using FractionalDuration = std::chrono::duration; + +} // namespace chess::util diff --git a/libchess/src/uci/Printing.cpp b/libchess/src/uci/Printing.cpp index 799642bb..b41fcfd7 100644 --- a/libchess/src/uci/Printing.cpp +++ b/libchess/src/uci/Printing.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -84,7 +85,7 @@ auto SearchInfo::Score::MateIn::moves() const noexcept -> int auto SearchInfo::get_nps() const noexcept -> size_t { - using FractionalSeconds = std::chrono::duration; + using FractionalSeconds = util::FractionalDuration; const auto seconds = duration_cast(time).count(); From 63065b9b0977467935750b7b4933a78f1c14ca7c Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Sat, 28 Feb 2026 00:50:34 -0600 Subject: [PATCH 3/5] chore: added -g to sanitizer flags --- CMakePresets.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index df02ac82..1fd84fc5 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -128,11 +128,11 @@ "cacheVariables": { "CMAKE_CXX_FLAGS_DEBUG": { "type": "STRING", - "value": "-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer" + "value": "-g -fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer" }, "CMAKE_C_FLAGS_DEBUG": { "type": "STRING", - "value": "-fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer" + "value": "-g -fsanitize=address -fsanitize-address-use-after-scope -fno-omit-frame-pointer" } }, "displayName": "Build with Ninja & Clang (ASAN)", @@ -143,11 +143,11 @@ "cacheVariables": { "CMAKE_CXX_FLAGS_DEBUG": { "type": "STRING", - "value": "-fsanitize=undefined" + "value": "-g -fsanitize=undefined" }, "CMAKE_C_FLAGS_DEBUG": { "type": "STRING", - "value": "-fsanitize=undefined" + "value": "-g -fsanitize=undefined" } }, "displayName": "Build with Ninja & Clang (UBSAN)", From 9980de897124cbc292db8272e25ef11daf35ac42 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Sat, 28 Feb 2026 01:17:15 -0600 Subject: [PATCH 4/5] test: unit tests for ICCF notation --- libchess/src/notation/ICCF.cpp | 3 +- tests/unit/libchess/notation/ICCF.cpp | 117 ++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 2 deletions(-) diff --git a/libchess/src/notation/ICCF.cpp b/libchess/src/notation/ICCF.cpp index b092e657..d029dfec 100644 --- a/libchess/src/notation/ICCF.cpp +++ b/libchess/src/notation/ICCF.cpp @@ -12,7 +12,6 @@ * ====================================================================================== */ -#include #include #include #include @@ -45,7 +44,7 @@ namespace { } [[nodiscard, gnu::const]] auto to_iccf_string(const Square square) - -> std::array + -> string { return { digit_to_char(std::to_underlying(square.file) + 1), diff --git a/tests/unit/libchess/notation/ICCF.cpp b/tests/unit/libchess/notation/ICCF.cpp index f2d6ce43..94f61a2f 100644 --- a/tests/unit/libchess/notation/ICCF.cpp +++ b/tests/unit/libchess/notation/ICCF.cpp @@ -13,4 +13,121 @@ */ #include +#include +#include +#include +#include +#include #include +#include +#include + +inline constexpr auto TAGS { "[notation][ICCF]" }; + +using PieceType = chess::pieces::Type; +using chess::board::File; +using chess::board::Rank; +using chess::board::Square; +using chess::game::Position; +using chess::notation::from_fen; +using chess::notation::from_iccf; +using chess::notation::to_iccf; + +TEST_CASE("ICCF notation", TAGS) +{ + const Position startingPosition { }; + + SECTION("Pawn push: e4") + { + const auto move = from_iccf(startingPosition, "5254").value(); + + REQUIRE(move.piece() == PieceType::Pawn); + REQUIRE(move.to() == Square { File::E, Rank::Four }); + REQUIRE(move.from() == Square { File::E, Rank::Two }); + + REQUIRE(to_iccf(move) == "5254"); + } + + SECTION("Promotion to rook") + { + const auto pos = from_fen("8/5P2/2k5/8/8/8/1K6/8 w - - 0 1").value(); + + const auto move = from_iccf(pos, "67682").value(); + + REQUIRE(move.piece() == PieceType::Pawn); + REQUIRE(move.to() == Square { File::F, Rank::Eight }); + REQUIRE(move.from() == Square { File::F, Rank::Seven }); + + REQUIRE(move.is_promotion()); + REQUIRE(move.promoted_type().value() == PieceType::Rook); + + REQUIRE(to_iccf(move) == "67682"); + } +} + +TEST_CASE("ICCF notation: castling", TAGS) +{ + const auto pos = from_fen("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1").value(); + + SECTION("Kingside") + { + SECTION("White") + { + const auto move = from_iccf(pos, "5171").value(); + + REQUIRE(move.piece() == PieceType::King); + REQUIRE(move.to() == Square { File::G, Rank::One }); + REQUIRE(move.from() == Square { File::E, Rank::One }); + + REQUIRE(move.is_castling()); + + REQUIRE(to_iccf(move) == "5171"); + } + + SECTION("Black") + { + const auto move = from_iccf( + after_null_move(pos), "5878") + .value(); + + REQUIRE(move.piece() == PieceType::King); + REQUIRE(move.to() == Square { File::G, Rank::Eight }); + REQUIRE(move.from() == Square { File::E, Rank::Eight }); + + REQUIRE(move.is_castling()); + + REQUIRE(to_iccf(move) == "5878"); + } + } + + SECTION("Queenside") + { + SECTION("White") + { + const auto move = from_iccf(pos, "5131").value(); + + REQUIRE(move.piece() == PieceType::King); + REQUIRE(move.to() == Square { File::C, Rank::One }); + REQUIRE(move.from() == Square { File::E, Rank::One }); + + REQUIRE(move.is_castling()); + + REQUIRE(to_iccf(move) == "5131"); + } + + SECTION("Black") + { + const auto move = from_iccf( + after_null_move(pos), "5838") + .value(); + + REQUIRE(move.piece() == PieceType::King); + REQUIRE(move.to() == Square { File::C, Rank::Eight }); + REQUIRE(move.from() == Square { File::E, Rank::Eight }); + + REQUIRE(move.is_castling()); + + REQUIRE(to_iccf(move) == "5838"); + } + } +} From 8630e7751ef1723f1a5592f164abdbd74de0b36c Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Sat, 28 Feb 2026 01:27:51 -0600 Subject: [PATCH 5/5] refactor: misc --- libchess/src/notation/ICCF.cpp | 25 ++++++++++--------------- libchess/src/notation/UCI.cpp | 7 ++++++- tests/unit/libchess/notation/ICCF.cpp | 1 - 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/libchess/src/notation/ICCF.cpp b/libchess/src/notation/ICCF.cpp index d029dfec..7c6fc0ac 100644 --- a/libchess/src/notation/ICCF.cpp +++ b/libchess/src/notation/ICCF.cpp @@ -17,13 +17,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include @@ -40,10 +40,11 @@ namespace { { assert(digit >= 0 and digit <= 9); - return '0' + digit; + return static_cast( + static_cast('0') + static_cast(digit)); } - [[nodiscard, gnu::const]] auto to_iccf_string(const Square square) + [[nodiscard]] auto to_iccf_string(const Square square) -> string { return { @@ -59,9 +60,9 @@ namespace { case PieceType::Rook : return '2'; case PieceType::Bishop: return '3'; case PieceType::Knight: return '4'; - default: - std::unreachable(); - return '0'; + case PieceType::Pawn : [[fallthrough]]; + case PieceType::King : [[fallthrough]]; + default : std::unreachable(); } } } // namespace @@ -168,21 +169,15 @@ auto from_iccf( return position.our_pieces() .get_piece_on(fromSq) - .transform([text, fromSq, toSq, side = position.sideToMove](const PieceType movedType) -> MoveOrError { + .transform([text, fromSq, toSq](const PieceType movedType) -> MoveOrError { if (text.empty()) { // non-promotion - - if (movedType == PieceType::King and file_distance(fromSq, toSq) > 1) { - if (toSq.is_kingside()) - return moves::castle_kingside(side); - - return moves::castle_queenside(side); - } - return Move { fromSq, toSq, movedType }; } // promotion + assert(movedType == PieceType::Pawn); + return parse_piece_type(text.front()) .transform([fromSq, toSq](const PieceType promotedType) { return Move { fromSq, toSq, PieceType::Pawn, promotedType }; diff --git a/libchess/src/notation/UCI.cpp b/libchess/src/notation/UCI.cpp index 11c10af6..5b7a5db4 100644 --- a/libchess/src/notation/UCI.cpp +++ b/libchess/src/notation/UCI.cpp @@ -12,6 +12,7 @@ * ====================================================================================== */ +#include #include #include #include @@ -81,10 +82,14 @@ auto from_uci( return position.our_pieces() .get_piece_on(from) .transform([text, from, dest](const PieceType movedType) -> MoveOrError { - if (text.empty()) + if (text.empty()) { + // non-promotion return Move { from, dest, movedType }; + } // promotion + assert(movedType == PieceType::Pawn); + return pieces::from_string(text) .transform([from, dest](const PieceType promotedType) { return Move { from, dest, PieceType::Pawn, promotedType }; diff --git a/tests/unit/libchess/notation/ICCF.cpp b/tests/unit/libchess/notation/ICCF.cpp index 94f61a2f..83e8325a 100644 --- a/tests/unit/libchess/notation/ICCF.cpp +++ b/tests/unit/libchess/notation/ICCF.cpp @@ -20,7 +20,6 @@ #include #include #include -#include inline constexpr auto TAGS { "[notation][ICCF]" };