Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand All @@ -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)",
Expand Down
10 changes: 6 additions & 4 deletions libbenbot/src/search/Callbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cassert>
#include <chrono>
#include <cmath> // IWYU pragma: keep - for std::abs()
#include <cstdint>
#include <format>
#include <functional>
#include <iostream>
Expand All @@ -23,6 +24,7 @@
#include <libchess/moves/Move.hpp>
#include <libchess/notation/UCI.hpp>
#include <libchess/uci/Printing.hpp>
#include <libchess/util/Chrono.hpp>
#include <libchess/util/Variant.hpp>
#include <optional>
#include <ratio>
Expand Down Expand Up @@ -57,7 +59,7 @@ namespace {

using std::string;

enum class Alignment {
enum class Alignment : std::uint_least8_t {
Left,
Right,
Center
Expand Down Expand Up @@ -105,14 +107,14 @@ namespace {
std::cout << get_column_text<Align>(text);
}

template <typename Duration>
template <chess::util::ChronoDuration Duration>
[[nodiscard]] auto get_duration_string(
const milliseconds duration) -> std::optional<string>
{
static constexpr auto msPerUnit = duration_cast<milliseconds>(Duration { 1uz });

if (duration >= msPerUnit) {
using FractionalDuration = std::chrono::duration<float, typename Duration::period>;
using FractionalDuration = chess::util::FractionalDuration<Duration>;

return std::format(
"{:.2%Q %q}",
Expand Down Expand Up @@ -195,7 +197,7 @@ namespace {
void print_score(
const Score& score)
{
enum class ScoreType {
enum class ScoreType : std::uint_least8_t {
Winning,
Losing,
Equal
Expand Down
57 changes: 57 additions & 0 deletions libchess/include/libchess/notation/ICCF.hpp
Original file line number Diff line number Diff line change
@@ -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 <expected>
#include <libchess/moves/Move.hpp>
#include <string>
#include <string_view>

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<Move, std::string>;

} // namespace chess::notation
55 changes: 55 additions & 0 deletions libchess/include/libchess/util/Chrono.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* ======================================================================================
*
* ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░
*
* ======================================================================================
*/

/** @file
This file provides some utilities for working with the ``std::chrono`` library.
@ingroup util
*/

#pragma once

#include <chrono>
#include <concepts>

namespace chess::util {

namespace detail {
template <typename>
inline constexpr bool IsChronoDuration = false;

template <class Rep, class Period>
inline constexpr bool IsChronoDuration<std::chrono::duration<Rep, Period>> = true;
} // namespace detail

/** This concept matches any specialization of ``std::chrono::duration``.

@ingroup util
*/
template <typename T>
concept ChronoDuration = detail::IsChronoDuration<T>;

/** 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<std::chrono::seconds>;

PartialSeconds secs { 1.5f };
@endcode
*/
template <ChronoDuration Duration, std::floating_point F = float>
using FractionalDuration = std::chrono::duration<F, typename Duration::period>;

} // namespace chess::util
195 changes: 195 additions & 0 deletions libchess/src/notation/ICCF.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* ======================================================================================
*
* ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░
* ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░
*
* ======================================================================================
*/

#include <cassert>
#include <concepts>
#include <expected>
#include <format>
#include <libchess/board/BitboardIndex.hpp>
#include <libchess/board/Square.hpp>
#include <libchess/game/Position.hpp>
#include <libchess/moves/Move.hpp>
#include <libchess/notation/ICCF.hpp>
#include <libchess/pieces/PieceTypes.hpp>
#include <libchess/util/Strings.hpp>
#include <magic_enum/magic_enum.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

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 static_cast<char>(
static_cast<int>('0') + static_cast<int>(digit));
}

[[nodiscard]] auto to_iccf_string(const Square square)
-> string
{
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';
case PieceType::Pawn : [[fallthrough]];
case PieceType::King : [[fallthrough]];
default : std::unreachable();
}
}
} // 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<BitboardIndex, string>
{
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<Square, string>
{
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, string> {
Square {
.file = static_cast<board::File>(fileNum - 1),
.rank = static_cast<board::Rank>(rankNum - 1) }
};
});
});
}

[[nodiscard, gnu::const]] auto parse_piece_type(const char input)
-> std::expected<PieceType, string>
{
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<Move, string>;

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](const PieceType movedType) -> MoveOrError {
if (text.empty()) {
// non-promotion
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 };
});
})
.value_or(
std::unexpected {
std::format(
"No piece for color {} can move from square {}",
magic_enum::enum_name(position.sideToMove), fromSq) });
});
});
}

} // namespace chess::notation
Loading
Loading