2023-08-05 00:34:31 +02:00
|
|
|
#ifndef DYNAMIC_PROGRAM_DOWNLOAD_H
|
|
|
|
#define DYNAMIC_PROGRAM_DOWNLOAD_H
|
|
|
|
|
|
|
|
#include <boost/json.hpp>
|
|
|
|
#include <boost/json/src.hpp>
|
|
|
|
#include <cpr/cpr.h>
|
|
|
|
#include <iostream>
|
2023-08-05 11:55:46 +02:00
|
|
|
#include <fstream>
|
|
|
|
#include <variant>
|
2023-08-05 00:34:31 +02:00
|
|
|
|
|
|
|
#include "game_state.h"
|
2023-08-06 15:02:50 +02:00
|
|
|
#include "myassert.h"
|
2023-08-05 00:34:31 +02:00
|
|
|
|
|
|
|
// This helper function deduces the type and assigns the value with the matching key
|
|
|
|
template<class T>
|
2023-08-05 13:51:55 +02:00
|
|
|
void extract(boost::json::object const &obj, T &t, std::string_view key) {
|
|
|
|
t = value_to<T>(obj.at(key));
|
2023-08-05 00:34:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace Hanabi {
|
2023-08-05 13:51:55 +02:00
|
|
|
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;
|
|
|
|
}
|
2023-08-05 13:04:51 +02:00
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card) {
|
|
|
|
jv = {{"suitIndex", card.suit},
|
|
|
|
{"rank", card.rank}};
|
|
|
|
}
|
2023-08-05 00:34:31 +02:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
namespace Download {
|
|
|
|
|
|
|
|
struct Action {
|
|
|
|
Hanabi::ActionType type{};
|
|
|
|
uint8_t target;
|
|
|
|
};
|
|
|
|
|
|
|
|
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:
|
|
|
|
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;
|
|
|
|
}
|
2023-08-05 00:34:31 +02:00
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
std::vector<Hanabi::Card> parse_deck(const boost::json::value &deck_json) {
|
|
|
|
auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(deck_json);
|
|
|
|
for (auto &card: deck) {
|
2023-08-06 15:02:50 +02:00
|
|
|
ASSERT(card.rank < 5);
|
|
|
|
ASSERT(card.rank >= 0);
|
|
|
|
ASSERT(card.suit < 6);
|
|
|
|
ASSERT(card.suit >= 0);
|
2023-08-05 13:51:55 +02:00
|
|
|
}
|
|
|
|
return deck;
|
2023-08-05 00:34:31 +02:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
std::vector<Action> parse_actions(const boost::json::value &action_json) {
|
|
|
|
return boost::json::value_to<std::vector<Action>>(action_json);
|
|
|
|
}
|
2023-08-05 13:04:51 +02:00
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
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();
|
|
|
|
}
|
2023-08-05 00:34:31 +02:00
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
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<char>(file)), std::istreambuf_iterator<char>());
|
|
|
|
return boost::json::parse(game_json).as_object();
|
|
|
|
}
|
2023-08-05 11:55:46 +02:00
|
|
|
|
2023-08-06 10:23:29 +02:00
|
|
|
template<std::size_t num_suits, Hanabi::player_t num_players, std::size_t hand_size>
|
|
|
|
Hanabi::HanabiState<num_suits, num_players, hand_size> produce_state(
|
2023-08-05 13:51:55 +02:00
|
|
|
const std::vector<Hanabi::Card>& deck,
|
|
|
|
const std::vector<Action>& actions,
|
|
|
|
size_t num_turns_to_replicate
|
|
|
|
) {
|
2023-08-06 10:23:29 +02:00
|
|
|
Hanabi::HanabiState<num_suits, num_players, hand_size> game(deck);
|
2023-08-05 13:51:55 +02:00
|
|
|
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]);
|
2023-08-06 16:44:21 +02:00
|
|
|
ASSERT(index != std::uint8_t(-1));
|
2023-08-05 13:51:55 +02:00
|
|
|
game.discard(index);
|
|
|
|
break;
|
|
|
|
case Hanabi::ActionType::play:
|
|
|
|
index = game.find_card_in_hand(deck[actions[i].target]);
|
2023-08-06 16:44:21 +02:00
|
|
|
ASSERT(index != std::uint8_t(-1));
|
2023-08-05 13:51:55 +02:00
|
|
|
game.play(index);
|
|
|
|
break;
|
|
|
|
case Hanabi::ActionType::vote_terminate:
|
|
|
|
case Hanabi::ActionType::end_game:
|
|
|
|
return game;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return game;
|
2023-08-05 11:55:46 +02:00
|
|
|
}
|
2023-08-05 00:34:31 +02:00
|
|
|
|
2023-08-06 10:23:29 +02:00
|
|
|
template <std::size_t num_suits, Hanabi::player_t num_players, std::size_t hand_size>
|
2023-08-06 11:54:57 +02:00
|
|
|
Hanabi::HanabiState<num_suits, num_players, hand_size> get_game(std::variant<int, const char *> game_spec, unsigned turn) {
|
2023-08-05 13:51:55 +02:00
|
|
|
const boost::json::object game_json = [&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));
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
const std::vector<Hanabi::Card> deck = parse_deck(game_json.at("deck"));
|
|
|
|
const std::vector<Action> actions = parse_actions(game_json.at("actions"));
|
2023-08-06 10:23:29 +02:00
|
|
|
const size_t num_players_js = game_json.at("players").as_array().size();
|
2023-08-06 15:02:50 +02:00
|
|
|
ASSERT (num_players_js == num_players);
|
2023-08-05 13:51:55 +02:00
|
|
|
|
2023-08-06 10:23:29 +02:00
|
|
|
auto game = produce_state<num_suits, num_players, hand_size>(deck, actions, turn);
|
|
|
|
game.normalize_draw_and_positions();
|
2023-08-06 11:54:57 +02:00
|
|
|
return game;
|
2023-08-05 13:51:55 +02:00
|
|
|
}
|
2023-08-05 00:34:31 +02:00
|
|
|
|
|
|
|
|
2023-08-05 13:51:55 +02:00
|
|
|
} // namespacen Download
|
2023-08-05 00:34:31 +02:00
|
|
|
|
|
|
|
#endif // DYNAMIC_PROGRAM_DOWNLOAD_H
|