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 {
|
||||
/**
|
||||
* 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 {};
|
||||
|
||||
/**
|
||||
* 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 {};
|
||||
|
||||
/**
|
||||
* Whether game_state_spec denotes a turn number or 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 };
|
||||
|
||||
/**
|
||||
* Whether to launch an interactive exploration shell for the game state after performing analysis.
|
||||
*/
|
||||
boost::optional<bool> interactive {};
|
||||
|
||||
/**
|
||||
* If true, deactivates non-essential output (to cout).
|
||||
*/
|
||||
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)};
|
||||
|
||||
/**
|
||||
* If true, then all states corresponding to smaller draw pile sizes
|
||||
*/
|
||||
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);
|
||||
|
||||
constexpr int download_failed = 1;
|
||||
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[]);
|
||||
|
||||
/**
|
||||
* @brief Execute parsed parameters.
|
||||
*/
|
||||
int run_cli(CLIParms parms);
|
||||
}
|
||||
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
||||
|
|
|
@ -187,33 +187,52 @@ namespace Hanabi {
|
|||
std::optional<CLIParms> parse_parms(int argc, char *argv[])
|
||||
{
|
||||
CLIParms parms;
|
||||
bpo::options_description desc("Allowed options");
|
||||
bpo::options_description desc("Allowed program options");
|
||||
desc.add_options()
|
||||
("help", "print this help message")
|
||||
("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")
|
||||
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. Turn 1 means no actions have been taken.")
|
||||
("draw,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
||||
("score,s", bpo::value<boost::optional<uint8_t>>(&parms.score_goal), "Score that counts as a win, i.e. is optimized for achieving.")
|
||||
("clue-modifier,c", bpo::value<int>(), "Modification to the number of clues applied to selected game state.")
|
||||
("all-clues", "Solve instance for all clue counts in all positions")
|
||||
("interactive,i", bpo::value<boost::optional<bool>>(&parms.interactive), "Drop into interactive shell to explore game")
|
||||
("recursive,r", "Print probabilities for all further sizes of draw pile.")
|
||||
("quiet,q", "Deactivate all non-essential prints")
|
||||
("help,h", "Print this help message.")
|
||||
("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.")
|
||||
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. "
|
||||
"Turn 1 means no actions have been taken. ")
|
||||
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
||||
("score-goal,s", bpo::value<boost::optional<uint8_t>>(&parms.score_goal),
|
||||
"Score that counts as a win, i.e. is optimized for achieving. If unspecified, the maximum possible "
|
||||
"score will be used.")
|
||||
("clue-modifier,c", bpo::value<int>(), "Optional relative modification to the number of clues applied to "
|
||||
"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::store(bpo::parse_command_line(argc, argv, desc), vm);
|
||||
bpo::notify(vm);
|
||||
|
||||
[[maybe_unused]] auto count = vm.count("clue-modifier");
|
||||
|
||||
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 << "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 << "If none of them is specified, it is assumed that --draw 5 was given." << std::endl;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Parse file or game id specification, ensuring at most one is given
|
||||
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 << "Use '--help' to print a help message." << std::endl;
|
||||
|
@ -228,7 +247,7 @@ namespace Hanabi {
|
|||
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) {
|
||||
std::cout << "Conflicting options --draw and --turn." << std::endl;
|
||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||
|
|
Loading…
Reference in a new issue