From 35b78cb4db5d922e93a96e2a83153232f51c43ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Fri, 12 Jan 2024 23:08:02 +0100 Subject: [PATCH] Implement option to list all actions in replay --- include/command_line_interface.h | 8 ++++- include/game_interface.h | 3 +- include/game_state.h | 2 +- include/game_state.hpp | 56 ++++++++++++++++++++------------ src/command_line_interface.cpp | 29 ++++++++++++++++- src/game_interface.cpp | 12 +++++-- 6 files changed, 84 insertions(+), 26 deletions(-) diff --git a/include/command_line_interface.h b/include/command_line_interface.h index 81faa80..82f7d35 100644 --- a/include/command_line_interface.h +++ b/include/command_line_interface.h @@ -63,9 +63,15 @@ namespace Hanabi std::variant clue_spec{static_cast(0)}; /** - * If true, then all states corresponding to smaller draw pile sizes + * If true, then all states corresponding to smaller draw pile sizes are evaluated as well. */ bool recursive{false}; + + /** + * If true, lists all possible actions and their win probabilities in the specified + * and future game states, including suboptimal ones. + */ + bool list_actions{false}; }; /** diff --git a/include/game_interface.h b/include/game_interface.h index 80d8149..1e511f1 100644 --- a/include/game_interface.h +++ b/include/game_interface.h @@ -80,7 +80,7 @@ namespace Hanabi [[nodiscard]] virtual std::pair, std::vector> dump_unique_id_parts() const = 0; - virtual std::vector>> get_reasonable_actions() = 0; + virtual std::vector>> get_reasonable_actions(bool evaluate_all = false) = 0; virtual std::vector>> possible_next_states(hand_index_t index, bool play) = 0; @@ -110,6 +110,7 @@ namespace Hanabi Game(std::unique_ptr state, GameInfo game_info); [[nodiscard]] unsigned cur_turn() const; + [[nodiscard]] unsigned num_turns() const; void make_turn(); diff --git a/include/game_state.h b/include/game_state.h index 94f3378..ee88e7e 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -127,7 +127,7 @@ namespace Hanabi [[nodiscard]] std::pair, std::vector> dump_unique_id_parts() const final; - std::vector>> get_reasonable_actions() final; + std::vector>> get_reasonable_actions(bool evaluate_all = false) final; std::vector>> possible_next_states(hand_index_t index, bool play) final; diff --git a/include/game_state.hpp b/include/game_state.hpp index b231f42..e1016ce 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -765,7 +765,7 @@ namespace Hanabi template std::vector>> - HanabiState::get_reasonable_actions() + HanabiState::get_reasonable_actions(bool evaluate_all) { std::vector>> reasonable_actions{}; @@ -784,9 +784,14 @@ namespace Hanabi bool known = true; probability_t sum_of_probabilities = 0; - do_for_each_potential_draw(index, true, [this, &sum_of_probabilities + do_for_each_potential_draw(index, true, [this, &sum_of_probabilities, &evaluate_all , &known](const unsigned long multiplicity) { - const std::optional prob = lookup(); + std::optional prob; + if (evaluate_all) { + prob = evaluate_state(); + } else { + prob = lookup(); + } if (prob.has_value()) { sum_of_probabilities += prob.value() * multiplicity; @@ -796,7 +801,6 @@ namespace Hanabi known = false; } }); - if (known) { const unsigned long total_weight = std::max(static_cast(_weighted_draw_pile_size), 1ul); @@ -821,9 +825,14 @@ namespace Hanabi bool known = true; probability_t sum_of_probabilities = 0; - do_for_each_potential_draw(index, false, [this, &sum_of_probabilities + do_for_each_potential_draw(index, false, [this, &sum_of_probabilities, &evaluate_all , &known](const unsigned long multiplicity) { - const std::optional prob = lookup(); + std::optional prob; + if (evaluate_all) { + prob = evaluate_state(); + } else { + prob = lookup(); + } if (prob.has_value()) { sum_of_probabilities += prob.value() * multiplicity; @@ -833,7 +842,6 @@ namespace Hanabi known = false; } }); - if (known) { const unsigned long total_weight = std::max(static_cast(_weighted_draw_pile_size), 1ul); @@ -854,7 +862,12 @@ namespace Hanabi if (_num_clues >= clue_t(1)) { give_clue(); - const std::optional prob = lookup(); + std::optional prob; + if (evaluate_all) { + prob = evaluate_state(); + } else { + prob = lookup(); + } const Action action = {ActionType::clue, Cards::unknown}; reasonable_actions.emplace_back(action, prob); revert_clue(); @@ -1273,20 +1286,23 @@ namespace Hanabi ) { // This macro can be activated if we want to dump details on all game states visited for analysis purposes. +#define DUMP_STATES #ifdef DUMP_STATES - std::cout << *this << std::endl; - const auto [id_parts, cards] = dump_unique_id_parts(); - std::cout << "id is: " << id << ", id parts are: "; - for (auto const & part: id_parts) { - std::cout << part << " "; + if (id == 87476369689) { + std::cout << *this << std::endl; + const auto [id_parts, cards] = dump_unique_id_parts(); + std::cout << "id is: " << id << ", id parts are: "; + for (auto const & part: id_parts) { + std::cout << part << " "; + } + std::cout << ", encoded cards are "; + for (auto const & part: cards) { + std::cout << part << " "; + } + std::cout << ", probability is "; + print_probability(std::cout, probability); + std::cout << "\n" << std::endl; } - std::cout << ", encoded cards are "; - for (auto const & part: cards) { - std::cout << part << " "; - } - std::cout << ", probability is "; - print_probability(std::cout, probability); - std::cout << "\n" << std::endl; #endif if (_position_tablebase.contains(id)) { diff --git a/src/command_line_interface.cpp b/src/command_line_interface.cpp index 1140fe8..c32ca1a 100644 --- a/src/command_line_interface.cpp +++ b/src/command_line_interface.cpp @@ -5,6 +5,7 @@ #include "state_explorer.h" #include "command_line_interface.h" +#include "myassert.h" namespace bpo = boost::program_options; @@ -121,6 +122,22 @@ namespace Hanabi // We are ready to start backtracking now game.state->init_backtracking_information(); + quiet_os << "Base state specified:\n" << *game.state << std::endl; + + if (parms.list_actions) { + unsigned max_turn = game.cur_turn(); + // Note this loop is safe even for unsigned max_turn, since turn() is at least 1. + for (unsigned turn = game.num_turns(); turn >= max_turn; turn--) { + bool reached = game.goto_turn(turn); + game.state->evaluate_state(); + ASSERT(reached); + for (auto const & [action, probability] : game.state->get_reasonable_actions(true)) { + std::cout << "Turn " << turn << ", " << action << ": "; + print_probability(std::cout, probability) << std::endl; + } + } + return EXIT_SUCCESS; + } if (parms.recursive) { @@ -198,7 +215,7 @@ namespace Hanabi cli(game); } } - return 0; + return EXIT_SUCCESS; } std::optional parse_parms(int argc, char *argv[]) @@ -225,6 +242,7 @@ namespace Hanabi ("recursive,r", "If specified, also analyzes all game states with fewer cards in the draw pile than the " "specified one, considering smaller draw pile sizes first. Also useful for infinite analysis " "(specifying this with a complex base state") + ("list-actions,l","List all actions (including suboptimal ones) of all turns after specified state.") ("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their " "probabilities.") ("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program."); @@ -247,6 +265,7 @@ namespace Hanabi std::cout << desc << std::endl; std::cout << "You have to either specify --game or --file as a data source.\n"; std::cout << "You may not specify both --turn and --draw at the same time.\n"; + std::cout << "You may not specifiy both --recursive and --list-actions at the same time.\n"; std::cout << "If none of them is specified, it is assumed that --draw 5 was given." << std::endl; return std::nullopt; } @@ -300,8 +319,16 @@ namespace Hanabi parms.clue_spec = std::monostate(); } + if (vm.count("recursive") + vm.count("list-actions") >= 2) + { + std::cout << "Conflicting options --recursive and --list-actions specified." << std::endl; + std::cout << "Use '--help' to print a help message." << std::endl; + return std::nullopt; + } + // Parse opt-in bool options parms.recursive = vm.count("recursive") > 0; + parms.list_actions = vm.count("list-actions") > 0; parms.quiet = vm.count("quiet") > 0; if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != clue_t(0)) diff --git a/src/game_interface.cpp b/src/game_interface.cpp index d2b89e6..dcda23f 100644 --- a/src/game_interface.cpp +++ b/src/game_interface.cpp @@ -34,11 +34,19 @@ namespace Hanabi return next_action + 1; } + unsigned Game::num_turns() const + { + return actions.size(); + } + void Game::make_turn() { ASSERT(next_action < actions.size()); - Card const next_draw = deck[deck.size() - state->draw_pile_size()]; - state->rotate_next_draw(next_draw); + size_t draw_pile_size = state->draw_pile_size(); + if (draw_pile_size > 0) { + Card const next_draw = deck.at(deck.size() - draw_pile_size); + state->rotate_next_draw(next_draw); + } Action const & action = actions[next_action]; std::uint8_t index; switch (action.type)