refactor attributes

This commit is contained in:
Maximilian Keßler 2023-08-11 13:47:57 +02:00
parent a116ec45e3
commit a6a8fb78af
Signed by: max
GPG key ID: BCC5A619923C0BA5
2 changed files with 63 additions and 48 deletions

View file

@ -25,8 +25,6 @@ namespace Hanabi {
using hand_index_t = std::uint8_t;
using probability_t = boost::rational<unsigned long>;
using state_t = std::uint32_t;
/**
* We will generally assume that stacks are played from n to 0
* Playing a 0 will yield a clue
@ -230,6 +228,29 @@ private:
bool was_on_8_clues {false};
};
// This keeps track of the representation of the gamestate relative to some starting state
// and is used for id calculation
struct RelativeRepresentationData {
// List of unique non-trash cards in draw pile
boost::container::static_vector<Card, 30> good_cards_draw;
// Card positions of these cards. Indexes correspond to the cards stored in _good_cards_draw vector
boost::container::static_vector<boost::container::static_vector<player_t, max_card_duplicity>, 30> card_positions_draw;
// Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities
size_t initial_draw_pile_size {};
// 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 {};
// Number of bits from above bitset that is meaningful
size_t num_useful_cards_in_starting_hands { 0 };
// Whether we initialized the values above and marked cards accordingly
bool initialized { false };
};
template<bool update_card_positions> unsigned long play_and_potentially_update(hand_index_t index);
template<bool update_card_positions> unsigned long discard_and_potentially_update(hand_index_t index);
@ -250,6 +271,7 @@ private:
std::uint64_t unique_id() const;
// Usual game state
player_t _turn{};
clue_t _num_clues{};
std::uint8_t _weighted_draw_pile_size{};
@ -258,26 +280,20 @@ private:
std::list<CardMultiplicity> _draw_pile{};
std::uint8_t _endgame_turns_left{};
// This will save the card positions of all cards that are in the draw pile when we start backtracking.
boost::container::static_vector<boost::container::static_vector<player_t, max_card_duplicity>, 10> _card_positions_draw;
// Note that 30 cards here is enough for all standard decks, since there are ot most 6 suits with 5 unique cards each.
boost::container::static_vector<Card, 30> _good_cards_draw;
// This will indicate whether cards that were in hands initially still are in hands
std::bitset<num_players * hand_size> _card_positions_hands;
size_t _num_useful_cards_in_starting_hands{};
size_t _initial_draw_pile_size{};
// further statistics that we might want to keep track of
// further values of game state that are technically determined, but we update them anyway
int8_t _pace{};
uint8_t _score{};
std::uint64_t _enumerated_states {};
// For reverting the current game
std::stack<BacktrackAction> _actions_log;
// For calculating ids of states during backtracking
RelativeRepresentationData _relative_representation;
// Lookup table for states. Uses the ids calculated using the relative representation
std::unordered_map<unsigned long, probability_t> _position_tablebase;
std::stack<BacktrackAction> _actions_log;
std::uint64_t _enumerated_states {};
};
template <std::size_t num_suits, player_t num_players, std::size_t hand_size>

View file

@ -81,12 +81,11 @@ namespace Hanabi {
_hands(),
_draw_pile(),
_endgame_turns_left(no_endgame),
_card_positions_draw(),
_card_positions_hands(),
_num_useful_cards_in_starting_hands(0),
_initial_draw_pile_size(0),
_pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)),
_score(0),
_actions_log(),
_relative_representation(),
_position_tablebase(),
_enumerated_states(0) {
std::ranges::fill(_stacks, starting_card_rank);
for (const Card &card: deck) {
@ -239,11 +238,11 @@ namespace Hanabi {
const Card discarded = _hands[_turn][index];
if (!discarded.initial_trash) {
if (discarded.in_starting_hand) {
ASSERT(_card_positions_hands[discarded.local_index] == true);
_card_positions_hands[discarded.local_index] = false;
ASSERT(_relative_representation.card_positions_hands[discarded.local_index] == true);
_relative_representation.card_positions_hands[discarded.local_index] = false;
} else {
auto replaced_card_it = std::ranges::find(_card_positions_draw[discarded.local_index], _turn);
ASSERT(replaced_card_it != _card_positions_draw[discarded.local_index].end());
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;
}
}
@ -266,8 +265,8 @@ 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(_card_positions_draw[draw.card.local_index], draw_pile);
ASSERT(new_card_it != _card_positions_draw[draw.card.local_index].end());
auto new_card_it = std::ranges::find(_relative_representation.card_positions_draw[draw.card.local_index], draw_pile);
ASSERT(new_card_it != _relative_representation.card_positions_draw[draw.card.local_index].end());
*new_card_it = _turn;
}
}
@ -300,8 +299,8 @@ namespace Hanabi {
if (!drawn.initial_trash) {
ASSERT(drawn.in_starting_hand == false);
auto drawn_card_it = std::ranges::find(_card_positions_draw[drawn.local_index], _turn);
ASSERT(drawn_card_it != _card_positions_draw[drawn.local_index].end());
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;
}
@ -313,12 +312,12 @@ namespace Hanabi {
if (!discarded_card.initial_trash) {
if (discarded_card.in_starting_hand) {
ASSERT(_card_positions_hands[discarded_card.local_index] == false);
_card_positions_hands[discarded_card.local_index] = true;
ASSERT(_relative_representation.card_positions_hands[discarded_card.local_index] == false);
_relative_representation.card_positions_hands[discarded_card.local_index] = true;
} else {
auto hand_card_it = std::ranges::find(_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);
ASSERT(hand_card_it != _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;
}
}
@ -351,17 +350,17 @@ namespace Hanabi {
_draw_pile.clear();
for (suit_t suit = 0; suit < num_suits; suit++) {
for (rank_t rank = 0; rank < starting_card_rank; rank++) {
Card card{suit, rank, static_cast<uint8_t>(_card_positions_draw.size()), false, is_trash(card)};
Card card{suit, rank, static_cast<uint8_t>(_relative_representation.card_positions_draw.size()), false, is_trash(card)};
if (nums_in_draw_pile[card] > 0) {
_draw_pile.push_back({card, nums_in_draw_pile[card]});
if (!is_trash(card)) {
_card_positions_draw.push_back({nums_in_draw_pile[card], draw_pile});
_good_cards_draw.push_back(card);
_relative_representation.card_positions_draw.push_back({nums_in_draw_pile[card], draw_pile});
_relative_representation.good_cards_draw.push_back(card);
}
}
}
}
_initial_draw_pile_size = _weighted_draw_pile_size;
_relative_representation.initial_draw_pile_size = _weighted_draw_pile_size;
// Prepare cards in hands
for (player_t player = 0; player < num_players; player++) {
@ -375,17 +374,17 @@ namespace Hanabi {
// This card is already in hand, so just replace the second copy by some trash
card = trash;
} else {
card.local_index = _num_useful_cards_in_starting_hands;
_num_useful_cards_in_starting_hands++;
card.local_index = _relative_representation.num_useful_cards_in_starting_hands;
_relative_representation.num_useful_cards_in_starting_hands++;
good_cards_in_hand.push_back(card);
}
}
}
}
_card_positions_hands.reset();
for (size_t i = 0; i < _num_useful_cards_in_starting_hands; i++) {
_card_positions_hands[i] = true;
_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;
}
}
@ -560,14 +559,14 @@ namespace Hanabi {
unsigned long id = 0;
// encode all positions of cards that started in draw pile
ASSERT(_card_positions_draw.size() == _good_cards_draw.size());
for(size_t i = 0; i < _card_positions_draw.size(); i++) {
for(player_t player : _card_positions_draw[i]) {
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;
// 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(_good_cards_draw[i])) {
if (!is_trash(_relative_representation.good_cards_draw[i])) {
id += player;
} else {
id += trash_or_play_stack;
@ -589,12 +588,12 @@ namespace Hanabi {
}
}();
id *= _initial_draw_pile_size + num_players;
id *= _relative_representation.initial_draw_pile_size + num_players;
id += draw_pile_size_and_extra_turns;
// encode positions of cards that started in hands
id = id << _num_useful_cards_in_starting_hands;
id += _card_positions_hands.to_ulong();
id = id << _relative_representation.num_useful_cards_in_starting_hands;
id += _relative_representation.card_positions_hands.to_ulong();
id *= num_players;
id += _turn;