diff --git a/include/download.h b/include/download.h index cdb82bb..c7b68a4 100644 --- a/include/download.h +++ b/include/download.h @@ -45,7 +45,7 @@ namespace Download { * * @note Turns start counting at 1, since this is also the way hanab.live does it. */ - std::unique_ptr get_game(std::variant game_spec, unsigned turn = 1, size_t draw_pile_break = 0); + std::unique_ptr get_game(std::variant game_spec, unsigned turn = 1, size_t draw_pile_break = 0, std::optional score_goal = std::nullopt); } // namespace Download diff --git a/include/game_state.h b/include/game_state.h index 39ff84a..5888e10 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -257,7 +257,7 @@ template class HanabiState : public HanabiStateIF { public: HanabiState() = default; - explicit HanabiState(const std::vector& deck); + explicit HanabiState(const std::vector& deck, uint8_t score_goal = 5 * num_suits); void give_clue() final; void discard(hand_index_t index) final; @@ -374,6 +374,7 @@ private: // further values of game state that are technically determined, but we update them anyway int8_t _pace{}; uint8_t _score{}; + uint8_t _score_goal{}; // For reverting the current game std::stack _actions_log; diff --git a/include/game_state.hpp b/include/game_state.hpp index 4b7eb94..e8ac360 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -111,7 +111,7 @@ namespace Hanabi { } template - HanabiState::HanabiState(const std::vector &deck): + HanabiState::HanabiState(const std::vector &deck, uint8_t score_goal): _turn(0), _num_clues(max_num_clues), _weighted_draw_pile_size(deck.size()), @@ -119,8 +119,9 @@ namespace Hanabi { _hands(), _draw_pile(), _endgame_turns_left(no_endgame), - _pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)), + _pace(deck.size() - score_goal - num_players * (hand_size - 1)), _score(0), + _score_goal(score_goal), _actions_log(), _relative_representation(), _position_tablebase(), @@ -561,7 +562,7 @@ namespace Hanabi { std::vector>> HanabiState::get_reasonable_actions() { std::vector>> reasonable_actions {}; - if(_score == 5 * num_suits or _pace < 0 or _endgame_turns_left == 0) { + if(_score == _score_goal or _pace < 0 or _endgame_turns_left == 0) { return reasonable_actions; } @@ -663,7 +664,7 @@ namespace Hanabi { _enumerated_states++; const unsigned long id_of_state = unique_id(); - if (_score == 5 * num_suits) { + if (_score == _score_goal) { return 1; } if(_pace < 0 || _endgame_turns_left == 0) { diff --git a/src/download.cpp b/src/download.cpp index 1187a0e..04c86dd 100644 --- a/src/download.cpp +++ b/src/download.cpp @@ -100,9 +100,11 @@ namespace Download { const std::vector& deck, const std::vector& actions, size_t start_turn, - size_t draw_pile_break = 0 + size_t draw_pile_break = 0, + std::optional score_goal = std::nullopt ) { - auto game = std::unique_ptr(new Hanabi::HanabiState(deck)); + uint8_t actual_score_goal = score_goal.value_or(5 * num_suits); + auto game = std::unique_ptr(new Hanabi::HanabiState(deck, actual_score_goal)); std::uint8_t index; for (size_t i = 0; i < std::min(start_turn - 1, actions.size()); i++) { if (game->draw_pile_size() == draw_pile_break) { @@ -133,7 +135,7 @@ namespace Download { return game; } - std::unique_ptr get_game(std::variant game_spec, unsigned turn, size_t draw_pile_break) { + std::unique_ptr get_game(std::variant game_spec, unsigned turn, size_t draw_pile_break, std::optional score_goal) { const std::optional game_json_opt = [&game_spec]() { if (game_spec.index() == 0) { return download_game_json(std::get(game_spec)); @@ -156,65 +158,65 @@ namespace Download { case 2: switch(num_suits) { case 3: - return produce_state<3,2,5>(deck, actions, turn, draw_pile_break); + return produce_state<3,2,5>(deck, actions, turn, draw_pile_break, score_goal); case 4: - return produce_state<4,2,5>(deck, actions, turn, draw_pile_break); + return produce_state<4,2,5>(deck, actions, turn, draw_pile_break, score_goal); case 5: - return produce_state<5,2,5>(deck, actions, turn, draw_pile_break); + return produce_state<5,2,5>(deck, actions, turn, draw_pile_break, score_goal); case 6: - return produce_state<6,2,5>(deck, actions, turn, draw_pile_break); + return produce_state<6,2,5>(deck, actions, turn, draw_pile_break, score_goal); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } case 3: switch(num_suits) { case 3: - return produce_state<3,3,5>(deck, actions, turn, draw_pile_break); + return produce_state<3,3,5>(deck, actions, turn, draw_pile_break, score_goal); case 4: - return produce_state<4,3,5>(deck, actions, turn, draw_pile_break); + return produce_state<4,3,5>(deck, actions, turn, draw_pile_break, score_goal); case 5: - return produce_state<5,3,5>(deck, actions, turn, draw_pile_break); + return produce_state<5,3,5>(deck, actions, turn, draw_pile_break, score_goal); case 6: - return produce_state<6,3,5>(deck, actions, turn, draw_pile_break); + return produce_state<6,3,5>(deck, actions, turn, draw_pile_break, score_goal); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } case 4: switch(num_suits) { case 3: - return produce_state<3,4,4>(deck, actions, turn, draw_pile_break); + return produce_state<3,4,4>(deck, actions, turn, draw_pile_break, score_goal); case 4: - return produce_state<4,4,4>(deck, actions, turn, draw_pile_break); + return produce_state<4,4,4>(deck, actions, turn, draw_pile_break, score_goal); case 5: - return produce_state<5,4,4>(deck, actions, turn, draw_pile_break); + return produce_state<5,4,4>(deck, actions, turn, draw_pile_break, score_goal); case 6: - return produce_state<6,4,4>(deck, actions, turn, draw_pile_break); + return produce_state<6,4,4>(deck, actions, turn, draw_pile_break, score_goal); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } case 5: switch(num_suits) { case 3: - return produce_state<3,5,4>(deck, actions, turn, draw_pile_break); + return produce_state<3,5,4>(deck, actions, turn, draw_pile_break, score_goal); case 4: - return produce_state<4,5,4>(deck, actions, turn, draw_pile_break); + return produce_state<4,5,4>(deck, actions, turn, draw_pile_break, score_goal); case 5: - return produce_state<5,5,4>(deck, actions, turn, draw_pile_break); + return produce_state<5,5,4>(deck, actions, turn, draw_pile_break, score_goal); case 6: - return produce_state<6,5,4>(deck, actions, turn, draw_pile_break); + return produce_state<6,5,4>(deck, actions, turn, draw_pile_break, score_goal); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } case 6: switch(num_suits) { case 3: - return produce_state<3,6,3>(deck, actions, turn, draw_pile_break); + return produce_state<3,6,3>(deck, actions, turn, draw_pile_break, score_goal); case 4: - return produce_state<4,6,3>(deck, actions, turn, draw_pile_break); + return produce_state<4,6,3>(deck, actions, turn, draw_pile_break, score_goal); case 5: - return produce_state<5,6,3>(deck, actions, turn, draw_pile_break); + return produce_state<5,6,3>(deck, actions, turn, draw_pile_break, score_goal); case 6: - return produce_state<6,6,3>(deck, actions, turn, draw_pile_break); + return produce_state<6,6,3>(deck, actions, turn, draw_pile_break, score_goal); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } diff --git a/src/main.cpp b/src/main.cpp index 1e3ca15..69fa8fe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,8 +9,8 @@ namespace Hanabi { - void analyze_game_and_start_cli(std::variant game_id, int turn) { - auto game = Download::get_game(game_id, turn); + void analyze_game_and_start_cli(std::variant game_id, int turn, std::optional score_goal) { + auto game = Download::get_game(game_id, turn, 0, score_goal); if (game == nullptr) { if(game_id.index() == 0) { std::cout << "Failed to download game " << std::get(game_id) << " from hanab.live." << std::endl; @@ -41,17 +41,19 @@ namespace Hanabi { } void print_usage(const char* program_name) { - std::cout << "Usage: " << program_name << "(GAME_ID | GAME_FILE) TURN" << std::endl; + std::cout << "Usage: " << program_name << "(GAME_ID | GAME_FILE) TURN [SCORE_GOAL]" << std::endl; std::cout << " GAME_ID A game id from hanab.live" << std::endl; std::cout << " GAME_FILE A path to a file describing the game in hanab.live json format." << std::endl; std::cout << " TURN Turn number of state to analyze. Turn 1 means no actions have been taken." << std::endl; + std::cout << " TURN Turn number of state to analyze. Turn 1 means no actions have been taken." << std::endl; + std::cout << " SCORE_GOAL Score that counts as a win, i.e. is optimized for achieving." << std::endl; } } int main(int argc, char *argv[]) { - if(argc == 3) { + if(argc == 3 or argc == 4) { std::string game_str(argv[1]); std::string turn_str (argv[2]); @@ -63,10 +65,21 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + std::optional score_goal = std::nullopt; + if (argc == 4) { + std::string score_goal_str(argv[3]); + try { + score_goal = std::stoi(score_goal_str); + } catch(std::invalid_argument&) { + std::cout << "Could not parse score goal number " << score_goal_str; + return EXIT_FAILURE; + } + } + try { - Hanabi::analyze_game_and_start_cli(std::stoi(game_str), turn); + Hanabi::analyze_game_and_start_cli(std::stoi(game_str), turn, score_goal); } catch(std::invalid_argument&) { - Hanabi::analyze_game_and_start_cli(game_str.c_str(), turn); + Hanabi::analyze_game_and_start_cli(game_str.c_str(), turn, score_goal); } } else {