diff --git a/CMakeLists.txt b/CMakeLists.txt index 425c329..def597a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,10 @@ add_executable(endgame-analyzer src/main.cpp src/state_explorer.cpp src/download src/game_state.cpp include/null_buffer.h include/command_line_interface.h - src/command_line_interface.cpp) + src/command_line_interface.cpp + include/parse_game.h + src/parse_game.cpp +) target_link_libraries(endgame-analyzer cpr) target_link_libraries(endgame-analyzer Boost::program_options) diff --git a/include/download.h b/include/download.h index f4847b9..5b0d1a3 100644 --- a/include/download.h +++ b/include/download.h @@ -11,24 +11,8 @@ #include "game_state.h" #include "myassert.h" -namespace Hanabi { - Card tag_invoke(boost::json::value_to_tag, boost::json::value const &jv); - void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card); -} - namespace Download { - struct Action { - Hanabi::ActionType type{}; - int8_t target{}; - }; - - Action tag_invoke(boost::json::value_to_tag, boost::json::value const &jv); - - std::pair, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json); - - std::vector parse_actions(const boost::json::value &action_json); - std::optional download_game_json(int game_id); std::optional open_game_json(const char *filename); diff --git a/include/parse_game.h b/include/parse_game.h new file mode 100644 index 0000000..471ff1c --- /dev/null +++ b/include/parse_game.h @@ -0,0 +1,65 @@ +#ifndef DYNAMIC_PROGRAM_PARSE_GAME_H +#define DYNAMIC_PROGRAM_PARSE_GAME_H + +#include + +#include "game_state.h" + +namespace Hanabi { + // These are overloads that the boost/json library uses for parsing. + // They convert a Card from/to json. + // This has to be in the same namespace as Hanabi::Card. + Card tag_invoke(boost::json::value_to_tag, boost::json::value const &jv); + void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card); +} + +namespace Parsing { + /** + * Represents a single action (turn) in a Hanab game. + * Note that this is slightly differen than Hanabi::Action, + * since this uses indices for specifying the discarded/played cards. + * We only want to work with this type while parsing, converting to Hanabi::Action after. + */ + struct HanabLiveAction { + Hanabi::ActionType type{}; + /** + * In case the action is of type discard or play, + * this value refers to the index of the discarded/played card in the deck. + */ + int8_t target{}; + }; + + // Overload for parsing from json to HanabLiveAction + HanabLiveAction tag_invoke(boost::json::value_to_tag, boost::json::value const &jv); + + /* + * @brief Parse deck from hanab.live format + * @return List of cards (in order) and number of suits + */ + std::pair, Hanabi::suit_t> parse_deck(const boost::json::value &deck_json); + + /** + * @brief Parse actions from hanab.live format. + * @return List of actions + */ + std::vector parse_actions(const boost::json::value &action_json); + + std::vector convert_actions( + std::vector const & hanab_live_actions, + std::vector const & deck + ); + + struct GameInfo + { + std::vector deck; + std::vector actions; + Hanabi::suit_t num_suits; + Hanabi::player_t num_players; + }; + + GameInfo parse_game(boost::json::object const & game_json); +} + + + +#endif //DYNAMIC_PROGRAM_PARSE_GAME_H diff --git a/src/download.cpp b/src/download.cpp index 79c3a69..492d067 100644 --- a/src/download.cpp +++ b/src/download.cpp @@ -1,82 +1,13 @@ #include #include +#include "parse_game.h" + #include "download.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 { - 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_players: - 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); - } - std::optional 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)); @@ -182,27 +113,11 @@ namespace Download { }(); if (!game_json_opt.has_value() or game_json_opt.value().empty()) { - return Hanabi::Game(nullptr, {}, {}); + return {nullptr, {}, {}}; } - const boost::json::object& game_json = game_json_opt.value(); - - const auto [deck, num_suits] = parse_deck(game_json.at("deck")); - const size_t num_players = game_json.at("players").as_array().size(); - - // Convert the actions from hanab.live format into local format used - const std::vector hanab_live_actions = parse_actions(game_json.at("actions")); - std::vector actions; - std::transform( - hanab_live_actions.begin(), - hanab_live_actions.end(), - std::back_inserter(actions), - [&deck](Action const & action){ - return Hanabi::Action {action.type, deck[action.target]}; - } - ); - - return {get_base_state(num_suits, num_players, deck, score_goal), actions, deck}; + Parsing::GameInfo game_info = Parsing::parse_game(game_json_opt.value()); + return {get_base_state(game_info.num_suits, game_info.num_players, game_info.deck, score_goal), game_info.actions, game_info.deck}; } } // namespace Download diff --git a/src/parse_game.cpp b/src/parse_game.cpp new file mode 100644 index 0000000..c92b457 --- /dev/null +++ b/src/parse_game.cpp @@ -0,0 +1,105 @@ +#include "parse_game.h" + +namespace Parsing { + // 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(); + Parsing::extract(obj, card.rank, "rank"); + Parsing::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 Parsing { + + HanabLiveAction tag_invoke(boost::json::value_to_tag, boost::json::value const &jv) { + HanabLiveAction 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_players: + 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::suit_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::suit_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); + } + + std::vector convert_actions(std::vector const & hanab_live_actions, std::vector const & deck) + { + std::vector actions; + std::transform( + hanab_live_actions.begin(), + hanab_live_actions.end(), + std::back_inserter(actions), + [&deck](HanabLiveAction const & action){ + return Hanabi::Action {action.type, deck[action.target]}; + } + ); + return actions; + } + + GameInfo parse_game(boost::json::object const & game_json) + { + auto const [deck, num_suits] = parse_deck(game_json.at("deck")); + const std::vector hanab_live_actions = parse_actions(game_json.at("actions")); + Hanabi::player_t num_players = game_json.at("players").as_array().size(); + std::vector actions = convert_actions(hanab_live_actions, deck); + + return {deck, actions, num_suits, num_players}; + } + + +} +