// // 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 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; 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, 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