// // Created by maximilian on 7/13/23. // #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"}; struct Card { suit_t suit; rank_t rank; uint8_t copy; uint8_t index; // index of card in the deck 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] << +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 CardPositions { const player_t &operator[](const Card &card) const; player_t &operator[](const Card &card); auto operator<=>(const CardPositions &) const = default; private: std::array< std::array, starting_card_rank>, num_suits> _card_positions; }; enum class ActionType { discard, clue, play }; struct Action { ActionType type{}; Card discarded{}; std::uint8_t index{}; }; template class HanabiState { public: Action clue(); /** * Plays a card from current hand, drawing top card of draw pile and rotating draw pile * @param index of card in hand to be played */ Action play(std::uint8_t index); Action discard(std::uint8_t index); void revert(const Action &action); void draw(std::uint8_t index); void revert_draw(std::uint8_t index, Card card); void incr_turn(); void decr_turn(); player_t _turn{}; clue_t _num_clues{}; std::uint8_t _draw_pile_size{}; Stacks _stacks{}; std::array, num_players> _hands{}; CardPositions _card_positions{}; std::list _draw_pile{}; // further statistics that we might want to keep track of uint8_t _pace{}; auto operator<=>(const HanabiState &) const = default; }; template std::ostream & operator<<(std::ostream &os, HanabiState hanabi_state); #include "game_state.hpp" template class HanabiState<5, 3, 4, 20>; } #endif // DYNAMIC_PROGRAM_GAME_STATE_H