From b69cb6d974abfb10d44aed6aa8cdd06c8566970d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Mon, 2 Oct 2023 12:23:55 +0200 Subject: [PATCH] Rework probability printing Outputting of probabilities is now done in unified way. This ensures that we can handle both rational and floating-point probabilities in all program parts properly. The compile-time macro NUSE_RATIONAL_PROBABILITIES can now be defined to use floating-point probabilities instead of real rational ones. Using floating-point results in roughly 10% speedup. --- include/game_state.h | 23 ++++++++++++++++++++++- include/game_state.hpp | 20 ++++++++++++++++++++ src/cli_interface.cpp | 26 +++++++++++++------------- src/main.cpp | 3 ++- 4 files changed, 57 insertions(+), 15 deletions(-) diff --git a/include/game_state.h b/include/game_state.h index d652b95..43b6b1f 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -23,7 +23,28 @@ namespace Hanabi { using clue_t = std::uint8_t; using player_t = std::uint8_t; using hand_index_t = std::uint8_t; - using probability_t = boost::rational; + + using probability_base_type = unsigned long; + using rational_probability = boost::rational; + +/** + * Define macro + * NUSE_RATIONAL_PROBABILITIES + * to use floating-point arithematic for the stored probabilities + * instead of rational representations +*/ + +#ifndef NUSE_RATIONAL_PROBABILITIES + using probability_t = boost::rational; +#else + using probability_t = double; +#endif + + inline std::ostream& print_probability(std::ostream& os, double prob); + inline std::ostream& print_probability(std::ostream& os, const rational_probability& prob); + + template + std::ostream& print_probability(std::ostream& os, const std::optional& prob); /** * We will generally assume that stacks are played from n to 0 diff --git a/include/game_state.hpp b/include/game_state.hpp index 6b64ac6..4b7eb94 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -5,6 +5,26 @@ namespace Hanabi { + template + std::ostream& print_probability(std::ostream& os, const std::optional& prob) { + if (prob.has_value()) { + return print_probability(os, prob.value()); + } else { + os << "unknown"; + } + return os; + } + + std::ostream& print_probability(std::ostream& os, const rational_probability & prob) { + os << prob << " ~ " << std::setprecision(5) << boost::rational_cast(prob) * 100 << "%"; + return os; + } + + std::ostream& print_probability(std::ostream& os, double prob) { + os << std::setprecision(5) << prob; + return os; + } + std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state) { hanabi_state.print(os); return os; diff --git a/src/cli_interface.cpp b/src/cli_interface.cpp index 1dbc690..82c8557 100644 --- a/src/cli_interface.cpp +++ b/src/cli_interface.cpp @@ -11,17 +11,10 @@ #include #include #include "game_state.h" +#include namespace Hanabi { - std::ostream& operator<<(std::ostream& os, const std::optional& prob) { - if (prob.has_value()) { - os << prob.value() << " ~ " << std::setprecision(5) << boost::rational_cast(prob.value()) * 100 << "%"; - } else { - os << "unknown"; - } - return os; - } std::string read_line_memory_safe(const char *prompt) { char *line = readline(prompt); @@ -89,9 +82,13 @@ namespace Hanabi { } } - int representation_length(probability_t probability) { + int representation_length(const rational_probability& probability) { return 1 + static_cast(std::ceil(std::log10(probability.denominator()))) + \ - static_cast(std::ceil(std::log10(probability.numerator()))); + static_cast(std::ceil(std::log10(probability.numerator()))); + } + + int representation_length(const double probability) { + return static_cast(std::ceil(std::log10(probability))); } bool ask_for_card_and_rotate_draw(const std::shared_ptr& game, hand_index_t index, bool play) { @@ -132,7 +129,8 @@ namespace Hanabi { for (const auto &[card_multiplicity, probability]: states_to_show) { std::cout << card_multiplicity.card << " (" << card_multiplicity.multiplicity; - std::cout << " copie(s) in draw) " << std::setw(max_rational_digit_len) << probability << std::endl; + std::cout << " copie(s) in draw) " << std::setw(max_rational_digit_len); + print_probability(std::cout, probability) << std::endl; } std::stringstream prompt; @@ -235,7 +233,8 @@ namespace Hanabi { if (prompt.starts_with("state")) { std::cout << *game << std::endl; const std::optional prob = game->lookup(); - std::cout << "Winning chance: " << prob << std::endl; + std::cout << "Winning chance: "; + print_probability(std::cout, prob) << std::endl; continue; } @@ -349,7 +348,8 @@ namespace Hanabi { std::cout.setf(std::ios_base::left, std::ios_base::adjustfield); std::cout << std::setw(7) << action << ": "; std::cout.setf(std::ios_base::right, std::ios_base::adjustfield); - std::cout << std::setw(max_rational_digit_len) << probability << std::endl; + std::cout << std::setw(max_rational_digit_len); + print_probability(std::cout, probability) << std::endl; } if(reasonable_actions.empty()) { std::cout << "Game is over, no actions to take." << std::endl; diff --git a/src/main.cpp b/src/main.cpp index 054261d..1e3ca15 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,8 @@ namespace Hanabi { std::cout.precision(10); std::cout << std::endl; - std::cout << "Probability with optimal play: " << res << " ~ " << std::setprecision(5) << boost::rational_cast(res) * 100 << "%" << std::endl; + std::cout << "Probability with optimal play: "; + print_probability(std::cout, res) << std::endl; std::cout << "Took " << std::chrono::duration_cast(end - start) << "." << std::endl; std::cout << "Visited " << game->enumerated_states() << " states." << std::endl; std::cout << "Enumerated " << game->position_tablebase().size() << " unique game states. " << std::endl;