game_state: support clue starved variants

This commit is contained in:
Maximilian Keßler 2024-01-09 00:36:32 +01:00
parent c7eac217a8
commit 930ba9b408
Signed by: max
GPG key ID: BCC5A619923C0BA5
5 changed files with 27 additions and 19 deletions

View file

@ -72,7 +72,7 @@ namespace Hanabi
public: public:
HanabiState() = default; HanabiState() = default;
explicit HanabiState(const std::vector<Card> & deck, uint8_t score_goal = 5 * num_suits); explicit HanabiState(const std::vector<Card> & deck, uint8_t score_goal = 5 * num_suits, clue_t num_clues_gained_on_discard_or_stack_finished = 1);
void give_clue() final; void give_clue() final;
@ -219,6 +219,9 @@ namespace Hanabi
static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max(); static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max();
// Usual game state // Usual game state
clue_t const _clues_gained_on_discard_or_stack_finished { 1 };
uint8_t const _score_goal{};
player_t _turn{}; player_t _turn{};
clue_t _num_clues{}; clue_t _num_clues{};
std::uint8_t _weighted_draw_pile_size{}; std::uint8_t _weighted_draw_pile_size{};
@ -230,7 +233,6 @@ namespace Hanabi
// further values of game state that are technically determined, but we update them anyway // further values of game state that are technically determined, but we update them anyway
int8_t _pace{}; int8_t _pace{};
uint8_t _score{}; uint8_t _score{};
uint8_t _score_goal{};
// For reverting the current game // For reverting the current game
std::stack<BacktrackAction> _actions_log; std::stack<BacktrackAction> _actions_log;

View file

@ -65,10 +65,11 @@ 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>
HanabiState<num_suits, num_players, hand_size>::HanabiState(const std::vector<Card> & deck, uint8_t score_goal): HanabiState<num_suits, num_players, hand_size>::HanabiState(const std::vector<Card> & deck, uint8_t score_goal, clue_t num_clues_gained_on_discard_or_stack_finished):
_turn(0), _num_clues(max_num_clues), _weighted_draw_pile_size(deck.size()), _stacks(), _hands(), _draw_pile() _clues_gained_on_discard_or_stack_finished(num_clues_gained_on_discard_or_stack_finished)
, _score_goal(score_goal), _turn(0), _num_clues(max_num_clues), _weighted_draw_pile_size(deck.size()), _stacks(), _hands(), _draw_pile()
, _endgame_turns_left(no_endgame), _pace(deck.size() - score_goal - num_players * (hand_size - 1)), _score(0) , _endgame_turns_left(no_endgame), _pace(deck.size() - score_goal - num_players * (hand_size - 1)), _score(0)
, _score_goal(score_goal), _actions_log(), _relative_representation(), _position_tablebase() , _actions_log(), _relative_representation(), _position_tablebase()
, _enumerated_states(0) , _enumerated_states(0)
{ {
std::ranges::fill(_stacks, starting_card_rank); std::ranges::fill(_stacks, starting_card_rank);
@ -183,7 +184,7 @@ namespace Hanabi
if (played_card.rank == 0 and _num_clues < max_num_clues) if (played_card.rank == 0 and _num_clues < max_num_clues)
{ {
// update clues if we played the last played_card of a stack // update clues if we played the last played_card of a stack
_num_clues++; _num_clues += _clues_gained_on_discard_or_stack_finished;
} }
} }
@ -209,7 +210,7 @@ namespace Hanabi
ASSERT(_num_clues != max_num_clues); ASSERT(_num_clues != max_num_clues);
const Card discarded_card = _hands[_turn][index]; const Card discarded_card = _hands[_turn][index];
_num_clues++; _num_clues += _clues_gained_on_discard_or_stack_finished;
_pace--; _pace--;
unsigned long multiplicity = draw(index, cycle, false); unsigned long multiplicity = draw(index, cycle, false);
@ -569,7 +570,7 @@ namespace Hanabi
decr_turn(); decr_turn();
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 -= _clues_gained_on_discard_or_stack_finished;
} }
revert_draw(last_action.index, last_action.discarded, cycle, !last_action.strike); revert_draw(last_action.index, last_action.discarded, cycle, !last_action.strike);
if (not last_action.strike) if (not last_action.strike)
@ -592,7 +593,7 @@ namespace Hanabi
decr_turn(); decr_turn();
ASSERT(_num_clues > 0); ASSERT(_num_clues > 0);
_num_clues--; _num_clues -= _clues_gained_on_discard_or_stack_finished;
_pace++; _pace++;
revert_draw(last_action.index, last_action.discarded, cycle, false); revert_draw(last_action.index, last_action.discarded, cycle, false);
@ -650,8 +651,8 @@ 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>::set_clues(Hanabi::clue_t clues) void HanabiState<num_suits, num_players, hand_size>::set_clues(Hanabi::clue_t clues)
{ {
ASSERT(0 <= clues); ASSERT(clue_t(0) <= clues);
ASSERT(clues <= 8); ASSERT(clues <= clue_t(8));
_num_clues = clues; _num_clues = clues;
} }
@ -764,6 +765,7 @@ namespace Hanabi
} }
} }
// Check for discards
if (_pace > 0 and _num_clues < max_num_clues) if (_pace > 0 and _num_clues < max_num_clues)
{ {
for (std::uint8_t index = 0; index < hand_size; index++) for (std::uint8_t index = 0; index < hand_size; index++)
@ -1036,8 +1038,10 @@ namespace Hanabi
} }
// encode number of clues // encode number of clues
id *= max_num_clues + 1; clue_t const scaled_clues = clue_t(2) * _num_clues;
id += _num_clues; assert(scaled_clues.denominator() == 1);
id *= (max_num_clues * clue_t(2)).numerator() + 1;
id += scaled_clues.numerator();
// we can encode draw pile size and extra turn in one metric, since we only have extra turns if draw pile is empty // we can encode draw pile size and extra turn in one metric, since we only have extra turns if draw pile is empty
const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t { const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t {
@ -1101,7 +1105,9 @@ namespace Hanabi
} }
// encode number of clues // encode number of clues
ret.push_back(_num_clues); clue_t const scaled_clues = clue_t(2) * _num_clues;
assert(scaled_clues.denominator() == 1);
ret.push_back((clue_t(2) * _num_clues).numerator());
// we can encode draw pile size and extra turn in one metric, since we only have extra turns if draw pile is empty // we can encode draw pile size and extra turn in one metric, since we only have extra turns if draw pile is empty
const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t { const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t {

View file

@ -13,7 +13,7 @@ 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::int8_t; using clue_t = boost::rational<uint16_t>;
using player_t = std::uint8_t; using player_t = std::uint8_t;
using hand_index_t = std::uint8_t; using hand_index_t = std::uint8_t;

View file

@ -38,7 +38,7 @@ namespace Hanabi
// We want to do this sanity check here again, // We want to do this sanity check here again,
// so that the run_cli method itself can ensure that arguments are fully valid // so that the run_cli method itself can ensure that arguments are fully valid
// and we cannot run into crashes due to bad specified parameters // and we cannot run into crashes due to bad specified parameters
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != 0) if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0))
{ {
throw std::logic_error("Cannot use nonzero clue modifier together with recursive evaluation mode."); throw std::logic_error("Cannot use nonzero clue modifier together with recursive evaluation mode.");
} }
@ -150,7 +150,7 @@ namespace Hanabi
// When modifying the game state, we want to reset to the actual number of clues // When modifying the game state, we want to reset to the actual number of clues
// to ensure that actions taken are legal. // to ensure that actions taken are legal.
clue_t const original_num_clues = game.state->num_clues(); clue_t const original_num_clues = game.state->num_clues();
for (clue_t num_clues = 0; num_clues <= 8; num_clues++) for (clue_t num_clues = 0; num_clues <= clue_t(8); num_clues++)
{ {
game.state->set_clues(num_clues); game.state->set_clues(num_clues);
probability_t const result = game.state->evaluate_state(); probability_t const result = game.state->evaluate_state();
@ -304,7 +304,7 @@ namespace Hanabi
parms.recursive = vm.count("recursive") > 0; parms.recursive = vm.count("recursive") > 0;
parms.quiet = vm.count("quiet") > 0; parms.quiet = vm.count("quiet") > 0;
if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != 0) if (parms.recursive and std::holds_alternative<clue_t>(parms.clue_spec) and std::get<clue_t>(parms.clue_spec) != clue_t(0))
{ {
std::cout << "Conflicting options --recursive and --clue-modifier" << std::endl; std::cout << "Conflicting options --recursive and --clue-modifier" << std::endl;
std::cout << "Use '--help' to print a help message." << std::endl; std::cout << "Use '--help' to print a help message." << std::endl;

View file

@ -386,7 +386,7 @@ namespace Hanabi
if (prompt.starts_with("clue")) if (prompt.starts_with("clue"))
{ {
if (game.state->num_clues() == 0) if (game.state->num_clues() == clue_t(0))
{ {
std::cout << "You cannot give a clue at 0 clues." << std::endl; std::cout << "You cannot give a clue at 0 clues." << std::endl;
continue; continue;