#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::vector 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); } return deck; } 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 Hanabi::HanabiState produce_state( const std::vector& deck, const std::vector& actions, size_t num_turns_to_replicate ) { Hanabi::HanabiState game(deck); std::uint8_t index; for (size_t i = 0; i < num_turns_to_replicate; i++) { 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; } } return game; } template Hanabi::HanabiState get_game(std::variant game_spec, unsigned turn) { 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 std::vector deck = parse_deck(game_json.at("deck")); const std::vector actions = parse_actions(game_json.at("actions")); const size_t num_players_js = game_json.at("players").as_array().size(); ASSERT (num_players_js == num_players); auto game = produce_state(deck, actions, turn); game.normalize_draw_and_positions(); return game; } } // namespacen Download #endif // DYNAMIC_PROGRAM_DOWNLOAD_H