Fix unique ids to allow for discarded / lost cards
This commit is contained in:
parent
27b8c08ed4
commit
ac4cf5e797
3 changed files with 86 additions and 37 deletions
|
@ -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{};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue