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};
/**
* 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.
*/

View File

@ -23,7 +23,7 @@ namespace Download
* @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.
@ -32,7 +32,7 @@ namespace Download
* @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

View File

@ -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<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
{
public:

View File

@ -62,21 +62,6 @@ namespace Hanabi
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
// history by making and reverting the stored actions.
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) {
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()) {

View File

@ -26,8 +26,7 @@ namespace Hanabi
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 = clue_t(1),
std::optional<uint8_t> score_goal = std::nullopt
Hanabi::HanabiStateConfig config
);
}

View File

@ -66,11 +66,11 @@ namespace Hanabi
Game game = [&parms] {
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
{
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.")
("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<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();
}
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);
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<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());
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

View File

@ -5,24 +5,22 @@ namespace Hanabi
{
std::unique_ptr<Hanabi::HanabiStateIF> make_game_state(
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<
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<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:
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:
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:
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:
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<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:
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:
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:
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:
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<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:
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:
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:
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:
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<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:
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:
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:
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:
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<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:
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:
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:
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:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}