Implement option to list all actions in replay

This commit is contained in:
Maximilian Keßler 2024-01-12 23:08:02 +01:00
parent cdf8575283
commit 35b78cb4db
Signed by: max
GPG key ID: BCC5A619923C0BA5
6 changed files with 84 additions and 26 deletions

View file

@ -63,9 +63,15 @@ namespace Hanabi
std::variant<std::monostate, clue_t> clue_spec{static_cast<clue_t>(0)}; std::variant<std::monostate, clue_t> clue_spec{static_cast<clue_t>(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}; 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};
}; };
/** /**

View file

@ -80,7 +80,7 @@ namespace Hanabi
[[nodiscard]] virtual std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const = 0; [[nodiscard]] virtual std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const = 0;
virtual std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() = 0; virtual std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions(bool evaluate_all = false) = 0;
virtual std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> virtual std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>>
possible_next_states(hand_index_t index, bool play) = 0; possible_next_states(hand_index_t index, bool play) = 0;
@ -110,6 +110,7 @@ namespace Hanabi
Game(std::unique_ptr<HanabiStateIF> state, GameInfo game_info); Game(std::unique_ptr<HanabiStateIF> state, GameInfo game_info);
[[nodiscard]] unsigned cur_turn() const; [[nodiscard]] unsigned cur_turn() const;
[[nodiscard]] unsigned num_turns() const;
void make_turn(); void make_turn();

View file

@ -127,7 +127,7 @@ namespace Hanabi
[[nodiscard]] std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const final; [[nodiscard]] std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const final;
std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() final; std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions(bool evaluate_all = false) final;
std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>>
possible_next_states(hand_index_t index, bool play) final; possible_next_states(hand_index_t index, bool play) final;

View file

@ -765,7 +765,7 @@ namespace Hanabi
template<suit_t num_suits, player_t num_players, hand_index_t hand_size> template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
std::vector<std::pair<Action, std::optional<probability_t>>> std::vector<std::pair<Action, std::optional<probability_t>>>
HanabiState<num_suits, num_players, hand_size>::get_reasonable_actions() HanabiState<num_suits, num_players, hand_size>::get_reasonable_actions(bool evaluate_all)
{ {
std::vector<std::pair<Action, std::optional<probability_t>>> reasonable_actions{}; std::vector<std::pair<Action, std::optional<probability_t>>> reasonable_actions{};
@ -784,9 +784,14 @@ namespace Hanabi
bool known = true; bool known = true;
probability_t sum_of_probabilities = 0; 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) { , &known](const unsigned long multiplicity) {
const std::optional<probability_t> prob = lookup(); std::optional<probability_t> prob;
if (evaluate_all) {
prob = evaluate_state();
} else {
prob = lookup();
}
if (prob.has_value()) if (prob.has_value())
{ {
sum_of_probabilities += prob.value() * multiplicity; sum_of_probabilities += prob.value() * multiplicity;
@ -796,7 +801,6 @@ namespace Hanabi
known = false; known = false;
} }
}); });
if (known) if (known)
{ {
const unsigned long total_weight = std::max(static_cast<unsigned long>(_weighted_draw_pile_size), 1ul); const unsigned long total_weight = std::max(static_cast<unsigned long>(_weighted_draw_pile_size), 1ul);
@ -821,9 +825,14 @@ namespace Hanabi
bool known = true; bool known = true;
probability_t sum_of_probabilities = 0; 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) { , &known](const unsigned long multiplicity) {
const std::optional<probability_t> prob = lookup(); std::optional<probability_t> prob;
if (evaluate_all) {
prob = evaluate_state();
} else {
prob = lookup();
}
if (prob.has_value()) if (prob.has_value())
{ {
sum_of_probabilities += prob.value() * multiplicity; sum_of_probabilities += prob.value() * multiplicity;
@ -833,7 +842,6 @@ namespace Hanabi
known = false; known = false;
} }
}); });
if (known) if (known)
{ {
const unsigned long total_weight = std::max(static_cast<unsigned long>(_weighted_draw_pile_size), 1ul); const unsigned long total_weight = std::max(static_cast<unsigned long>(_weighted_draw_pile_size), 1ul);
@ -854,7 +862,12 @@ namespace Hanabi
if (_num_clues >= clue_t(1)) if (_num_clues >= clue_t(1))
{ {
give_clue(); give_clue();
const std::optional<probability_t> prob = lookup(); std::optional<probability_t> prob;
if (evaluate_all) {
prob = evaluate_state();
} else {
prob = lookup();
}
const Action action = {ActionType::clue, Cards::unknown}; const Action action = {ActionType::clue, Cards::unknown};
reasonable_actions.emplace_back(action, prob); reasonable_actions.emplace_back(action, prob);
revert_clue(); revert_clue();
@ -1273,7 +1286,9 @@ namespace Hanabi
) )
{ {
// This macro can be activated if we want to dump details on all game states visited for analysis purposes. // 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 #ifdef DUMP_STATES
if (id == 87476369689) {
std::cout << *this << std::endl; std::cout << *this << std::endl;
const auto [id_parts, cards] = dump_unique_id_parts(); const auto [id_parts, cards] = dump_unique_id_parts();
std::cout << "id is: " << id << ", id parts are: "; std::cout << "id is: " << id << ", id parts are: ";
@ -1287,6 +1302,7 @@ namespace Hanabi
std::cout << ", probability is "; std::cout << ", probability is ";
print_probability(std::cout, probability); print_probability(std::cout, probability);
std::cout << "\n" << std::endl; std::cout << "\n" << std::endl;
}
#endif #endif
if (_position_tablebase.contains(id)) if (_position_tablebase.contains(id))
{ {

View file

@ -5,6 +5,7 @@
#include "state_explorer.h" #include "state_explorer.h"
#include "command_line_interface.h" #include "command_line_interface.h"
#include "myassert.h"
namespace bpo = boost::program_options; namespace bpo = boost::program_options;
@ -121,6 +122,22 @@ namespace Hanabi
// We are ready to start backtracking now // We are ready to start backtracking now
game.state->init_backtracking_information(); 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) if (parms.recursive)
{ {
@ -198,7 +215,7 @@ namespace Hanabi
cli(game); cli(game);
} }
} }
return 0; return EXIT_SUCCESS;
} }
std::optional<CLIParms> parse_parms(int argc, char *argv[]) std::optional<CLIParms> 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 " ("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 " "specified one, considering smaller draw pile sizes first. Also useful for infinite analysis "
"(specifying this with a complex base state") "(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 " ("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their "
"probabilities.") "probabilities.")
("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program."); ("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 << desc << std::endl;
std::cout << "You have to either specify --game or --file as a data source.\n"; 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 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; std::cout << "If none of them is specified, it is assumed that --draw 5 was given." << std::endl;
return std::nullopt; return std::nullopt;
} }
@ -300,8 +319,16 @@ namespace Hanabi
parms.clue_spec = std::monostate(); 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 // Parse opt-in bool options
parms.recursive = vm.count("recursive") > 0; parms.recursive = vm.count("recursive") > 0;
parms.list_actions = vm.count("list-actions") > 0;
parms.quiet = vm.count("quiet") > 0; parms.quiet = vm.count("quiet") > 0;
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0)) if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0))

View file

@ -34,11 +34,19 @@ namespace Hanabi
return next_action + 1; return next_action + 1;
} }
unsigned Game::num_turns() const
{
return actions.size();
}
void Game::make_turn() void Game::make_turn()
{ {
ASSERT(next_action < actions.size()); ASSERT(next_action < actions.size());
Card const next_draw = deck[deck.size() - state->draw_pile_size()]; 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); state->rotate_next_draw(next_draw);
}
Action const & action = actions[next_action]; Action const & action = actions[next_action];
std::uint8_t index; std::uint8_t index;
switch (action.type) switch (action.type)