From afb6fee54051527e583be80498e868c20f553910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Thu, 8 Feb 2024 22:08:44 +0100 Subject: [PATCH] intoduce CLI option to reduce memory consumption --- include/command_line_interface.h | 8 ++++++ include/download.h | 4 +-- include/game_interface.h | 15 +++++++++++ include/game_state.h | 15 ----------- include/game_state.hpp | 2 +- include/make_state.h | 3 +-- src/command_line_interface.cpp | 6 +++-- src/download.cpp | 19 +++++++++++--- src/make_state.cpp | 44 +++++++++++++++----------------- 9 files changed, 67 insertions(+), 49 deletions(-) diff --git a/include/command_line_interface.h b/include/command_line_interface.h index 835a574..528fa9a 100644 --- a/include/command_line_interface.h +++ b/include/command_line_interface.h @@ -73,6 +73,14 @@ namespace Hanabi */ bool list_actions{false}; + /** + * If set to true, only roughly half of the game states will be stored in the internal lookup table. + * This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed + * (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated). + * You should typically never need this, unless you are very specifically short on memory and. + * */ + bool save_memory{false}; + /** * If true, prints version information of the program and exits immediately. */ diff --git a/include/download.h b/include/download.h index ff8bccf..ddcf60b 100644 --- a/include/download.h +++ b/include/download.h @@ -23,7 +23,7 @@ namespace Download * @return Game state * */ - Hanabi::Game get_game(int game_id, std::optional score_goal); + Hanabi::Game get_game(int game_id, std::optional score_goal, bool save_memory = false); /** * @brief Create game object from given source. @@ -32,7 +32,7 @@ namespace Download * @return Game state * */ - Hanabi::Game get_game(std::string const & filename, std::optional score_goal); + Hanabi::Game get_game(std::string const & filename, std::optional score_goal, bool save_memory = false); } // namespace Download diff --git a/include/game_interface.h b/include/game_interface.h index 281f130..6da5651 100644 --- a/include/game_interface.h +++ b/include/game_interface.h @@ -24,6 +24,21 @@ namespace Hanabi bool operator!=(const CardMultiplicity &) const; }; + struct HanabiStateConfig + { + /** What score to consider as a win. If left empty, automatically replaced by max score on construction. */ + std::optional score_goal; + /** The number of clues gained when a stack is finished or a card is discarded. Usually 1, 1/2 in clue starved. */ + clue_t num_clues_gained_on_discard_or_stack_finished {1}; + /** + * If set to true, only roughly half of the game states will be stored in the internal lookup table. + * This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed + * (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated). + * You should typically never need this, unless you are very specifically short on memory and. + * */ + bool save_memory {false}; + }; + class HanabiStateIF { public: diff --git a/include/game_state.h b/include/game_state.h index 43d8c3e..a721d66 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -62,21 +62,6 @@ namespace Hanabi std::array _array{}; }; - struct HanabiStateConfig - { - /** What score to consider as a win. If left empty, automatically replaced by max score on construction. */ - std::optional score_goal; - /** The number of clues gained when a stack is finished or a card is discarded. Usually 1, 1/2 in clue starved. */ - clue_t num_clues_gained_on_discard_or_stack_finished {1}; - /** - * If set to true, only roughly half of the game states will be stored in the internal lookup table. - * This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed - * (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated). - * You should typically never need this, unless you are very specifically short on memory and. - * */ - bool save_memory {false}; - }; - // A game mimics a game state together with a list of actions and allows to traverse the game // history by making and reverting the stored actions. template diff --git a/include/game_state.hpp b/include/game_state.hpp index c414d77..30b9199 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -1070,7 +1070,7 @@ namespace Hanabi if (discard_index == invalid_index) { for (std::uint8_t index = 0; index < hand_size; index++) { Card const card = _hands[_turn][index]; - auto it = std::find_if(_hands[_turn].begin() + index + 1, _hands[_turn].end(), [&card, this](Card const & card_in_hand) { + auto it = std::find_if(_hands[_turn].begin() + index + 1, _hands[_turn].end(), [&card](Card const & card_in_hand) { return card_in_hand == card; }); if (it != _hands[_turn].end()) { diff --git a/include/make_state.h b/include/make_state.h index c0fbae3..8ecf5b9 100644 --- a/include/make_state.h +++ b/include/make_state.h @@ -26,8 +26,7 @@ namespace Hanabi std::size_t num_suits, Hanabi::player_t num_players, std::vector const &deck, - clue_t num_clues_gained_on_discard_or_stack_finished = clue_t(1), - std::optional score_goal = std::nullopt + Hanabi::HanabiStateConfig config ); } diff --git a/src/command_line_interface.cpp b/src/command_line_interface.cpp index 50ff49b..1b4b623 100644 --- a/src/command_line_interface.cpp +++ b/src/command_line_interface.cpp @@ -66,11 +66,11 @@ namespace Hanabi Game game = [&parms] { if (std::holds_alternative(parms.game)) { - return Download::get_game(std::get(parms.game), convert_optional(parms.score_goal)); + return Download::get_game(std::get(parms.game), convert_optional(parms.score_goal), parms.save_memory); } else { - return Download::get_game(std::get(parms.game), convert_optional(parms.score_goal)); + return Download::get_game(std::get(parms.game), convert_optional(parms.score_goal), parms.save_memory); } }(); @@ -257,6 +257,7 @@ namespace Hanabi ("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 " "probabilities.") + ("save-memory", "Reduce memory consumption by roughly 50%. This results in roughly 5 times as much execution time, so use this only if you are really short on memory.") ("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.") ("version,v", "Print version information and exit."); @@ -349,6 +350,7 @@ namespace Hanabi parms.recursive = vm.count("recursive") > 0; parms.list_actions = vm.count("list-actions") > 0; parms.quiet = vm.count("quiet") > 0; + parms.save_memory = vm.count("save-memory") > 0; if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != clue_t(0)) { diff --git a/src/download.cpp b/src/download.cpp index c660b76..3024ef0 100644 --- a/src/download.cpp +++ b/src/download.cpp @@ -32,7 +32,7 @@ namespace Download return boost::json::parse(game_json).as_object(); } - Hanabi::Game get_game(int game_id, std::optional score_goal) + Hanabi::Game get_game(int game_id, std::optional score_goal, bool save_memory) { std::optional const game_json = download_game_json(game_id); if (!game_json.has_value() or game_json.value().empty()) @@ -42,10 +42,15 @@ namespace Download Hanabi::GameInfo game_info = Parsing::parse_game(game_json.value()); - return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, game_info.num_clues_gained_per_discard_or_stack_finished, score_goal), game_info}; + Hanabi::HanabiStateConfig config; + config.num_clues_gained_on_discard_or_stack_finished = game_info.num_clues_gained_per_discard_or_stack_finished; + config.save_memory = save_memory; + config.score_goal = score_goal; + + return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, config), game_info}; } - Hanabi::Game get_game(std::string const & filename, std::optional score_goal) + Hanabi::Game get_game(std::string const & filename, std::optional score_goal, bool save_memory) { std::optional const game_json = open_game_json(filename.c_str()); if (!game_json.has_value() or game_json.value().empty()) @@ -54,6 +59,12 @@ namespace Download } Hanabi::GameInfo game_info = Parsing::parse_game(game_json.value()); - return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, game_info.num_clues_gained_per_discard_or_stack_finished, score_goal), game_info}; + + Hanabi::HanabiStateConfig config; + config.num_clues_gained_on_discard_or_stack_finished = game_info.num_clues_gained_per_discard_or_stack_finished; + config.save_memory = save_memory; + config.score_goal = score_goal; + + return {make_game_state(game_info.num_suits, game_info.num_players, game_info.deck, config), game_info}; } } // namespace Download diff --git a/src/make_state.cpp b/src/make_state.cpp index 6abd81c..10c2a3f 100644 --- a/src/make_state.cpp +++ b/src/make_state.cpp @@ -5,24 +5,22 @@ namespace Hanabi { std::unique_ptr make_game_state( std::size_t num_suits, Hanabi::player_t num_players, std::vector const & deck, - clue_t num_clues_gained_on_discard_or_stack_finished, std::optional< - uint8_t> score_goal + Hanabi::HanabiStateConfig config ) { - uint8_t actual_score_goal = score_goal.value_or(5 * num_suits); switch (num_players) { case 2: switch (num_suits) { case 3: - return std::unique_ptr(new Hanabi::HanabiState<3, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<3, 2, 5>(deck, config)); case 4: - return std::unique_ptr(new Hanabi::HanabiState<4, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<4, 2, 5>(deck, config)); case 5: - return std::unique_ptr(new Hanabi::HanabiState<5, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<5, 2, 5>(deck, config)); case 6: - return std::unique_ptr(new Hanabi::HanabiState<6, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<6, 2, 5>(deck, config)); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } @@ -30,13 +28,13 @@ namespace Hanabi switch (num_suits) { case 3: - return std::unique_ptr(new Hanabi::HanabiState<3, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<3, 3, 5>(deck, config)); case 4: - return std::unique_ptr(new Hanabi::HanabiState<4, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<4, 3, 5>(deck, config)); case 5: - return std::unique_ptr(new Hanabi::HanabiState<5, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<5, 3, 5>(deck, config)); case 6: - return std::unique_ptr(new Hanabi::HanabiState<6, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<6, 3, 5>(deck, config)); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } @@ -44,13 +42,13 @@ namespace Hanabi switch (num_suits) { case 3: - return std::unique_ptr(new Hanabi::HanabiState<3, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<3, 4, 4>(deck, config)); case 4: - return std::unique_ptr(new Hanabi::HanabiState<4, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<4, 4, 4>(deck, config)); case 5: - return std::unique_ptr(new Hanabi::HanabiState<5, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<5, 4, 4>(deck, config)); case 6: - return std::unique_ptr(new Hanabi::HanabiState<6, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<6, 4, 4>(deck, config)); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } @@ -58,13 +56,13 @@ namespace Hanabi switch (num_suits) { case 3: - return std::unique_ptr(new Hanabi::HanabiState<3, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<3, 5, 4>(deck, config)); case 4: - return std::unique_ptr(new Hanabi::HanabiState<4, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<4, 5, 4>(deck, config)); case 5: - return std::unique_ptr(new Hanabi::HanabiState<5, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<5, 5, 4>(deck, config)); case 6: - return std::unique_ptr(new Hanabi::HanabiState<6, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<6, 5, 4>(deck, config)); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } @@ -72,13 +70,13 @@ namespace Hanabi switch (num_suits) { case 3: - return std::unique_ptr(new Hanabi::HanabiState<3, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<3, 6, 3>(deck, config)); case 4: - return std::unique_ptr(new Hanabi::HanabiState<4, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<4, 6, 3>(deck, config)); case 5: - return std::unique_ptr(new Hanabi::HanabiState<5, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<5, 6, 3>(deck, config)); case 6: - return std::unique_ptr(new Hanabi::HanabiState<6, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); + return std::unique_ptr(new Hanabi::HanabiState<6, 6, 3>(deck, config)); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); }