Rework CLI
This should cover all use cases / exceptions now and is in a reasonably good code state.
This commit is contained in:
parent
3f69d0ac71
commit
32af52ae9e
9 changed files with 344 additions and 152 deletions
|
@ -16,7 +16,10 @@ include_directories(.)
|
||||||
include_directories(${Boost_INCLUDE_DIR})
|
include_directories(${Boost_INCLUDE_DIR})
|
||||||
|
|
||||||
add_executable(endgame-analyzer src/main.cpp src/state_explorer.cpp src/download.cpp
|
add_executable(endgame-analyzer src/main.cpp src/state_explorer.cpp src/download.cpp
|
||||||
src/game_state.cpp)
|
src/game_state.cpp
|
||||||
|
include/null_buffer.h
|
||||||
|
include/command_line_interface.h
|
||||||
|
src/command_line_interface.cpp)
|
||||||
|
|
||||||
target_link_libraries(endgame-analyzer cpr)
|
target_link_libraries(endgame-analyzer cpr)
|
||||||
target_link_libraries(endgame-analyzer Boost::program_options)
|
target_link_libraries(endgame-analyzer Boost::program_options)
|
||||||
|
|
35
include/command_line_interface.h
Normal file
35
include/command_line_interface.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
||||||
|
#define DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
#include "game_state.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Hanabi {
|
||||||
|
enum class GameStateSpecType {
|
||||||
|
turn = 0,
|
||||||
|
draw_pile_size = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CLIParms {
|
||||||
|
std::variant<int, std::string> game {};
|
||||||
|
boost::optional<uint8_t> score_goal {};
|
||||||
|
GameStateSpecType game_state_spec_type { GameStateSpecType::draw_pile_size };
|
||||||
|
unsigned game_state_spec { 5 };
|
||||||
|
boost::optional<bool> interactive {};
|
||||||
|
bool quiet { false };
|
||||||
|
// If this holds std::monostate, then all clue numbers are evaluated
|
||||||
|
std::variant<std::monostate, clue_t> clue_spec;
|
||||||
|
bool recursive { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream & quiet_ostream(bool quiet);
|
||||||
|
|
||||||
|
constexpr int download_failed = 1;
|
||||||
|
constexpr int state_unreachable = 2;
|
||||||
|
|
||||||
|
std::optional<CLIParms> parse_parms(int argc, char *argv[]);
|
||||||
|
int run_cli(CLIParms parms);
|
||||||
|
}
|
||||||
|
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
|
@ -43,7 +43,7 @@ namespace Download {
|
||||||
* draw pile hits the given size, whichever comes first
|
* draw pile hits the given size, whichever comes first
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
Hanabi::Game get_game(std::variant<int, const char*> game_spec, std::optional<uint8_t> score_goal);
|
Hanabi::Game get_game(std::variant<int, std::string> game_spec, std::optional<uint8_t> score_goal);
|
||||||
|
|
||||||
} // namespace Download
|
} // namespace Download
|
||||||
|
|
||||||
|
|
|
@ -266,6 +266,8 @@ struct Game {
|
||||||
bool goto_draw_pile_size(size_t draw_pile_break);
|
bool goto_draw_pile_size(size_t draw_pile_break);
|
||||||
bool goto_turn(size_t turn);
|
bool goto_turn(size_t turn);
|
||||||
|
|
||||||
|
bool holds_state();
|
||||||
|
|
||||||
std::unique_ptr<HanabiStateIF> state;
|
std::unique_ptr<HanabiStateIF> state;
|
||||||
std::vector<Action> actions;
|
std::vector<Action> actions;
|
||||||
std::vector<Card> deck;
|
std::vector<Card> deck;
|
||||||
|
|
25
include/null_buffer.h
Normal file
25
include/null_buffer.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef DYNAMIC_PROGRAM_NULL_BUFFER_H
|
||||||
|
#define DYNAMIC_PROGRAM_NULL_BUFFER_H
|
||||||
|
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
namespace NullBuffer {
|
||||||
|
|
||||||
|
class NullBuffer final : public std::streambuf {
|
||||||
|
public:
|
||||||
|
int overflow(int c) override { return c; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class NullStream final : public std::ostream {
|
||||||
|
public:
|
||||||
|
NullStream() : std::ostream(&_m_sb) {}
|
||||||
|
private:
|
||||||
|
NullBuffer _m_sb;
|
||||||
|
};
|
||||||
|
|
||||||
|
NullStream null_stream;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif //DYNAMIC_PROGRAM_NULL_BUFFER_H
|
264
src/command_line_interface.cpp
Normal file
264
src/command_line_interface.cpp
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
#include "command_line_interface.h"
|
||||||
|
#include "download.h"
|
||||||
|
#include "null_buffer.h"
|
||||||
|
#include "state_explorer.h"
|
||||||
|
#include "boost/program_options.hpp"
|
||||||
|
|
||||||
|
namespace bpo = boost::program_options;
|
||||||
|
|
||||||
|
namespace Hanabi {
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
std::optional<T> convert_optional(boost::optional<T> val)
|
||||||
|
{
|
||||||
|
if (not val.has_value())
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return { std::move(val.value()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& quiet_ostream(bool const quiet)
|
||||||
|
{
|
||||||
|
if (quiet)
|
||||||
|
{
|
||||||
|
return NullBuffer::null_stream;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return std::cout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int run_cli(CLIParms parms)
|
||||||
|
{
|
||||||
|
// We want to do this sanity check here again,
|
||||||
|
// so that the run_cli method itself can ensure that arguments are fully valid
|
||||||
|
// and we cannot run into crashes due to bad specified parameters
|
||||||
|
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != 0)
|
||||||
|
{
|
||||||
|
throw std::logic_error("Cannot use nonzero clue modifier together with recursive evaluation mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// For convenience, we use a custom output stream,
|
||||||
|
// which is either std:cout or an ostream that does nothing.
|
||||||
|
// This enables us to easily respect the 'quiet option' without
|
||||||
|
// too much logic overhead.
|
||||||
|
std::ostream & quiet_os = quiet_ostream(parms.quiet);
|
||||||
|
quiet_os.precision(10);
|
||||||
|
|
||||||
|
// Convert unset option to useful default, depending on some other options
|
||||||
|
if (not parms.interactive.has_value())
|
||||||
|
{
|
||||||
|
parms.interactive = !(parms.quiet or parms.recursive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load game, either from file or from hanab.live
|
||||||
|
Game game = Download::get_game(parms.game, convert_optional(parms.score_goal));
|
||||||
|
if (not game.holds_state())
|
||||||
|
{
|
||||||
|
if(std::holds_alternative<int>(parms.game)) {
|
||||||
|
std::cout << "Failed to download game " << std::get<int>(parms.game) << " from hanab.live." << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Failed to open file " << std::get<std::string>(parms.game) << "." << std::endl;
|
||||||
|
}
|
||||||
|
return download_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go to specified game state
|
||||||
|
bool reached_state;
|
||||||
|
switch (parms.game_state_spec_type)
|
||||||
|
{
|
||||||
|
case GameStateSpecType::turn:
|
||||||
|
reached_state = game.goto_turn(parms.game_state_spec);
|
||||||
|
break;
|
||||||
|
case GameStateSpecType::draw_pile_size:
|
||||||
|
reached_state = game.goto_draw_pile_size(parms.game_state_spec);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Invalid game state specification type encountered");
|
||||||
|
}
|
||||||
|
|
||||||
|
// In some cases, it is not possible to reach the specified game state,
|
||||||
|
// because the game simply does not contain enough actions for a specific turn
|
||||||
|
// or draw pile size to be reached. In this case, we can't analyze anything.
|
||||||
|
if (not reached_state)
|
||||||
|
{
|
||||||
|
switch (parms.game_state_spec_type)
|
||||||
|
{
|
||||||
|
case GameStateSpecType::turn:
|
||||||
|
std::cout << "Specified turn number of ";
|
||||||
|
break;
|
||||||
|
case GameStateSpecType::draw_pile_size:
|
||||||
|
std::cout << "Specified draw pile size of ";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw std::logic_error("Invalid game state specification type encountered");
|
||||||
|
}
|
||||||
|
std::cout << parms.game_state_spec << " cannot be reached with specified replay." << std::endl;
|
||||||
|
return state_unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust clues now. Note that we already checked that this does nothing
|
||||||
|
// in case 'recursive' is specified, so further game actions will still
|
||||||
|
// be legal in that case.
|
||||||
|
if (std::holds_alternative<clue_t>(parms.clue_spec))
|
||||||
|
{
|
||||||
|
game.state->modify_clues(std::get<clue_t>(parms.clue_spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are ready to start backtracking now
|
||||||
|
game.state->init_backtracking_information();
|
||||||
|
|
||||||
|
if (parms.recursive)
|
||||||
|
{
|
||||||
|
// Regardless of whether the game state was specified by turn or by the number of cards in the
|
||||||
|
// draw pile, we will always want to iterate with respect to draw pile size here:
|
||||||
|
// This already evaluates all intermediate game states as well, because stalling is an option
|
||||||
|
// (except for rare cases, where there is a forced win that does not need stalling).
|
||||||
|
size_t const max_draw_pile_size = game.state->draw_pile_size();
|
||||||
|
for(size_t remaining_cards = 1; remaining_cards <= max_draw_pile_size; remaining_cards++) {
|
||||||
|
if (!game.goto_draw_pile_size(remaining_cards))
|
||||||
|
{
|
||||||
|
std::cout << "The given draw pile size (" << remaining_cards << ") cannot be obtained with the specified replay." << std::endl;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if (std::holds_alternative<std::monostate>(parms.clue_spec))
|
||||||
|
{
|
||||||
|
// Here, it is important that we keep track of the correct number of clues:
|
||||||
|
// When modifying the game state, we want to reset to the actual number of clues
|
||||||
|
// to ensure that actions taken are legal.
|
||||||
|
clue_t const original_num_clues = game.state->num_clues();
|
||||||
|
for(clue_t num_clues = 0; num_clues <= 8; num_clues++) {
|
||||||
|
game.state->set_clues(num_clues);
|
||||||
|
probability_t const result = game.state->evaluate_state();
|
||||||
|
std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues
|
||||||
|
<< " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos;
|
||||||
|
print_probability(std::cout, result) << std::endl;
|
||||||
|
}
|
||||||
|
game.state->set_clues(original_num_clues);
|
||||||
|
} else {
|
||||||
|
probability_t const result = game.state->evaluate_state();
|
||||||
|
std::cout << "Probability with " << remaining_cards << " cards left in deck: ";
|
||||||
|
print_probability(std::cout, result) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
quiet_os << "Analysing state: \n\n" << *game.state << "\n" << std::endl;
|
||||||
|
auto const start = std::chrono::high_resolution_clock::now();
|
||||||
|
probability_t const result = game.state->evaluate_state();
|
||||||
|
auto const end = std::chrono::high_resolution_clock::now();
|
||||||
|
|
||||||
|
std::chrono::milliseconds const duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||||
|
|
||||||
|
// Output information now
|
||||||
|
std::cout << "Probability with optimal play: ";
|
||||||
|
print_probability(std::cout, result) << std::endl;
|
||||||
|
quiet_os << "Took " << duration.count() << "ms." << std::endl;
|
||||||
|
quiet_os << "Visited " << game.state->enumerated_states() << " states." << std::endl;
|
||||||
|
quiet_os << "Enumerated " << game.state->position_tablebase().size() << " unique game states. " << std::endl;
|
||||||
|
|
||||||
|
// If specified, we can now launch the interactive shell
|
||||||
|
if (parms.interactive)
|
||||||
|
{
|
||||||
|
quiet_os << "\nDropping into interactive command line to explore result (type 'help'):" << std::endl;
|
||||||
|
auto game_shared = std::shared_ptr<HanabiStateIF>(game.state.release());
|
||||||
|
cli(game_shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<CLIParms> parse_parms(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
CLIParms parms;
|
||||||
|
bpo::options_description desc("Allowed 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")
|
||||||
|
;
|
||||||
|
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 << desc << std::endl;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (vm.count("file"))
|
||||||
|
{
|
||||||
|
parms.game = vm["file"].as<std::string>();
|
||||||
|
}
|
||||||
|
if (vm.count("game"))
|
||||||
|
{
|
||||||
|
parms.game = vm["game"].as<int>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse game state options
|
||||||
|
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;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (vm.count("turn"))
|
||||||
|
{
|
||||||
|
parms.game_state_spec_type = GameStateSpecType::turn;
|
||||||
|
}
|
||||||
|
if (vm.count("draw"))
|
||||||
|
{
|
||||||
|
parms.game_state_spec_type = GameStateSpecType::draw_pile_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse clue change options
|
||||||
|
if (vm.count("clue-modifier") + vm.count("all-clues") >= 2)
|
||||||
|
{
|
||||||
|
std::cout << "Conflicting options --clue-modifier and --all-clues." << std::endl;
|
||||||
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (vm.count("clue-modifier"))
|
||||||
|
{
|
||||||
|
[[maybe_unused]] auto c = vm.count("clue-modifier");
|
||||||
|
parms.clue_spec = vm["clue-modifier"].as<clue_t>();
|
||||||
|
}
|
||||||
|
if (vm.count("all-clues"))
|
||||||
|
{
|
||||||
|
parms.clue_spec = std::monostate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse opt-in bool options
|
||||||
|
parms.recursive = vm.count("recursive") > 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) != 0)
|
||||||
|
{
|
||||||
|
std::cout << "Conflicting options --recursive and --clue-modifier" << std::endl;
|
||||||
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing went wrong, we correctly parsed the parms now.
|
||||||
|
return parms;
|
||||||
|
}
|
||||||
|
}
|
|
@ -172,12 +172,12 @@ namespace Download {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Hanabi::Game get_game(std::variant<int, const char*> game_spec, std::optional<uint8_t> score_goal){
|
Hanabi::Game get_game(std::variant<int, std::string> game_spec, std::optional<uint8_t> score_goal){
|
||||||
const std::optional<boost::json::object> game_json_opt = [&game_spec]() {
|
const std::optional<boost::json::object> game_json_opt = [&game_spec]() {
|
||||||
if (game_spec.index() == 0) {
|
if (game_spec.index() == 0) {
|
||||||
return download_game_json(std::get<int>(game_spec));
|
return download_game_json(std::get<int>(game_spec));
|
||||||
} else {
|
} else {
|
||||||
return open_game_json(std::get<const char *>(game_spec));
|
return open_game_json(std::get<std::string>(game_spec).c_str());
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,11 @@ namespace Hanabi {
|
||||||
return next_action + 1 == turn;
|
return next_action + 1 == turn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Game::holds_state()
|
||||||
|
{
|
||||||
|
return state != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool Game::goto_draw_pile_size(size_t draw_pile_break)
|
bool Game::goto_draw_pile_size(size_t draw_pile_break)
|
||||||
{
|
{
|
||||||
while (state->draw_pile_size() > draw_pile_break and next_action < actions.size()) {
|
while (state->draw_pile_size() > draw_pile_break and next_action < actions.size()) {
|
||||||
|
|
154
src/main.cpp
154
src/main.cpp
|
@ -1,154 +1,12 @@
|
||||||
#include <iostream>
|
#include <optional>
|
||||||
#include <vector>
|
#include "command_line_interface.h"
|
||||||
#include <variant>
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include <boost/program_options.hpp>
|
|
||||||
|
|
||||||
#include "game_state.h"
|
|
||||||
#include "download.h"
|
|
||||||
#include "state_explorer.h"
|
|
||||||
|
|
||||||
|
|
||||||
namespace Hanabi {
|
|
||||||
void analyze_game_and_start_cli(std::variant<int, const char*> game_id, int turn, int draw_pile_size, std::optional<uint8_t> score_goal,
|
|
||||||
bool start_cli, bool print_remaining_states, bool quiet, clue_t clue_modifier, bool all_clues) {
|
|
||||||
auto game = Download::get_game(game_id, score_goal);
|
|
||||||
if (game.state == nullptr) {
|
|
||||||
if(game_id.index() == 0) {
|
|
||||||
std::cout << "Failed to download game " << std::get<int>(game_id) << " from hanab.live." << std::endl;
|
|
||||||
} else {
|
|
||||||
std::cout << "Failed to open file " << std::get<const char*>(game_id) << "." << std::endl;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
game.goto_draw_pile_size(draw_pile_size);
|
|
||||||
if (draw_pile_size != 0 and game.state->draw_pile_size() != static_cast<size_t>(draw_pile_size)) {
|
|
||||||
std::cout << "The given draw pile size (" << draw_pile_size << ") cannot be obtained with the specified replay." << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
game.state->modify_clues(clue_modifier);
|
|
||||||
|
|
||||||
if (not quiet) {
|
|
||||||
std::cout << "Analysing state: " << std::endl << std::endl << *game.state << std::endl;
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
game.state->init_backtracking_information();
|
|
||||||
auto start = std::chrono::high_resolution_clock::now();
|
|
||||||
boost::rational<probability_base_type> result;
|
|
||||||
if (not print_remaining_states) {
|
|
||||||
result = game.state->evaluate_state();
|
|
||||||
}
|
|
||||||
auto end = std::chrono::high_resolution_clock::now();
|
|
||||||
|
|
||||||
std::cout.precision(10);
|
|
||||||
|
|
||||||
if (not print_remaining_states) {
|
|
||||||
std::cout << "Probability with optimal play: ";
|
|
||||||
print_probability(std::cout, result) << std::endl;
|
|
||||||
}
|
|
||||||
if (not quiet) {
|
|
||||||
std::cout << "Took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start) << "." << std::endl;
|
|
||||||
std::cout << "Visited " << game.state->enumerated_states() << " states." << std::endl;
|
|
||||||
std::cout << "Enumerated " << game.state->position_tablebase().size() << " unique game states. " << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (print_remaining_states)
|
|
||||||
{
|
|
||||||
size_t const max_draw_pile_size = game.state->draw_pile_size();
|
|
||||||
for(size_t remaining_cards = 1; remaining_cards <= max_draw_pile_size; remaining_cards++) {
|
|
||||||
if (!game.goto_draw_pile_size(remaining_cards))
|
|
||||||
{
|
|
||||||
std::cout << "The given draw pile size (" << remaining_cards << ") cannot be obtained with the specified replay." << std::endl;
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if (all_clues) {
|
|
||||||
clue_t original_num_clues = game.state->num_clues();
|
|
||||||
for(clue_t num_clues = 0; num_clues <= 8; num_clues++) {
|
|
||||||
game.state->set_clues(num_clues);
|
|
||||||
result = game.state->evaluate_state();
|
|
||||||
std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues
|
|
||||||
<< " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos;
|
|
||||||
print_probability(std::cout, result) << std::endl;
|
|
||||||
}
|
|
||||||
game.state->set_clues(original_num_clues);
|
|
||||||
} else {
|
|
||||||
result = game.state->evaluate_state();
|
|
||||||
std::cout << "Probability with " << remaining_cards << " cards left in deck: ";
|
|
||||||
print_probability(std::cout, result) << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start_cli) {
|
|
||||||
std::cout << std::endl;
|
|
||||||
std::cout << "Dropping into interactive command line to explore result (type 'help'):" << std::endl;
|
|
||||||
auto game_shared = std::shared_ptr<HanabiStateIF>(game.state.release());
|
|
||||||
auto states = game_shared->possible_next_states(0, false);
|
|
||||||
cli(game_shared);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int turn = 100;
|
std::optional<Hanabi::CLIParms> parms = Hanabi::parse_parms(argc, argv);
|
||||||
int draw_pile_size = 0;
|
if (parms.has_value())
|
||||||
std::optional<int> score;
|
{
|
||||||
bool interactive_shell;
|
return Hanabi::run_cli(parms.value());
|
||||||
int clue_modifier = 0;
|
|
||||||
|
|
||||||
boost::program_options::options_description desc("Allowed options");
|
|
||||||
desc.add_options()
|
|
||||||
("help", "print this help message")
|
|
||||||
("id,g", boost::program_options::value<int>(), "Game ID from hanab.live")
|
|
||||||
("file,f", boost::program_options::value<std::string>(), "Input file containing game in hanab.live json format")
|
|
||||||
("turn,t", boost::program_options::value<int>(&turn), "Turn number of state to analyze. Turn 1 means no actions have been taken.")
|
|
||||||
("draw,d", boost::program_options::value<int>(&draw_pile_size), "Draw pile size of state to analyze.")
|
|
||||||
("score,s", boost::program_options::value<int>(), "Score that counts as a win, i.e. is optimized for achieving.")
|
|
||||||
("interactive,i", boost::program_options::value<bool>(&interactive_shell)->default_value(true), "Drop into interactive shell to explore game")
|
|
||||||
("remaining-states,r", "Print probabilities for all further sizes of draw pile.")
|
|
||||||
("clue-modifier,c", boost::program_options::value<int>(&clue_modifier)->default_value(0), "Modification to the number of clues applied to selected game state.")
|
|
||||||
("all-clues", "Solve instance for all clue counts in all positions")
|
|
||||||
("quiet,q", "Deactivate all non-essential prints")
|
|
||||||
;
|
|
||||||
boost::program_options::variables_map vm;
|
|
||||||
boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm);
|
|
||||||
boost::program_options::notify(vm);
|
|
||||||
|
|
||||||
if (vm.count("help")) {
|
|
||||||
std::cout << desc << std::endl;
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("file") + vm.count("id") != 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;
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("remaining-states") and clue_modifier != 0) {
|
|
||||||
std::cout << "You cannot use a clue modifier and solve the remaining states, the further game progress might be impossible with modified clue count." << std::endl;
|
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("draw") + vm.count("turn") != 1) {
|
|
||||||
std::cout << "Exactly one option of 'draw' and 'turn' has to be given." << std::endl;
|
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("score")) {
|
|
||||||
score = vm["score"].as<int>();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.count("file")) {
|
|
||||||
Hanabi::analyze_game_and_start_cli(vm["file"].as<std::string>().c_str(), turn, draw_pile_size, score, interactive_shell, vm.count("remaining-states"), vm.count("quiet"), clue_modifier, vm.count("all-clues"));
|
|
||||||
} else {
|
|
||||||
Hanabi::analyze_game_and_start_cli(vm["id"].as<int>(), turn, draw_pile_size, score, interactive_shell, vm.count("remaining-states"), vm.count("quiet"), clue_modifier, vm.count("all-clues"));
|
|
||||||
}
|
}
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue