implement tablebase

This commit is contained in:
Maximilian Keßler 2023-08-08 00:29:19 +02:00
parent 1f4949c1e5
commit a77de7efe8
Signed by: max
GPG key ID: BCC5A619923C0BA5
2 changed files with 86 additions and 25 deletions

View file

@ -111,11 +111,15 @@ namespace Download {
std::unique_ptr<Hanabi::HanabiStateIF> produce_state( std::unique_ptr<Hanabi::HanabiStateIF> produce_state(
const std::vector<Hanabi::Card>& deck, const std::vector<Hanabi::Card>& deck,
const std::vector<Action>& actions, const std::vector<Action>& actions,
size_t num_turns_to_replicate size_t num_turns_to_replicate,
size_t draw_pile_break = 0
) { ) {
auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck)); auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck));
std::uint8_t index; 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) { switch(actions[i].type) {
case Hanabi::ActionType::color_clue: case Hanabi::ActionType::color_clue:
case Hanabi::ActionType::rank_clue: case Hanabi::ActionType::rank_clue:
@ -140,7 +144,7 @@ namespace Download {
return game; return game;
} }
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn) { std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn, size_t draw_pile_break = 0) {
const boost::json::object game_json = [&game_spec]() { const boost::json::object game_json = [&game_spec]() {
if (game_spec.index() == 0) { if (game_spec.index() == 0) {
return download_game_json(std::get<int>(game_spec)); return download_game_json(std::get<int>(game_spec));
@ -157,65 +161,65 @@ namespace Download {
case 2: case 2:
switch(num_suits) { switch(num_suits) {
case 3: 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: 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: 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: case 6:
return produce_state<6,2,5>(deck, actions, turn); return produce_state<6,2,5>(deck, actions, turn, draw_pile_break);
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));
} }
case 3: case 3:
switch(num_suits) { switch(num_suits) {
case 3: 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: 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: 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: case 6:
return produce_state<6,3,5>(deck, actions, turn); return produce_state<6,3,5>(deck, actions, turn, draw_pile_break);
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));
} }
case 4: case 4:
switch(num_suits) { switch(num_suits) {
case 3: 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: 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: 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: case 6:
return produce_state<6,4,4>(deck, actions, turn); return produce_state<6,4,4>(deck, actions, turn, draw_pile_break);
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));
} }
case 5: case 5:
switch(num_suits) { switch(num_suits) {
case 3: 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: 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: 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: case 6:
return produce_state<6,5,4>(deck, actions, turn); return produce_state<6,5,4>(deck, actions, turn, draw_pile_break);
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));
} }
case 6: case 6:
switch(num_suits) { switch(num_suits) {
case 3: 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: 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: 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: case 6:
return produce_state<6,6,3>(deck, actions, turn); return produce_state<6,6,3>(deck, actions, turn, draw_pile_break);
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));
} }

View file

@ -16,12 +16,18 @@
namespace Hanabi { namespace Hanabi {
void download(int game_id, int turn) { void download(int game_id, int turn) {
auto game = Download::get_game(game_id, 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); auto res = game->backtrack(1);
std::cout.precision(10); std::cout.precision(10);
std::cout << std::endl;
std::cout << "Probability with optimal play: " << res << std::endl; std::cout << "Probability with optimal play: " << res << std::endl;
std::cout << "Enumerated " << game->enumerated_states() << " states" << std::endl; std::cout << "Enumerated " << game->enumerated_states() << " states" << std::endl;
std::cout << "Visited " << game->visited_states().size() << " unique game 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() { 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<std::vector<double>> 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[]) { int main(int argc, char *argv[]) {
test(); #ifndef NDEBUG
test();
#endif
check_games(4, 9);
if(argc == 3) { if(argc == 3) {
std::string game (argv[1]); std::string game (argv[1]);
std::string turn (argv[2]); std::string turn (argv[2]);