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
|
// 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{};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue