compact storage of positions of draw pile cards

This commit is contained in:
Maximilian Keßler 2023-08-07 12:04:56 +02:00
parent 0100e8c987
commit f29e3d1202
Signed by: max
GPG key ID: BCC5A619923C0BA5
2 changed files with 60 additions and 42 deletions

View file

@ -15,13 +15,13 @@
namespace Hanabi { namespace Hanabi {
using rank_t = std::uint8_t; using rank_t = std::uint8_t;
using suit_t = std::uint8_t; using suit_t = std::uint8_t;
using clue_t = std::uint8_t; using clue_t = std::uint8_t;
using player_t = std::int8_t; using player_t = std::uint8_t;
using hand_index_t = std::uint8_t; using hand_index_t = std::uint8_t;
using state_t = std::uint32_t; using state_t = std::uint32_t;
/** /**
* We will generally assume that stacks are played from n to 0 * We will generally assume that stacks are played from n to 0
@ -31,25 +31,39 @@ using state_t = std::uint32_t;
* This is just easier to implement, since then the remaining number of cards * This is just easier to implement, since then the remaining number of cards
* to be played is always the current number of the stack * to be played is always the current number of the stack
*/ */
constexpr rank_t starting_card_rank = 5; constexpr rank_t starting_card_rank = 5;
constexpr suit_t max_suit_index = 5; constexpr suit_t max_suit_index = 5;
constexpr size_t max_card_duplicity = 3; constexpr size_t max_card_duplicity = 3;
constexpr clue_t max_num_clues = 8; constexpr clue_t max_num_clues = 8;
constexpr uint8_t not_in_starting_hand = std::numeric_limits<uint8_t>::max(); constexpr uint8_t not_in_starting_hand = std::numeric_limits<uint8_t>::max();
constexpr std::array<std::string, 6> suit_initials{"r", "y", "g", "b", "p", "t"}; constexpr std::array<std::string, 6> suit_initials{"r", "y", "g", "b", "p", "t"};
struct Card { struct Card {
suit_t suit; suit_t suit;
rank_t rank; rank_t rank;
uint8_t local_index_among_good_starting_hand_cards; uint8_t local_index;
bool initial_trash; bool in_starting_hand;
bool initial_trash;
Card &operator++(); Card &operator++();
const Card operator++(int);
auto operator<=>(const Card &) const = default; const Card operator++(int);
};
auto operator<=>(const Card &) const = default;
};
}
namespace std {
template<>
struct hash<Hanabi::Card> {
std::size_t operator()(Hanabi::Card const& card) const noexcept {
return card.suit * 6 + card.rank;
}
};
}
namespace Hanabi {
std::ostream &operator<<(std::ostream &os, const Card &card); std::ostream &operator<<(std::ostream &os, const Card &card);
@ -213,11 +227,12 @@ private:
std::uint8_t _endgame_turns_left{}; 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 // This will save the card positions of all cards that are in the draw pile when we start backtracking
CardArray<num_suits, boost::container::static_vector<player_t, max_card_duplicity>> _card_positions_draw; // TODO: currently, we support at most 10 useful different cards in draw pile
boost::container::static_vector<boost::container::static_vector<player_t, max_card_duplicity>, 10> _card_positions_draw;
// This will indicate whether cards that were in hands initially still are in hands // This will indicate whether cards that were in hands initially still are in hands
std::bitset<num_players * hand_size> _card_positions_hands; std::bitset<num_players * hand_size> _card_positions_hands;
size_t _num_useful_cards_in_starting_hands; uint8_t _num_useful_cards_in_starting_hands;
// further statistics that we might want to keep track of // further statistics that we might want to keep track of
uint8_t _pace{}; uint8_t _pace{};

View file

@ -3,6 +3,7 @@
#include "myassert.h" #include "myassert.h"
#include "game_state.h" #include "game_state.h"
#include <vector> #include <vector>
#include <map>
namespace Hanabi { namespace Hanabi {
@ -222,12 +223,12 @@ namespace Hanabi {
if constexpr(update_card_positions) { if constexpr(update_card_positions) {
const Card discarded = _hands[_turn][index]; const Card discarded = _hands[_turn][index];
if (!discarded.initial_trash) { if (!discarded.initial_trash) {
if (discarded.local_index_among_good_starting_hand_cards != not_in_starting_hand) { if (discarded.in_starting_hand) {
ASSERT(_card_positions_hands[discarded.local_index_among_good_starting_hand_cards] == true); ASSERT(_card_positions_hands[discarded.local_index] == true);
_card_positions_hands[discarded.local_index_among_good_starting_hand_cards] = false; _card_positions_hands[discarded.local_index] = false;
} else { } else {
auto replaced_card_it = std::ranges::find(_card_positions_draw[discarded], _turn); auto replaced_card_it = std::ranges::find(_card_positions_draw[discarded.local_index], _turn);
ASSERT(replaced_card_it != _card_positions_draw[discarded].end()); ASSERT(replaced_card_it != _card_positions_draw[discarded.local_index].end());
*replaced_card_it = trash_or_play_stack; *replaced_card_it = trash_or_play_stack;
} }
} }
@ -249,8 +250,9 @@ namespace Hanabi {
if constexpr(update_card_positions) { if constexpr(update_card_positions) {
// update card position of the drawn card // update card position of the drawn card
if (!draw.card.initial_trash) { if (!draw.card.initial_trash) {
auto new_card_it = std::ranges::find(_card_positions_draw[draw.card], draw_pile); ASSERT(draw.card.in_starting_hand == false);
ASSERT(new_card_it != _card_positions_draw[draw.card].end()); 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());
*new_card_it = _turn; *new_card_it = _turn;
} }
} }
@ -282,8 +284,9 @@ namespace Hanabi {
} }
if (!drawn.initial_trash) { if (!drawn.initial_trash) {
auto drawn_card_it = std::ranges::find(_card_positions_draw[drawn], _turn); ASSERT(drawn.in_starting_hand == false);
ASSERT(drawn_card_it != _card_positions_draw[drawn].end()); 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());
*drawn_card_it = draw_pile; *drawn_card_it = draw_pile;
} }
@ -294,12 +297,12 @@ namespace Hanabi {
} }
if (!discarded_card.initial_trash) { if (!discarded_card.initial_trash) {
if (discarded_card.local_index_among_good_starting_hand_cards != not_in_starting_hand) { if (discarded_card.in_starting_hand) {
ASSERT(_card_positions_hands[discarded_card.local_index_among_good_starting_hand_cards] == false); ASSERT(_card_positions_hands[discarded_card.local_index] == false);
_card_positions_hands[discarded_card.local_index_among_good_starting_hand_cards] = true; _card_positions_hands[discarded_card.local_index] = true;
} else { } else {
auto hand_card_it = std::ranges::find(_card_positions_draw[discarded_card], trash_or_play_stack); auto hand_card_it = std::ranges::find(_card_positions_draw[discarded_card.local_index], trash_or_play_stack);
ASSERT(hand_card_it != _card_positions_draw[discarded_card].end()); ASSERT(hand_card_it != _card_positions_draw[discarded_card.local_index].end());
*hand_card_it = _turn; *hand_card_it = _turn;
} }
} }
@ -313,7 +316,7 @@ namespace Hanabi {
const Card trash = [this]() -> Card { const Card trash = [this]() -> Card {
for(suit_t suit = 0; suit < num_suits; suit++) { for(suit_t suit = 0; suit < num_suits; suit++) {
if(_stacks[suit] < starting_card_rank) { if(_stacks[suit] < starting_card_rank) {
return {suit, starting_card_rank - 1, not_in_starting_hand, true}; return {suit, starting_card_rank - 1, 0, false, true};
} }
} }
return {0,0}; return {0,0};
@ -332,12 +335,11 @@ namespace Hanabi {
_draw_pile.clear(); _draw_pile.clear();
for(suit_t suit = 0; suit < num_suits; suit++) { for(suit_t suit = 0; suit < num_suits; suit++) {
for(rank_t rank = 0; rank < starting_card_rank; rank++) { for(rank_t rank = 0; rank < starting_card_rank; rank++) {
Card card {suit, rank, not_in_starting_hand, is_trash(card)}; Card card {suit, rank, static_cast<uint8_t>(_card_positions_draw.size()), false, is_trash(card)};
_card_positions_draw[card].clear();
if (nums_in_draw_pile[card] > 0) { if (nums_in_draw_pile[card] > 0) {
_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)) {
_card_positions_draw[card].resize(nums_in_draw_pile[card], draw_pile); _card_positions_draw.push_back({nums_in_draw_pile[card], draw_pile});
} }
} }
} }
@ -347,6 +349,7 @@ namespace Hanabi {
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]) {
card.initial_trash = is_trash(card); card.initial_trash = is_trash(card);
card.in_starting_hand = true;
// Needed to check for dupes in same hand // Needed to check for dupes in same hand
boost::container::static_vector<Card, hand_size> good_cards_in_hand; boost::container::static_vector<Card, hand_size> good_cards_in_hand;
if(!is_trash(card)) { if(!is_trash(card)) {
@ -354,7 +357,7 @@ 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_among_good_starting_hand_cards = _num_useful_cards_in_starting_hands; card.local_index = _num_useful_cards_in_starting_hands;
_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);