Endgame-Analyzer/src/download.cpp

209 lines
9.2 KiB
C++
Raw Normal View History

#include <boost/json/src.hpp>
2023-08-12 19:43:22 +02:00
#include <cpr/cpr.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:
2023-09-14 17:06:06 +02:00
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));
if (r.header["content-type"] != "application/json; charset=utf-8") {
return std::nullopt;
}
return boost::json::parse(r.text).as_object();
}
std::optional<boost::json::object> open_game_json(const char *filename) {
std::ifstream file(filename);
if (!file.is_open()) {
return std::nullopt;
}
std::string game_json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return boost::json::parse(game_json).as_object();
}
std::unique_ptr<Hanabi::HanabiStateIF> get_base_state(
std::size_t num_suits,
Hanabi::player_t num_players,
std::vector<Hanabi::Card> const & deck,
std::optional<uint8_t> score_goal) {
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
switch(num_players) {
case 2:
switch(num_suits) {
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,2,5>(deck, actual_score_goal));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,2,5>(deck, actual_score_goal));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,2,5>(deck, actual_score_goal));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,2,5>(deck, actual_score_goal));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
case 3:
switch(num_suits) {
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,3,5>(deck, actual_score_goal));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,3,5>(deck, actual_score_goal));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,3,5>(deck, actual_score_goal));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,3,5>(deck, actual_score_goal));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
case 4:
switch(num_suits) {
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,4,4>(deck, actual_score_goal));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,4,4>(deck, actual_score_goal));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,4,4>(deck, actual_score_goal));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,4,4>(deck, actual_score_goal));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
case 5:
switch(num_suits) {
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,5,4>(deck, actual_score_goal));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,5,4>(deck, actual_score_goal));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,5,4>(deck, actual_score_goal));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,5,4>(deck, actual_score_goal));
default:
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
}
case 6:
switch(num_suits) {
case 3:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,6,3>(deck, actual_score_goal));
case 4:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,6,3>(deck, actual_score_goal));
case 5:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,6,3>(deck, actual_score_goal));
case 6:
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,6,3>(deck, actual_score_goal));
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));
}
}
Hanabi::Game get_game(std::variant<int, const char*> game_spec, std::optional<uint8_t> score_goal){
const std::optional<boost::json::object> game_json_opt = [&game_spec]() {
if (game_spec.index() == 0) {
return download_game_json(std::get<int>(game_spec));
} else {
return open_game_json(std::get<const char *>(game_spec));
}
}();
if (!game_json_opt.has_value() or game_json_opt.value().empty()) {
return {nullptr, {}, 0};
}
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, 0};
}
} // namespace Download