split download.h into separate header and source
This commit is contained in:
parent
4e860f49cc
commit
034a7a95a1
4 changed files with 246 additions and 213 deletions
|
@ -15,7 +15,7 @@ find_package(Boost 1.81 COMPONENTS program_options REQUIRED)
|
||||||
include_directories(.)
|
include_directories(.)
|
||||||
include_directories(${Boost_INCLUDE_DIR})
|
include_directories(${Boost_INCLUDE_DIR})
|
||||||
|
|
||||||
add_executable(endgame-analyzer src/main.cpp src/cli_interface.cpp)
|
add_executable(endgame-analyzer src/main.cpp src/cli_interface.cpp src/download.cpp)
|
||||||
|
|
||||||
target_link_libraries(endgame-analyzer cpr)
|
target_link_libraries(endgame-analyzer cpr)
|
||||||
target_link_libraries(endgame-analyzer Boost::program_options)
|
target_link_libraries(endgame-analyzer Boost::program_options)
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define DYNAMIC_PROGRAM_DOWNLOAD_H
|
#define DYNAMIC_PROGRAM_DOWNLOAD_H
|
||||||
|
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
#include <boost/json/src.hpp>
|
|
||||||
#include <cpr/cpr.h>
|
#include <cpr/cpr.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -11,230 +10,42 @@
|
||||||
#include "game_state.h"
|
#include "game_state.h"
|
||||||
#include "myassert.h"
|
#include "myassert.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 {
|
namespace Hanabi {
|
||||||
Card tag_invoke(boost::json::value_to_tag<Card>,
|
Card tag_invoke(boost::json::value_to_tag<Card>, boost::json::value const &jv);
|
||||||
boost::json::value const &jv) {
|
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card);
|
||||||
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 {
|
namespace Download {
|
||||||
|
|
||||||
struct Action {
|
struct Action {
|
||||||
Hanabi::ActionType type{};
|
Hanabi::ActionType type{};
|
||||||
uint8_t target;
|
uint8_t target{};
|
||||||
};
|
};
|
||||||
|
|
||||||
Action tag_invoke(boost::json::value_to_tag<Action>, boost::json::value const &jv) {
|
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");
|
std::pair<std::vector<Hanabi::Card>, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json);
|
||||||
extract(obj, type, "type");
|
|
||||||
|
|
||||||
action.type = static_cast<Hanabi::ActionType>(type);
|
std::vector<Action> parse_actions(const boost::json::value &action_json);
|
||||||
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::pair<std::vector<Hanabi::Card>, Hanabi::rank_t> parse_deck(const boost::json::value &deck_json) {
|
std::optional<boost::json::object> download_game_json(int game_id);
|
||||||
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) {
|
std::optional<boost::json::object> open_game_json(const char *filename);
|
||||||
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);
|
* @brief Create game object from given source
|
||||||
cpr::Response r = cpr::Get(cpr::Url(request_str));
|
* @param game_spec Either an id to download from hanab.live or a filename with a json specification
|
||||||
if (r.header["content-type"] != "application/json; charset=utf-8") {
|
* @param turn Turn to skip to
|
||||||
return std::nullopt;
|
* @param draw_pile_break Minimum draw pile size of produced game
|
||||||
}
|
* @return Game state
|
||||||
return boost::json::parse(r.text).as_object();
|
*
|
||||||
}
|
* If both turn and draw_pile_break are specified, the game skips until the specified turn or the first time the
|
||||||
|
* draw pile hits the given size, whichever comes first
|
||||||
|
*
|
||||||
|
* @note Turns start counting at 1, since this is also the way hanab.live does it.
|
||||||
|
*/
|
||||||
|
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn = 1, size_t draw_pile_break = 0);
|
||||||
|
|
||||||
std::optional<boost::json::object> open_game_json(const char *filename) {
|
} // namespace Download
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<std::size_t num_suits, Hanabi::player_t num_players, std::size_t hand_size>
|
|
||||||
std::unique_ptr<Hanabi::HanabiStateIF> produce_state(
|
|
||||||
const std::vector<Hanabi::Card>& deck,
|
|
||||||
const std::vector<Action>& actions,
|
|
||||||
size_t num_turns_to_replicate,
|
|
||||||
size_t draw_pile_break = 0
|
|
||||||
) {
|
|
||||||
auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck));
|
|
||||||
std::uint8_t index;
|
|
||||||
for (size_t i = 0; i < std::min(num_turns_to_replicate, actions.size()); i++) {
|
|
||||||
if (game->draw_pile_size() == draw_pile_break) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch(actions[i].type) {
|
|
||||||
case Hanabi::ActionType::color_clue:
|
|
||||||
case Hanabi::ActionType::rank_clue:
|
|
||||||
game->give_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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
game->init_backtracking_information();
|
|
||||||
return game;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn, size_t draw_pile_break = 0) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const boost::json::object& game_json = game_json_opt.value();
|
|
||||||
|
|
||||||
const auto [deck, num_suits] = parse_deck(game_json.at("deck"));
|
|
||||||
const std::vector<Action> actions = parse_actions(game_json.at("actions"));
|
|
||||||
const size_t num_players = game_json.at("players").as_array().size();
|
|
||||||
|
|
||||||
switch(num_players) {
|
|
||||||
case 2:
|
|
||||||
switch(num_suits) {
|
|
||||||
case 3:
|
|
||||||
return produce_state<3,2,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 4:
|
|
||||||
return produce_state<4,2,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 5:
|
|
||||||
return produce_state<5,2,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 6:
|
|
||||||
return produce_state<6,2,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
switch(num_suits) {
|
|
||||||
case 3:
|
|
||||||
return produce_state<3,3,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 4:
|
|
||||||
return produce_state<4,3,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 5:
|
|
||||||
return produce_state<5,3,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 6:
|
|
||||||
return produce_state<6,3,5>(deck, actions, turn, draw_pile_break);
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
|
||||||
}
|
|
||||||
case 4:
|
|
||||||
switch(num_suits) {
|
|
||||||
case 3:
|
|
||||||
return produce_state<3,4,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 4:
|
|
||||||
return produce_state<4,4,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 5:
|
|
||||||
return produce_state<5,4,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 6:
|
|
||||||
return produce_state<6,4,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
|
||||||
}
|
|
||||||
case 5:
|
|
||||||
switch(num_suits) {
|
|
||||||
case 3:
|
|
||||||
return produce_state<3,5,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 4:
|
|
||||||
return produce_state<4,5,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 5:
|
|
||||||
return produce_state<5,5,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 6:
|
|
||||||
return produce_state<6,5,4>(deck, actions, turn, draw_pile_break);
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
switch(num_suits) {
|
|
||||||
case 3:
|
|
||||||
return produce_state<3,6,3>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 4:
|
|
||||||
return produce_state<4,6,3>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 5:
|
|
||||||
return produce_state<5,6,3>(deck, actions, turn, draw_pile_break);
|
|
||||||
case 6:
|
|
||||||
return produce_state<6,6,3>(deck, actions, turn, draw_pile_break);
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespacen Download
|
|
||||||
|
|
||||||
#endif // DYNAMIC_PROGRAM_DOWNLOAD_H
|
#endif // DYNAMIC_PROGRAM_DOWNLOAD_H
|
||||||
|
|
222
src/download.cpp
Normal file
222
src/download.cpp
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
#include "download.h"
|
||||||
|
#include <boost/json/src.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::size_t num_suits, Hanabi::player_t num_players, std::size_t hand_size>
|
||||||
|
std::unique_ptr<Hanabi::HanabiStateIF> produce_state(
|
||||||
|
const std::vector<Hanabi::Card>& deck,
|
||||||
|
const std::vector<Action>& actions,
|
||||||
|
size_t num_turns_to_replicate,
|
||||||
|
size_t draw_pile_break = 0
|
||||||
|
) {
|
||||||
|
auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck));
|
||||||
|
std::uint8_t index;
|
||||||
|
for (size_t i = 0; i < std::min(num_turns_to_replicate, actions.size()); i++) {
|
||||||
|
if (game->draw_pile_size() == draw_pile_break) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(actions[i].type) {
|
||||||
|
case Hanabi::ActionType::color_clue:
|
||||||
|
case Hanabi::ActionType::rank_clue:
|
||||||
|
game->give_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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
game->init_backtracking_information();
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn, size_t draw_pile_break) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const boost::json::object& game_json = game_json_opt.value();
|
||||||
|
|
||||||
|
const auto [deck, num_suits] = parse_deck(game_json.at("deck"));
|
||||||
|
const std::vector<Action> actions = parse_actions(game_json.at("actions"));
|
||||||
|
const size_t num_players = game_json.at("players").as_array().size();
|
||||||
|
|
||||||
|
switch(num_players) {
|
||||||
|
case 2:
|
||||||
|
switch(num_suits) {
|
||||||
|
case 3:
|
||||||
|
return produce_state<3,2,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 4:
|
||||||
|
return produce_state<4,2,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 5:
|
||||||
|
return produce_state<5,2,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 6:
|
||||||
|
return produce_state<6,2,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
switch(num_suits) {
|
||||||
|
case 3:
|
||||||
|
return produce_state<3,3,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 4:
|
||||||
|
return produce_state<4,3,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 5:
|
||||||
|
return produce_state<5,3,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 6:
|
||||||
|
return produce_state<6,3,5>(deck, actions, turn, draw_pile_break);
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
|
}
|
||||||
|
case 4:
|
||||||
|
switch(num_suits) {
|
||||||
|
case 3:
|
||||||
|
return produce_state<3,4,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 4:
|
||||||
|
return produce_state<4,4,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 5:
|
||||||
|
return produce_state<5,4,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 6:
|
||||||
|
return produce_state<6,4,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
|
}
|
||||||
|
case 5:
|
||||||
|
switch(num_suits) {
|
||||||
|
case 3:
|
||||||
|
return produce_state<3,5,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 4:
|
||||||
|
return produce_state<4,5,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 5:
|
||||||
|
return produce_state<5,5,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 6:
|
||||||
|
return produce_state<6,5,4>(deck, actions, turn, draw_pile_break);
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
switch(num_suits) {
|
||||||
|
case 3:
|
||||||
|
return produce_state<3,6,3>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 4:
|
||||||
|
return produce_state<4,6,3>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 5:
|
||||||
|
return produce_state<5,6,3>(deck, actions, turn, draw_pile_break);
|
||||||
|
case 6:
|
||||||
|
return produce_state<6,6,3>(deck, actions, turn, draw_pile_break);
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Download
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi {
|
||||||
void analyze_game_and_start_cli(std::variant<int, const char*> game_id, int turn) {
|
void analyze_game_and_start_cli(std::variant<int, const char*> game_id, int turn) {
|
||||||
auto game = Download::get_game(game_id, turn - 1);
|
auto game = Download::get_game(game_id, turn);
|
||||||
if (game == nullptr) {
|
if (game == nullptr) {
|
||||||
if(game_id.index() == 0) {
|
if(game_id.index() == 0) {
|
||||||
std::cout << "Failed to download game " << std::get<int>(game_id) << " from hanab.live." << std::endl;
|
std::cout << "Failed to download game " << std::get<int>(game_id) << " from hanab.live." << std::endl;
|
||||||
|
|
Loading…
Reference in a new issue