From a77de7efe821dbf0b8b34f4aaeb59265bf9b53e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Tue, 8 Aug 2023 00:29:19 +0200 Subject: [PATCH] implement tablebase --- download.h | 50 ++++++++++++++++++++++++-------------------- main.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 25 deletions(-) diff --git a/download.h b/download.h index 668c314..c75b537 100644 --- a/download.h +++ b/download.h @@ -111,11 +111,15 @@ namespace Download { std::unique_ptr produce_state( const std::vector& deck, const std::vector& actions, - size_t num_turns_to_replicate + size_t num_turns_to_replicate, + size_t draw_pile_break = 0 ) { auto game = std::unique_ptr(new Hanabi::HanabiState(deck)); std::uint8_t index; - for (size_t i = 0; i < num_turns_to_replicate; i++) { + for (size_t i = 0; i < std::min(num_turns_to_replicate, actions.size()); i++) { + if (game->draw_pile_size() == draw_pile_break) { + break; + } switch(actions[i].type) { case Hanabi::ActionType::color_clue: case Hanabi::ActionType::rank_clue: @@ -140,7 +144,7 @@ namespace Download { return game; } - std::unique_ptr get_game(std::variant game_spec, unsigned turn) { + std::unique_ptr get_game(std::variant game_spec, unsigned turn, size_t draw_pile_break = 0) { const boost::json::object game_json = [&game_spec]() { if (game_spec.index() == 0) { return download_game_json(std::get(game_spec)); @@ -157,65 +161,65 @@ namespace Download { case 2: switch(num_suits) { case 3: - return produce_state<3,2,5>(deck, actions, turn); + return produce_state<3,2,5>(deck, actions, turn, draw_pile_break); case 4: - return produce_state<4,2,5>(deck, actions, turn); + return produce_state<4,2,5>(deck, actions, turn, draw_pile_break); case 5: - return produce_state<5,2,5>(deck, actions, turn); + return produce_state<5,2,5>(deck, actions, turn, draw_pile_break); case 6: - return produce_state<6,2,5>(deck, actions, turn); + return produce_state<6,2,5>(deck, actions, turn, draw_pile_break); 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); + return produce_state<3,3,5>(deck, actions, turn, draw_pile_break); case 4: - return produce_state<4,3,5>(deck, actions, turn); + return produce_state<4,3,5>(deck, actions, turn, draw_pile_break); case 5: - return produce_state<5,3,5>(deck, actions, turn); + return produce_state<5,3,5>(deck, actions, turn, draw_pile_break); case 6: - return produce_state<6,3,5>(deck, actions, turn); + return produce_state<6,3,5>(deck, actions, turn, draw_pile_break); 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); + return produce_state<3,4,4>(deck, actions, turn, draw_pile_break); case 4: - return produce_state<4,4,4>(deck, actions, turn); + return produce_state<4,4,4>(deck, actions, turn, draw_pile_break); case 5: - return produce_state<5,4,4>(deck, actions, turn); + return produce_state<5,4,4>(deck, actions, turn, draw_pile_break); case 6: - return produce_state<6,4,4>(deck, actions, turn); + return produce_state<6,4,4>(deck, actions, turn, draw_pile_break); 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); + return produce_state<3,5,4>(deck, actions, turn, draw_pile_break); case 4: - return produce_state<4,5,4>(deck, actions, turn); + return produce_state<4,5,4>(deck, actions, turn, draw_pile_break); case 5: - return produce_state<5,5,4>(deck, actions, turn); + return produce_state<5,5,4>(deck, actions, turn, draw_pile_break); case 6: - return produce_state<6,5,4>(deck, actions, turn); + return produce_state<6,5,4>(deck, actions, turn, draw_pile_break); 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); + return produce_state<3,6,3>(deck, actions, turn, draw_pile_break); case 4: - return produce_state<4,6,3>(deck, actions, turn); + return produce_state<4,6,3>(deck, actions, turn, draw_pile_break); case 5: - return produce_state<5,6,3>(deck, actions, turn); + return produce_state<5,6,3>(deck, actions, turn, draw_pile_break); case 6: - return produce_state<6,6,3>(deck, actions, turn); + return produce_state<6,6,3>(deck, actions, turn, draw_pile_break); default: throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits)); } diff --git a/main.cpp b/main.cpp index 34d6376..d1f346a 100644 --- a/main.cpp +++ b/main.cpp @@ -16,12 +16,18 @@ namespace Hanabi { void download(int game_id, int turn) { auto game = Download::get_game(game_id, turn); - std::cout << "Analysing state: " << *game << std::endl; + std::cout << "Analysing state: " << std::endl << *game << std::endl; auto res = game->backtrack(1); std::cout.precision(10); + std::cout << std::endl; std::cout << "Probability with optimal play: " << res << std::endl; std::cout << "Enumerated " << game->enumerated_states() << " states" << std::endl; std::cout << "Visited " << game->visited_states().size() << " unique game states. " << std::endl; + unsigned long biggest_key = 0; + for(const auto& [key, prob] : game->visited_states()) { + biggest_key = std::max(biggest_key, key); + } + std::cout << "Biggest key generated is " << biggest_key << std::endl; } void print_sizes() { @@ -51,8 +57,59 @@ void test() { } } +void check_games(unsigned num_players, unsigned max_draw_pile_size, unsigned first_game = 4, unsigned last_game = 9999) { + + std::vector> winning_percentages(last_game + 2); + + for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) { + double total_chance = 0; + const std::string output_fname = "games_" + std::to_string(num_players) + "p_draw_size_" + std::to_string(draw_pile_size) + ".txt"; + std::ofstream file (output_fname); + for(size_t game_id = first_game; game_id <= last_game; game_id++) { + const std::string input_fname = "json/" + std::to_string(num_players) + "p/" + std::to_string(game_id) + ".json"; + auto game = Download::get_game(input_fname.c_str(), 50, draw_pile_size); + const double chance = game->backtrack(0); + winning_percentages[game_id].push_back(chance); + if(chance != 1) { + file << "Game " << game_id << ": " << chance << std::endl; + file << *game << std::endl << std::endl; + } + std::cout << "Finished game " << game_id << ": " << chance << std::endl; + + total_chance += chance; + } + const double total_average = total_chance / (last_game - first_game + 1); + winning_percentages.back().push_back(total_average); + file << "Total chance found over " << last_game - first_game + 1 << " many games: " << total_average << std::endl; + file.close(); + } + const std::string results_file_name {"results_" + std::to_string(num_players) + "p.txt"}; + std::ofstream results_file (results_file_name); + results_file << "game_id, "; + for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) { + results_file << std::to_string(draw_pile_size) << ", "; + } + results_file << "\n"; + for(size_t game_id = first_game; game_id <= last_game; game_id++) { + results_file << game_id << ", "; + for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) { + results_file << winning_percentages[game_id][draw_pile_size] << ", "; + } + results_file << std::endl; + } + results_file << "total, "; + for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) { + results_file << winning_percentages.back()[draw_pile_size] << ", "; + } + results_file << std::endl; + results_file.close(); +} + int main(int argc, char *argv[]) { - test(); + #ifndef NDEBUG + test(); + #endif + check_games(4, 9); if(argc == 3) { std::string game (argv[1]); std::string turn (argv[2]);