Implement option to list all actions in replay
This commit is contained in:
parent
cdf8575283
commit
35b78cb4db
6 changed files with 84 additions and 26 deletions
|
@ -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};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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))
|
||||||
{
|
{
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue