From ac4cf5e797d67b31a11127762f5a01249b7cd801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Tue, 14 Nov 2023 14:17:58 +0100 Subject: [PATCH] Fix unique ids to allow for discarded / lost cards --- include/game_state.h | 19 ++++--- include/game_state.hpp | 98 ++++++++++++++++++++++++---------- src/command_line_interface.cpp | 6 +++ 3 files changed, 86 insertions(+), 37 deletions(-) diff --git a/include/game_state.h b/include/game_state.h index c6a2e56..8b03a2d 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -357,6 +357,14 @@ private: // This keeps track of the representation of the gamestate relative to some starting state // and is used for id calculation struct RelativeRepresentationData { + static constexpr player_t draw_pile = num_players; + static constexpr player_t discard_pile = num_players + 1; + static constexpr player_t play_stack = num_players + 2; + enum CardPosition : uint8_t { + hand = 0, + played = 1, + discarded = 2 + }; // List of unique non-trash cards in draw pile boost::container::static_vector good_cards_draw; @@ -365,14 +373,11 @@ private: // This will indicate whether cards that were in hands initially still are in hand // The first n bits are used and cards are assumed to have been marked with their indices in this bitset - std::bitset card_positions_hands {}; + boost::container::static_vector card_positions_hands {}; // Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities std::uint8_t initial_draw_pile_size { 0 }; - // Number of bits from above bitset that is meaningful - std::uint8_t num_useful_cards_in_starting_hands { 0 }; - // Whether we initialized the values above and marked cards accordingly bool initialized { false }; }; @@ -380,9 +385,9 @@ private: unsigned long discard_and_potentially_update(hand_index_t index, bool cycle = false); unsigned long play_and_potentially_update(hand_index_t index, bool cycle = false); - unsigned draw(hand_index_t index, bool cycle = false); + unsigned draw(hand_index_t index, bool cycle = false, bool played = true); - void revert_draw(hand_index_t index, Card discarded_card, bool cycle = false); + void revert_draw(hand_index_t index, Card discarded_card, bool cycle = false, bool played = true); void revert_clue(); void revert_discard(bool cycle = false); void revert_play(bool cycle = false); @@ -399,8 +404,6 @@ private: void check_draw_pile_integrity() const; static constexpr uint8_t no_endgame = std::numeric_limits::max(); - static constexpr player_t draw_pile = num_players; - static constexpr player_t trash_or_play_stack = num_players + 1; // Usual game state player_t _turn{}; diff --git a/include/game_state.hpp b/include/game_state.hpp index 86ab781..294b739 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -211,7 +211,9 @@ namespace Hanabi { ASSERT(index < _hands[_turn].size()); const Card played_card = _hands[_turn][index]; - _actions_log.emplace(ActionType::play, played_card, index, _num_clues == 8, !is_playable(played_card)); + bool const strike = !is_playable(played_card); + + _actions_log.emplace(ActionType::play, played_card, index, _num_clues == 8, strike); if(is_playable(played_card)) { @@ -223,7 +225,7 @@ namespace Hanabi { } } - const unsigned long multiplicity = draw(index, cycle); + const unsigned long multiplicity = draw(index, cycle, !strike); incr_turn(); check_draw_pile_integrity(); @@ -245,7 +247,7 @@ namespace Hanabi { _num_clues++; _pace--; - unsigned long multiplicity = draw(index, cycle); + unsigned long multiplicity = draw(index, cycle, false); _actions_log.emplace(ActionType::discard, discarded_card, index); incr_turn(); @@ -304,7 +306,7 @@ namespace Hanabi { } template - unsigned HanabiState::draw(uint8_t index, bool cycle) { + unsigned HanabiState::draw(uint8_t index, bool cycle, bool played) { ASSERT(index < _hands[_turn].size()); // update card position of the card we are about to discard @@ -312,12 +314,26 @@ namespace Hanabi { const Card discarded = _hands[_turn][index]; if (!discarded.initial_trash) { if (discarded.in_starting_hand) { - ASSERT(_relative_representation.card_positions_hands[discarded.local_index] == true); - _relative_representation.card_positions_hands[discarded.local_index] = false; + ASSERT(_relative_representation.card_positions_hands[discarded.local_index] == RelativeRepresentationData::hand); + if (played) + { + _relative_representation.card_positions_hands[discarded.local_index] = RelativeRepresentationData::played; + } + else + { + _relative_representation.card_positions_hands[discarded.local_index] = RelativeRepresentationData::discarded; + } } else { auto replaced_card_it = std::ranges::find(_relative_representation.card_positions_draw[discarded.local_index], _turn); ASSERT(replaced_card_it != _relative_representation.card_positions_draw[discarded.local_index].end()); - *replaced_card_it = trash_or_play_stack; + if (played) + { + *replaced_card_it = RelativeRepresentationData::play_stack; + } + else + { + *replaced_card_it = RelativeRepresentationData::discard_pile; + } std::ranges::sort(_relative_representation.card_positions_draw[discarded.local_index]); } } @@ -345,7 +361,7 @@ namespace Hanabi { // update card position of the drawn card if (!draw.card.initial_trash) { ASSERT(draw.card.in_starting_hand == false); - auto new_card_it = std::ranges::find(_relative_representation.card_positions_draw[draw.card.local_index], draw_pile); + auto new_card_it = std::ranges::find(_relative_representation.card_positions_draw[draw.card.local_index], RelativeRepresentationData::draw_pile); ASSERT(new_card_it != _relative_representation.card_positions_draw[draw.card.local_index].end()); *new_card_it = _turn; std::ranges::sort(_relative_representation.card_positions_draw[draw.card.local_index]); @@ -364,9 +380,9 @@ namespace Hanabi { } template - void HanabiState::revert_draw(std::uint8_t index, Card discarded_card, bool cycle) { + void HanabiState::revert_draw(std::uint8_t index, Card discarded_card, bool cycle, bool played) { + // Put the card that is currently in hand back into the draw pile (this does not happen in the last round!) if (_endgame_turns_left == num_players + 1 || _endgame_turns_left == no_endgame) { - // Put the card that is currently in hand back into the draw pile ASSERT(index < _hands[_turn].size()); const Card &drawn = _hands[_turn][index]; @@ -401,7 +417,7 @@ namespace Hanabi { ASSERT(drawn.in_starting_hand == false); auto drawn_card_it = std::ranges::find(_relative_representation.card_positions_draw[drawn.local_index], _turn); ASSERT(drawn_card_it != _relative_representation.card_positions_draw[drawn.local_index].end()); - *drawn_card_it = draw_pile; + *drawn_card_it = RelativeRepresentationData::draw_pile; std::ranges::sort(_relative_representation.card_positions_draw[drawn.local_index]); } @@ -413,11 +429,21 @@ namespace Hanabi { if (_relative_representation.initialized && !discarded_card.initial_trash) { if (discarded_card.in_starting_hand) { - ASSERT(_relative_representation.card_positions_hands[discarded_card.local_index] == false); - _relative_representation.card_positions_hands[discarded_card.local_index] = true; + ASSERT(_relative_representation.card_positions_hands[discarded_card.local_index] != RelativeRepresentationData::hand); + _relative_representation.card_positions_hands[discarded_card.local_index] = RelativeRepresentationData::hand; } else { + player_t const old_position = [&played]{ + if (played) + { + return RelativeRepresentationData::play_stack; + } + else + { + return RelativeRepresentationData::discard_pile; + } + }(); auto hand_card_it = std::ranges::find(_relative_representation.card_positions_draw[discarded_card.local_index], - trash_or_play_stack); + old_position); ASSERT(hand_card_it != _relative_representation.card_positions_draw[discarded_card.local_index].end()); *hand_card_it = _turn; std::ranges::sort(_relative_representation.card_positions_draw[discarded_card.local_index]); @@ -458,7 +484,7 @@ namespace Hanabi { _draw_pile.push_back({card, nums_in_draw_pile[card]}); if (!is_trash(card)) { _relative_representation.card_positions_draw.push_back({}); - _relative_representation.card_positions_draw.back().resize(nums_in_draw_pile[card], draw_pile); + _relative_representation.card_positions_draw.back().resize(nums_in_draw_pile[card], RelativeRepresentationData::draw_pile); _relative_representation.good_cards_draw.push_back(card); } } @@ -466,6 +492,8 @@ namespace Hanabi { } _relative_representation.initial_draw_pile_size = _weighted_draw_pile_size; + size_t num_useful_cards_in_starting_hands = 0; + // Prepare cards in hands for (player_t player = 0; player < num_players; player++) { for (Card &card: _hands[player]) { @@ -478,18 +506,18 @@ namespace Hanabi { // This card is already in hand, so just replace the second copy by some trash card = trash; } else { - card.local_index = _relative_representation.num_useful_cards_in_starting_hands; - _relative_representation.num_useful_cards_in_starting_hands++; + card.local_index = num_useful_cards_in_starting_hands; + num_useful_cards_in_starting_hands++; good_cards_in_hand.push_back(card); } } } } - _relative_representation.card_positions_hands.reset(); - for (size_t i = 0; i < _relative_representation.num_useful_cards_in_starting_hands; i++) { - _relative_representation.card_positions_hands[i] = true; - } + + _relative_representation.card_positions_hands.clear(); + _relative_representation.card_positions_hands.resize(num_useful_cards_in_starting_hands, RelativeRepresentationData::hand); + _relative_representation.initialized = true; } @@ -506,7 +534,7 @@ namespace Hanabi { if (last_action.discarded.rank == 0 and not last_action.was_on_8_clues and not last_action.strike) { _num_clues--; } - revert_draw(last_action.index, last_action.discarded, cycle); + revert_draw(last_action.index, last_action.discarded, cycle, !last_action.strike); if(not last_action.strike) { _stacks[last_action.discarded.suit]++; _score--; @@ -528,7 +556,7 @@ namespace Hanabi { _num_clues--; _pace++; - revert_draw(last_action.index, last_action.discarded, cycle); + revert_draw(last_action.index, last_action.discarded, cycle, false); check_draw_pile_integrity(); } @@ -747,6 +775,12 @@ namespace Hanabi { _enumerated_states++; const unsigned long id_of_state = unique_id(); + const unsigned id = 55032; + if (id_of_state == id) + { + std::cout << "Found state with id of " << id << "\n" << *this << std::endl; + } + if (_score == _score_goal) { return 1; } @@ -871,14 +905,14 @@ namespace Hanabi { ASSERT(_relative_representation.card_positions_draw.size() == _relative_representation.good_cards_draw.size()); for(size_t i = 0; i < _relative_representation.card_positions_draw.size(); i++) { for(player_t player : _relative_representation.card_positions_draw[i]) { - id *= num_players + 2; + id *= num_players + 3; // We normalize here: If a card is already played, then the positions of its other copies // do not matter, so we can just pretend that they are all in the trash already. // The resulting states will be equivalent. if (!is_trash(_relative_representation.good_cards_draw[i])) { id += player; } else { - id += trash_or_play_stack; + id += RelativeRepresentationData::discard_pile; } } } @@ -901,8 +935,11 @@ namespace Hanabi { id += draw_pile_size_and_extra_turns; // encode positions of cards that started in hands - id = id << _relative_representation.num_useful_cards_in_starting_hands; - id += _relative_representation.card_positions_hands.to_ulong(); + for (typename RelativeRepresentationData::CardPosition const & position : _relative_representation.card_positions_hands) + { + id *= 3; + id += static_cast>(position); + } id *= num_players; id += _turn; @@ -930,7 +967,7 @@ namespace Hanabi { if (!is_trash(_relative_representation.good_cards_draw[i])) { ret.push_back(player); } else { - ret.push_back(trash_or_play_stack); + ret.push_back(RelativeRepresentationData::discard_pile); } cards.push_back(_relative_representation.good_cards_draw[i]); } @@ -952,7 +989,10 @@ namespace Hanabi { ret.push_back(draw_pile_size_and_extra_turns); // encode positions of cards that started in hands - ret.push_back(_relative_representation.card_positions_hands.to_ulong()); + for (typename RelativeRepresentationData::CardPosition const & position : _relative_representation.card_positions_hands) + { + ret.push_back(static_cast>(position)); + } ret.push_back(_turn); diff --git a/src/command_line_interface.cpp b/src/command_line_interface.cpp index 442259e..a6e465c 100644 --- a/src/command_line_interface.cpp +++ b/src/command_line_interface.cpp @@ -142,6 +142,12 @@ namespace Hanabi { std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues << " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos; print_probability(std::cout, result) << std::endl; + std::cout << *game.state << std::endl; + auto const [a,b] = game.state->dump_unique_id_parts(); + for (auto elem : a) { + std::cout << elem << ", "; + } + std::cout << "-> " << game.state->unique_id() << std::endl; } game.state->set_clues(original_num_clues); } else {