#ifndef DYNAMIC_PROGRAM_GAME_STATE_H #define DYNAMIC_PROGRAM_GAME_STATE_H #include #include #include #include #include #include #include #include #include #include namespace Hanabi { using rank_t = std::uint8_t; using suit_t = std::uint8_t; using clue_t = std::uint8_t; using player_t = std::int8_t; using state_t = std::uint32_t; /** * 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 player_t draw_pile = -1; constexpr player_t trash_or_play_stack = -2; constexpr clue_t max_num_clues = 8; constexpr std::array suit_initials{"r", "y", "g", "b", "p", "t"}; struct Card { suit_t suit; rank_t rank; uint8_t copy; Card &operator++(); Card successor() const; const Card operator++(int); auto operator<=>(const Card &) const = default; }; std::ostream &operator<<(std::ostream &os, const Card &card) { os << suit_initials[card.suit] << 5 - card.rank; return os; } constexpr Card r0 = {0, 0, 0}; constexpr Card r1 = {0, 1, 0}; constexpr Card r2 = {0, 2, 0}; constexpr Card r3 = {0, 3, 0}; constexpr Card r4 = {0, 4, 0}; constexpr Card y0 = {1, 0, 0}; constexpr Card y1 = {1, 1, 0}; constexpr Card y2 = {1, 2, 0}; constexpr Card y3 = {1, 3, 0}; constexpr Card y4 = {1, 4, 0}; /** * To store: * - Draw pile size * - Distribution of cards * - Which cards exist? * - Number of clues */ template using Stacks = std::array; template std::ostream &operator<<(std::ostream &os, const Stacks &stacks); struct CardMultiplicity { Card card; std::uint8_t multiplicity; auto operator<=>(const CardMultiplicity &) const = default; }; template struct CardArrayMember { }; template struct CardArrayMember { auto operator<=>(const CardArrayMember &) const = default; std::array, starting_card_rank>, num_suits> array {}; }; template struct CardArrayMember { auto operator<=>(const CardArrayMember &) const = default; std::array, num_suits> array {}; }; template struct CardArray { using value_type = T; CardArray() = default; explicit CardArray(value_type default_val); const value_type &operator[](const Card &card) const; value_type &operator[](const Card &card); auto operator<=>(const CardArray &) const = default; private: CardArrayMember _vals; }; enum class ActionType { play = 0, discard = 1, clue = 2, color_clue = 2, rank_clue = 3, end_game = 4, vote_terminate = 10, }; struct BacktrackAction { // The card that was discarded or played Card discarded{}; // Index of card in hand that was discarded or played std::uint8_t index{}; // Multiplicity of new draw (needed for probability calculations) std::uint8_t multiplicity{}; }; class HanabiStateIF { public: virtual double backtrack(size_t depth) = 0; virtual void clue() = 0; virtual BacktrackAction discard(std::uint8_t index) = 0; virtual BacktrackAction play(std::uint8_t index) = 0; virtual void revert_clue() = 0; virtual void revert_play(const BacktrackAction &action, bool was_on_8_clues) = 0; virtual void revert_discard(const BacktrackAction &action) = 0; [[nodiscard]] virtual std::uint8_t find_card_in_hand(const Card& card) const = 0; [[nodiscard]] virtual bool is_trash(const Card& card) const = 0; [[nodiscard]] virtual bool is_playable(const Card& card) const = 0; [[nodiscard]] virtual std::uint64_t enumerated_states() const = 0; virtual void normalize_draw_and_positions() = 0; virtual ~HanabiStateIF() = default; protected: virtual void print(std::ostream& os) const = 0; friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&); }; template class HanabiState : public HanabiStateIF { public: HanabiState() = default; explicit HanabiState(const std::vector& deck); double backtrack(size_t depth) final; void clue() final; BacktrackAction play(std::uint8_t index) final; BacktrackAction discard(std::uint8_t index) final; void revert_clue() final; void revert_play(const BacktrackAction &action, bool was_on_8_clues) final; void revert_discard(const BacktrackAction &action) final; [[nodiscard]] std::uint8_t find_card_in_hand(const Card& card) const final; [[nodiscard]] bool is_trash(const Card& card) const final; [[nodiscard]] bool is_playable(const Card& card) const final; [[nodiscard]] std::uint64_t enumerated_states() const final; void normalize_draw_and_positions() final; auto operator<=>(const HanabiState &) const = default; protected: void print(std::ostream& os) const final; private: uint8_t draw(uint8_t index); void revert_draw(std::uint8_t index, Card discarded_card); void incr_turn(); void decr_turn(); player_t _turn{}; clue_t _num_clues{}; std::uint8_t _weighted_draw_pile_size{}; Stacks _stacks{}; std::array, num_players> _hands{}; std::list _draw_pile{}; std::uint8_t _endgame_turns_left{}; static constexpr uint8_t no_endgame = std::numeric_limits::max(); // further statistics that we might want to keep track of uint8_t _pace{}; uint8_t _score{}; std::uint64_t _enumerated_states {}; }; template bool same_up_to_discard_permutation(HanabiState state1, HanabiState state2) { auto comp = [](CardMultiplicity &m1, CardMultiplicity &m2) -> bool { return m1.card.suit < m2.card.suit || (m1.card.suit == m2.card.suit and m1.card.rank < m2.card.rank) || (m1.card.suit == m2.card.suit and m1.card.rank == m2.card.rank and m1.multiplicity < m2.multiplicity); }; state1._draw_pile.sort(comp); state2._draw_pile.sort(comp); return state1 == state2; } } #include "game_state.hpp" #endif // DYNAMIC_PROGRAM_GAME_STATE_H