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
// 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<Card, 30> 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<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
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<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
player_t _turn{};

View file

@ -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<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());
// 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<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) {
// 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<std::underlying_type_t<typename RelativeRepresentationData::CardPosition>>(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<std::underlying_type_t<typename RelativeRepresentationData::CardPosition>>(position));
}
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
<< " 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 {