diff --git a/include/game_state.h b/include/game_state.h index 622ca83..06774d1 100644 --- a/include/game_state.h +++ b/include/game_state.h @@ -72,7 +72,7 @@ namespace Hanabi public: HanabiState() = default; - explicit HanabiState(const std::vector & deck, uint8_t score_goal = 5 * num_suits); + explicit HanabiState(const std::vector & deck, uint8_t score_goal = 5 * num_suits, clue_t num_clues_gained_on_discard_or_stack_finished = 1); void give_clue() final; @@ -219,6 +219,9 @@ namespace Hanabi static constexpr uint8_t no_endgame = std::numeric_limits::max(); // Usual game state + clue_t const _clues_gained_on_discard_or_stack_finished { 1 }; + uint8_t const _score_goal{}; + player_t _turn{}; clue_t _num_clues{}; 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 int8_t _pace{}; uint8_t _score{}; - uint8_t _score_goal{}; // For reverting the current game std::stack _actions_log; diff --git a/include/game_state.hpp b/include/game_state.hpp index 52d8cc7..872d308 100644 --- a/include/game_state.hpp +++ b/include/game_state.hpp @@ -65,10 +65,11 @@ namespace Hanabi } template - HanabiState::HanabiState(const std::vector & deck, uint8_t score_goal): - _turn(0), _num_clues(max_num_clues), _weighted_draw_pile_size(deck.size()), _stacks(), _hands(), _draw_pile() + HanabiState::HanabiState(const std::vector & deck, uint8_t score_goal, clue_t num_clues_gained_on_discard_or_stack_finished): + _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) - , _score_goal(score_goal), _actions_log(), _relative_representation(), _position_tablebase() + , _actions_log(), _relative_representation(), _position_tablebase() , _enumerated_states(0) { std::ranges::fill(_stacks, starting_card_rank); @@ -183,7 +184,7 @@ namespace Hanabi if (played_card.rank == 0 and _num_clues < max_num_clues) { // 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); const Card discarded_card = _hands[_turn][index]; - _num_clues++; + _num_clues += _clues_gained_on_discard_or_stack_finished; _pace--; unsigned long multiplicity = draw(index, cycle, false); @@ -569,7 +570,7 @@ namespace Hanabi decr_turn(); 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); if (not last_action.strike) @@ -592,7 +593,7 @@ namespace Hanabi decr_turn(); ASSERT(_num_clues > 0); - _num_clues--; + _num_clues -= _clues_gained_on_discard_or_stack_finished; _pace++; revert_draw(last_action.index, last_action.discarded, cycle, false); @@ -650,8 +651,8 @@ namespace Hanabi template void HanabiState::set_clues(Hanabi::clue_t clues) { - ASSERT(0 <= clues); - ASSERT(clues <= 8); + ASSERT(clue_t(0) <= clues); + ASSERT(clues <= clue_t(8)); _num_clues = clues; } @@ -764,6 +765,7 @@ namespace Hanabi } } + // Check for discards if (_pace > 0 and _num_clues < max_num_clues) { for (std::uint8_t index = 0; index < hand_size; index++) @@ -1036,8 +1038,10 @@ namespace Hanabi } // encode number of clues - id *= max_num_clues + 1; - id += _num_clues; + clue_t const scaled_clues = clue_t(2) * _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 const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t { @@ -1101,7 +1105,9 @@ namespace Hanabi } // 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 const std::uint8_t draw_pile_size_and_extra_turns = [this]() -> uint8_t { diff --git a/include/hanabi_types.hpp b/include/hanabi_types.hpp index da91613..4eaadc1 100644 --- a/include/hanabi_types.hpp +++ b/include/hanabi_types.hpp @@ -13,7 +13,7 @@ namespace Hanabi using rank_t = std::uint8_t; using suit_t = std::uint8_t; - using clue_t = std::int8_t; + using clue_t = boost::rational; using player_t = std::uint8_t; using hand_index_t = std::uint8_t; diff --git a/src/command_line_interface.cpp b/src/command_line_interface.cpp index 64599c1..437197a 100644 --- a/src/command_line_interface.cpp +++ b/src/command_line_interface.cpp @@ -38,7 +38,7 @@ namespace Hanabi // We want to do this sanity check here again, // 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 - if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != 0) + if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != clue_t(0)) { 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 // to ensure that actions taken are legal. 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); probability_t const result = game.state->evaluate_state(); @@ -304,7 +304,7 @@ namespace Hanabi parms.recursive = vm.count("recursive") > 0; parms.quiet = vm.count("quiet") > 0; - if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != 0) + if (parms.recursive and std::holds_alternative(parms.clue_spec) and std::get(parms.clue_spec) != clue_t(0)) { std::cout << "Conflicting options --recursive and --clue-modifier" << std::endl; std::cout << "Use '--help' to print a help message." << std::endl; diff --git a/src/state_explorer.cpp b/src/state_explorer.cpp index a07186c..c2c25ee 100644 --- a/src/state_explorer.cpp +++ b/src/state_explorer.cpp @@ -386,7 +386,7 @@ namespace Hanabi 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; continue;