improve program output and CLI documentation
This commit is contained in:
parent
ac4cf5e797
commit
6ce692a06f
2 changed files with 93 additions and 16 deletions
|
@ -13,23 +13,81 @@ namespace Hanabi {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CLIParms {
|
struct CLIParms {
|
||||||
|
/**
|
||||||
|
* The data source for the game to be analysed.
|
||||||
|
* If of type int, assumed to be a game ID from hanab.live
|
||||||
|
* If of type string, assumed to be a (relative) filename to a game in hanab.live json format,
|
||||||
|
* see https://raw.githubusercontent.com/Hanabi-Live/hanabi-live/main/misc/example_game_with_comments.jsonc
|
||||||
|
* for a format specification.
|
||||||
|
*/
|
||||||
std::variant<int, std::string> game {};
|
std::variant<int, std::string> game {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of a 'winning' game, i.e. what score (number of cards played) is considered
|
||||||
|
* to be winning.
|
||||||
|
* If std::nullopt, then the maximum score of the game will be used.
|
||||||
|
*/
|
||||||
boost::optional<uint8_t> score_goal {};
|
boost::optional<uint8_t> score_goal {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether game_state_spec denotes a turn number or draw pile size.
|
||||||
|
*/
|
||||||
GameStateSpecType game_state_spec_type { GameStateSpecType::draw_pile_size };
|
GameStateSpecType game_state_spec_type { GameStateSpecType::draw_pile_size };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Either a turn number or a draw pile size, depending on game_state_spec_type.
|
||||||
|
* If this represents a draw pile size, we mean the first turn of the game where the draw pile
|
||||||
|
* had the given size, i.e. the turn immediately after drawing a card.
|
||||||
|
* If this represents a turn, we just mean the corresponding turn number, starting numbering at 1
|
||||||
|
* (since this is also the case on hanab.live)
|
||||||
|
* Thus, a turn number of 0 is undefined and a turn number of 1 corresponds to no actions taken in the game.
|
||||||
|
*/
|
||||||
unsigned game_state_spec { 5 };
|
unsigned game_state_spec { 5 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to launch an interactive exploration shell for the game state after performing analysis.
|
||||||
|
*/
|
||||||
boost::optional<bool> interactive {};
|
boost::optional<bool> interactive {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, deactivates non-essential output (to cout).
|
||||||
|
*/
|
||||||
bool quiet { false };
|
bool quiet { false };
|
||||||
// If this holds std::monostate, then all clue numbers are evaluated
|
|
||||||
|
/**
|
||||||
|
* If this holds std::monostate, then all clue numbers are evaluated.
|
||||||
|
* Otherwise, the specified clue modifier is applied (relative to actual number of clues).
|
||||||
|
* Thus, setting this to 0 has no effect.
|
||||||
|
*/
|
||||||
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
|
||||||
|
*/
|
||||||
bool recursive { false };
|
bool recursive { false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get an output stream that is std::cout or a Null-Stream.
|
||||||
|
* @param quiet If true, NullStream is returned, otherwise std::cout
|
||||||
|
*/
|
||||||
std::ostream & quiet_ostream(bool quiet);
|
std::ostream & quiet_ostream(bool quiet);
|
||||||
|
|
||||||
constexpr int download_failed = 1;
|
constexpr int download_failed = 1;
|
||||||
constexpr int state_unreachable = 2;
|
constexpr int state_unreachable = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse parameters from command-line arguments.
|
||||||
|
* @param argc Number of arguments
|
||||||
|
* @param argv Array of C-Style strings, where argv[0] is ignored (as the program name).
|
||||||
|
*
|
||||||
|
* @note This is exactly the signature of the main() method
|
||||||
|
*/
|
||||||
std::optional<CLIParms> parse_parms(int argc, char *argv[]);
|
std::optional<CLIParms> parse_parms(int argc, char *argv[]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Execute parsed parameters.
|
||||||
|
*/
|
||||||
int run_cli(CLIParms parms);
|
int run_cli(CLIParms parms);
|
||||||
}
|
}
|
||||||
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
||||||
|
|
|
@ -187,33 +187,52 @@ namespace Hanabi {
|
||||||
std::optional<CLIParms> parse_parms(int argc, char *argv[])
|
std::optional<CLIParms> parse_parms(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
CLIParms parms;
|
CLIParms parms;
|
||||||
bpo::options_description desc("Allowed options");
|
bpo::options_description desc("Allowed program options");
|
||||||
desc.add_options()
|
desc.add_options()
|
||||||
("help", "print this help message")
|
("help,h", "Print this help message.")
|
||||||
("game,g", bpo::value<int>(), "Game ID from hanab.live")
|
("game,g", bpo::value<int>(), "Game ID from hanab.live.")
|
||||||
("file,f", bpo::value<std::string>(), "Input file containing game in hanab.live json format")
|
("file,f", bpo::value<std::string>(), "Input file containing game in hanab.live json format.")
|
||||||
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. Turn 1 means no actions have been taken.")
|
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. "
|
||||||
("draw,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
"Turn 1 means no actions have been taken. ")
|
||||||
("score,s", bpo::value<boost::optional<uint8_t>>(&parms.score_goal), "Score that counts as a win, i.e. is optimized for achieving.")
|
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
||||||
("clue-modifier,c", bpo::value<int>(), "Modification to the number of clues applied to selected game state.")
|
("score-goal,s", bpo::value<boost::optional<uint8_t>>(&parms.score_goal),
|
||||||
("all-clues", "Solve instance for all clue counts in all positions")
|
"Score that counts as a win, i.e. is optimized for achieving. If unspecified, the maximum possible "
|
||||||
("interactive,i", bpo::value<boost::optional<bool>>(&parms.interactive), "Drop into interactive shell to explore game")
|
"score will be used.")
|
||||||
("recursive,r", "Print probabilities for all further sizes of draw pile.")
|
("clue-modifier,c", bpo::value<int>(), "Optional relative modification to the number of clues applied to "
|
||||||
("quiet,q", "Deactivate all non-essential prints")
|
"selected game state. If unspecified, has value 0 and thus no effect.")
|
||||||
|
("interactive,i", bpo::value<boost::optional<bool>>(&parms.interactive),
|
||||||
|
"After computation, drop into interactive shell to explore game. If unspecified, a reasonable default "
|
||||||
|
"is chosen depending on other options.")
|
||||||
|
("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")
|
||||||
|
("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.")
|
||||||
;
|
;
|
||||||
|
|
||||||
bpo::variables_map vm;
|
bpo::variables_map vm;
|
||||||
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
|
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
|
||||||
bpo::notify(vm);
|
bpo::notify(vm);
|
||||||
|
|
||||||
[[maybe_unused]] auto count = vm.count("clue-modifier");
|
|
||||||
|
|
||||||
if (vm.count("help")) {
|
if (vm.count("help")) {
|
||||||
|
std::cout << "This program performs endgame analysis of Hanabi. It calculates optimum strategies\n"
|
||||||
|
"(and their winning percentages assuming a random draw pile distribution) under the\n"
|
||||||
|
"assumption that all players know their hands at all times during the game.\n"
|
||||||
|
"Note that this is thus not a legal Hanabi strategy, but provides an upper bound on\n"
|
||||||
|
"any legal strategy. Experience shows that in real-world games (with easy variants),\n"
|
||||||
|
"the computed winning percentages are often not far from what humans (or programs)\n"
|
||||||
|
"can achieve when playing well, though there are certainly counterexamples.\n"
|
||||||
|
"Therefore, treat the displayed winning percentages with care, since they might not\n"
|
||||||
|
"be realistic for real-world play in some cases.\n" << std::endl;
|
||||||
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 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 << "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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse file or game id specification, ensuring at most one is given
|
||||||
if (vm.count("file") + vm.count("game") != 1) {
|
if (vm.count("file") + vm.count("game") != 1) {
|
||||||
std::cout << "Exactly one option of 'file' and 'id' has to be given." << std::endl;
|
std::cout << "Exactly one option of 'file' and 'id' has to be given." << std::endl;
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
|
@ -228,7 +247,7 @@ namespace Hanabi {
|
||||||
parms.game = vm["game"].as<int>();
|
parms.game = vm["game"].as<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse game state options
|
// Parse game state options (turn or draw), ensuring at most one is given.
|
||||||
if (vm.count("draw") + vm.count("turn") != 1) {
|
if (vm.count("draw") + vm.count("turn") != 1) {
|
||||||
std::cout << "Conflicting options --draw and --turn." << std::endl;
|
std::cout << "Conflicting options --draw and --turn." << std::endl;
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
|
|
Loading…
Reference in a new issue