Code cleanup: split download into parsing + downloads
This commit is contained in:
parent
6ce692a06f
commit
4b427c40f9
5 changed files with 179 additions and 107 deletions
|
@ -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)
|
||||
|
|
|
@ -11,24 +11,8 @@
|
|||
#include "game_state.h"
|
||||
#include "myassert.h"
|
||||
|
||||
namespace Hanabi {
|
||||
Card tag_invoke(boost::json::value_to_tag<Card>, 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<Action>, boost::json::value const &jv);
|
||||
|
||||
std::pair<std::vector<Hanabi::Card>, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json);
|
||||
|
||||
std::vector<Action> parse_actions(const boost::json::value &action_json);
|
||||
|
||||
std::optional<boost::json::object> download_game_json(int game_id);
|
||||
|
||||
std::optional<boost::json::object> open_game_json(const char *filename);
|
||||
|
|
65
include/parse_game.h
Normal file
65
include/parse_game.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef DYNAMIC_PROGRAM_PARSE_GAME_H
|
||||
#define DYNAMIC_PROGRAM_PARSE_GAME_H
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
#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<Card>, 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<HanabLiveAction>, boost::json::value const &jv);
|
||||
|
||||
/*
|
||||
* @brief Parse deck from hanab.live format
|
||||
* @return List of cards (in order) and number of suits
|
||||
*/
|
||||
std::pair<std::vector<Hanabi::Card>, Hanabi::suit_t> parse_deck(const boost::json::value &deck_json);
|
||||
|
||||
/**
|
||||
* @brief Parse actions from hanab.live format.
|
||||
* @return List of actions
|
||||
*/
|
||||
std::vector<HanabLiveAction> parse_actions(const boost::json::value &action_json);
|
||||
|
||||
std::vector<Hanabi::Action> convert_actions(
|
||||
std::vector<HanabLiveAction> const & hanab_live_actions,
|
||||
std::vector<Hanabi::Card> const & deck
|
||||
);
|
||||
|
||||
struct GameInfo
|
||||
{
|
||||
std::vector<Hanabi::Card> deck;
|
||||
std::vector<Hanabi::Action> 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
|
|
@ -1,82 +1,13 @@
|
|||
#include <boost/json/src.hpp>
|
||||
#include <cpr/cpr.h>
|
||||
|
||||
#include "parse_game.h"
|
||||
|
||||
#include "download.h"
|
||||
|
||||
|
||||
// This helper function deduces the type and assigns the value with the matching key
|
||||
template<class T>
|
||||
void extract(boost::json::object const &obj, T &t, std::string_view key) {
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
|
||||
namespace Hanabi {
|
||||
Card tag_invoke(boost::json::value_to_tag<Card>,
|
||||
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<Action>, 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<Hanabi::ActionType>(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<std::vector<Hanabi::Card>, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json) {
|
||||
auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(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<Action> parse_actions(const boost::json::value &action_json) {
|
||||
return boost::json::value_to<std::vector<Action>>(action_json);
|
||||
}
|
||||
|
||||
std::optional<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));
|
||||
|
@ -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<Action> hanab_live_actions = parse_actions(game_json.at("actions"));
|
||||
std::vector<Hanabi::Action> 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
|
||||
|
|
105
src/parse_game.cpp
Normal file
105
src/parse_game.cpp
Normal file
|
@ -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<class T>
|
||||
void extract(boost::json::object const &obj, T &t, std::string_view key) {
|
||||
t = value_to<T>(obj.at(key));
|
||||
}
|
||||
}
|
||||
|
||||
namespace Hanabi {
|
||||
Card tag_invoke(boost::json::value_to_tag<Card>,
|
||||
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<HanabLiveAction>, 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<Hanabi::ActionType>(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<std::vector<Hanabi::Card>, Hanabi::suit_t> parse_deck(const boost::json::value &deck_json) {
|
||||
auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(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<HanabLiveAction> parse_actions(const boost::json::value &action_json)
|
||||
{
|
||||
return boost::json::value_to<std::vector<HanabLiveAction>>(action_json);
|
||||
}
|
||||
|
||||
std::vector<Hanabi::Action> convert_actions(std::vector<HanabLiveAction> const & hanab_live_actions, std::vector<Hanabi::Card> const & deck)
|
||||
{
|
||||
std::vector<Hanabi::Action> 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<Parsing::HanabLiveAction> hanab_live_actions = parse_actions(game_json.at("actions"));
|
||||
Hanabi::player_t num_players = game_json.at("players").as_array().size();
|
||||
std::vector<Hanabi::Action> actions = convert_actions(hanab_live_actions, deck);
|
||||
|
||||
return {deck, actions, num_suits, num_players};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in a new issue