diff --git a/include/game_interface.h b/include/game_interface.h index 515a7e7..80d8149 100644 --- a/include/game_interface.h +++ b/include/game_interface.h @@ -57,6 +57,9 @@ namespace Hanabi [[nodiscard]] virtual bool is_trash(const Card & card) const = 0; + /** Returns whether the card is critical, assuming it is not trash already */ + [[nodiscard]] virtual bool is_critical(const Card & card) const = 0; + [[nodiscard]] virtual bool is_playable(const Card & card) const = 0; [[nodiscard]] virtual bool is_relative_state_initialized() const = 0; diff --git a/include/game_state.h b/include/game_state.h index 06774d1..bb27ca3 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -106,6 +106,9 @@ namespace Hanabi [[nodiscard]] bool is_trash(const Card & card) const final; + /** Returns whether the card is critical, assuming that it is non-trash */ + [[nodiscard]] bool is_critical(const Card & card) const final; + [[nodiscard]] bool is_playable(const Card & card) const final; [[nodiscard]] bool is_relative_state_initialized() const final; @@ -230,6 +233,11 @@ namespace Hanabi std::list _draw_pile{}; std::uint8_t _endgame_turns_left{}; + // This will actually not always be updated exactly, but only for those cards that are not + // trash yet, since for trash, this is simply not interesting. + // Thus, we only need to update this on discards or misplays. + CardArray _num_copies_left {0}; + // further values of game state that are technically determined, but we update them anyway int8_t _pace{}; uint8_t _score{}; diff --git a/include/game_state.hpp b/include/game_state.hpp index f5b0546..9ce759f 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -86,6 +86,17 @@ namespace Hanabi incr_turn(); } ASSERT(_turn == 0); + + // Prepare card counting + CardArray card_multiplicities (0); + for (auto const hand: _hands) { + for (Card const card: hand) { + _num_copies_left[card] += 1; + } + } + for (CardMultiplicity const card_mult : _draw_pile) { + _num_copies_left[card_mult.card] += card_mult.multiplicity; + } } template @@ -147,6 +158,12 @@ namespace Hanabi return card.rank == _stacks[card.suit] - 1; } + template + bool HanabiState::is_critical(Card const & card) const + { + return _num_copies_left[card] == 1; + } + template std::uint64_t HanabiState::enumerated_states() const { @@ -186,6 +203,8 @@ namespace Hanabi // update clues if we played the last played_card of a stack _num_clues += _clues_gained_on_discard_or_stack_finished; } + } else { + _num_copies_left[played_card]--; } const unsigned long multiplicity = draw(index, cycle, !strike); @@ -213,6 +232,8 @@ namespace Hanabi _num_clues += _clues_gained_on_discard_or_stack_finished; _pace--; + _num_copies_left[discarded_card]--; + unsigned long multiplicity = draw(index, cycle, false); _actions_log.emplace(ActionType::discard, discarded_card, index); @@ -274,6 +295,9 @@ namespace Hanabi for (hand_index_t index = 0; index < hand.size(); index++) { os << hand[index]; + if (is_critical(hand[index])) { + os << "!"; + } if (index < hand.size() - 1) { os << " "; @@ -577,6 +601,9 @@ namespace Hanabi { _stacks[last_action.discarded.suit]++; _score--; + } else { + // If we misplayed, then we lost the card and have to regain it now + _num_copies_left[last_action.discarded]++; } check_draw_pile_integrity(); } @@ -597,6 +624,8 @@ namespace Hanabi _pace++; + _num_copies_left[last_action.discarded]++; + revert_draw(last_action.index, last_action.discarded, cycle, false); check_draw_pile_integrity(); }