#ifndef DYNAMIC_PROGRAM_DOWNLOAD_H #define DYNAMIC_PROGRAM_DOWNLOAD_H #include #include #include #include #include #include #include "game_state.h" #include "myassert.h" // This helper function deduces the type and assigns the value with the matching key template void extract(boost::json::object const &obj, T &t, std::string_view key) { t = value_to(obj.at(key)); } namespace Hanabi { Card tag_invoke(boost::json::value_to_tag, boost::json::value const &jv) { Hanabi::Card card{}; boost::json::object const &obj = jv.as_object(); extract(obj, card.rank, "rank"); extract(obj, card.suit, "suitIndex"); card.rank = 5 - card.rank; return card; } void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card) { jv = {{"suitIndex", card.suit}, {"rank", card.rank}}; } } namespace Download { struct Action { Hanabi::ActionType type{}; uint8_t target; }; Action tag_invoke(boost::json::value_to_tag, boost::json::value const &jv) { Action action{}; uint8_t type; boost::json::object const &obj = jv.as_object(); extract(obj, action.target, "target"); extract(obj, type, "type"); action.type = static_cast(type); switch (action.type) { case Hanabi::ActionType::color_clue: case Hanabi::ActionType::rank_clue: action.type = Hanabi::ActionType::clue; break; case Hanabi::ActionType::end_game: case Hanabi::ActionType::vote_terminate: action.type = Hanabi::ActionType::end_game; break; case Hanabi::ActionType::play: case Hanabi::ActionType::discard: break; default: throw std::runtime_error( "Invalid game format, could not parse action type " + std::to_string(type)); } return action; } std::pair, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json) { auto deck = boost::json::value_to>(deck_json); for (auto &card: deck) { ASSERT(card.rank < 5); ASSERT(card.rank >= 0); ASSERT(card.suit < 6); ASSERT(card.suit >= 0); } Hanabi::rank_t num_suits = 0; for(const auto& card: deck) { num_suits = std::max(num_suits, card.suit); } return {deck, num_suits + 1}; } std::vector parse_actions(const boost::json::value &action_json) { return boost::json::value_to>(action_json); } boost::json::object download_game_json(int game_id) { std::string request_str = "https://hanab.live/export/" + std::to_string(game_id); cpr::Response r = cpr::Get(cpr::Url(request_str)); if (r.header["content-type"] != "application/json; charset=utf-8") { return {}; } return boost::json::parse(r.text).as_object(); } boost::json::object open_game_json(const char *filename) { std::ifstream file(filename); if (!file.is_open()) { std::cout << "Failed to open " << filename << "." << std::endl; return {}; } std::string game_json((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return boost::json::parse(game_json).as_object(); } template std::unique_ptr produce_state( const std::vector& deck, const std::vector& actions, 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 < 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: game->clue(); break; case Hanabi::ActionType::discard: index = game->find_card_in_hand(deck[actions[i].target]); ASSERT(index != std::uint8_t(-1)); game->discard(index); break; case Hanabi::ActionType::play: index = game->find_card_in_hand(deck[actions[i].target]); ASSERT(index != std::uint8_t(-1)); game->play(index); break; case Hanabi::ActionType::vote_terminate: case Hanabi::ActionType::end_game: return game; } } game->normalize_draw_and_positions(); return game; } 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)); } else { return open_game_json(std::get(game_spec)); } }(); const auto [deck, num_suits] = parse_deck(game_json.at("deck")); const std::vector actions = parse_actions(game_json.at("actions")); const size_t num_players = game_json.at("players").as_array().size(); switch(num_players) { case 2: switch(num_suits) { case 3: return produce_state<3,2,5>(deck, actions, turn, draw_pile_break); case 4: return produce_state<4,2,5>(deck, actions, turn, draw_pile_break); case 5: return produce_state<5,2,5>(deck, actions, turn, draw_pile_break); case 6: 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, draw_pile_break); case 4: return produce_state<4,3,5>(deck, actions, turn, draw_pile_break); case 5: return produce_state<5,3,5>(deck, actions, turn, draw_pile_break); case 6: 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, draw_pile_break); case 4: return produce_state<4,4,4>(deck, actions, turn, draw_pile_break); case 5: return produce_state<5,4,4>(deck, actions, turn, draw_pile_break); case 6: 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, draw_pile_break); case 4: return produce_state<4,5,4>(deck, actions, turn, draw_pile_break); case 5: return produce_state<5,5,4>(deck, actions, turn, draw_pile_break); case 6: 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, draw_pile_break); case 4: return produce_state<4,6,3>(deck, actions, turn, draw_pile_break); case 5: return produce_state<5,6,3>(deck, actions, turn, draw_pile_break); case 6: 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)); } default: throw std::runtime_error("Invalid number of players: " + std::to_string(num_players)); } } } // namespacen Download #endif // DYNAMIC_PROGRAM_DOWNLOAD_H