intoduce CLI option to reduce memory consumption

This commit is contained in:
Maximilian Keßler 2024-02-08 22:08:44 +01:00
parent bfc731ae36
commit afb6fee540
Signed by: max
GPG key ID: BCC5A619923C0BA5
9 changed files with 67 additions and 49 deletions

View file

@ -73,6 +73,14 @@ namespace Hanabi
*/ */
bool list_actions{false}; 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. * If true, prints version information of the program and exits immediately.
*/ */

View file

@ -23,7 +23,7 @@ namespace Download
* @return Game state * @return Game state
* *
*/ */
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal); Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal, bool save_memory = false);
/** /**
* @brief Create game object from given source. * @brief Create game object from given source.
@ -32,7 +32,7 @@ namespace Download
* @return Game state * @return Game state
* *
*/ */
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal); Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal, bool save_memory = false);
} // namespace Download } // namespace Download

View file

@ -24,6 +24,21 @@ namespace Hanabi
bool operator!=(const CardMultiplicity &) const; 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<uint8_t> 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 class HanabiStateIF
{ {
public: public:

View file

@ -62,21 +62,6 @@ namespace Hanabi
std::array<inner_array_t, num_suits> _array{}; std::array<inner_array_t, num_suits> _array{};
}; };
struct HanabiStateConfig
{
/** What score to consider as a win. If left empty, automatically replaced by max score on construction. */
std::optional<uint8_t> 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 // 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. // history by making and reverting the stored actions.
template<suit_t num_suits, player_t num_players, hand_index_t hand_size> template<suit_t num_suits, player_t num_players, hand_index_t hand_size>

View file

@ -1070,7 +1070,7 @@ namespace Hanabi
if (discard_index == invalid_index) { if (discard_index == invalid_index) {
for (std::uint8_t index = 0; index < hand_size; index++) { for (std::uint8_t index = 0; index < hand_size; index++) {
Card const card = _hands[_turn][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; return card_in_hand == card;
}); });
if (it != _hands[_turn].end()) { if (it != _hands[_turn].end()) {

View file

@ -26,8 +26,7 @@ namespace Hanabi
std::size_t num_suits, std::size_t num_suits,
Hanabi::player_t num_players, Hanabi::player_t num_players,
std::vector<Hanabi::Card> const &deck, std::vector<Hanabi::Card> const &deck,
clue_t num_clues_gained_on_discard_or_stack_finished = clue_t(1), Hanabi::HanabiStateConfig config
std::optional<uint8_t> score_goal = std::nullopt
); );
} }

View file

@ -66,11 +66,11 @@ namespace Hanabi
Game game = [&parms] { Game game = [&parms] {
if (std::holds_alternative<int>(parms.game)) if (std::holds_alternative<int>(parms.game))
{ {
return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal)); return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal), parms.save_memory);
} }
else else
{ {
return Download::get_game(std::get<std::string>(parms.game), convert_optional(parms.score_goal)); return Download::get_game(std::get<std::string>(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.") ("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 " ("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their "
"probabilities.") "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.") ("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.")
("version,v", "Print version information and exit."); ("version,v", "Print version information and exit.");
@ -349,6 +350,7 @@ namespace Hanabi
parms.recursive = vm.count("recursive") > 0; parms.recursive = vm.count("recursive") > 0;
parms.list_actions = vm.count("list-actions") > 0; parms.list_actions = vm.count("list-actions") > 0;
parms.quiet = vm.count("quiet") > 0; parms.quiet = vm.count("quiet") > 0;
parms.save_memory = vm.count("save-memory") > 0;
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0)) if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0))
{ {

View file

@ -32,7 +32,7 @@ namespace Download
return boost::json::parse(game_json).as_object(); return boost::json::parse(game_json).as_object();
} }
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal) Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal, bool save_memory)
{ {
std::optional<boost::json::object> const game_json = download_game_json(game_id); std::optional<boost::json::object> const game_json = download_game_json(game_id);
if (!game_json.has_value() or game_json.value().empty()) 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()); 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<uint8_t> score_goal) Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal, bool save_memory)
{ {
std::optional<boost::json::object> const game_json = open_game_json(filename.c_str()); std::optional<boost::json::object> const game_json = open_game_json(filename.c_str());
if (!game_json.has_value() or game_json.value().empty()) 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()); 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 } // namespace Download

View file

@ -5,24 +5,22 @@ namespace Hanabi
{ {
std::unique_ptr<Hanabi::HanabiStateIF> make_game_state( std::unique_ptr<Hanabi::HanabiStateIF> make_game_state(
std::size_t num_suits, Hanabi::player_t num_players, std::vector<Hanabi::Card> const & deck, std::size_t num_suits, Hanabi::player_t num_players, std::vector<Hanabi::Card> const & deck,
clue_t num_clues_gained_on_discard_or_stack_finished, std::optional< Hanabi::HanabiStateConfig config
uint8_t> score_goal
) )
{ {
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
switch (num_players) switch (num_players)
{ {
case 2: case 2:
switch (num_suits) switch (num_suits)
{ {
case 3: case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 2, 5>(deck, config));
case 4: case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 2, 5>(deck, config));
case 5: case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 2, 5>(deck, config));
case 6: case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 2, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 2, 5>(deck, config));
default: default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
} }
@ -30,13 +28,13 @@ namespace Hanabi
switch (num_suits) switch (num_suits)
{ {
case 3: case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 3, 5>(deck, config));
case 4: case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 3, 5>(deck, config));
case 5: case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 3, 5>(deck, config));
case 6: case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 3, 5>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 3, 5>(deck, config));
default: default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
} }
@ -44,13 +42,13 @@ namespace Hanabi
switch (num_suits) switch (num_suits)
{ {
case 3: case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 4, 4>(deck, config));
case 4: case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 4, 4>(deck, config));
case 5: case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 4, 4>(deck, config));
case 6: case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 4, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 4, 4>(deck, config));
default: default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
} }
@ -58,13 +56,13 @@ namespace Hanabi
switch (num_suits) switch (num_suits)
{ {
case 3: case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 5, 4>(deck, config));
case 4: case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 5, 4>(deck, config));
case 5: case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 5, 4>(deck, config));
case 6: case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 5, 4>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 5, 4>(deck, config));
default: default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
} }
@ -72,13 +70,13 @@ namespace Hanabi
switch (num_suits) switch (num_suits)
{ {
case 3: case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 6, 3>(deck, config));
case 4: case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 6, 3>(deck, config));
case 5: case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 6, 3>(deck, config));
case 6: case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 6, 3>(deck, actual_score_goal, num_clues_gained_on_discard_or_stack_finished)); return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 6, 3>(deck, config));
default: default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
} }