Fix unique ids to allow for discarded / lost cards

This commit is contained in:
Maximilian Keßler 2023-11-14 14:17:58 +01:00
parent 27b8c08ed4
commit ac4cf5e797
Signed by: max
GPG key ID: BCC5A619923C0BA5
3 changed files with 86 additions and 37 deletions

View file

@ -357,6 +357,14 @@ private:
// This keeps track of the representation of the gamestate relative to some starting state // This keeps track of the representation of the gamestate relative to some starting state
// and is used for id calculation // and is used for id calculation
struct RelativeRepresentationData { 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 // List of unique non-trash cards in draw pile
boost::container::static_vector<Card, 30> good_cards_draw; boost::container::static_vector<Card, 30> good_cards_draw;
@ -365,14 +373,11 @@ private:
// This will indicate whether cards that were in hands initially still are in hand // 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 // The first n bits are used and cards are assumed to have been marked with their indices in this bitset
std::bitset<num_players * hand_size> card_positions_hands {}; boost::container::static_vector<CardPosition, num_players * hand_size> card_positions_hands {};
// Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities // Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities
std::uint8_t initial_draw_pile_size { 0 }; 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 // Whether we initialized the values above and marked cards accordingly
bool initialized { false }; bool initialized { false };
}; };
@ -380,9 +385,9 @@ private:
unsigned long discard_and_potentially_update(hand_index_t index, bool cycle = false); 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 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_clue();
void revert_discard(bool cycle = false); void revert_discard(bool cycle = false);
void revert_play(bool cycle = false); void revert_play(bool cycle = false);
@ -399,8 +404,6 @@ private:
void check_draw_pile_integrity() const; void check_draw_pile_integrity() const;
static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max(); static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max();
static constexpr player_t draw_pile = num_players;
static constexpr player_t trash_or_play_stack = num_players + 1;
// Usual game state // Usual game state
player_t _turn{}; player_t _turn{};

View file

@ -211,7 +211,9 @@ namespace Hanabi {
ASSERT(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
const Card played_card = _hands[_turn][index]; 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)) 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(); incr_turn();
check_draw_pile_integrity(); check_draw_pile_integrity();
@ -245,7 +247,7 @@ namespace Hanabi {
_num_clues++; _num_clues++;
_pace--; _pace--;
unsigned long multiplicity = draw(index, cycle); unsigned long multiplicity = draw(index, cycle, false);
_actions_log.emplace(ActionType::discard, discarded_card, index); _actions_log.emplace(ActionType::discard, discarded_card, index);
incr_turn(); incr_turn();
@ -304,7 +306,7 @@ namespace Hanabi {
} }
template<suit_t num_suits, player_t num_players, hand_index_t hand_size> template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
unsigned HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index, bool cycle) { unsigned HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index, bool cycle, bool played) {
ASSERT(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
// update card position of the card we are about to discard // update card position of the card we are about to discard
@ -312,12 +314,26 @@ namespace Hanabi {
const Card discarded = _hands[_turn][index]; const Card discarded = _hands[_turn][index];
if (!discarded.initial_trash) { if (!discarded.initial_trash) {
if (discarded.in_starting_hand) { if (discarded.in_starting_hand) {
ASSERT(_relative_representation.card_positions_hands[discarded.local_index] == true); ASSERT(_relative_representation.card_positions_hands[discarded.local_index] == RelativeRepresentationData::hand);
_relative_representation.card_positions_hands[discarded.local_index] = false; if (played)
{
_relative_representation.card_positions_hands[discarded.local_index] = RelativeRepresentationData::played;
}
else
{
_relative_representation.card_positions_hands[discarded.local_index] = RelativeRepresentationData::discarded;
}
} else { } else {
auto replaced_card_it = std::ranges::find(_relative_representation.card_positions_draw[discarded.local_index], _turn); 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()); 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]); std::ranges::sort(_relative_representation.card_positions_draw[discarded.local_index]);
} }
} }
@ -345,7 +361,7 @@ namespace Hanabi {
// update card position of the drawn card // update card position of the drawn card
if (!draw.card.initial_trash) { if (!draw.card.initial_trash) {
ASSERT(draw.card.in_starting_hand == false); 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()); ASSERT(new_card_it != _relative_representation.card_positions_draw[draw.card.local_index].end());
*new_card_it = _turn; *new_card_it = _turn;
std::ranges::sort(_relative_representation.card_positions_draw[draw.card.local_index]); std::ranges::sort(_relative_representation.card_positions_draw[draw.card.local_index]);
@ -364,9 +380,9 @@ namespace Hanabi {
} }
template<suit_t num_suits, player_t num_players, hand_index_t hand_size> template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::revert_draw(std::uint8_t index, Card discarded_card, bool cycle) { void HanabiState<num_suits, num_players, hand_size>::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) { 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()); ASSERT(index < _hands[_turn].size());
const Card &drawn = _hands[_turn][index]; const Card &drawn = _hands[_turn][index];
@ -401,7 +417,7 @@ namespace Hanabi {
ASSERT(drawn.in_starting_hand == false); ASSERT(drawn.in_starting_hand == false);
auto drawn_card_it = std::ranges::find(_relative_representation.card_positions_draw[drawn.local_index], _turn); 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()); 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]); 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 (_relative_representation.initialized && !discarded_card.initial_trash) {
if (discarded_card.in_starting_hand) { if (discarded_card.in_starting_hand) {
ASSERT(_relative_representation.card_positions_hands[discarded_card.local_index] == false); ASSERT(_relative_representation.card_positions_hands[discarded_card.local_index] != RelativeRepresentationData::hand);
_relative_representation.card_positions_hands[discarded_card.local_index] = true; _relative_representation.card_positions_hands[discarded_card.local_index] = RelativeRepresentationData::hand;
} else { } 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], 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()); ASSERT(hand_card_it != _relative_representation.card_positions_draw[discarded_card.local_index].end());
*hand_card_it = _turn; *hand_card_it = _turn;
std::ranges::sort(_relative_representation.card_positions_draw[discarded_card.local_index]); 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]}); _draw_pile.push_back({card, nums_in_draw_pile[card]});
if (!is_trash(card)) { if (!is_trash(card)) {
_relative_representation.card_positions_draw.push_back({}); _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); _relative_representation.good_cards_draw.push_back(card);
} }
} }
@ -466,6 +492,8 @@ namespace Hanabi {
} }
_relative_representation.initial_draw_pile_size = _weighted_draw_pile_size; _relative_representation.initial_draw_pile_size = _weighted_draw_pile_size;
size_t num_useful_cards_in_starting_hands = 0;
// Prepare cards in hands // Prepare cards in hands
for (player_t player = 0; player < num_players; player++) { for (player_t player = 0; player < num_players; player++) {
for (Card &card: _hands[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 // This card is already in hand, so just replace the second copy by some trash
card = trash; card = trash;
} else { } else {
card.local_index = _relative_representation.num_useful_cards_in_starting_hands; card.local_index = num_useful_cards_in_starting_hands;
_relative_representation.num_useful_cards_in_starting_hands++; num_useful_cards_in_starting_hands++;
good_cards_in_hand.push_back(card); 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.clear();
_relative_representation.card_positions_hands[i] = true; _relative_representation.card_positions_hands.resize(num_useful_cards_in_starting_hands, RelativeRepresentationData::hand);
}
_relative_representation.initialized = true; _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) { if (last_action.discarded.rank == 0 and not last_action.was_on_8_clues and not last_action.strike) {
_num_clues--; _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) { if(not last_action.strike) {
_stacks[last_action.discarded.suit]++; _stacks[last_action.discarded.suit]++;
_score--; _score--;
@ -528,7 +556,7 @@ namespace Hanabi {
_num_clues--; _num_clues--;
_pace++; _pace++;
revert_draw(last_action.index, last_action.discarded, cycle); revert_draw(last_action.index, last_action.discarded, cycle, false);
check_draw_pile_integrity(); check_draw_pile_integrity();
} }
@ -747,6 +775,12 @@ namespace Hanabi {
_enumerated_states++; _enumerated_states++;
const unsigned long id_of_state = unique_id(); 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) { if (_score == _score_goal) {
return 1; return 1;
} }
@ -871,14 +905,14 @@ namespace Hanabi {
ASSERT(_relative_representation.card_positions_draw.size() == _relative_representation.good_cards_draw.size()); 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(size_t i = 0; i < _relative_representation.card_positions_draw.size(); i++) {
for(player_t player : _relative_representation.card_positions_draw[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 // 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. // do not matter, so we can just pretend that they are all in the trash already.
// The resulting states will be equivalent. // The resulting states will be equivalent.
if (!is_trash(_relative_representation.good_cards_draw[i])) { if (!is_trash(_relative_representation.good_cards_draw[i])) {
id += player; id += player;
} else { } else {
id += trash_or_play_stack; id += RelativeRepresentationData::discard_pile;
} }
} }
} }
@ -901,8 +935,11 @@ namespace Hanabi {
id += draw_pile_size_and_extra_turns; id += draw_pile_size_and_extra_turns;
// encode positions of cards that started in hands // encode positions of cards that started in hands
id = id << _relative_representation.num_useful_cards_in_starting_hands; for (typename RelativeRepresentationData::CardPosition const & position : _relative_representation.card_positions_hands)
id += _relative_representation.card_positions_hands.to_ulong(); {
id *= 3;
id += static_cast<std::underlying_type_t<typename RelativeRepresentationData::CardPosition>>(position);
}
id *= num_players; id *= num_players;
id += _turn; id += _turn;
@ -930,7 +967,7 @@ namespace Hanabi {
if (!is_trash(_relative_representation.good_cards_draw[i])) { if (!is_trash(_relative_representation.good_cards_draw[i])) {
ret.push_back(player); ret.push_back(player);
} else { } else {
ret.push_back(trash_or_play_stack); ret.push_back(RelativeRepresentationData::discard_pile);
} }
cards.push_back(_relative_representation.good_cards_draw[i]); cards.push_back(_relative_representation.good_cards_draw[i]);
} }
@ -952,7 +989,10 @@ namespace Hanabi {
ret.push_back(draw_pile_size_and_extra_turns); ret.push_back(draw_pile_size_and_extra_turns);
// encode positions of cards that started in hands // 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<std::underlying_type_t<typename RelativeRepresentationData::CardPosition>>(position));
}
ret.push_back(_turn); ret.push_back(_turn);

View file

@ -142,6 +142,12 @@ namespace Hanabi {
std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues
<< " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos; << " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos;
print_probability(std::cout, result) << std::endl; 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); game.state->set_clues(original_num_clues);
} else { } else {