commit 7bcb018244cffc2ea4e7abde3447b578908facfb Author: Maximilian Keßler Date: Fri Aug 4 16:28:41 2023 +0200 initial commit of game state diff --git a/game_state.h b/game_state.h new file mode 100644 index 0000000..a306624 --- /dev/null +++ b/game_state.h @@ -0,0 +1,198 @@ +// +// 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++() { + rank++; + return *this; + } + + Card successor() { + return {suit, static_cast(rank + 1)}; + } + + const Card operator++(int) { + Card ret = *this; + rank++; + return ret; + } + + 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) { + for (size_t i = 0; i < stacks.size() - 1; i++) { + os << +stacks[i] << ", "; + } + os << +stacks.back(); + return os; +} + +template +struct CardSetSpecification { + using AdditionalCardsFlags = std::bitset; + + Stacks stacks {}; + AdditionalCardsFlags additional_cards_flags {}; +}; + + +template +struct PartialHanabiInstance { + std::array, num_players> initial_cards; + CardSetSpecification initial_state; +}; + +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 { + return _card_positions[card.suit][card.rank][card.copy]; + }; + + player_t & operator[](const Card& card) { + return _card_positions[card.suit][card.rank][card.copy]; + }; + + 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, const HanabiState hanabi_state); + +#include "game_state.hpp" + +template class HanabiState<5, 3, 4, 20>; + +#endif // DYNAMIC_PROGRAM_GAME_STATE_H diff --git a/game_state.hpp b/game_state.hpp new file mode 100644 index 0000000..e7b2196 --- /dev/null +++ b/game_state.hpp @@ -0,0 +1,143 @@ +#include +#include +#include + + +template +Action HanabiState::clue() { + assert(_num_clues > 0); + --_num_clues; + + incr_turn(); + + return Action {ActionType::clue, {}, {}}; +} + +template +void HanabiState::incr_turn() { + _turn = (_turn + 1) % num_players; +} + +template +void HanabiState::decr_turn() { + _turn = (_turn + num_players - 1) % num_players; +} + +template +Action HanabiState::play(std::uint8_t index) { + assert(index < _hands[_turn].size()); + const Card card = _hands[_turn][index]; + assert(card.rank == _stacks[card.suit] - 1); + + --_stacks[card.suit]; + + Action ret {ActionType::play, _hands[_turn][index], index}; + + if (card.rank == 0) { + // update clues if we played the last card of a stack + _num_clues++; + } + + draw(index); + incr_turn(); + + return ret; +} + +template +Action HanabiState::discard(std::uint8_t index) { + assert(index < _hands[_turn].size()); + assert(_num_clues != max_num_clues); + + _num_clues++; + + Action ret {ActionType::discard, _hands[_turn][index], index}; + + draw(index); + incr_turn(); + + return ret; +} + + +template +std::ostream& operator<<(std::ostream& os, const HanabiState hanabi_state) { + os << "Stacks: " << hanabi_state._stacks << std::endl; + os << "Draw pile: "; + for (const auto &[card, mul] : hanabi_state._draw_pile) { + os << card; + if (mul > 1) { + os << " (" << +mul << ")"; + } + } + os << std::endl; + os << "Hands: "; + for (const auto& hand: hanabi_state._hands) { + for (const auto &card: hand) { + os << card << ", "; + } + os << " | "; + } + return os; +} + +template +void HanabiState::draw(std::uint8_t index) { + assert(index < _hands[_turn].size()); + + _card_positions[_hands[_turn][index]] = trash_or_play_stack; + + // draw a new card if the draw pile is not empty + if (!_draw_pile.empty()) { + --_draw_pile_size; + CardMultiplicity draw = _draw_pile.front(); + _draw_pile.pop_front(); + assert(draw.multiplicity > 0); + if (draw.multiplicity > 1) { + draw.multiplicity--; + _draw_pile.push_back(draw); + } + _hands[_turn][index] = draw.card; + _card_positions[draw.card] = _turn; + } +} + +template +void HanabiState::revert_draw(std::uint8_t index, Card card) { + assert(index < _hands[_turn].size()); + + _card_positions[_hands[_turn][index]] = draw_pile; + + // draw a new card if the draw pile is not empty + if (_draw_pile.back().card == _hands[_turn][index]) { + _draw_pile.back().multiplicity++; + } else { + _draw_pile.push_back({_hands[_turn][index], 1}); + } + + _hands[_turn][index] = card; + _card_positions[card] = _turn; + _draw_pile_size++; +} + +template +void HanabiState::revert(const Action &action) { + decr_turn(); + switch (action.type) { + case ActionType::clue: + assert(_num_clues < max_num_clues); + _num_clues++; + break; + case ActionType::discard: + assert(_num_clues > 0); + _num_clues--; + revert_draw(action.index, action.discarded); + break; + case ActionType::play: + if (action.discarded.rank == 0) { + _num_clues--; + } + revert_draw(action.index, action.discarded); + _stacks[action.discarded.suit]++; + } +} diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..e31ebee --- /dev/null +++ b/main.cpp @@ -0,0 +1,55 @@ +// +// Created by maximilian on 7/13/23. +// + +#include +#include +#include +#include +#include + +#include "game_state.h" + + +void test_game() { + HanabiState<2, 2, 5, 10> state; + state._stacks[0] = 2; + state._stacks[1] = 3; + Card r41 = {0,4,1}; + state._draw_pile.push_back({r41, 1}); + state._hands[0] = {y0, y1, y2, r0, r1}; + state._hands[1] = {r1, r1, y1, r3, r2}; + state._draw_pile_size = 1; + state._card_positions[r41] = draw_pile; + + auto state2 = state; + + auto a = state.play(4); + std::cout << state; + state.revert(a); + + std::cout << state << std::endl; + std::cout << state2 << std::endl; + assert(state._hands == state2._hands); + assert(state._draw_pile == state2._draw_pile); + assert(state._card_positions == state2._card_positions); + assert(state == state2); +} + +void print_sizes() { + std::cout << "size of card -> hand map: " << sizeof(HanabiState<5,3,4, 5>) << std::endl; + + CardSetSpecification<5> test; + std::cout << sizeof(CardSetSpecification<8>::AdditionalCardsFlags) << std::endl; + + unsigned exp = 32; + std::cout << "Pair size: " << sizeof(std::pair) << std::endl; + std::cout << sizeof(boost::rational) << std::endl; + std::cout << (1ul << exp) << std::endl; +} + +int main() { + test_game(); + + return 0; +} \ No newline at end of file