From d7d999edfd34dbbbb30ff84ea496b1076b2cd990 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 02:30:54 -0500 Subject: [PATCH 01/23] style: no longer using info_string() to print current position info --- ben-bot/src/Printing.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index a3d06486..1ce4ee20 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -148,22 +148,22 @@ void Engine::print_current_position(const string_view arguments) const utf8 ? print_utf8(pos) : print_ascii(pos)); println(""); - info_string(std::format("FEN: {}", chess::notation::to_fen(pos))); - info_string(std::format("Zobrist key: {}", pos.hash)); + println("FEN: {}", chess::notation::to_fen(pos)); + println("Zobrist key: {}", pos.hash); searcher.context.transTable.find(pos) .transform([](const TTData& data) { - info_string(std::format( + println( "TT hit: depth {} eval {} type {} probed {} bestmove {}", data.searchedDepth, data.eval, magic_enum::enum_name(data.evalType), eval::Score::from_tt(data.eval, 0uz), - chess::notation::to_uci(data.bestMove.value_or(Move {})))); + chess::notation::to_uci(data.bestMove.value_or(Move {}))); return std::monostate {}; }); - info_string(std::format("Static eval: {}", eval::evaluate(pos))); + println("Static eval: {}", eval::evaluate(pos)); } void Engine::print_compiler_info() From 8ae96f45c0d8c46d3b5d7c995548bacac5a4edd4 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 02:51:35 -0500 Subject: [PATCH 02/23] feat: initial commit of termcolor dependency (#385) --- ben-bot/CMakeLists.txt | 15 +++++++++++++++ ben-bot/src/Printing.cpp | 34 ++++++++++++++++++++++++---------- vcpkg.json | 4 ++++ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/ben-bot/CMakeLists.txt b/ben-bot/CMakeLists.txt index 16cf4f11..42dec5cf 100644 --- a/ben-bot/CMakeLists.txt +++ b/ben-bot/CMakeLists.txt @@ -12,12 +12,27 @@ add_subdirectory (resources) +FetchContent_Declare ( + termcolor + SYSTEM + GIT_REPOSITORY "https://github.com/ikalnytskyi/termcolor.git" + GIT_TAG v2.1.0 + GIT_SHALLOW ON + SOURCE_SUBDIR foo # we need to disable adding termcolor as a subdirectory, because they list a + # too-old cmake version + FIND_PACKAGE_ARGS 2.1.0 +) + +FetchContent_MakeAvailable (termcolor) + add_executable (ben_bot) target_compile_features (ben_bot PRIVATE cxx_std_23) target_link_libraries (ben_bot PRIVATE ben_bot::libbenbot ben_bot::resources) +target_include_directories (ben_bot PRIVATE "${termcolor_SOURCE_DIR}/include") + set_target_properties ( ben_bot PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF DEBUG_POSTFIX -d ) diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index 1ce4ee20..0b93e3ba 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include #include +#include #include #include @@ -138,6 +140,14 @@ void Engine::print_options(const string_view args) const println("Debug mode: {}", debugMode.load()); } +namespace { + void print_labeled_info(const string_view label, const string_view info) + { + std::cout << termcolor::white << label << termcolor::bright_white << info << '\n' + << termcolor::reset; + } +} // namespace + void Engine::print_current_position(const string_view arguments) const { const auto& pos = searcher.context.options.position; @@ -148,22 +158,26 @@ void Engine::print_current_position(const string_view arguments) const utf8 ? print_utf8(pos) : print_ascii(pos)); println(""); - println("FEN: {}", chess::notation::to_fen(pos)); - println("Zobrist key: {}", pos.hash); + + print_labeled_info("FEN: ", chess::notation::to_fen(pos)); + + print_labeled_info("Zobrist key: ", std::format("{}", pos.hash)); + + print_labeled_info("Static eval: ", std::format("{}", eval::evaluate(pos))); searcher.context.transTable.find(pos) .transform([](const TTData& data) { - println( - "TT hit: depth {} eval {} type {} probed {} bestmove {}", - data.searchedDepth, data.eval, - magic_enum::enum_name(data.evalType), - eval::Score::from_tt(data.eval, 0uz), - chess::notation::to_uci(data.bestMove.value_or(Move {}))); + print_labeled_info( + "TT hit: ", + std::format( + "depth {} eval {} type {} probed {} bestmove {}", + data.searchedDepth, data.eval, + magic_enum::enum_name(data.evalType), + eval::Score::from_tt(data.eval, 0uz), + chess::notation::to_uci(data.bestMove.value_or(Move {})))); return std::monostate {}; }); - - println("Static eval: {}", eval::evaluate(pos)); } void Engine::print_compiler_info() diff --git a/vcpkg.json b/vcpkg.json index 8ef9a39e..afae01d1 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -11,6 +11,10 @@ { "name": "nlohmann-json", "version>=": "3.12.0" + }, + { + "name": "termcolor", + "version>=": "2.1.0" } ], "name": "BenBot", From 14f51c2239673e12d1910c5734bc768d7bb33841 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 03:08:10 -0500 Subject: [PATCH 03/23] feat: position printing now uses some color (#385) --- ben-bot/src/Printing.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index 0b93e3ba..b722feb4 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -12,9 +12,11 @@ * ====================================================================================== */ +#include #include #include #include +#include #include #include #include @@ -27,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -141,6 +144,26 @@ void Engine::print_options(const string_view args) const } namespace { + void print_colored_board(const Position& pos, const bool utf8) + { + const auto boardStr = utf8 ? print_utf8(pos) : print_ascii(pos); + + using Lines = beman::inplace_vector::inplace_vector; + + const auto lines = chess::util::lines_view(boardStr) + | std::ranges::to(); + + assert(lines.size() == Lines::capacity()); + + for (const auto line : lines | std::views::take(Lines::capacity() - 1uz)) { + std::cout << termcolor::bright_white << line.substr(0uz, line.length() - 1uz) + << termcolor::white << line.back() << '\n'; + } + + std::cout << termcolor::white << lines.back() << '\n' + << termcolor::reset; + } + void print_labeled_info(const string_view label, const string_view info) { std::cout << termcolor::white << label << termcolor::bright_white << info << '\n' @@ -152,10 +175,8 @@ void Engine::print_current_position(const string_view arguments) const { const auto& pos = searcher.context.options.position; - const bool utf8 = chess::util::trim(arguments) == "utf8"; - - println("{}", - utf8 ? print_utf8(pos) : print_ascii(pos)); + print_colored_board(pos, + chess::util::trim(arguments) == "utf8"); println(""); From 5f676f813a7e8ba5fa43b506e130d0d509b4e956 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 03:56:53 -0500 Subject: [PATCH 04/23] feat: printing text tables using color (#385) --- ben-bot/include/ben-bot/TextTable.hpp | 32 ++++++++++++++++ ben-bot/src/Printing.cpp | 36 +++++++++++++++++- ben-bot/src/TextTable.cpp | 53 +++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) diff --git a/ben-bot/include/ben-bot/TextTable.hpp b/ben-bot/include/ben-bot/TextTable.hpp index ed93fb3c..125c88e3 100644 --- a/ben-bot/include/ben-bot/TextTable.hpp +++ b/ben-bot/include/ben-bot/TextTable.hpp @@ -20,6 +20,7 @@ #pragma once #include // IWYU pragma: keep - for size_t +#include #include #include #include @@ -49,6 +50,32 @@ struct [[nodiscard]] TextTable final { /** Concatenates all the rows in the table into a single string, with separators between rows and columns. */ [[nodiscard]] auto to_string() const -> string; + /** Typedef for a function that accepts a string as an argument. */ + using PrintFunc = std::function; + + /** Prints the table by calling lambda functions for each different element of the table. + This allows you to do things like print the table to a terminal using ANSI color codes; using + just the ``to_string()`` API would make it much more difficult to intersperse the needed escape + codes to switch between styles for the cell text and outlines, etc. + + @param printHeading Function object that will be called to print the contents of each heading cell. + Note that the strings sent to this function will include trailing whitespace, to create the table's alignment. + + @param printCell Function object that will be called to print the contents of each non-heading cell. + Note that the strings sent to this function will include trailing whitespace, to create the table's alignment. + + @param printOutline Function object that will be called to print outline/border characters between cells + and surrounding the entire table. + + @param printNewline Function object that accepts no arguments and should insert a single newline character + into the text stream being constructed. + */ + void print( + PrintFunc&& printHeading, + PrintFunc&& printCell, + PrintFunc&& printOutline, + std::function printNewline) const; + private: struct Row final { void add_column(string_view text) { columns.emplace_back(text); } @@ -60,6 +87,11 @@ struct [[nodiscard]] TextTable final { [[nodiscard]] auto to_string(std::span widths) const -> string; + void print( + PrintFunc printCell, + PrintFunc printOutline, + std::span widths) const; + private: std::vector columns; }; diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index b722feb4..fb85455d 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -57,6 +57,36 @@ void Engine::print_logo_and_version() const get_name(), get_author()); } +namespace { + void print_colored_table(const TextTable& table) + { + table.print( + [](const string_view heading) { + // we want the heading text to be underlined, but not the + // whitespace that follows the text to complete the cell + const auto trimmed = chess::util::trim(heading); + + std::cout << termcolor::bold << termcolor::underline << termcolor::bright_white + << trimmed + << termcolor::reset; + + const auto numSpaces = heading.length() - trimmed.length(); + + for (auto i = 0uz; i < numSpaces; ++i) + std::cout << ' '; + }, + [](const string_view cell) { + std::cout << termcolor::bright_white << cell; + }, + [](const string_view outline) { + std::cout << termcolor::white << outline; + }, + [] { std::cout << '\n'; }); + + std::cout << termcolor::reset; + } +} // namespace + void Engine::print_help(const string_view args) const { const bool noLogo = [args] { @@ -88,7 +118,7 @@ void Engine::print_help(const string_view args) const .append_column(command.description); } - println("{}", table.to_string()); + print_colored_table(table); } void Engine::print_options(const string_view args) const @@ -137,7 +167,9 @@ void Engine::print_options(const string_view args) const } } - println("{}", table.to_string()); + print_colored_table(table); + + println(""); if (not noCurrent) println("Debug mode: {}", debugMode.load()); diff --git a/ben-bot/src/TextTable.cpp b/ben-bot/src/TextTable.cpp index 0bb2afc8..198e4915 100644 --- a/ben-bot/src/TextTable.cpp +++ b/ben-bot/src/TextTable.cpp @@ -15,10 +15,12 @@ #include #include #include // IWYU pragma: keep - for size_t; +#include #include #include #include #include +#include #include namespace ben_bot { @@ -88,6 +90,29 @@ auto TextTable::to_string() const -> string return result; } +void TextTable::print( + PrintFunc&& printHeading, + PrintFunc&& printCell, + PrintFunc&& printOutline, + std::function printNewline) const +{ + const auto widths = get_column_widths(); + + rows.front().print( + std::move(printHeading), printOutline, widths); + + printNewline(); + + printOutline(make_header_sep_row(widths)); + + printNewline(); + + for (const auto& row : rows | std::views::drop(1uz)) { + row.print(printCell, printOutline, widths); + printNewline(); + } +} + auto TextTable::num_columns() const -> size_t { return std::transform_reduce( @@ -142,4 +167,32 @@ auto TextTable::Row::to_string( return result; } +void TextTable::Row::print( + PrintFunc printCell, + PrintFunc printOutline, + std::span widths) const +{ + printOutline(LINE_START); + + auto index { 0uz }; + + for (const auto width : widths) { + if (index > 0uz) + printOutline(COLUMN_SEPARATOR); + + string padded; + + if (index < columns.size()) + padded = columns[index]; + + padded.resize(width, ' '); + + printCell(padded); + + ++index; + } + + printOutline(LINE_ENDING); +} + } // namespace ben_bot From 925f1256e872461fcfae97be83241d9968e53859 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 04:15:26 -0500 Subject: [PATCH 05/23] feat: printing logo in color (#385) --- ben-bot/src/Printing.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index fb85455d..91933b71 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -50,11 +50,24 @@ auto Engine::get_name() const -> std::string void Engine::print_logo_and_version() const { - println("{}", resources::get_ascii_logo()); + using Lines = beman::inplace_vector::inplace_vector; - println( - "{}, by {}", - get_name(), get_author()); + const auto logoLines = chess::util::lines_view(resources::get_ascii_logo()) + | std::views::take(Lines::capacity()) + | std::ranges::to(); + + assert(logoLines.size() == Lines::capacity()); + + std::cout << termcolor::grey << logoLines.front() << '\n' + << termcolor::blue; + + for (const auto line : logoLines | std::views::drop(1) | std::views::take(Lines::capacity() - 2uz)) + std::cout << line << '\n'; + + std::cout << termcolor::grey << logoLines.back() << "\n\n" + << termcolor::bright_white << termcolor::bold << get_name() << ", " + << termcolor::reset << "by " << get_author() << '\n' + << termcolor::reset; } namespace { From 385ea566c6f6770c17cd689502aae381e70eb493 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 16 Oct 2025 04:24:15 -0500 Subject: [PATCH 06/23] fix: better text colors --- ben-bot/src/Printing.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index 91933b71..582dc92c 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -65,7 +65,7 @@ void Engine::print_logo_and_version() const std::cout << line << '\n'; std::cout << termcolor::grey << logoLines.back() << "\n\n" - << termcolor::bright_white << termcolor::bold << get_name() << ", " + << termcolor::reset << termcolor::bold << get_name() << ", " << termcolor::reset << "by " << get_author() << '\n' << termcolor::reset; } @@ -79,7 +79,7 @@ namespace { // whitespace that follows the text to complete the cell const auto trimmed = chess::util::trim(heading); - std::cout << termcolor::bold << termcolor::underline << termcolor::bright_white + std::cout << termcolor::bold << termcolor::underline << trimmed << termcolor::reset; @@ -89,10 +89,10 @@ namespace { std::cout << ' '; }, [](const string_view cell) { - std::cout << termcolor::bright_white << cell; + std::cout << cell; }, [](const string_view outline) { - std::cout << termcolor::white << outline; + std::cout << termcolor::white << outline << termcolor::reset; }, [] { std::cout << '\n'; }); @@ -201,8 +201,9 @@ namespace { assert(lines.size() == Lines::capacity()); for (const auto line : lines | std::views::take(Lines::capacity() - 1uz)) { - std::cout << termcolor::bright_white << line.substr(0uz, line.length() - 1uz) - << termcolor::white << line.back() << '\n'; + std::cout << line.substr(0uz, line.length() - 1uz) + << termcolor::white << line.back() << '\n' + << termcolor::reset; } std::cout << termcolor::white << lines.back() << '\n' @@ -211,8 +212,7 @@ namespace { void print_labeled_info(const string_view label, const string_view info) { - std::cout << termcolor::white << label << termcolor::bright_white << info << '\n' - << termcolor::reset; + std::cout << termcolor::white << label << termcolor::reset << info << '\n'; } } // namespace From c1777d7e63eb0e162f104be4cb38f2c09c084592 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 17 Oct 2025 18:36:43 -0500 Subject: [PATCH 07/23] refactor: moved colored printing functions to separate TU --- ben-bot/CMakeLists.txt | 5 +- ben-bot/include/ben-bot/ColorPrinting.hpp | 53 +++++++++++ ben-bot/src/ColorPrinting.cpp | 108 ++++++++++++++++++++++ ben-bot/src/Printing.cpp | 91 +----------------- 4 files changed, 169 insertions(+), 88 deletions(-) create mode 100644 ben-bot/include/ben-bot/ColorPrinting.hpp create mode 100644 ben-bot/src/ColorPrinting.cpp diff --git a/ben-bot/CMakeLists.txt b/ben-bot/CMakeLists.txt index 42dec5cf..17526073 100644 --- a/ben-bot/CMakeLists.txt +++ b/ben-bot/CMakeLists.txt @@ -31,7 +31,10 @@ target_compile_features (ben_bot PRIVATE cxx_std_23) target_link_libraries (ben_bot PRIVATE ben_bot::libbenbot ben_bot::resources) -target_include_directories (ben_bot PRIVATE "${termcolor_SOURCE_DIR}/include") +set_property ( + SOURCE src/ColorPrinting.cpp APPEND PROPERTY INCLUDE_DIRECTORIES + "${termcolor_SOURCE_DIR}/include" +) set_target_properties ( ben_bot PROPERTIES CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF DEBUG_POSTFIX -d diff --git a/ben-bot/include/ben-bot/ColorPrinting.hpp b/ben-bot/include/ben-bot/ColorPrinting.hpp new file mode 100644 index 00000000..b6b3ecbb --- /dev/null +++ b/ben-bot/include/ben-bot/ColorPrinting.hpp @@ -0,0 +1,53 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +/** @file + This file provides functions for colored printing. + + @ingroup benbot + */ + +#pragma once + +#include + +namespace chess::game { +struct Position; +} // namespace chess::game + +namespace ben_bot { + +struct TextTable; + +using chess::game::Position; +using std::string_view; + +/** @ingroup benbot + @{ + */ + +/** Prints the given table with bold headings, faint outlines, + and regular content cells. + */ +void print_colored_table(const TextTable& table); + +/** Prints the given position with faint file/rank labels. */ +void print_colored_board(const Position& pos, bool utf8); + +/** Prints the given label faintly, and the info as regular text. */ +void print_labeled_info(string_view label, string_view info); + +/** @} */ + +} // namespace ben_bot diff --git a/ben-bot/src/ColorPrinting.cpp b/ben-bot/src/ColorPrinting.cpp new file mode 100644 index 00000000..a93f33e0 --- /dev/null +++ b/ben-bot/src/ColorPrinting.cpp @@ -0,0 +1,108 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // this is the only TU that includes the termcolor library + +namespace ben_bot { + +using std::string_view; + +void Engine::print_logo_and_version() const +{ + using Lines = beman::inplace_vector::inplace_vector; + + const auto logoLines = chess::util::lines_view(resources::get_ascii_logo()) + | std::views::take(Lines::capacity()) + | std::ranges::to(); + + assert(logoLines.size() == Lines::capacity()); + + std::cout << termcolor::grey << logoLines.front() << '\n' + << termcolor::blue; + + for (const auto line : logoLines | std::views::drop(1) | std::views::take(Lines::capacity() - 2uz)) + std::cout << line << '\n'; + + std::cout << termcolor::grey << logoLines.back() << "\n\n" + << termcolor::reset << termcolor::bold << get_name() << ", " + << termcolor::reset << "by " << get_author() << '\n' + << termcolor::reset; +} + +void print_colored_table(const TextTable& table) +{ + table.print( + [](const string_view heading) { + // we want the heading text to be underlined, but not the + // whitespace that follows the text to complete the cell + const auto trimmed = chess::util::trim(heading); + + std::cout << termcolor::bold << termcolor::underline + << trimmed + << termcolor::reset; + + const auto numSpaces = heading.length() - trimmed.length(); + + for (auto i = 0uz; i < numSpaces; ++i) + std::cout << ' '; + }, + [](const string_view cell) { + std::cout << cell; + }, + [](const string_view outline) { + std::cout << termcolor::white << outline << termcolor::reset; + }, + [] { std::cout << '\n'; }); + + std::cout << termcolor::reset; +} + +void print_colored_board(const Position& pos, const bool utf8) +{ + const auto boardStr = utf8 ? print_utf8(pos) : print_ascii(pos); + + using Lines = beman::inplace_vector::inplace_vector; + + const auto lines = chess::util::lines_view(boardStr) + | std::ranges::to(); + + assert(lines.size() == Lines::capacity()); + + for (const auto line : lines | std::views::take(Lines::capacity() - 1uz)) { + std::cout << line.substr(0uz, line.length() - 1uz) + << termcolor::white << line.back() << '\n' + << termcolor::reset; + } + + std::cout << termcolor::white << lines.back() << '\n' + << termcolor::reset; +} + +void print_labeled_info(const string_view label, const string_view info) +{ + std::cout << termcolor::white << label << termcolor::reset << info << '\n'; +} + +} // namespace ben_bot diff --git a/ben-bot/src/Printing.cpp b/ben-bot/src/Printing.cpp index 582dc92c..ab8d1fff 100644 --- a/ben-bot/src/Printing.cpp +++ b/ben-bot/src/Printing.cpp @@ -12,13 +12,11 @@ * ====================================================================================== */ -#include +#include #include #include #include -#include #include -#include #include #include #include @@ -29,11 +27,8 @@ #include #include #include -#include #include #include -#include -#include #include namespace ben_bot { @@ -41,6 +36,7 @@ namespace ben_bot { using Result = search::Result; using std::println; +using std::string_view; using uci::printing::info_string; auto Engine::get_name() const -> std::string @@ -48,58 +44,6 @@ auto Engine::get_name() const -> std::string return std::format("BenBot {}", resources::get_version_string()); } -void Engine::print_logo_and_version() const -{ - using Lines = beman::inplace_vector::inplace_vector; - - const auto logoLines = chess::util::lines_view(resources::get_ascii_logo()) - | std::views::take(Lines::capacity()) - | std::ranges::to(); - - assert(logoLines.size() == Lines::capacity()); - - std::cout << termcolor::grey << logoLines.front() << '\n' - << termcolor::blue; - - for (const auto line : logoLines | std::views::drop(1) | std::views::take(Lines::capacity() - 2uz)) - std::cout << line << '\n'; - - std::cout << termcolor::grey << logoLines.back() << "\n\n" - << termcolor::reset << termcolor::bold << get_name() << ", " - << termcolor::reset << "by " << get_author() << '\n' - << termcolor::reset; -} - -namespace { - void print_colored_table(const TextTable& table) - { - table.print( - [](const string_view heading) { - // we want the heading text to be underlined, but not the - // whitespace that follows the text to complete the cell - const auto trimmed = chess::util::trim(heading); - - std::cout << termcolor::bold << termcolor::underline - << trimmed - << termcolor::reset; - - const auto numSpaces = heading.length() - trimmed.length(); - - for (auto i = 0uz; i < numSpaces; ++i) - std::cout << ' '; - }, - [](const string_view cell) { - std::cout << cell; - }, - [](const string_view outline) { - std::cout << termcolor::white << outline << termcolor::reset; - }, - [] { std::cout << '\n'; }); - - std::cout << termcolor::reset; - } -} // namespace - void Engine::print_help(const string_view args) const { const bool noLogo = [args] { @@ -188,34 +132,6 @@ void Engine::print_options(const string_view args) const println("Debug mode: {}", debugMode.load()); } -namespace { - void print_colored_board(const Position& pos, const bool utf8) - { - const auto boardStr = utf8 ? print_utf8(pos) : print_ascii(pos); - - using Lines = beman::inplace_vector::inplace_vector; - - const auto lines = chess::util::lines_view(boardStr) - | std::ranges::to(); - - assert(lines.size() == Lines::capacity()); - - for (const auto line : lines | std::views::take(Lines::capacity() - 1uz)) { - std::cout << line.substr(0uz, line.length() - 1uz) - << termcolor::white << line.back() << '\n' - << termcolor::reset; - } - - std::cout << termcolor::white << lines.back() << '\n' - << termcolor::reset; - } - - void print_labeled_info(const string_view label, const string_view info) - { - std::cout << termcolor::white << label << termcolor::reset << info << '\n'; - } -} // namespace - void Engine::print_current_position(const string_view arguments) const { const auto& pos = searcher.context.options.position; @@ -231,7 +147,8 @@ void Engine::print_current_position(const string_view arguments) const print_labeled_info("Static eval: ", std::format("{}", eval::evaluate(pos))); - searcher.context.transTable.find(pos) + searcher.context.transTable + .find(pos) .transform([](const TTData& data) { print_labeled_info( "TT hit: ", From 02b9c60b1f1e161e6bcb11e9447916901b1294cf Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Sun, 19 Oct 2025 16:33:36 -0500 Subject: [PATCH 08/23] refactor: get_at_most_n_lines() utility function --- ben-bot/src/ColorPrinting.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ben-bot/src/ColorPrinting.cpp b/ben-bot/src/ColorPrinting.cpp index a93f33e0..a3933965 100644 --- a/ben-bot/src/ColorPrinting.cpp +++ b/ben-bot/src/ColorPrinting.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -29,20 +28,26 @@ namespace ben_bot { using std::string_view; -void Engine::print_logo_and_version() const -{ - using Lines = beman::inplace_vector::inplace_vector; +namespace { + template + [[nodiscard, gnu::const]] auto get_at_most_n_lines(const string_view input) + { + using Lines = beman::inplace_vector::inplace_vector; - const auto logoLines = chess::util::lines_view(resources::get_ascii_logo()) - | std::views::take(Lines::capacity()) - | std::ranges::to(); + return chess::util::lines_view(input) + | std::views::take(MaxLines) + | std::ranges::to(); + } +} // namespace - assert(logoLines.size() == Lines::capacity()); +void Engine::print_logo_and_version() const +{ + const auto logoLines = get_at_most_n_lines<11uz>(resources::get_ascii_logo()); std::cout << termcolor::grey << logoLines.front() << '\n' << termcolor::blue; - for (const auto line : logoLines | std::views::drop(1) | std::views::take(Lines::capacity() - 2uz)) + for (const auto line : logoLines | std::views::drop(1) | std::views::take(logoLines.capacity() - 2uz)) std::cout << line << '\n'; std::cout << termcolor::grey << logoLines.back() << "\n\n" @@ -83,14 +88,9 @@ void print_colored_board(const Position& pos, const bool utf8) { const auto boardStr = utf8 ? print_utf8(pos) : print_ascii(pos); - using Lines = beman::inplace_vector::inplace_vector; - - const auto lines = chess::util::lines_view(boardStr) - | std::ranges::to(); - - assert(lines.size() == Lines::capacity()); + const auto lines = get_at_most_n_lines<9uz>(boardStr); - for (const auto line : lines | std::views::take(Lines::capacity() - 1uz)) { + for (const auto line : lines | std::views::take(lines.capacity() - 1uz)) { std::cout << line.substr(0uz, line.length() - 1uz) << termcolor::white << line.back() << '\n' << termcolor::reset; From d7d41e17bae573f494bc7e6d65fd8edf5d016edc Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Tue, 28 Oct 2025 02:46:43 -0500 Subject: [PATCH 09/23] test: test case to verify TextTable::to_string() and TextTable::print() --- tests/unit/libchess/util/TextTable.cpp | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/unit/libchess/util/TextTable.cpp diff --git a/tests/unit/libchess/util/TextTable.cpp b/tests/unit/libchess/util/TextTable.cpp new file mode 100644 index 00000000..01dae432 --- /dev/null +++ b/tests/unit/libchess/util/TextTable.cpp @@ -0,0 +1,64 @@ +/* + * ====================================================================================== + * + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░▒▓████████▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓█▓▒░ + * ░▒▓███████▓▒░░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ░▒▓███████▓▒░ ░▒▓██████▓▒░ ░▒▓█▓▒░ + * + * ====================================================================================== + */ + +#include +#include +#include +#include + +static constexpr auto TAGS { "[util][strings][TextTable]" }; + +using chess::util::strings::TextTable; + +namespace { +[[nodiscard]] auto get_printed_string(const TextTable& table) -> std::string +{ + std::string result; + + auto appendToString = [&result](const std::string_view toAdd) { + result.append(toAdd); + }; + + table.print( + appendToString, appendToString, appendToString, + [&result] { result.append(1uz, '\n'); }); + + return result; +} +} // namespace + +TEST_CASE("TextTable - to_string() should give same result as print()", TAGS) +{ + TextTable table; + + table.append_column("Option") + .append_column("Type") + .append_column("Notes") + .append_column("Default"); + + table.new_row() + .append_column("Option 1") + .append_column("Integer") + .append_column("Comments") + .append_column("0"); + + table.new_row() + .append_column("Option 2") + .append_column("String") + .append_column("More comments") + .append_column("foo"); + + REQUIRE( + get_printed_string(table) == table.to_string()); +} From 8d359723a460fe110e89ced76c23b8d48e524ef1 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 26 Feb 2026 22:48:48 -0600 Subject: [PATCH 10/23] chore: updating precommit hooks --- .pre-commit-config.yaml | 16 ++++++++-------- libchess/src/BoardPrinting.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d322c7f1..77b3a84d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: # insert license header - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.5 + rev: v1.5.6 hooks: # we have to run this step once for each different comment style - id: insert-license @@ -92,7 +92,7 @@ repos: # C++ formatting & linting - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v21.1.2 + rev: v22.1.0 hooks: - id: clang-format exclude_types: [json] @@ -117,7 +117,7 @@ repos: args: [--config-files, config/.cmake-format.json] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.1 + rev: 0.36.2 hooks: - id: check-github-workflows - id: check-jsonschema @@ -127,7 +127,7 @@ repos: # Python formatting & linting - repo: https://github.com/asottile/pyupgrade - rev: v3.21.0 + rev: v3.21.2 hooks: - id: pyupgrade @@ -137,7 +137,7 @@ repos: - id: reorder-python-imports - repo: https://github.com/psf/black-pre-commit-mirror - rev: 25.9.0 + rev: 26.1.0 hooks: - id: black @@ -194,7 +194,7 @@ repos: - id: forbid-bidi-controls - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks.git - rev: v2.15.0 + rev: v2.16.0 hooks: - id: pretty-format-ini args: [--autofix] @@ -203,14 +203,14 @@ repos: # check for dead links in markdown - repo: https://github.com/AlexanderDokuchaev/md-dead-link-check - rev: "v1.2.0" + rev: "v1.3.0" hooks: - id: md-dead-link-check args: [--config, config/link-checker.toml] # check editorconfig rules - repo: https://github.com/editorconfig-checker/editorconfig-checker.python - rev: 3.4.1 + rev: 3.6.0 hooks: - id: editorconfig-checker args: [-config, config/.editorconfig-checker.json] diff --git a/libchess/src/BoardPrinting.cpp b/libchess/src/BoardPrinting.cpp index 0d3a878d..1049b566 100644 --- a/libchess/src/BoardPrinting.cpp +++ b/libchess/src/BoardPrinting.cpp @@ -53,7 +53,7 @@ namespace { // and must return the text to go inside that square, or // a space if it's empty template - [[nodiscard, gnu::cold]] auto generate_board_string(Func getSquareText) -> string + [[nodiscard]] auto generate_board_string(Func getSquareText) -> string { static constexpr bool FuncReturnsChar = std::is_same_v< std::invoke_result_t, From 7511881f4dda57cfd7aa75af65034dac73d856f6 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 26 Feb 2026 22:54:17 -0600 Subject: [PATCH 11/23] chore: formatting --- ben-bot/resources/src/BuildTime.cpp | 2 +- ben-bot/src/Bench.cpp | 4 ++-- .../libbenbot/data-structures/KillerMoves.hpp | 2 +- .../src/data-structures/TranspositionTable.cpp | 10 +++++----- libbenbot/src/eval/PieceSquareTables.cpp | 2 +- libbenbot/src/search/Result.cpp | 2 +- libbenbot/src/search/Search.cpp | 16 ++++++++-------- libchess/include/libchess/board/Bitboard.hpp | 2 +- libchess/include/libchess/board/Pieces.hpp | 4 ++-- libchess/include/libchess/game/Position.hpp | 6 +++--- libchess/include/libchess/moves/MoveGen.hpp | 8 ++++---- libchess/include/libchess/moves/Perft.hpp | 2 +- libchess/include/libchess/uci/EngineBase.hpp | 2 +- libchess/include/libchess/uci/Options.hpp | 4 ++-- libchess/include/libchess/util/Strings.hpp | 4 ++-- libchess/src/game/Zobrist.cpp | 2 +- libchess/src/moves/Magics.cpp | 8 ++++---- libchess/src/notation/Algebraic.cpp | 4 ++-- libchess/src/notation/EPD.cpp | 2 +- libchess/src/notation/FENHelpers.cpp | 8 ++++---- libchess/src/notation/PGN.cpp | 10 +++++----- libchess/src/notation/UCI.cpp | 2 +- libchess/src/uci/CommandParsing.cpp | 2 +- libchess/src/uci/EngineBase.cpp | 6 +++--- libchess/src/uci/Printing.cpp | 8 ++++---- libchess/src/util/Files.cpp | 2 +- libchess/src/util/Logger.cpp | 4 ++-- libchess/src/util/Strings.cpp | 4 ++-- .../util/memory/PageAlignedAlloc_Windows.hpp | 8 ++++---- tests/rampart/main.cpp | 2 +- .../data-structures/TranspositionTable.cpp | 18 +++++++++--------- tests/unit/libbenbot/eval/Score.cpp | 2 +- tests/unit/libchess/game/Position.cpp | 12 ++++++------ .../unit/libchess/game/ThreefoldRepetition.cpp | 4 ++-- tests/unit/libchess/game/ZobristHashing.cpp | 8 ++++---- tests/unit/libchess/moves/PseudoLegal.cpp | 8 ++++---- tests/unit/libchess/notation/Algebraic.cpp | 6 +++--- tests/unit/libchess/notation/EPD.cpp | 2 +- tests/unit/libchess/notation/UCI.cpp | 6 +++--- tests/unit/libchess/uci/CommandParsing.cpp | 4 ++-- tests/unit/libchess/util/Strings.cpp | 10 +++++----- 41 files changed, 111 insertions(+), 111 deletions(-) diff --git a/ben-bot/resources/src/BuildTime.cpp b/ben-bot/resources/src/BuildTime.cpp index dae075c3..30864b53 100644 --- a/ben-bot/resources/src/BuildTime.cpp +++ b/ben-bot/resources/src/BuildTime.cpp @@ -132,7 +132,7 @@ namespace { [[nodiscard, gnu::cold]] auto to_utc_time(const std::time_t time) -> std::tm { - std::tm ret {}; + std::tm ret { }; // this is written this way to avoid using the thread-unsafe function gmtime() // gmtime_s() doesn't appear to be present on MacOS, and Windows's argument order diff --git a/ben-bot/src/Bench.cpp b/ben-bot/src/Bench.cpp index d3d02f02..461327eb 100644 --- a/ben-bot/src/Bench.cpp +++ b/ben-bot/src/Bench.cpp @@ -161,13 +161,13 @@ namespace { const auto totalNodes = std::transform_reduce( results.begin(), results.end(), 0uz, - std::plus {}, + std::plus { }, [](const SearchResult& result) { return result.nodesSearched; }); const auto totalTime = std::transform_reduce( results.begin(), results.end(), milliseconds { 0 }, - std::plus {}, + std::plus { }, [](const SearchResult& result) { return result.duration; }); const auto seconds = static_cast(totalTime.count()) * 0.001; diff --git a/libbenbot/include/libbenbot/data-structures/KillerMoves.hpp b/libbenbot/include/libbenbot/data-structures/KillerMoves.hpp index fa8e5fb8..67f19d28 100644 --- a/libbenbot/include/libbenbot/data-structures/KillerMoves.hpp +++ b/libbenbot/include/libbenbot/data-structures/KillerMoves.hpp @@ -68,7 +68,7 @@ struct KillerMoves final { private: using Killers = std::vector; - std::array lists {}; + std::array lists { }; }; } // namespace ben_bot diff --git a/libbenbot/src/data-structures/TranspositionTable.cpp b/libbenbot/src/data-structures/TranspositionTable.cpp index 21b37315..6619ff38 100644 --- a/libbenbot/src/data-structures/TranspositionTable.cpp +++ b/libbenbot/src/data-structures/TranspositionTable.cpp @@ -96,7 +96,7 @@ struct TranspositionTable::Entry final { generation = gen; eval = static_cast(data.eval); evalType = data.evalType; - move = data.bestMove.value_or(Move {}); + move = data.bestMove.value_or(Move { }); } }; @@ -105,7 +105,7 @@ inline constexpr auto CACHE_LINE_SIZE = std::hardware_constructive_interference_ struct alignas(CACHE_LINE_SIZE) TranspositionTable::Cluster final { static constexpr auto NumRecords = CACHE_LINE_SIZE / sizeof(Entry); - std::array records {}; + std::array records { }; }; TranspositionTable::TranspositionTable(TranspositionTable&& other) noexcept @@ -162,7 +162,7 @@ void TranspositionTable::resize(const size_t sizeMB) if (table == nullptr) { clusterCount = 0uz; - throw std::bad_alloc {}; + throw std::bad_alloc { }; } std::uninitialized_default_construct_n(table, clusterCount); @@ -171,7 +171,7 @@ void TranspositionTable::resize(const size_t sizeMB) void TranspositionTable::clear() { for (auto i = 0uz; i < clusterCount; ++i) - std::ranges::fill(index_table(i).records, Entry {}); + std::ranges::fill(index_table(i).records, Entry { }); } void TranspositionTable::deallocate() @@ -193,7 +193,7 @@ auto TranspositionTable::hashfull() const -> size_t const auto count = std::transform_reduce( indices.begin(), indices.end(), 0uz, - std::plus {}, + std::plus { }, [this](const size_t idx) { return static_cast(std::ranges::count_if( index_table(idx).records, diff --git a/libbenbot/src/eval/PieceSquareTables.cpp b/libbenbot/src/eval/PieceSquareTables.cpp index c58ae3c1..97466ea3 100644 --- a/libbenbot/src/eval/PieceSquareTables.cpp +++ b/libbenbot/src/eval/PieceSquareTables.cpp @@ -43,7 +43,7 @@ namespace { return std::transform_reduce( indices.begin(), indices.end(), 0, - std::plus {}, + std::plus { }, [table](const auto idx) { assert(idx < table.size()); return table[idx]; diff --git a/libbenbot/src/search/Result.cpp b/libbenbot/src/search/Result.cpp index 8777470e..97c6e9d3 100644 --- a/libbenbot/src/search/Result.cpp +++ b/libbenbot/src/search/Result.cpp @@ -25,7 +25,7 @@ namespace { const Result& res, const bool includeDebugInfo) -> std::string { if (not includeDebugInfo) - return {}; + return { }; if (res.nodesSearched == 0uz) { return std::format( diff --git a/libbenbot/src/search/Search.cpp b/libbenbot/src/search/Search.cpp index 770b442b..cb7f52ec 100644 --- a/libbenbot/src/search/Search.cpp +++ b/libbenbot/src/search/Search.cpp @@ -86,7 +86,7 @@ namespace { } private: - std::array moves {}; + std::array moves { }; size_t length { 0uz }; }; @@ -114,7 +114,7 @@ namespace { [[nodiscard]] auto alpha_beta() -> Score // NOLINT(readability-function-cognitive-complexity) { if (interrupter.should_abort(plyFromRoot)) - return {}; + return { }; transTable.prefetch(position); @@ -122,7 +122,7 @@ namespace { // because the table only contains static evaluations and doesn't consider game // history, so its stored evaluations can't detect threefold repetition draws if (position.is_threefold_repetition()) - return {}; + return { }; if (const auto cutoff = bounds.mate_distance_pruning(plyFromRoot)) { ++stats.mdpCutoffs; @@ -143,7 +143,7 @@ namespace { .evalType = EvalType::Exact, .bestMove = std::nullopt }); - return {}; + return { }; } const bool inCheck = position.is_check(); @@ -208,7 +208,7 @@ namespace { ++stats.nodesSearched; if (interrupter.was_aborted()) - return {}; + return { }; if (eval >= bounds.beta) { transTable.store( @@ -251,7 +251,7 @@ namespace { [[nodiscard]] auto quiescence() -> Score { if (interrupter.should_abort(plyFromRoot) or position.is_draw()) - return {}; + return { }; stats.qDepth = std::max(stats.qDepth, plyFromRoot); @@ -287,7 +287,7 @@ namespace { ++stats.nodesSearched; if (interrupter.was_aborted()) - return {}; + return { }; if (evaluation >= bounds.beta) { ++stats.betaCutoffs; @@ -366,7 +366,7 @@ namespace { killerMoves.clear(); detail::order_moves_for_search( - options.position, options.movesToSearch, transTable, {}); + options.position, options.movesToSearch, transTable, { }); Stats stats; Bounds bounds; diff --git a/libchess/include/libchess/board/Bitboard.hpp b/libchess/include/libchess/board/Bitboard.hpp index 76f1e5ac..921e60ec 100644 --- a/libchess/include/libchess/board/Bitboard.hpp +++ b/libchess/include/libchess/board/Bitboard.hpp @@ -453,7 +453,7 @@ constexpr auto Bitboard::indices() const noexcept { return std::ranges::subrange { detail::BitboardIterator { *this }, - detail::BitboardIterator {}, + detail::BitboardIterator { }, count() }; } diff --git a/libchess/include/libchess/board/Pieces.hpp b/libchess/include/libchess/board/Pieces.hpp index d568b742..b1b8df9d 100644 --- a/libchess/include/libchess/board/Pieces.hpp +++ b/libchess/include/libchess/board/Pieces.hpp @@ -273,7 +273,7 @@ constexpr void Pieces::our_move(const moves::Move move, const Color ourColor) no .transform([move, this](const PieceType type) { get_type(move.piece()).unset(move.from()); get_type(type).set(move.to()); - return std::monostate {}; + return std::monostate { }; }) .or_else([move, movementMask, ourColor, this] { get_type(move.piece()) ^= movementMask; @@ -287,7 +287,7 @@ constexpr void Pieces::our_move(const moves::Move move, const Color ourColor) no occupied ^= castleMask; } - return std::make_optional(std::monostate {}); + return std::make_optional(std::monostate { }); }); } diff --git a/libchess/include/libchess/game/Position.hpp b/libchess/include/libchess/game/Position.hpp index 71067bf6..8451f577 100644 --- a/libchess/include/libchess/game/Position.hpp +++ b/libchess/include/libchess/game/Position.hpp @@ -426,10 +426,10 @@ struct [[nodiscard]] Position final { inline auto Position::empty() -> Position { - Position pos {}; + Position pos { }; - pos.whitePieces = {}; - pos.blackPieces = {}; + pos.whitePieces = { }; + pos.blackPieces = { }; pos.refresh_zobrist(); diff --git a/libchess/include/libchess/moves/MoveGen.hpp b/libchess/include/libchess/moves/MoveGen.hpp index 9344545e..89d1ab89 100644 --- a/libchess/include/libchess/moves/MoveGen.hpp +++ b/libchess/include/libchess/moves/MoveGen.hpp @@ -314,7 +314,7 @@ namespace detail { }) | std::ranges::to(); }) - .value_or(EPMoves {}); + .value_or(EPMoves { }); } template @@ -592,20 +592,20 @@ namespace detail { // castling out of check is not allowed if (position.is_check()) - return Moves {}; + return Moves { }; beman::inplace_vector::inplace_vector moves; get_kingside_castling(position, allOccupied) .transform([&moves](const Move move) { moves.emplace_back(move); - return std::monostate {}; + return std::monostate { }; }); get_queenside_castling(position, allOccupied) .transform([&moves](const Move move) { moves.emplace_back(move); - return std::monostate {}; + return std::monostate { }; }); return moves; diff --git a/libchess/include/libchess/moves/Perft.hpp b/libchess/include/libchess/moves/Perft.hpp index 4779e83d..b51cb4d5 100644 --- a/libchess/include/libchess/moves/Perft.hpp +++ b/libchess/include/libchess/moves/Perft.hpp @@ -131,7 +131,7 @@ auto perft( // NOLINT(readability-function-cognitive-complexity) .checks = 0uz, .checkmates = 0uz, .stalemates = 0uz, - .rootNodes = {} + .rootNodes = { } }; PerftResult result; diff --git a/libchess/include/libchess/uci/EngineBase.hpp b/libchess/include/libchess/uci/EngineBase.hpp index 1daad3d9..26ac953c 100644 --- a/libchess/include/libchess/uci/EngineBase.hpp +++ b/libchess/include/libchess/uci/EngineBase.hpp @@ -68,7 +68,7 @@ struct EngineBase { [[nodiscard]] virtual auto get_author() const -> string_view = 0; /** This must return the list of all options the engine supports. */ - [[nodiscard]] virtual auto get_options() -> std::span { return {}; } + [[nodiscard]] virtual auto get_options() -> std::span { return { }; } /** This function will be called when the "isready" command is received, and may block while waiting for background tasks to complete. This diff --git a/libchess/include/libchess/uci/Options.hpp b/libchess/include/libchess/uci/Options.hpp index afa63479..8f0579bc 100644 --- a/libchess/include/libchess/uci/Options.hpp +++ b/libchess/include/libchess/uci/Options.hpp @@ -251,14 +251,14 @@ struct StringOption final : Option { [[nodiscard]] auto get_value() const noexcept -> string_view { if (value == "") - return {}; + return { }; return value; } [[nodiscard]] auto get_value_variant() const -> Variant override { return get_value(); } - [[nodiscard]] auto get_default_value_variant() const -> Variant override { return string_view {}; } + [[nodiscard]] auto get_default_value_variant() const -> Variant override { return string_view { }; } [[nodiscard]] auto get_name() const noexcept -> string_view override { return optionName; } diff --git a/libchess/include/libchess/util/Strings.hpp b/libchess/include/libchess/util/Strings.hpp index 7e68c874..204916b2 100644 --- a/libchess/include/libchess/util/Strings.hpp +++ b/libchess/include/libchess/util/Strings.hpp @@ -166,7 +166,7 @@ void write_integer( const std::integral auto value, std::string& output) { - std::array buffer {}; + std::array buffer { }; const auto result = std::to_chars( buffer.data(), @@ -174,7 +174,7 @@ void write_integer( value); // simply do nothing on failure - if (result.ec != std::errc {}) + if (result.ec != std::errc { }) return; output.append( diff --git a/libchess/src/game/Zobrist.cpp b/libchess/src/game/Zobrist.cpp index 185b46e8..2c2852ff 100644 --- a/libchess/src/game/Zobrist.cpp +++ b/libchess/src/game/Zobrist.cpp @@ -69,7 +69,7 @@ namespace { return std::transform_reduce( squares.begin(), squares.end(), prevValue, - std::bit_xor<> {}, + std::bit_xor<> { }, [type](const Square square) { return piece_key(type, Side, square); }); diff --git a/libchess/src/moves/Magics.cpp b/libchess/src/moves/Magics.cpp index f5cf4533..e7e08c6d 100644 --- a/libchess/src/moves/Magics.cpp +++ b/libchess/src/moves/Magics.cpp @@ -43,7 +43,7 @@ namespace { [[nodiscard]] consteval auto calculate_bishop_masks() -> MaskArray { - MaskArray result {}; + MaskArray result { }; std::ranges::transform( masks::ALL.subboards(), @@ -60,7 +60,7 @@ namespace { using board::File; using board::Rank; - MaskArray result {}; + MaskArray result { }; for (const auto square : masks::ALL.squares()) { auto& value = result.at(square.index()); @@ -153,7 +153,7 @@ namespace { value = pseudo_legal::bishop( Bitboard::from_square(square), - occupied.inverse(), {}); + occupied.inverse(), { }); occupied = permute(BISHOP_MASKS.at(i), occupied); } while (occupied.any()); @@ -168,7 +168,7 @@ namespace { value = pseudo_legal::rook( Bitboard::from_square(square), - occupied.inverse(), {}); + occupied.inverse(), { }); occupied = permute(ROOK_MASKS.at(i), occupied); } while (occupied.any()); diff --git a/libchess/src/notation/Algebraic.cpp b/libchess/src/notation/Algebraic.cpp index 59a51305..0f5a03af 100644 --- a/libchess/src/notation/Algebraic.cpp +++ b/libchess/src/notation/Algebraic.cpp @@ -66,7 +66,7 @@ namespace { const auto newPos = after_move(position, move); if (not newPos.is_check()) - return {}; + return { }; if (not moves::any_legal_moves(newPos)) return "#"; // checkmate @@ -80,7 +80,7 @@ namespace { const auto pieceMoves = get_possible_move_origins(position, move.to(), move.piece()); if (std::cmp_less(pieceMoves.size(), 2)) - return {}; + return { }; // Order of preference for disambiguation: // 1. file of departure if different diff --git a/libchess/src/notation/EPD.cpp b/libchess/src/notation/EPD.cpp index 5b79c4a6..d4df709d 100644 --- a/libchess/src/notation/EPD.cpp +++ b/libchess/src/notation/EPD.cpp @@ -81,7 +81,7 @@ auto from_epd(string_view epdString) -> PositionOrError EPDPosition pos { .position = Position::empty(), - .operations = {} + .operations = { } }; const auto [piecePositions, rest1] = split_at_first_space(epdString); diff --git a/libchess/src/notation/FENHelpers.cpp b/libchess/src/notation/FENHelpers.cpp index e074a550..be585864 100644 --- a/libchess/src/notation/FENHelpers.cpp +++ b/libchess/src/notation/FENHelpers.cpp @@ -129,12 +129,12 @@ void write_en_passant_target_square( output.push_back(file_to_char(epSquare.file)); output.push_back(rank_to_char(epSquare.rank)); - return std::monostate {}; + return std::monostate { }; }) .or_else([&output] { output.push_back('-'); - return std::make_optional(std::monostate {}); + return std::make_optional(std::monostate { }); }); } @@ -219,7 +219,7 @@ auto parse_piece_positions( position.whitePieces.refresh_occupied(); position.blackPieces.refresh_occupied(); - return {}; + return { }; } auto parse_side_to_move( @@ -238,7 +238,7 @@ auto parse_side_to_move( position.sideToMove = isBlack ? Color::Black : Color::White; - return {}; + return { }; } void parse_castling_rights( diff --git a/libchess/src/notation/PGN.cpp b/libchess/src/notation/PGN.cpp index 5141fbb9..f979f613 100644 --- a/libchess/src/notation/PGN.cpp +++ b/libchess/src/notation/PGN.cpp @@ -138,7 +138,7 @@ namespace { if (not output.empty()) output.back().comment = trim(pgnText.substr(1uz)); - return {}; + return { }; } if (not output.empty()) @@ -210,7 +210,7 @@ namespace { pgnText = trim(pgnText); if (pgnText.empty()) - return {}; + return { }; switch (pgnText.front()) { case '{': { @@ -348,7 +348,7 @@ auto from_pgn(const string_view pgnText) -> GameOrError .and_then([&game](const string_view afterMeta) -> GameOrError { if (const auto posStr = game.metadata.find("FEN"); posStr != game.metadata.end()) { - game.startingPosition = from_fen(posStr->second).value_or(Position {}); + game.startingPosition = from_fen(posStr->second).value_or(Position { }); } return parse_move_list(afterMeta, game.startingPosition, game.moves) @@ -467,7 +467,7 @@ namespace { write_metadata_item(key, value, output); } - static const Position startPos {}; + static const Position startPos { }; if (startingPosition != startPos) { if (not metadata.contains("FEN"s)) { @@ -559,7 +559,7 @@ namespace { default: std::unreachable(); } - return std::monostate {}; + return std::monostate { }; }); } diff --git a/libchess/src/notation/UCI.cpp b/libchess/src/notation/UCI.cpp index e0a0cd98..ef29efe2 100644 --- a/libchess/src/notation/UCI.cpp +++ b/libchess/src/notation/UCI.cpp @@ -68,7 +68,7 @@ auto from_uci( return std::unexpected { "Cannot parse UCI move from empty string" }; if (text == UCI_NULL_MOVE) - return Move {}; + return Move { }; return Square::from_string(text.substr(0uz, 2uz)) .and_then([&text, &position](const Square from) { diff --git a/libchess/src/uci/CommandParsing.cpp b/libchess/src/uci/CommandParsing.cpp index 4dc0d73b..72f24c4d 100644 --- a/libchess/src/uci/CommandParsing.cpp +++ b/libchess/src/uci/CommandParsing.cpp @@ -50,7 +50,7 @@ auto parse_position_options(string_view options) options = trim(options); - Position position {}; + Position position { }; auto [secondWord, rest] = split_at_first_space(options); diff --git a/libchess/src/uci/EngineBase.cpp b/libchess/src/uci/EngineBase.cpp index 38909c0a..41b2ae0f 100644 --- a/libchess/src/uci/EngineBase.cpp +++ b/libchess/src/uci/EngineBase.cpp @@ -165,13 +165,13 @@ void EngineBase::handle_setpos(const string_view arguments) set_position(pos); - return {}; + return { }; }) .transform_error([this](const string_view error) { info_string(std::format("Error setting position: {}", error)); info_string(std::format("Retained previous position: {}", notation::to_fen(position))); - return std::monostate {}; + return std::monostate { }; }); } @@ -209,7 +209,7 @@ void EngineBase::handle_setoption(const string_view arguments) auto* option = *it; if (isNPos) - option->handle_setvalue({}); + option->handle_setvalue({ }); else option->handle_setvalue(trim(rest.substr(valueTokenIdx))); } else { diff --git a/libchess/src/uci/Printing.cpp b/libchess/src/uci/Printing.cpp index bd0f2aef..6bbecab4 100644 --- a/libchess/src/uci/Printing.cpp +++ b/libchess/src/uci/Printing.cpp @@ -44,7 +44,7 @@ std::monostate info_string(const string_view info) { println(cout, "info string {}", info); - return std::monostate {}; + return std::monostate { }; } namespace { @@ -56,7 +56,7 @@ namespace { .transform([](const Move move) { return std::format(" ponder {}", to_uci(move)); }) - .value_or(string {}); + .value_or(string { }); } } // namespace @@ -106,7 +106,7 @@ namespace { { if (pv.empty()) { // this is possible if we're checkmated - return {}; + return { }; } string result { " pv " }; @@ -136,7 +136,7 @@ namespace { [[nodiscard]] auto get_extra_info_string(const string_view info) -> string { if (info.empty()) - return {}; + return { }; return std::format(" string {}", info); } diff --git a/libchess/src/util/Files.cpp b/libchess/src/util/Files.cpp index 0773f12f..fbb689df 100644 --- a/libchess/src/util/Files.cpp +++ b/libchess/src/util/Files.cpp @@ -44,7 +44,7 @@ auto load_file_as_string( using Iterator = std::istreambuf_iterator; - return string { Iterator { input }, Iterator {} }; + return string { Iterator { input }, Iterator { } }; } catch (const std::exception& exception) { return std::unexpected { std::format( "Error while reading file at path '{}': {}", diff --git a/libchess/src/util/Logger.cpp b/libchess/src/util/Logger.cpp index d0880c5b..59af4486 100644 --- a/libchess/src/util/Logger.cpp +++ b/libchess/src/util/Logger.cpp @@ -133,7 +133,7 @@ auto Logger::start(const path& logFile) -> MaybeError logger.close_log_file(); if (logFile.empty()) - return {}; + return { }; logger.file.open(logFile, std::ios_base::out); @@ -148,7 +148,7 @@ auto Logger::start(const path& logFile) -> MaybeError std::cin.rdbuf(&logger.in); std::cout.rdbuf(&logger.out); - return {}; + return { }; } void Logger::close_log_file() diff --git a/libchess/src/util/Strings.cpp b/libchess/src/util/Strings.cpp index 67a35c80..917a6ff9 100644 --- a/libchess/src/util/Strings.cpp +++ b/libchess/src/util/Strings.cpp @@ -103,7 +103,7 @@ auto split_at_first_space(const string_view input) -> StringViewPair const auto spaceIdx = input.find(' '); if (spaceIdx == string_view::npos) - return { input, {} }; + return { input, { } }; return { input.substr(0uz, spaceIdx), @@ -118,7 +118,7 @@ auto split_at_first_space_or_newline(const string_view input) -> StringViewPair input.find('\n')); if (firstDelimIdx == string_view::npos) - return { input, {} }; + return { input, { } }; return { input.substr(0uz, firstDelimIdx), diff --git a/libchess/src/util/memory/PageAlignedAlloc_Windows.hpp b/libchess/src/util/memory/PageAlignedAlloc_Windows.hpp index 0a8c1e1d..4a6c6661 100644 --- a/libchess/src/util/memory/PageAlignedAlloc_Windows.hpp +++ b/libchess/src/util/memory/PageAlignedAlloc_Windows.hpp @@ -86,19 +86,19 @@ namespace impl { // We need SeLockMemoryPrivilege, so try to enable it for the process - HANDLE hProcessToken {}; + HANDLE hProcessToken { }; if (! OpenProcessToken_f( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hProcessToken)) { return nullptr; } - LUID luid {}; + LUID luid { }; void* mem { nullptr }; if (LookupPrivilegeValueA_f(nullptr, "SeLockMemoryPrivilege", &luid)) { - TOKEN_PRIVILEGES tp {}; // NOLINT(readability-identifier-length) - TOKEN_PRIVILEGES prevTp {}; + TOKEN_PRIVILEGES tp { }; // NOLINT(readability-identifier-length) + TOKEN_PRIVILEGES prevTp { }; DWORD prevTpLen = 0; tp.PrivilegeCount = 1; diff --git a/tests/rampart/main.cpp b/tests/rampart/main.cpp index 68342ce8..a6d9fe2d 100644 --- a/tests/rampart/main.cpp +++ b/tests/rampart/main.cpp @@ -102,7 +102,7 @@ try { }) .transform_error([](const std::string_view error) { std::println(std::cerr, "Error: {}", error); - return std::monostate {}; + return std::monostate { }; }); return EXIT_SUCCESS; diff --git a/tests/unit/libbenbot/data-structures/TranspositionTable.cpp b/tests/unit/libbenbot/data-structures/TranspositionTable.cpp index cfd3374c..578b5c89 100644 --- a/tests/unit/libbenbot/data-structures/TranspositionTable.cpp +++ b/tests/unit/libbenbot/data-structures/TranspositionTable.cpp @@ -28,7 +28,7 @@ using EvalType = ben_bot::EvalType; namespace notation = chess::notation; -static const Position startPos {}; +static const Position startPos { }; TEST_CASE("Transposition table - find()", TAGS) { @@ -36,7 +36,7 @@ TEST_CASE("Transposition table - find()", TAGS) .searchedDepth = 1uz, .eval = 0, .evalType = EvalType::Exact, - .bestMove = {} + .bestMove = { } }; const auto pos2 = notation::from_fen("8/8/4n3/2B1k1p1/3Pn3/2K5/5R2/8 b - - 0 1") @@ -118,7 +118,7 @@ TEST_CASE("Transposition table - probe_eval()", TAGS) { .searchedDepth = DEPTH, .eval = EVAL, .evalType = EvalType::Exact, - .bestMove = {} }); + .bestMove = { } }); const auto probed = table.probe_eval(startPos, DEPTH, BOUNDS); @@ -136,7 +136,7 @@ TEST_CASE("Transposition table - probe_eval()", TAGS) { .searchedDepth = DEPTH, .eval = EVAL, .evalType = EvalType::Alpha, - .bestMove = {} }); + .bestMove = { } }); SECTION("Probing with alpha that doesn't cutoff") { @@ -170,7 +170,7 @@ TEST_CASE("Transposition table - probe_eval()", TAGS) { .searchedDepth = DEPTH, .eval = EVAL, .evalType = EvalType::Beta, - .bestMove = {} }); + .bestMove = { } }); SECTION("Probing with beta that doesn't cutoff") { @@ -207,7 +207,7 @@ TEST_CASE("Transposition table - store() overwriting rules", TAGS) .searchedDepth = 6uz, .eval = 2, .evalType = EvalType::Exact, - .bestMove = {} + .bestMove = { } }; table.store(startPos, oldRecord); @@ -220,7 +220,7 @@ TEST_CASE("Transposition table - store() overwriting rules", TAGS) .searchedDepth = oldRecord.searchedDepth - 1uz, .eval = 4, .evalType = EvalType::Exact, - .bestMove = {} + .bestMove = { } }; table.store(startPos, newRecord); @@ -236,7 +236,7 @@ TEST_CASE("Transposition table - store() overwriting rules", TAGS) .searchedDepth = oldRecord.searchedDepth, .eval = -6, .evalType = EvalType::Alpha, - .bestMove = {} + .bestMove = { } }; table.store(startPos, newRecord); @@ -250,7 +250,7 @@ TEST_CASE("Transposition table - store() overwriting rules", TAGS) .searchedDepth = oldRecord.searchedDepth, .eval = 6, .evalType = EvalType::Beta, - .bestMove = {} + .bestMove = { } }; table.store(startPos, newRecord); diff --git a/tests/unit/libbenbot/eval/Score.cpp b/tests/unit/libbenbot/eval/Score.cpp index fbd68be0..07a49842 100644 --- a/tests/unit/libbenbot/eval/Score.cpp +++ b/tests/unit/libbenbot/eval/Score.cpp @@ -21,7 +21,7 @@ using ben_bot::eval::Score; TEST_CASE("Score - is_mate()", TAGS) { - REQUIRE_FALSE(Score {}.is_mate()); + REQUIRE_FALSE(Score { }.is_mate()); static constexpr auto mate = Score::mate(0uz); diff --git a/tests/unit/libchess/game/Position.cpp b/tests/unit/libchess/game/Position.cpp index 38e76b01..529c8901 100644 --- a/tests/unit/libchess/game/Position.cpp +++ b/tests/unit/libchess/game/Position.cpp @@ -36,7 +36,7 @@ using chess::notation::from_fen; TEST_CASE("Position - starting", TAGS) { - const Position pos {}; + const Position pos { }; REQUIRE(pos.sideToMove == Color::White); REQUIRE_FALSE(pos.enPassantTargetSquare); @@ -115,7 +115,7 @@ TEST_CASE("Position - is_check()", TAGS) { SECTION("Starting position") { - const Position startingPosition {}; + const Position startingPosition { }; REQUIRE_FALSE(startingPosition.is_check()); REQUIRE_FALSE(startingPosition.is_checkmate()); @@ -248,7 +248,7 @@ TEST_CASE("Position - passed pawns", TAGS) { SECTION("Starting position") { - const Position startingPosition {}; + const Position startingPosition { }; REQUIRE(startingPosition.get_passed_pawns().none()); REQUIRE(startingPosition.get_passed_pawns().none()); @@ -277,7 +277,7 @@ TEST_CASE("Position - backward pawns", TAGS) { SECTION("Starting position") { - const Position startingPosition {}; + const Position startingPosition { }; REQUIRE(startingPosition.get_backward_pawns().none()); REQUIRE(startingPosition.get_backward_pawns().none()); @@ -307,7 +307,7 @@ TEST_CASE("Position - backward pawns", TAGS) TEST_CASE("Position - null move", TAGS) { - const Position startingPosition {}; + const Position startingPosition { }; const auto afterNull = after_null_move(startingPosition); @@ -316,7 +316,7 @@ TEST_CASE("Position - null move", TAGS) TEST_CASE("Position - fifty-move draws", TAGS) { - REQUIRE(! Position {}.is_fifty_move_draw()); + REQUIRE(! Position { }.is_fifty_move_draw()); REQUIRE(from_fen("7k/4NK2/5r2/5BN1/8/8/8/8 w - - 103 115").value().is_fifty_move_draw()); REQUIRE(from_fen("8/7k/8/1r3KR1/5B2/8/8/8 w - - 105 122").value().is_fifty_move_draw()); diff --git a/tests/unit/libchess/game/ThreefoldRepetition.cpp b/tests/unit/libchess/game/ThreefoldRepetition.cpp index 0e2b86c1..09dbf23e 100644 --- a/tests/unit/libchess/game/ThreefoldRepetition.cpp +++ b/tests/unit/libchess/game/ThreefoldRepetition.cpp @@ -23,7 +23,7 @@ using chess::notation::from_alg; TEST_CASE("Position - threefold repetitions", TAGS) { - chess::game::Position pos {}; + chess::game::Position pos { }; REQUIRE_FALSE(pos.is_threefold_repetition()); @@ -58,7 +58,7 @@ TEST_CASE("Position - threefold repetitions", TAGS) TEST_CASE("Threefold repetition from differing moves", TAGS) { - chess::game::Position pos {}; + chess::game::Position pos { }; pos.make_move(from_alg(pos, "Nf3").value()); pos.make_move(from_alg(pos, "Nf6").value()); diff --git a/tests/unit/libchess/game/ZobristHashing.cpp b/tests/unit/libchess/game/ZobristHashing.cpp index 5cfe7490..2ce68035 100644 --- a/tests/unit/libchess/game/ZobristHashing.cpp +++ b/tests/unit/libchess/game/ZobristHashing.cpp @@ -27,7 +27,7 @@ using chess::notation::from_fen; TEST_CASE("Zobrist - starting position", TAGS) { - const chess::game::Position startPosition {}; + const chess::game::Position startPosition { }; const auto startPos = from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") .value(); @@ -40,7 +40,7 @@ TEST_CASE("Zobrist - reaching identical positions", TAGS) const auto position = from_fen("rnbqkbnr/ppp1pppp/8/3P4/8/8/PPPP1PPP/RNBQKBNR b KQkq - 0 2") .value(); - chess::game::Position pos {}; + chess::game::Position pos { }; pos.make_move(from_alg(pos, "e4").value()); pos.make_move(from_alg(pos, "d5").value()); @@ -55,7 +55,7 @@ TEST_CASE("Zobrist - hash changes", TAGS) SECTION("From start pos") { - const chess::game::Position pos {}; + const chess::game::Position pos { }; const auto oldHash = pos.hash; @@ -102,7 +102,7 @@ TEST_CASE("Zobrist - loading identical FENs", TAGS) TEST_CASE("Zobrist - repeated positions", TAGS) { - chess::game::Position pos {}; + chess::game::Position pos { }; const auto origHash = pos.hash; diff --git a/tests/unit/libchess/moves/PseudoLegal.cpp b/tests/unit/libchess/moves/PseudoLegal.cpp index beb50c70..a1b7fbed 100644 --- a/tests/unit/libchess/moves/PseudoLegal.cpp +++ b/tests/unit/libchess/moves/PseudoLegal.cpp @@ -126,7 +126,7 @@ TEST_CASE("Pseudo-legal - pawn double pushes", TAGS) static constexpr auto starting = starting_masks::white::PAWNS; static constexpr auto allPushes = move_gen::pawn_double_pushes( - starting, {}); + starting, { }); STATIC_REQUIRE(allPushes == board_masks::ranks::FOUR); @@ -153,7 +153,7 @@ TEST_CASE("Pseudo-legal - pawn double pushes", TAGS) static constexpr auto starting = starting_masks::black::PAWNS; static constexpr auto allPushes = move_gen::pawn_double_pushes( - starting, {}); + starting, { }); STATIC_REQUIRE(allPushes == board_masks::ranks::FIVE); @@ -357,7 +357,7 @@ TEST_CASE("Pseudo-legal - knights", TAGS) { static constexpr auto starting = Bitboard::from_square(Square { File::D, Rank::One }); - static constexpr auto allMoves = move_gen::knight(starting, {}); + static constexpr auto allMoves = move_gen::knight(starting, { }); STATIC_REQUIRE(allMoves.count() == 4uz); @@ -386,7 +386,7 @@ TEST_CASE("Pseudo-legal - knights", TAGS) starting.set(Square { File::E, Rank::Four }); starting.set(Square { File::D, Rank::Four }); - const auto allMoves = move_gen::knight(starting, {}); + const auto allMoves = move_gen::knight(starting, { }); REQUIRE(allMoves.count() == 16uz); diff --git a/tests/unit/libchess/notation/Algebraic.cpp b/tests/unit/libchess/notation/Algebraic.cpp index 4dd0fcf5..d1b56da8 100644 --- a/tests/unit/libchess/notation/Algebraic.cpp +++ b/tests/unit/libchess/notation/Algebraic.cpp @@ -39,7 +39,7 @@ TEST_CASE("Algebraic notation - piece moves", TAGS) { SECTION("Knights") { - Position position {}; + Position position { }; { const auto move = from_alg(position, "Nc3").value(); @@ -405,7 +405,7 @@ TEST_CASE("Algebraic notation - pawn pushes", TAGS) { SECTION("Normal") { - const Position startingPosition {}; + const Position startingPosition { }; const auto move = from_alg(startingPosition, "e3").value(); @@ -457,7 +457,7 @@ TEST_CASE("Algebraic notation - pawn double pushes", TAGS) { SECTION("Normal") { - Position startingPosition {}; + Position startingPosition { }; const auto move = from_alg(startingPosition, "e4").value(); diff --git a/tests/unit/libchess/notation/EPD.cpp b/tests/unit/libchess/notation/EPD.cpp index 37b808af..ccb35e69 100644 --- a/tests/unit/libchess/notation/EPD.cpp +++ b/tests/unit/libchess/notation/EPD.cpp @@ -23,7 +23,7 @@ using chess::notation::from_epd; TEST_CASE("EPD - start position", TAGS) { - const chess::game::Position startPos {}; + const chess::game::Position startPos { }; const auto epd = from_epd("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - hmvc 0; fmvn 1;") .value(); diff --git a/tests/unit/libchess/notation/UCI.cpp b/tests/unit/libchess/notation/UCI.cpp index 81c04223..391d644a 100644 --- a/tests/unit/libchess/notation/UCI.cpp +++ b/tests/unit/libchess/notation/UCI.cpp @@ -35,7 +35,7 @@ using chess::notation::to_uci; TEST_CASE("UCI notation - normal moves", TAGS) { - const chess::game::Position startingPosition {}; + const chess::game::Position startingPosition { }; SECTION("Pawn move") { @@ -238,11 +238,11 @@ TEST_CASE("UCI notation - promotions", TAGS) TEST_CASE("UCI notation - null moves", TAGS) { - const chess::game::Position position {}; + const chess::game::Position position { }; const auto move = from_uci(position, "0000").value(); REQUIRE(move.is_null()); - REQUIRE(to_uci(chess::moves::Move {}) == "0000"); + REQUIRE(to_uci(chess::moves::Move { }) == "0000"); } diff --git a/tests/unit/libchess/uci/CommandParsing.cpp b/tests/unit/libchess/uci/CommandParsing.cpp index 16ab77ad..6f3323ff 100644 --- a/tests/unit/libchess/uci/CommandParsing.cpp +++ b/tests/unit/libchess/uci/CommandParsing.cpp @@ -33,7 +33,7 @@ TEST_CASE("UCI parsing - position", TAGS) SECTION("From start position") { - const Position startPos {}; + const Position startPos { }; REQUIRE(parse_position_options(" startpos \n") == startPos); @@ -59,7 +59,7 @@ TEST_CASE("UCI parsing - go", TAGS) { using chess::uci::parse_go_options; - const Position startPos {}; + const Position startPos { }; SECTION("No arguments") { diff --git a/tests/unit/libchess/util/Strings.cpp b/tests/unit/libchess/util/Strings.cpp index 646c99e8..ba641a71 100644 --- a/tests/unit/libchess/util/Strings.cpp +++ b/tests/unit/libchess/util/Strings.cpp @@ -24,7 +24,7 @@ TEST_CASE("Strings - trim()", TAGS) { using chess::util::strings::trim; - REQUIRE(trim({}).empty()); + REQUIRE(trim({ }).empty()); REQUIRE(trim(" ").empty()); REQUIRE(trim("\n").empty()); @@ -44,7 +44,7 @@ TEST_CASE("Strings - split_at_first_space()", TAGS) SECTION("Empty string") { - const auto [before, after] = split_at_first_space({}); + const auto [before, after] = split_at_first_space({ }); REQUIRE(before.empty()); REQUIRE(after.empty()); @@ -89,7 +89,7 @@ TEST_CASE("Strings - split_at_first_space_or_newline()", TAGS) SECTION("Empty string") { - const auto [before, after] = split_at_first_space_or_newline({}); + const auto [before, after] = split_at_first_space_or_newline({ }); REQUIRE(before.empty()); REQUIRE(after.empty()); @@ -173,7 +173,7 @@ TEST_CASE("Strings - lines_view()", TAGS) | std::ranges::to(); }; - REQUIRE(lines_vector({}).empty()); + REQUIRE(lines_vector({ }).empty()); SECTION("Single line") { @@ -201,7 +201,7 @@ TEST_CASE("Strings - words_view()", TAGS) | std::ranges::to(); }; - REQUIRE(words_vector({}).empty()); + REQUIRE(words_vector({ }).empty()); SECTION("Single word") { From bfcb56745960eaa3ae7890c90522be65792dd53c Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 26 Feb 2026 23:14:24 -0600 Subject: [PATCH 12/23] refactor: misc --- libbenbot/include/libbenbot/search/Callbacks.hpp | 2 +- libbenbot/src/search/Callbacks.cpp | 13 +++++++++---- libchess/src/uci/Printing.cpp | 3 ++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/libbenbot/include/libbenbot/search/Callbacks.hpp b/libbenbot/include/libbenbot/search/Callbacks.hpp index 2bbd746f..1acffd4f 100644 --- a/libbenbot/include/libbenbot/search/Callbacks.hpp +++ b/libbenbot/include/libbenbot/search/Callbacks.hpp @@ -67,7 +67,7 @@ struct Callbacks final { should be included in the information output. */ [[nodiscard]] static auto make_uci_printer( - std::function isDebugMode) + std::function&& isDebugMode) -> Callbacks; }; diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 0eff73fc..2cc03204 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -16,18 +16,23 @@ #include #include #include +#include namespace ben_bot::search { auto Callbacks::make_uci_printer( - std::function isDebugMode) + std::function&& isDebugMode) -> Callbacks { + auto printInfo = [isDebug = std::move(isDebugMode)](const Result& res) { + search_info(res.to_libchess(isDebug())); + }; + return { - .onSearchComplete = [isDebugMode](const Result& res) { - search_info(res.to_libchess(isDebugMode())); + .onSearchComplete = [printInfo](const Result& res) { + printInfo(res); chess::uci::printing::best_move(res.best_move(), res.ponder_move()); }, - .onIteration = [isDebugMode](const Result& res) { search_info(res.to_libchess(isDebugMode())); } + .onIteration = printInfo }; } diff --git a/libchess/src/uci/Printing.cpp b/libchess/src/uci/Printing.cpp index 6bbecab4..567333a4 100644 --- a/libchess/src/uci/Printing.cpp +++ b/libchess/src/uci/Printing.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -102,7 +103,7 @@ namespace { return string; } - [[nodiscard]] auto pv_string(const moves::MoveList& pv) -> string + [[nodiscard]] auto pv_string(const std::span pv) -> string { if (pv.empty()) { // this is possible if we're checkmated From 0f713f18870fb6bb1efea130eb49b80cdbf989a6 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Thu, 26 Feb 2026 23:59:32 -0600 Subject: [PATCH 13/23] feat: UCI option to switch between pretty/UCI printing --- ben-bot/include/ben-bot/Engine.hpp | 20 +++++++++++++- ben-bot/src/Engine.cpp | 27 +++++++++++++++++-- .../include/libbenbot/search/Callbacks.hpp | 12 ++++++++- libbenbot/src/search/Callbacks.cpp | 13 +++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/ben-bot/include/ben-bot/Engine.hpp b/ben-bot/include/ben-bot/Engine.hpp index cf181656..840db559 100644 --- a/ben-bot/include/ben-bot/Engine.hpp +++ b/ben-bot/include/ben-bot/Engine.hpp @@ -81,14 +81,21 @@ class [[nodiscard]] Engine final : public uci::EngineBase { void print_options(string_view args) const; void print_current_position(string_view arguments) const; + void set_pretty_printing(bool shouldPrettyPrint); + static void print_compiler_info(); static void start_file_logger(string_view path); + static constexpr bool PRETTY_PRINT_DEFAULT = false; + std::atomic_bool debugMode { false }; + std::atomic_bool prettyPrinting { PRETTY_PRINT_DEFAULT }; search::Thread searcher; + /* ----- UCI options ----- */ + uci::IntOption ttSize { "Hash", 1, 2048, 16, @@ -132,7 +139,18 @@ class [[nodiscard]] Engine final : public uci::EngineBase { [](const string_view path) { start_file_logger(path); } }; - std::array options { &ttSize, &clearTT, &ponder, &threads, &moveOverhead, &logFile }; + uci::BoolOption prettyPrintMode { + "Pretty Print", + PRETTY_PRINT_DEFAULT, + "When on, search output is pretty-printed instead of printed in UCI format", + [this](const bool usePretty) { set_pretty_printing(usePretty); } + }; + + std::array options { + &ttSize, &clearTT, &ponder, &threads, &moveOverhead, &logFile, &prettyPrintMode + }; + + /* ----- Custom commands ----- */ // clang-format off std::array customCommands { diff --git a/ben-bot/src/Engine.cpp b/ben-bot/src/Engine.cpp index 9051f5db..cf222b40 100644 --- a/ben-bot/src/Engine.cpp +++ b/ben-bot/src/Engine.cpp @@ -26,6 +26,7 @@ namespace ben_bot { +using std::memory_order_relaxed; using std::size_t; using uci::printing::info_string; @@ -39,8 +40,30 @@ void Engine::new_game(const bool firstCall) // we use delayed initialization for these callbacks instead of // initializing them in the constructor to avoid referencing the // `this` pointer in the constructor - searcher.context.callbacks = search::Callbacks::make_uci_printer( - [this] { return debugMode.load(std::memory_order::relaxed); }); + if (prettyPrinting.load()) { + searcher.context.callbacks = search::Callbacks::make_pretty_printer(); + } else { + searcher.context.callbacks = search::Callbacks::make_uci_printer( + [this] noexcept { // cppcheck-suppress syntaxError + return debugMode.load(memory_order_relaxed); + }); + } +} + +void Engine::set_pretty_printing(const bool shouldPrettyPrint) +{ + // check if the requested printing mode was already active + if (prettyPrinting.exchange(shouldPrettyPrint, memory_order_relaxed) == shouldPrettyPrint) + return; + + wait(); + + if (shouldPrettyPrint) { + searcher.context.callbacks = search::Callbacks::make_pretty_printer(); + } else { + searcher.context.callbacks = search::Callbacks::make_uci_printer( + [this] noexcept { return debugMode.load(memory_order_relaxed); }); + } } void Engine::go(const uci::GoCommandOptions& opts) diff --git a/libbenbot/include/libbenbot/search/Callbacks.hpp b/libbenbot/include/libbenbot/search/Callbacks.hpp index 1acffd4f..689e27a5 100644 --- a/libbenbot/include/libbenbot/search/Callbacks.hpp +++ b/libbenbot/include/libbenbot/search/Callbacks.hpp @@ -56,8 +56,10 @@ struct Callbacks final { /** Can be safely called without checking if ``onIteration`` is null. */ void iteration_complete(const Result& result) const { - if (onIteration != nullptr) + if (onIteration != nullptr) { + [[likely]]; onIteration(result); + } } /** Creates a set of callbacks that print UCI-formatted information and bestmove @@ -69,6 +71,14 @@ struct Callbacks final { [[nodiscard]] static auto make_uci_printer( std::function&& isDebugMode) -> Callbacks; + + /** Creates a set of callbacks that print search information in a human-readable + table-aligned format. + + @note The output produced by these callbacks does not conform to the UCI protocol! + */ + [[nodiscard]] static auto make_pretty_printer() + -> Callbacks; }; } // namespace ben_bot::search diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 2cc03204..5da7c10e 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -36,4 +36,17 @@ auto Callbacks::make_uci_printer( }; } +auto Callbacks::make_pretty_printer() + -> Callbacks +{ + return { + .onSearchComplete = [](const Result& res) { + + }, + .onIteration = [](const Result& res) { + + } + }; +} + } // namespace ben_bot::search From da60d9a0928d32af509c95ec308e47ea70dd7512 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 01:15:55 -0600 Subject: [PATCH 14/23] feat: initial commit of pretty printing depth & time --- ben-bot/include/ben-bot/Engine.hpp | 4 +- libbenbot/src/search/Callbacks.cpp | 102 +++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/ben-bot/include/ben-bot/Engine.hpp b/ben-bot/include/ben-bot/Engine.hpp index 840db559..d9e2a7b7 100644 --- a/ben-bot/include/ben-bot/Engine.hpp +++ b/ben-bot/include/ben-bot/Engine.hpp @@ -87,7 +87,7 @@ class [[nodiscard]] Engine final : public uci::EngineBase { static void start_file_logger(string_view path); - static constexpr bool PRETTY_PRINT_DEFAULT = false; + static constexpr bool PRETTY_PRINT_DEFAULT = true; std::atomic_bool debugMode { false }; std::atomic_bool prettyPrinting { PRETTY_PRINT_DEFAULT }; @@ -136,7 +136,7 @@ class [[nodiscard]] Engine final : public uci::EngineBase { uci::StringOption logFile { "Debug Log File", "", "If not empty, engine I/O will be mirrored to this file", - [](const string_view path) { start_file_logger(path); } + start_file_logger }; uci::BoolOption prettyPrintMode { diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 5da7c10e..6abca44e 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -12,10 +12,18 @@ * ====================================================================================== */ +#include +#include +#include #include +#include #include #include #include +#include +#include +#include +#include #include namespace ben_bot::search { @@ -36,16 +44,98 @@ auto Callbacks::make_uci_printer( }; } +namespace { + // TODO: do column padding via std::format width specifiers? + + [[nodiscard]] auto get_column_text( + const std::string_view text, + const size_t totalColumnWidth, + const bool leftAlign) -> std::string + { + assert(text.size() < totalColumnWidth); + + const auto trimmedInput = text.substr(0uz, totalColumnWidth); + + std::string padded; + + if (leftAlign) { + padded = trimmedInput; + + padded.resize(totalColumnWidth, ' '); + } else { + padded.resize(totalColumnWidth - trimmedInput.size(), ' '); + + padded.append(trimmedInput); + } + + return padded; + } + + void print_column_text( + const std::string_view text, + const size_t totalColumnWidth, + const bool leftAlign = true) + { + std::print(std::cout, + "{}", + get_column_text( + text, totalColumnWidth, leftAlign)); + } + + 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; + + return std::format( + "{:.2%Q %q}", + duration_cast(duration)); + } + + return std::nullopt; + } + + [[nodiscard]] auto format_duration( + const milliseconds duration) -> std::string + { + // NB. it should be quite rare that a search will run for 1 day or more... + return get_duration_string(duration) + .or_else([duration] { return get_duration_string(duration); }) + .or_else([duration] { return get_duration_string(duration); }) + .or_else([duration] { return std::make_optional(std::format("{:%Q %q}", duration)); }) + .value(); + } + + constexpr auto COL_DEPTH = 10uz; + constexpr auto COL_TIME = 10uz; + + void pretty_print(const Result& res) + { + // depth + print_column_text( + std::format("{}/{}", res.depth, res.qDepth), + COL_DEPTH); + + // time + print_column_text( + format_duration(res.duration), + COL_TIME, false); + + // final newline + std::print(std::cout, "\n"); + } +} // namespace + auto Callbacks::make_pretty_printer() -> Callbacks { return { - .onSearchComplete = [](const Result& res) { - - }, - .onIteration = [](const Result& res) { - - } + .onSearchComplete = pretty_print, + .onIteration = pretty_print }; } From 5ab8c31c5fb114179a46a9457569b225a54c9a13 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 01:39:53 -0600 Subject: [PATCH 15/23] feat: pretty printing number of nodes --- libbenbot/src/search/Callbacks.cpp | 56 ++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 6abca44e..1a39576b 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -47,10 +48,13 @@ auto Callbacks::make_uci_printer( namespace { // TODO: do column padding via std::format width specifiers? + enum class Alignment { Left, + Right }; + + template [[nodiscard]] auto get_column_text( const std::string_view text, - const size_t totalColumnWidth, - const bool leftAlign) -> std::string + const size_t totalColumnWidth) -> std::string { assert(text.size() < totalColumnWidth); @@ -58,7 +62,7 @@ namespace { std::string padded; - if (leftAlign) { + if constexpr (Align == Alignment::Left) { padded = trimmedInput; padded.resize(totalColumnWidth, ' '); @@ -71,15 +75,15 @@ namespace { return padded; } + template void print_column_text( const std::string_view text, - const size_t totalColumnWidth, - const bool leftAlign = true) + const size_t totalColumnWidth) { - std::print(std::cout, + std::print( + std::cout, "{}", - get_column_text( - text, totalColumnWidth, leftAlign)); + get_column_text(text, totalColumnWidth)); } template @@ -110,20 +114,50 @@ namespace { .value(); } + template + [[nodiscard]] auto get_nodes_string( + const size_t nodes) -> std::optional + { + if (nodes >= Ratio::num) { + const auto display = static_cast(nodes) / static_cast(Ratio::num); + + return std::format( + "{:.2f}{}", + display, Suffix); + } + + return std::nullopt; + } + + [[nodiscard]] auto format_nodes( + const size_t nodes) -> std::string + { + return get_nodes_string(nodes) + .or_else([nodes] { return get_nodes_string(nodes); }) + .or_else([nodes] { return std::make_optional(std::format("{}", nodes)); }) + .value(); + } + constexpr auto COL_DEPTH = 10uz; constexpr auto COL_TIME = 10uz; + constexpr auto COL_NODES = 10uz; void pretty_print(const Result& res) { // depth - print_column_text( + print_column_text( std::format("{}/{}", res.depth, res.qDepth), COL_DEPTH); // time - print_column_text( + print_column_text( format_duration(res.duration), - COL_TIME, false); + COL_TIME); + + // nodes + print_column_text( + format_nodes(res.nodesSearched), + COL_NODES); // final newline std::print(std::cout, "\n"); From 7913f41fd8b0b0f0e37df67eca1f724906682080 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 01:52:26 -0600 Subject: [PATCH 16/23] feat: pretty printing NPS --- libbenbot/src/search/Callbacks.cpp | 57 +++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 1a39576b..6de60b8c 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -48,19 +49,23 @@ auto Callbacks::make_uci_printer( namespace { // TODO: do column padding via std::format width specifiers? - enum class Alignment { Left, - Right }; + using std::string; + + enum class Alignment { + Left, + Right + }; template [[nodiscard]] auto get_column_text( const std::string_view text, - const size_t totalColumnWidth) -> std::string + const size_t totalColumnWidth) -> string { assert(text.size() < totalColumnWidth); const auto trimmedInput = text.substr(0uz, totalColumnWidth); - std::string padded; + string padded; if constexpr (Align == Alignment::Left) { padded = trimmedInput; @@ -88,7 +93,7 @@ namespace { template [[nodiscard]] auto get_duration_string( - const milliseconds duration) -> std::optional + const milliseconds duration) -> std::optional { static constexpr auto msPerUnit = duration_cast(Duration { 1uz }); @@ -104,7 +109,7 @@ namespace { } [[nodiscard]] auto format_duration( - const milliseconds duration) -> std::string + const milliseconds duration) -> string { // NB. it should be quite rare that a search will run for 1 day or more... return get_duration_string(duration) @@ -114,33 +119,54 @@ namespace { .value(); } - template + template [[nodiscard]] auto get_nodes_string( - const size_t nodes) -> std::optional + const size_t nodes) -> std::optional { if (nodes >= Ratio::num) { const auto display = static_cast(nodes) / static_cast(Ratio::num); return std::format( - "{:.2f}{}", - display, Suffix); + "{:.{}f}{}", + display, Precision, Suffix); } return std::nullopt; } + template [[nodiscard]] auto format_nodes( - const size_t nodes) -> std::string + const size_t nodes) -> string { - return get_nodes_string(nodes) - .or_else([nodes] { return get_nodes_string(nodes); }) + return get_nodes_string(nodes) + .or_else([nodes] { return get_nodes_string(nodes); }) .or_else([nodes] { return std::make_optional(std::format("{}", nodes)); }) .value(); } + [[nodiscard, gnu::const]] auto get_nps(const Result& res) -> size_t + { + const auto seconds = static_cast(res.duration.count()) * 0.001; + + if (seconds <= 0.) + return 0uz; + + const auto nps = static_cast(res.nodesSearched) / seconds; + + return static_cast(std::round(nps)); + } + + [[nodiscard]] auto format_nps(const size_t nps) -> string + { + return std::format( + "{}/s", + format_nodes<1uz>(nps)); + } + constexpr auto COL_DEPTH = 10uz; constexpr auto COL_TIME = 10uz; constexpr auto COL_NODES = 10uz; + constexpr auto COL_NPS = 10uz; void pretty_print(const Result& res) { @@ -159,6 +185,11 @@ namespace { format_nodes(res.nodesSearched), COL_NODES); + // nodes per second + print_column_text( + format_nps(get_nps(res)), + COL_NPS); + // final newline std::print(std::cout, "\n"); } From 0eaf8f220967ec5bafe1bab04a510a0a8d111255 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 02:01:43 -0600 Subject: [PATCH 17/23] feat: pretty printing hashfull --- libbenbot/src/search/Callbacks.cpp | 46 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 6de60b8c..139f1273 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -56,23 +56,24 @@ namespace { Right }; + constexpr auto COLUMN_WIDTH = 10uz; + template [[nodiscard]] auto get_column_text( - const std::string_view text, - const size_t totalColumnWidth) -> string + const std::string_view text) -> string { - assert(text.size() < totalColumnWidth); + assert(text.size() < COLUMN_WIDTH); - const auto trimmedInput = text.substr(0uz, totalColumnWidth); + const auto trimmedInput = text.substr(0uz, COLUMN_WIDTH); string padded; if constexpr (Align == Alignment::Left) { padded = trimmedInput; - padded.resize(totalColumnWidth, ' '); + padded.resize(COLUMN_WIDTH, ' '); } else { - padded.resize(totalColumnWidth - trimmedInput.size(), ' '); + padded.resize(COLUMN_WIDTH - trimmedInput.size(), ' '); padded.append(trimmedInput); } @@ -82,13 +83,12 @@ namespace { template void print_column_text( - const std::string_view text, - const size_t totalColumnWidth) + const std::string_view text) { std::print( std::cout, "{}", - get_column_text(text, totalColumnWidth)); + get_column_text(text)); } template @@ -160,35 +160,37 @@ namespace { { return std::format( "{}/s", - format_nodes<1uz>(nps)); + format_nodes<1uz>(nps)); // use this function for its transformation of the value to a k/M representation } - constexpr auto COL_DEPTH = 10uz; - constexpr auto COL_TIME = 10uz; - constexpr auto COL_NODES = 10uz; - constexpr auto COL_NPS = 10uz; + [[nodiscard]] auto format_hashfull(const size_t permille) -> string + { + return std::format( + "{}%", + permille / 10uz); + } void pretty_print(const Result& res) { // depth print_column_text( - std::format("{}/{}", res.depth, res.qDepth), - COL_DEPTH); + std::format("{}/{}", res.depth, res.qDepth)); // time print_column_text( - format_duration(res.duration), - COL_TIME); + format_duration(res.duration)); // nodes print_column_text( - format_nodes(res.nodesSearched), - COL_NODES); + format_nodes(res.nodesSearched)); // nodes per second print_column_text( - format_nps(get_nps(res)), - COL_NPS); + format_nps(get_nps(res))); + + // hashfull + print_column_text( + format_hashfull(res.hashfull)); // final newline std::print(std::cout, "\n"); From 78c7df911d84c7c75643cf090452e678d89a0a71 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 02:28:11 -0600 Subject: [PATCH 18/23] feat: pretty printing score --- ben-bot/src/ColorPrinting.cpp | 43 +++++++++++------------ libbenbot/src/search/Callbacks.cpp | 55 +++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/ben-bot/src/ColorPrinting.cpp b/ben-bot/src/ColorPrinting.cpp index 03c9cc69..034f0c26 100644 --- a/ben-bot/src/ColorPrinting.cpp +++ b/ben-bot/src/ColorPrinting.cpp @@ -26,6 +26,7 @@ namespace ben_bot { +using std::cout; using std::string_view; namespace { @@ -44,16 +45,16 @@ void Engine::print_logo_and_version() const { const auto logoLines = get_at_most_n_lines<11uz>(resources::get_ascii_logo()); - std::cout << termcolor::grey << logoLines.front() << '\n' - << termcolor::blue; + cout << termcolor::grey << logoLines.front() << '\n' + << termcolor::blue; for (const auto line : logoLines | std::views::drop(1) | std::views::take(logoLines.capacity() - 2uz)) - std::cout << line << '\n'; + cout << line << '\n'; - std::cout << termcolor::grey << logoLines.back() << "\n\n" - << termcolor::reset << termcolor::bold << get_name() << ", " - << termcolor::reset << "by " << get_author() << '\n' - << termcolor::reset; + cout << termcolor::grey << logoLines.back() << "\n\n" + << termcolor::reset << termcolor::bold << get_name() << ", " + << termcolor::reset << "by " << get_author() << '\n' + << termcolor::reset; } void print_colored_table(const chess::util::strings::TextTable& table) @@ -64,24 +65,24 @@ void print_colored_table(const chess::util::strings::TextTable& table) // whitespace that follows the text to complete the cell const auto trimmed = chess::util::strings::trim(heading); - std::cout << termcolor::bold << termcolor::underline - << trimmed - << termcolor::reset; + cout << termcolor::bold << termcolor::underline + << trimmed + << termcolor::reset; const auto numSpaces = heading.length() - trimmed.length(); for (auto i = 0uz; i < numSpaces; ++i) - std::cout << ' '; + cout << ' '; }, [](const string_view cell) { - std::cout << cell; + cout << cell; }, [](const string_view outline) { - std::cout << termcolor::white << outline << termcolor::reset; + cout << termcolor::white << outline << termcolor::reset; }, - [] { std::cout << '\n'; }); + [] { cout << '\n'; }); - std::cout << termcolor::reset; + cout << termcolor::reset; } void print_colored_board(const Position& pos, const bool utf8) @@ -91,18 +92,18 @@ void print_colored_board(const Position& pos, const bool utf8) const auto lines = get_at_most_n_lines<9uz>(boardStr); for (const auto line : lines | std::views::take(lines.capacity() - 1uz)) { - std::cout << line.substr(0uz, line.length() - 1uz) - << termcolor::white << line.back() << '\n' - << termcolor::reset; + cout << line.substr(0uz, line.length() - 1uz) + << termcolor::white << line.back() << '\n' + << termcolor::reset; } - std::cout << termcolor::white << lines.back() << '\n' - << termcolor::reset; + cout << termcolor::white << lines.back() << '\n' + << termcolor::reset; } void print_labeled_info(const string_view label, const string_view info) { - std::cout << termcolor::white << label << termcolor::reset << info << '\n'; + cout << termcolor::white << label << termcolor::reset << info << '\n'; } } // namespace ben_bot diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index 139f1273..e692b06f 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -21,12 +21,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include namespace ben_bot::search { @@ -53,7 +55,8 @@ namespace { enum class Alignment { Left, - Right + Right, + Center }; constexpr auto COLUMN_WIDTH = 10uz; @@ -73,9 +76,19 @@ namespace { padded.resize(COLUMN_WIDTH, ' '); } else { - padded.resize(COLUMN_WIDTH - trimmedInput.size(), ' '); + auto padding = COLUMN_WIDTH - trimmedInput.size(); + + if constexpr (Align == Alignment::Center) { + padding /= 2uz; + } + + padded.resize(padding, ' '); padded.append(trimmedInput); + + if constexpr (Align == Alignment::Center) { + padded.append(COLUMN_WIDTH - padded.size(), ' '); + } } return padded; @@ -170,10 +183,38 @@ namespace { permille / 10uz); } + [[nodiscard, gnu::const]] auto plies_to_moves(int plies) noexcept -> int + { + if (std::cmp_greater(plies, 0)) + ++plies; + + return plies / 2; + } + + using Score = chess::uci::printing::SearchInfo::Score; + + [[nodiscard]] auto format_score( + const Score& score) -> string + { + return std::visit( + chess::util::Visitor { + [](const Score::Centipawns& centipawns) { + return std::format( + "{:+}", + centipawns.value); + }, + [](const Score::MateIn& mate) { + return std::format( + "#{}", + std::abs(plies_to_moves(mate.plies))); + } }, + score.value); + } + void pretty_print(const Result& res) { // depth - print_column_text( + print_column_text( std::format("{}/{}", res.depth, res.qDepth)); // time @@ -189,9 +230,15 @@ namespace { format_nps(get_nps(res))); // hashfull - print_column_text( + print_column_text( format_hashfull(res.hashfull)); + // score + print_column_text( + format_score(res.score.to_libchess())); + + // PV + // final newline std::print(std::cout, "\n"); } From a12b2411cc6f7166290285ede010ebe3b7346915 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 02:33:40 -0600 Subject: [PATCH 19/23] feat: pretty printing PV --- libbenbot/src/search/Callbacks.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index e692b06f..c1263188 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -20,11 +20,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -211,6 +214,26 @@ namespace { score.value); } + [[nodiscard]] auto format_pv( + const std::span pv) -> string + { + if (pv.empty()) { + // this is possible if we're checkmated + return { }; + } + + string result; + + for (const auto move : pv) { + result.append(chess::notation::to_uci(move)); + result.append(1uz, ' '); + } + + result.pop_back(); // trim last space + + return result; + } + void pretty_print(const Result& res) { // depth @@ -238,9 +261,10 @@ namespace { format_score(res.score.to_libchess())); // PV - - // final newline - std::print(std::cout, "\n"); + std::println( + std::cout, + "{}", + format_pv(res.pv)); } } // namespace From f8df0780aab956f67287c2532f62e1af71ee88ae Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 02:47:30 -0600 Subject: [PATCH 20/23] feat: printing table header --- ben-bot/src/Bench.cpp | 1 + .../include/libbenbot/search/Callbacks.hpp | 11 ++++++++ libbenbot/src/search/Callbacks.cpp | 26 ++++++++++++++++++- libbenbot/src/search/Search.cpp | 1 + 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ben-bot/src/Bench.cpp b/ben-bot/src/Bench.cpp index 461327eb..a3cf44b1 100644 --- a/ben-bot/src/Bench.cpp +++ b/ben-bot/src/Bench.cpp @@ -96,6 +96,7 @@ namespace { // clang-format off search::Thread thread { search::Callbacks { + .onSearchStart = nullptr, .onSearchComplete = [this](const SearchResult& res) { print_info(res); result = res; }, .onIteration = [this](const SearchResult& res) { print_info(res); } } diff --git a/libbenbot/include/libbenbot/search/Callbacks.hpp b/libbenbot/include/libbenbot/search/Callbacks.hpp index 689e27a5..f52f4208 100644 --- a/libbenbot/include/libbenbot/search/Callbacks.hpp +++ b/libbenbot/include/libbenbot/search/Callbacks.hpp @@ -23,6 +23,7 @@ namespace ben_bot::search { +struct Options; struct Result; /** This struct encapsulates a set of functions that will be called to @@ -36,6 +37,9 @@ struct Callbacks final { /** Function type that accepts a single Result argument. */ using Callback = std::function; + /** Function object that will be invoked when a new search is started. */ + std::function onSearchStart; + /** Function object that will be invoked with results from a completed search. */ Callback onSearchComplete; @@ -44,6 +48,13 @@ struct Callbacks final { */ Callback onIteration; + /** Can be safely called without checking if ``onSearchStart`` is null. */ + void search_start(const Options& options) const + { + if (onSearchStart != nullptr) + onSearchStart(options); + } + /** Can be safely called without checking if ``onSearchComplete`` is null. */ void search_complete(const Result& result) const { diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index c1263188..f63480e1 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -44,6 +44,7 @@ auto Callbacks::make_uci_printer( }; return { + .onSearchStart = nullptr, .onSearchComplete = [printInfo](const Result& res) { printInfo(res); chess::uci::printing::best_move(res.best_move(), res.ponder_move()); }, @@ -234,6 +235,26 @@ namespace { return result; } + void print_table_header() + { + print_column_text("Depth"); + + print_column_text("Time"); + + print_column_text("Nodes"); + + print_column_text("NPS"); + + print_column_text("Hashfull"); + + print_column_text("Score"); + + std::println( + std::cout, + "{}", + "PV"); + } + void pretty_print(const Result& res) { // depth @@ -257,7 +278,7 @@ namespace { format_hashfull(res.hashfull)); // score - print_column_text( + print_column_text( format_score(res.score.to_libchess())); // PV @@ -272,6 +293,9 @@ auto Callbacks::make_pretty_printer() -> Callbacks { return { + .onSearchStart = []([[maybe_unused]] const Options& options) { + print_table_header(); + }, .onSearchComplete = pretty_print, .onIteration = pretty_print }; diff --git a/libbenbot/src/search/Search.cpp b/libbenbot/src/search/Search.cpp index cb7f52ec..a52b234f 100644 --- a/libbenbot/src/search/Search.cpp +++ b/libbenbot/src/search/Search.cpp @@ -461,6 +461,7 @@ void Context::search() // NOLINT(readability-function-cognitive-complexity) Interrupter interrupter { exitFlag, pondering, options.searchTime, options.infinite }; transTable.new_search(); + callbacks.search_start(options); // if the movesToSearch was empty, then we search all legal moves if (options.movesToSearch.empty()) { From f27e5e546fed8c530ea953e14698ad039adfb65c Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 03:42:06 -0600 Subject: [PATCH 21/23] refactor: deduplicating code between UCI & pretty printers --- libbenbot/src/search/Callbacks.cpp | 30 +++------------ libchess/include/libchess/uci/Printing.hpp | 8 ++++ libchess/src/uci/Printing.cpp | 44 +++++++++++----------- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index f63480e1..d4f43f72 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include // IWYU pragma: keep - for std::abs() #include #include #include @@ -161,18 +161,6 @@ namespace { .value(); } - [[nodiscard, gnu::const]] auto get_nps(const Result& res) -> size_t - { - const auto seconds = static_cast(res.duration.count()) * 0.001; - - if (seconds <= 0.) - return 0uz; - - const auto nps = static_cast(res.nodesSearched) / seconds; - - return static_cast(std::round(nps)); - } - [[nodiscard]] auto format_nps(const size_t nps) -> string { return std::format( @@ -187,14 +175,6 @@ namespace { permille / 10uz); } - [[nodiscard, gnu::const]] auto plies_to_moves(int plies) noexcept -> int - { - if (std::cmp_greater(plies, 0)) - ++plies; - - return plies / 2; - } - using Score = chess::uci::printing::SearchInfo::Score; [[nodiscard]] auto format_score( @@ -210,7 +190,7 @@ namespace { [](const Score::MateIn& mate) { return std::format( "#{}", - std::abs(plies_to_moves(mate.plies))); + std::abs(mate.moves())); } }, score.value); } @@ -269,9 +249,11 @@ namespace { print_column_text( format_nodes(res.nodesSearched)); + const auto libchess = res.to_libchess(false); + // nodes per second print_column_text( - format_nps(get_nps(res))); + format_nps(libchess.get_nps())); // hashfull print_column_text( @@ -279,7 +261,7 @@ namespace { // score print_column_text( - format_score(res.score.to_libchess())); + format_score(libchess.score)); // PV std::println( diff --git a/libchess/include/libchess/uci/Printing.hpp b/libchess/include/libchess/uci/Printing.hpp index d6c202b1..3404ea80 100644 --- a/libchess/include/libchess/uci/Printing.hpp +++ b/libchess/include/libchess/uci/Printing.hpp @@ -79,6 +79,11 @@ struct SearchInfo final { The value should be negative if the engine is getting mated. */ int plies { 0 }; + + /** Returns the number of full moves to mate. + The value is negative if the engine is getting mated. + */ + [[nodiscard]] auto moves() const noexcept -> int; }; /** The evaluation value. */ @@ -119,6 +124,9 @@ struct SearchInfo final { with the rest of the search information. */ std::string extraInformation; + + /** Returns the nodes-per-second for this search. */ + [[nodiscard]] auto get_nps() const noexcept -> size_t; }; /** Prints a UCI-formatted search info string to standard output. diff --git a/libchess/src/uci/Printing.cpp b/libchess/src/uci/Printing.cpp index 567333a4..b3b5ee2f 100644 --- a/libchess/src/uci/Printing.cpp +++ b/libchess/src/uci/Printing.cpp @@ -71,15 +71,29 @@ void best_move( cout.flush(); } -namespace { - [[nodiscard, gnu::const]] auto plies_to_moves(int plies) noexcept -> int - { - if (std::cmp_greater(plies, 0)) - ++plies; +auto SearchInfo::Score::MateIn::moves() const noexcept -> int +{ + return [ply = plies]() mutable { + if (std::cmp_greater(ply, 0)) + ++ply; - return plies / 2; - } + return ply / 2; + }(); +} + +auto SearchInfo::get_nps() const noexcept -> size_t +{ + const auto seconds = static_cast(time.count()) * 0.001; + + if (seconds <= 0.) + return 0uz; + + const auto nps = static_cast(nodes) / seconds; + return static_cast(std::round(nps)); +} + +namespace { using Score = SearchInfo::Score; [[nodiscard]] auto base_score_string(const Score& score) -> string @@ -87,7 +101,7 @@ namespace { return std::visit( util::Visitor { [](const Score::Centipawns& centipawns) { return std::format("cp {}", centipawns.value); }, - [](const Score::MateIn& mate) { return std::format("mate {}", plies_to_moves(mate.plies)); } }, + [](const Score::MateIn& mate) { return std::format("mate {}", mate.moves()); } }, score.value); } @@ -122,18 +136,6 @@ namespace { return result; } - [[nodiscard, gnu::const]] auto get_nodes_per_second(const SearchInfo& info) -> size_t - { - const auto seconds = static_cast(info.time.count()) * 0.001; - - if (seconds <= 0.) - return 0uz; - - const auto nps = static_cast(info.nodes) / seconds; - - return static_cast(std::round(nps)); - } - [[nodiscard]] auto get_extra_info_string(const string_view info) -> string { if (info.empty()) @@ -150,7 +152,7 @@ void search_info(const SearchInfo& info) info.depth, score_string(info.score), info.time.count(), info.hashfull, info.nodes, - get_nodes_per_second(info), + info.get_nps(), info.selDepth, info.tbHits, pv_string(info.pv), get_extra_info_string(info.extraInformation)); From 2be8e23b35cbebb6de885f4f3f5b3e2c0ddfff25 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 12:31:37 -0600 Subject: [PATCH 22/23] feat: color printing in pretty output --- CMakeLists.txt | 19 ++++++++ ben-bot/CMakeLists.txt | 16 +------ ben-bot/src/ColorPrinting.cpp | 2 +- libbenbot/CMakeLists.txt | 4 ++ libbenbot/src/search/Callbacks.cpp | 69 +++++++++++++++++++++++------- 5 files changed, 79 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 935a5cf6..b15a1ca6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,6 +37,25 @@ cpack_add_component_group ( ben_bot_all DISPLAY_NAME "BenBot" DESCRIPTION "All components of BenBot" EXPANDED BOLD_TITLE ) +FetchContent_Declare ( + termcolor + SYSTEM + GIT_REPOSITORY "https://github.com/ikalnytskyi/termcolor.git" + GIT_TAG v2.1.0 + GIT_SHALLOW ON + SOURCE_SUBDIR foo # we need to disable adding termcolor as a subdirectory, because they list a + # too-old cmake version + FIND_PACKAGE_ARGS 2.1.0 +) + +FetchContent_MakeAvailable (termcolor) + +if (termcolor_SOURCE_DIR) + set (TERMCOLOR_INCLUDES "${termcolor_SOURCE_DIR}/include") +elseif (termcolor_DIR) + set (TERMCOLOR_INCLUDES "${termcolor_DIR}/include") +endif () + add_subdirectory (libchess) add_subdirectory (libbenbot) add_subdirectory (ben-bot) diff --git a/ben-bot/CMakeLists.txt b/ben-bot/CMakeLists.txt index 715f40b1..d88b17ce 100644 --- a/ben-bot/CMakeLists.txt +++ b/ben-bot/CMakeLists.txt @@ -12,19 +12,6 @@ add_subdirectory (resources) -FetchContent_Declare ( - termcolor - SYSTEM - GIT_REPOSITORY "https://github.com/ikalnytskyi/termcolor.git" - GIT_TAG v2.1.0 - GIT_SHALLOW ON - SOURCE_SUBDIR foo # we need to disable adding termcolor as a subdirectory, because they list a - # too-old cmake version - FIND_PACKAGE_ARGS 2.1.0 -) - -FetchContent_MakeAvailable (termcolor) - add_executable (ben_bot) target_compile_features (ben_bot PRIVATE cxx_std_23) @@ -32,8 +19,7 @@ target_compile_features (ben_bot PRIVATE cxx_std_23) target_link_libraries (ben_bot PRIVATE ben_bot::libbenbot ben_bot::resources) set_property ( - SOURCE src/ColorPrinting.cpp APPEND PROPERTY INCLUDE_DIRECTORIES - "${termcolor_SOURCE_DIR}/include" + SOURCE src/ColorPrinting.cpp APPEND PROPERTY INCLUDE_DIRECTORIES "${TERMCOLOR_INCLUDES}" ) set_target_properties ( diff --git a/ben-bot/src/ColorPrinting.cpp b/ben-bot/src/ColorPrinting.cpp index 034f0c26..1de58959 100644 --- a/ben-bot/src/ColorPrinting.cpp +++ b/ben-bot/src/ColorPrinting.cpp @@ -22,7 +22,7 @@ #include #include #include -#include // this is the only TU that includes the termcolor library +#include namespace ben_bot { diff --git a/libbenbot/CMakeLists.txt b/libbenbot/CMakeLists.txt index f9bcf921..d6f562fe 100644 --- a/libbenbot/CMakeLists.txt +++ b/libbenbot/CMakeLists.txt @@ -16,6 +16,10 @@ target_compile_features (libbenbot PUBLIC cxx_std_23) target_link_libraries (libbenbot PUBLIC ben_bot::libchess) +set_property ( + SOURCE src/search/Callbacks.cpp APPEND PROPERTY INCLUDE_DIRECTORIES "${TERMCOLOR_INCLUDES}" +) + set_target_properties ( libbenbot PROPERTIES CXX_STANDARD_REQUIRED ON diff --git a/libbenbot/src/search/Callbacks.cpp b/libbenbot/src/search/Callbacks.cpp index d4f43f72..7dd4bf4b 100644 --- a/libbenbot/src/search/Callbacks.cpp +++ b/libbenbot/src/search/Callbacks.cpp @@ -25,11 +25,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include @@ -102,10 +102,7 @@ namespace { void print_column_text( const std::string_view text) { - std::print( - std::cout, - "{}", - get_column_text(text)); + std::cout << get_column_text(text); } template @@ -195,6 +192,52 @@ namespace { score.value); } + void print_score( + const Score& score) + { + enum class ScoreType { + Winning, + Losing, + Equal + }; + + const auto type = std::visit( + chess::util::Visitor { + [](const Score::Centipawns& value) { + if (value.value == 0) + return ScoreType::Equal; + + if (value.value > 0) + return ScoreType::Winning; + + return ScoreType::Losing; + }, + [](const Score::MateIn& mate) { + if (mate.plies > 0) + return ScoreType::Winning; + + return ScoreType::Losing; + } }, + score.value); + + switch (type) { + case ScoreType::Winning: + std::cout << termcolor::green; + break; + case ScoreType::Losing: + std::cout << termcolor::red; + break; + case ScoreType::Equal: + std::cout << termcolor::grey; + break; + } + + print_column_text( + format_score(score)); + + std::cout << termcolor::reset; + } + [[nodiscard]] auto format_pv( const std::span pv) -> string { @@ -217,6 +260,8 @@ namespace { void print_table_header() { + std::cout << termcolor::bold; + print_column_text("Depth"); print_column_text("Time"); @@ -229,10 +274,8 @@ namespace { print_column_text("Score"); - std::println( - std::cout, - "{}", - "PV"); + std::cout << "PV\n" + << termcolor::reset; } void pretty_print(const Result& res) @@ -260,14 +303,10 @@ namespace { format_hashfull(res.hashfull)); // score - print_column_text( - format_score(libchess.score)); + print_score(libchess.score); // PV - std::println( - std::cout, - "{}", - format_pv(res.pv)); + std::cout << format_pv(res.pv) << '\n'; } } // namespace From f410950fc5dcc2de0082f7a5d6d4e72605c3df37 Mon Sep 17 00:00:00 2001 From: Ben Vining Date: Fri, 27 Feb 2026 12:35:59 -0600 Subject: [PATCH 23/23] refactor: misc --- ben-bot/include/ben-bot/Engine.hpp | 2 +- libchess/src/uci/Printing.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ben-bot/include/ben-bot/Engine.hpp b/ben-bot/include/ben-bot/Engine.hpp index d9e2a7b7..ef752b2a 100644 --- a/ben-bot/include/ben-bot/Engine.hpp +++ b/ben-bot/include/ben-bot/Engine.hpp @@ -87,7 +87,7 @@ class [[nodiscard]] Engine final : public uci::EngineBase { static void start_file_logger(string_view path); - static constexpr bool PRETTY_PRINT_DEFAULT = true; + static constexpr bool PRETTY_PRINT_DEFAULT = false; std::atomic_bool debugMode { false }; std::atomic_bool prettyPrinting { PRETTY_PRINT_DEFAULT }; diff --git a/libchess/src/uci/Printing.cpp b/libchess/src/uci/Printing.cpp index b3b5ee2f..799642bb 100644 --- a/libchess/src/uci/Printing.cpp +++ b/libchess/src/uci/Printing.cpp @@ -12,6 +12,7 @@ * ====================================================================================== */ +#include #include #include #include @@ -83,7 +84,9 @@ auto SearchInfo::Score::MateIn::moves() const noexcept -> int auto SearchInfo::get_nps() const noexcept -> size_t { - const auto seconds = static_cast(time.count()) * 0.001; + using FractionalSeconds = std::chrono::duration; + + const auto seconds = duration_cast(time).count(); if (seconds <= 0.) return 0uz;