diff --git a/CMakeLists.txt b/CMakeLists.txt index def597a..8f10524 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,12 +17,15 @@ include_directories(.) include_directories(${Boost_INCLUDE_DIR}) add_executable(endgame-analyzer src/main.cpp src/state_explorer.cpp src/download.cpp - src/game_state.cpp include/null_buffer.h include/command_line_interface.h src/command_line_interface.cpp include/parse_game.h src/parse_game.cpp + include/hanabi_types.hpp + include/game_interface.h + src/hanabi_types.cpp + src/game_interface.cpp ) target_link_libraries(endgame-analyzer cpr) diff --git a/include/game_interface.h b/include/game_interface.h new file mode 100644 index 0000000..9a2c884 --- /dev/null +++ b/include/game_interface.h @@ -0,0 +1,91 @@ +#ifndef DYNAMIC_PROGRAM_GAME_INTERFACE_H +#define DYNAMIC_PROGRAM_GAME_INTERFACE_H + +#include +#include +#include +#include +#include +#include "hanabi_types.hpp" + + +namespace Hanabi +{ + + struct CardMultiplicity + { + Card card; + unsigned multiplicity; + + bool operator==(const CardMultiplicity &) const = default; + }; + + class HanabiStateIF { + public: + virtual void give_clue() = 0; + virtual void discard(hand_index_t index) = 0; + virtual void play(hand_index_t index) = 0; + + virtual void rotate_next_draw(const Card& card) = 0; + virtual ActionType last_action_type() const = 0; + virtual void revert() = 0; + + virtual void modify_clues(clue_t change) = 0; + virtual void set_clues(clue_t clues) = 0; + + [[nodiscard]] virtual player_t turn() const = 0; + [[nodiscard]] virtual clue_t num_clues() const = 0; + [[nodiscard]] virtual unsigned score() const = 0; + [[nodiscard]] virtual std::vector> hands() const = 0; + [[nodiscard]] virtual std::vector cur_hand() const = 0; + [[nodiscard]] virtual size_t draw_pile_size() const = 0; + + [[nodiscard]] virtual bool is_trash(const Card& card) const = 0; + [[nodiscard]] virtual bool is_playable(const Card& card) const = 0; + [[nodiscard]] virtual bool is_relative_state_initialized() const = 0; + [[nodiscard]] virtual hand_index_t find_card_in_hand(const Card& card) const = 0; + + [[nodiscard]] virtual std::uint64_t enumerated_states() const = 0; + [[nodiscard]] virtual const std::unordered_map& position_tablebase() const = 0; + + virtual void init_backtracking_information() = 0; + virtual probability_t evaluate_state() = 0; + + [[nodiscard]] virtual std::optional lookup() const = 0; + [[nodiscard]] virtual std::uint64_t unique_id() const = 0; + [[nodiscard]] virtual std::pair, std::vector> dump_unique_id_parts() const = 0; + + virtual std::vector>> get_reasonable_actions() = 0; + virtual std::vector>> possible_next_states(hand_index_t index, bool play) = 0; + + virtual ~HanabiStateIF() = default; + + protected: + virtual void print(std::ostream& os) const = 0; + + friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&); + }; + + std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state); + + struct Game { + Game(std::unique_ptr state, std::vector actions, std::vector deck); + + [[nodiscard]] unsigned cur_turn() const; + + void make_turn(); + void revert_turn(); + + bool goto_draw_pile_size(size_t draw_pile_break); + bool goto_turn(size_t turn); + + [[nodiscard]] bool holds_state() const; + + std::unique_ptr state; + std::vector actions; + std::vector deck; + unsigned next_action; + }; + +} +#endif //DYNAMIC_PROGRAM_GAME_INTERFACE_H diff --git a/include/game_state.h b/include/game_state.h index b1acc26..575955f 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -16,135 +16,15 @@ #include #include +#include "game_interface.h" namespace Hanabi { - using rank_t = std::uint8_t; - using suit_t = std::uint8_t; - using clue_t = std::int8_t; - using player_t = std::uint8_t; - using hand_index_t = std::uint8_t; - - using probability_base_type = unsigned long; - using rational_probability = boost::rational; - -/** - * Define macro - * NUSE_RATIONAL_PROBABILITIES - * to use floating-point arithematic for the stored probabilities - * instead of rational representations -*/ - -#ifndef NUSE_RATIONAL_PROBABILITIES - using probability_t = boost::rational; -#else - using probability_t = double; -#endif - - inline std::ostream& print_probability(std::ostream& os, double prob); - inline std::ostream& print_probability(std::ostream& os, const rational_probability& prob); - - template - std::ostream& print_probability(std::ostream& os, const std::optional& prob); - - /** - * We will generally assume that stacks are played from n to 0 - * Playing a 0 will yield a clue - * Therefore, for the default hanabi, we will play 4,3,2,1,0 in that order - * on each stack. A stack with no cards played implicitly has value 5 on it - * This is just easier to implement, since then the remaining number of cards - * to be played is always the current number of the stack - */ - constexpr rank_t starting_card_rank = 5; - constexpr suit_t max_suit_index = 5; - constexpr size_t max_card_duplicity = 3; - constexpr clue_t max_num_clues = 8; - constexpr uint8_t not_in_starting_hand = std::numeric_limits::max(); - constexpr hand_index_t invalid_hand_idx = std::numeric_limits::max(); - - // We might want to change these at runtime to adapt to other variants. - // However, a global variable is used so that we can have an output operator for cards reading from here - // Note that this is therefore not static so that we have external linking - inline std::array suit_initials = {'r', 'y', 'g', 'b', 'p', 't'}; - - struct Card { - suit_t suit; - rank_t rank; - uint8_t local_index; - bool in_starting_hand; - bool initial_trash; - - inline bool operator==(const Card &other) const; - }; - - namespace Cards { - static constexpr Card r0 = {0, 5}; - static constexpr Card r1 = {0, 4}; - static constexpr Card r2 = {0, 3}; - static constexpr Card r3 = {0, 2}; - static constexpr Card r4 = {0, 1}; - static constexpr Card r5 = {0, 0}; - static constexpr Card y0 = {1, 5}; - static constexpr Card y1 = {1, 4}; - static constexpr Card y2 = {1, 3}; - static constexpr Card y3 = {1, 2}; - static constexpr Card y4 = {1, 1}; - static constexpr Card y5 = {1, 0}; - static constexpr Card g0 = {2, 5}; - static constexpr Card g1 = {2, 4}; - static constexpr Card g2 = {2, 3}; - static constexpr Card g3 = {2, 2}; - static constexpr Card g4 = {2, 1}; - static constexpr Card g5 = {2, 0}; - static constexpr Card b0 = {3, 5}; - static constexpr Card b1 = {3, 4}; - static constexpr Card b2 = {3, 3}; - static constexpr Card b3 = {3, 2}; - static constexpr Card b4 = {3, 1}; - static constexpr Card b5 = {3, 0}; - static constexpr Card p0 = {4, 5}; - static constexpr Card p1 = {4, 4}; - static constexpr Card p2 = {4, 3}; - static constexpr Card p3 = {4, 2}; - static constexpr Card p4 = {4, 1}; - static constexpr Card p5 = {4, 0}; - static constexpr Card t0 = {5, 5}; - static constexpr Card t1 = {5, 4}; - static constexpr Card t2 = {5, 3}; - static constexpr Card t3 = {5, 2}; - static constexpr Card t4 = {5, 1}; - static constexpr Card t5 = {5, 0}; - static constexpr Card unknown = {std::numeric_limits::max(), 0}; - static constexpr Card trash = {std::numeric_limits::max(), 1}; - } -} - -namespace Hanabi { - -inline std::string to_string(const Hanabi::Card &card); -inline std::ostream &operator<<(std::ostream &os, const Card &card); - - -/** - * To store: - * - Draw pile size - * - Distribution of cards - * - Which cards exist? - * - Number of clues - */ - -template +template using Stacks = std::array; -template -std::ostream& operator<<(std::ostream &os, const Stacks &stacks); - -struct CardMultiplicity { - Card card; - unsigned multiplicity; - - bool operator==(const CardMultiplicity &) const = default; -}; +template +std::ostream &operator<<(std::ostream &os, const Stacks &stacks); template struct InnerCardArray { @@ -177,100 +57,9 @@ private: std::array _array {}; }; -enum class ActionType : std::uint8_t { - play = 0, - discard = 1, - clue = 2, - color_clue = 2, - rank_clue = 3, - end_game = 4, - vote_terminate_players = 5, - vote_terminate = 10, -}; - -struct Action { - ActionType type {}; - Card card {}; -}; - -inline std::ostream& operator<<(std::ostream& os, const Action& action); - -/** Would like to have 2 versions: - * All: - * - support playing cards, querying basic information - * - support going back, but with a different interface: efficient (needs arguments, does not store) or using a stack - * - */ - -class HanabiStateIF { -public: - virtual void give_clue() = 0; - virtual void discard(hand_index_t index) = 0; - virtual void play(hand_index_t index) = 0; - - virtual void rotate_next_draw(const Card& card) = 0; - virtual ActionType last_action_type() const = 0; - virtual void revert() = 0; - - virtual void modify_clues(clue_t change) = 0; - virtual void set_clues(clue_t clues) = 0; - - [[nodiscard]] virtual player_t turn() const = 0; - [[nodiscard]] virtual clue_t num_clues() const = 0; - [[nodiscard]] virtual unsigned score() const = 0; - [[nodiscard]] virtual std::vector> hands() const = 0; - [[nodiscard]] virtual std::vector cur_hand() const = 0; - [[nodiscard]] virtual size_t draw_pile_size() const = 0; - - [[nodiscard]] virtual bool is_trash(const Card& card) const = 0; - [[nodiscard]] virtual bool is_playable(const Card& card) const = 0; - [[nodiscard]] virtual bool is_relative_state_initialized() const = 0; - [[nodiscard]] virtual hand_index_t find_card_in_hand(const Card& card) const = 0; - - [[nodiscard]] virtual std::uint64_t enumerated_states() const = 0; - [[nodiscard]] virtual const std::unordered_map& position_tablebase() const = 0; - - virtual void init_backtracking_information() = 0; - virtual probability_t evaluate_state() = 0; - - [[nodiscard]] virtual std::optional lookup() const = 0; - [[nodiscard]] virtual std::uint64_t unique_id() const = 0; - [[nodiscard]] virtual std::pair, std::vector> dump_unique_id_parts() const = 0; - - virtual std::vector>> get_reasonable_actions() = 0; - virtual std::vector>> possible_next_states(hand_index_t index, bool play) = 0; - - virtual ~HanabiStateIF() = default; - -protected: - virtual void print(std::ostream& os) const = 0; - - friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&); -}; // A game mimics a game state together with a list of actions and allows to traverse the game // history by making and reverting the stored actions. -struct Game { - Game(std::unique_ptr state, std::vector actions, std::vector deck); - - unsigned cur_turn() const; - - void make_turn(); - void revert_turn(); - - bool goto_draw_pile_size(size_t draw_pile_break); - bool goto_turn(size_t turn); - - bool holds_state(); - - std::unique_ptr state; - std::vector actions; - std::vector deck; - unsigned next_action; -}; - -inline std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state); - template class HanabiState : public HanabiStateIF { public: diff --git a/include/game_state.hpp b/include/game_state.hpp index 294b739..61bc49b 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -6,65 +6,6 @@ namespace Hanabi { - template - std::ostream& print_probability(std::ostream& os, const std::optional& prob) { - if (prob.has_value()) { - return print_probability(os, prob.value()); - } else { - os << "unknown"; - } - return os; - } - - std::ostream& print_probability(std::ostream& os, const rational_probability & prob) { - os << prob << " ~ " << std::setprecision(5) << boost::rational_cast(prob) * 100 << "%"; - return os; - } - - std::ostream& print_probability(std::ostream& os, double prob) { - os << std::setprecision(5) << prob; - return os; - } - - std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state) { - hanabi_state.print(os); - return os; - } - - std::string to_string(const Hanabi::Card &card) { - if (card == Hanabi::Cards::trash) { - return "kt"; - } else { - return Hanabi::suit_initials[card.suit] + std::to_string(5 - card.rank); - } - } - - std::ostream &operator<<(std::ostream &os, Action const& action) { - switch(action.type) { - case ActionType::play: - os << "play " + to_string(action.card); - break; - case ActionType::discard: - os << "discard"; - break; - case ActionType::clue: - os << "clue"; - break; - default: - break; - } - return os; - } - - bool Card::operator==(const Card &other) const { - return suit == other.suit and rank == other.rank; - } - - std::ostream &operator<<(std::ostream &os, const Card &card) { - os << to_string(card); - return os; - } - template std::ostream &operator<<(std::ostream &os, const Stacks &stacks) { for (size_t i = 0; i < stacks.size(); i++) { diff --git a/include/hanabi_types.hpp b/include/hanabi_types.hpp new file mode 100644 index 0000000..2fa170e --- /dev/null +++ b/include/hanabi_types.hpp @@ -0,0 +1,157 @@ +#ifndef DYNAMIC_PROGRAM_HANABI_TYPES_H +#define DYNAMIC_PROGRAM_HANABI_TYPES_H + +#include +#include +#include +#include + +#include + +namespace Hanabi { + + using rank_t = std::uint8_t; + using suit_t = std::uint8_t; + using clue_t = std::int8_t; + using player_t = std::uint8_t; + using hand_index_t = std::uint8_t; + + using probability_base_type = unsigned long; + using rational_probability = boost::rational; + +/** + * Define macro + * NUSE_RATIONAL_PROBABILITIES + * to use floating-point arithematic for the stored probabilities + * instead of rational representations +*/ + +#ifndef NUSE_RATIONAL_PROBABILITIES + using probability_t = boost::rational; +#else + using probability_t = double; +#endif + + std::ostream& print_probability(std::ostream& os, const rational_probability& prob); + std::ostream& print_probability(std::ostream& os, double prob); + + template + std::ostream& print_probability(std::ostream& os, const std::optional& prob); + + /** + * We will generally assume that stacks are played from n to 0 + * Playing a 0 will yield a clue + * Therefore, for the default hanabi, we will play 4,3,2,1,0 in that order + * on each stack. A stack with no cards played implicitly has value 5 on it + * This is just easier to implement, since then the remaining number of cards + * to be played is always the current number of the stack + */ + constexpr rank_t starting_card_rank = 5; + constexpr suit_t max_suit_index = 5; + constexpr size_t max_card_duplicity = 3; + constexpr clue_t max_num_clues = 8; + constexpr hand_index_t invalid_hand_idx = std::numeric_limits::max(); + + // We might want to change these at runtime to adapt to other variants. + // However, a global variable is used so that we can have an output operator for cards reading from here + // Note that this is therefore not static so that we have external linking + inline std::array suit_initials = {'r', 'y', 'g', 'b', 'p', 't'}; + + struct Card { + suit_t suit; + rank_t rank; + + // These attributes are not needed in general for a card, + // they represent internal states during backtracking. + uint8_t local_index; + bool in_starting_hand; + bool initial_trash; + + /** + * @brief Compares cards *only* regarding suit and rank. + * This is inlined as this is a runtime critical function when backtracking. + */ + inline bool operator==(const Card &other) const; + }; + + enum class ActionType : std::uint8_t { + play = 0, + discard = 1, + clue = 2, + color_clue = 2, + rank_clue = 3, + end_game = 4, + vote_terminate_players = 5, + vote_terminate = 10, + }; + + struct Action { + ActionType type {}; + Card card {}; + }; + + // Output utilities for Cards and Actions + std::string to_string(const Card &card); + std::ostream &operator<<(std::ostream &os, const Card & card); + std::ostream& operator<<(std::ostream& os, const Action& action); + + + namespace Cards { + static constexpr Card r0 = {0, 5}; + static constexpr Card r1 = {0, 4}; + static constexpr Card r2 = {0, 3}; + static constexpr Card r3 = {0, 2}; + static constexpr Card r4 = {0, 1}; + static constexpr Card r5 = {0, 0}; + static constexpr Card y0 = {1, 5}; + static constexpr Card y1 = {1, 4}; + static constexpr Card y2 = {1, 3}; + static constexpr Card y3 = {1, 2}; + static constexpr Card y4 = {1, 1}; + static constexpr Card y5 = {1, 0}; + static constexpr Card g0 = {2, 5}; + static constexpr Card g1 = {2, 4}; + static constexpr Card g2 = {2, 3}; + static constexpr Card g3 = {2, 2}; + static constexpr Card g4 = {2, 1}; + static constexpr Card g5 = {2, 0}; + static constexpr Card b0 = {3, 5}; + static constexpr Card b1 = {3, 4}; + static constexpr Card b2 = {3, 3}; + static constexpr Card b3 = {3, 2}; + static constexpr Card b4 = {3, 1}; + static constexpr Card b5 = {3, 0}; + static constexpr Card p0 = {4, 5}; + static constexpr Card p1 = {4, 4}; + static constexpr Card p2 = {4, 3}; + static constexpr Card p3 = {4, 2}; + static constexpr Card p4 = {4, 1}; + static constexpr Card p5 = {4, 0}; + static constexpr Card t0 = {5, 5}; + static constexpr Card t1 = {5, 4}; + static constexpr Card t2 = {5, 3}; + static constexpr Card t3 = {5, 2}; + static constexpr Card t4 = {5, 1}; + static constexpr Card t5 = {5, 0}; + static constexpr Card unknown = {std::numeric_limits::max(), 0}; + static constexpr Card trash = {std::numeric_limits::max(), 1}; + } + + //// INLINE SECTION + + bool Card::operator==(const Card &other) const { + return suit == other.suit and rank == other.rank; + } + + template + std::ostream& print_probability(std::ostream& os, const std::optional& prob) { + if (prob.has_value()) { + return print_probability(os, prob.value()); + } else { + os << "unknown"; + } + return os; + } + +} // namespace Hanabi +#endif //DYNAMIC_PROGRAM_HANABI_TYPES_H diff --git a/src/game_state.cpp b/src/game_interface.cpp similarity index 88% rename from src/game_state.cpp rename to src/game_interface.cpp index c026b5c..a83947d 100644 --- a/src/game_state.cpp +++ b/src/game_interface.cpp @@ -1,11 +1,16 @@ -#include -#include -#include "game_state.h" +#include "game_interface.h" +#include "myassert.h" namespace Hanabi { + std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state) + { + hanabi_state.print(os); + return os; + } + Game::Game(std::unique_ptr state, std::vector actions, std::vector deck): - state(std::move(state)), actions(std::move(actions)), deck(std::move(deck)), next_action(0) + state(std::move(state)), actions(std::move(actions)), deck(std::move(deck)), next_action(0) { // If there is a 'Null' action that only signals the game's end, we want to get rid of it now, // as this will mess with our moves. @@ -77,7 +82,7 @@ namespace Hanabi { return next_action + 1 == turn; } - bool Game::holds_state() + bool Game::holds_state() const { return state != nullptr; } @@ -92,6 +97,4 @@ namespace Hanabi { } return state->draw_pile_size() == draw_pile_break; } - - -} +} \ No newline at end of file diff --git a/src/hanabi_types.cpp b/src/hanabi_types.cpp new file mode 100644 index 0000000..b82ef5d --- /dev/null +++ b/src/hanabi_types.cpp @@ -0,0 +1,45 @@ +#include "hanabi_types.hpp" + +namespace Hanabi { + std::ostream &operator<<(std::ostream &os, Action const& action) { + switch(action.type) { + case ActionType::play: + os << "play " + to_string(action.card); + break; + case ActionType::discard: + os << "discard"; + break; + case ActionType::clue: + os << "clue"; + break; + default: + break; + } + return os; + } + + + std::string to_string(const Hanabi::Card &card) { + if (card == Hanabi::Cards::trash) { + return "kt"; + } else { + return Hanabi::suit_initials[card.suit] + std::to_string(5 - card.rank); + } + } + + std::ostream &operator<<(std::ostream &os, const Card &card) { + os << to_string(card); + return os; + } + + std::ostream& print_probability(std::ostream& os, const rational_probability & prob) { + os << prob << " ~ " << std::setprecision(5) << boost::rational_cast(prob) * 100 << "%"; + return os; + } + + std::ostream& print_probability(std::ostream& os, double prob) { + os << std::setprecision(5) << prob; + return os; + } + +} // namespace Hanabi