game_state: support clue starved variants
This commit is contained in:
parent
c7eac217a8
commit
930ba9b408
5 changed files with 27 additions and 19 deletions
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue