Add optional argument to define score being optimized for
A third CLI argument is now accepted, describing a score goal. The program will then treat reaching this score as a win and calculate lines optimized for this score, ignoring any (possible) higher scores.
This commit is contained in:
parent
87c429e586
commit
4e67ffa9ee
5 changed files with 52 additions and 35 deletions
|
@ -45,7 +45,7 @@ namespace Download {
|
||||||
*
|
*
|
||||||
* @note Turns start counting at 1, since this is also the way hanab.live does it.
|
* @note Turns start counting at 1, since this is also the way hanab.live does it.
|
||||||
*/
|
*/
|
||||||
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn = 1, size_t draw_pile_break = 0);
|
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn = 1, size_t draw_pile_break = 0, std::optional<uint8_t> score_goal = std::nullopt);
|
||||||
|
|
||||||
} // namespace Download
|
} // namespace Download
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ template <suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||||
class HanabiState : public HanabiStateIF {
|
class HanabiState : public HanabiStateIF {
|
||||||
public:
|
public:
|
||||||
HanabiState() = default;
|
HanabiState() = default;
|
||||||
explicit HanabiState(const std::vector<Card>& deck);
|
explicit HanabiState(const std::vector<Card>& deck, uint8_t score_goal = 5 * num_suits);
|
||||||
|
|
||||||
void give_clue() final;
|
void give_clue() final;
|
||||||
void discard(hand_index_t index) final;
|
void discard(hand_index_t index) final;
|
||||||
|
@ -374,6 +374,7 @@ private:
|
||||||
// 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;
|
||||||
|
|
|
@ -111,7 +111,7 @@ 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):
|
HanabiState<num_suits, num_players, hand_size>::HanabiState(const std::vector<Card> &deck, uint8_t score_goal):
|
||||||
_turn(0),
|
_turn(0),
|
||||||
_num_clues(max_num_clues),
|
_num_clues(max_num_clues),
|
||||||
_weighted_draw_pile_size(deck.size()),
|
_weighted_draw_pile_size(deck.size()),
|
||||||
|
@ -119,8 +119,9 @@ namespace Hanabi {
|
||||||
_hands(),
|
_hands(),
|
||||||
_draw_pile(),
|
_draw_pile(),
|
||||||
_endgame_turns_left(no_endgame),
|
_endgame_turns_left(no_endgame),
|
||||||
_pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)),
|
_pace(deck.size() - score_goal - num_players * (hand_size - 1)),
|
||||||
_score(0),
|
_score(0),
|
||||||
|
_score_goal(score_goal),
|
||||||
_actions_log(),
|
_actions_log(),
|
||||||
_relative_representation(),
|
_relative_representation(),
|
||||||
_position_tablebase(),
|
_position_tablebase(),
|
||||||
|
@ -561,7 +562,7 @@ namespace Hanabi {
|
||||||
std::vector<std::pair<Action, std::optional<probability_t>>> HanabiState<num_suits, num_players, hand_size>::get_reasonable_actions() {
|
std::vector<std::pair<Action, std::optional<probability_t>>> HanabiState<num_suits, num_players, hand_size>::get_reasonable_actions() {
|
||||||
std::vector<std::pair<Action, std::optional<probability_t>>> reasonable_actions {};
|
std::vector<std::pair<Action, std::optional<probability_t>>> reasonable_actions {};
|
||||||
|
|
||||||
if(_score == 5 * num_suits or _pace < 0 or _endgame_turns_left == 0) {
|
if(_score == _score_goal or _pace < 0 or _endgame_turns_left == 0) {
|
||||||
return reasonable_actions;
|
return reasonable_actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,7 +664,7 @@ namespace Hanabi {
|
||||||
_enumerated_states++;
|
_enumerated_states++;
|
||||||
const unsigned long id_of_state = unique_id();
|
const unsigned long id_of_state = unique_id();
|
||||||
|
|
||||||
if (_score == 5 * num_suits) {
|
if (_score == _score_goal) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if(_pace < 0 || _endgame_turns_left == 0) {
|
if(_pace < 0 || _endgame_turns_left == 0) {
|
||||||
|
|
|
@ -100,9 +100,11 @@ namespace Download {
|
||||||
const std::vector<Hanabi::Card>& deck,
|
const std::vector<Hanabi::Card>& deck,
|
||||||
const std::vector<Action>& actions,
|
const std::vector<Action>& actions,
|
||||||
size_t start_turn,
|
size_t start_turn,
|
||||||
size_t draw_pile_break = 0
|
size_t draw_pile_break = 0,
|
||||||
|
std::optional<uint8_t> score_goal = std::nullopt
|
||||||
) {
|
) {
|
||||||
auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck));
|
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
|
||||||
|
auto game = std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<num_suits, num_players, hand_size>(deck, actual_score_goal));
|
||||||
std::uint8_t index;
|
std::uint8_t index;
|
||||||
for (size_t i = 0; i < std::min(start_turn - 1, actions.size()); i++) {
|
for (size_t i = 0; i < std::min(start_turn - 1, actions.size()); i++) {
|
||||||
if (game->draw_pile_size() == draw_pile_break) {
|
if (game->draw_pile_size() == draw_pile_break) {
|
||||||
|
@ -133,7 +135,7 @@ namespace Download {
|
||||||
return game;
|
return game;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn, size_t draw_pile_break) {
|
std::unique_ptr<Hanabi::HanabiStateIF> get_game(std::variant<int, const char*> game_spec, unsigned turn, size_t draw_pile_break, std::optional<uint8_t> score_goal) {
|
||||||
const std::optional<boost::json::object> game_json_opt = [&game_spec]() {
|
const std::optional<boost::json::object> game_json_opt = [&game_spec]() {
|
||||||
if (game_spec.index() == 0) {
|
if (game_spec.index() == 0) {
|
||||||
return download_game_json(std::get<int>(game_spec));
|
return download_game_json(std::get<int>(game_spec));
|
||||||
|
@ -156,65 +158,65 @@ namespace Download {
|
||||||
case 2:
|
case 2:
|
||||||
switch(num_suits) {
|
switch(num_suits) {
|
||||||
case 3:
|
case 3:
|
||||||
return produce_state<3,2,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<3,2,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 4:
|
case 4:
|
||||||
return produce_state<4,2,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<4,2,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 5:
|
case 5:
|
||||||
return produce_state<5,2,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<5,2,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 6:
|
case 6:
|
||||||
return produce_state<6,2,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<6,2,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
}
|
}
|
||||||
case 3:
|
case 3:
|
||||||
switch(num_suits) {
|
switch(num_suits) {
|
||||||
case 3:
|
case 3:
|
||||||
return produce_state<3,3,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<3,3,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 4:
|
case 4:
|
||||||
return produce_state<4,3,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<4,3,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 5:
|
case 5:
|
||||||
return produce_state<5,3,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<5,3,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 6:
|
case 6:
|
||||||
return produce_state<6,3,5>(deck, actions, turn, draw_pile_break);
|
return produce_state<6,3,5>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
}
|
}
|
||||||
case 4:
|
case 4:
|
||||||
switch(num_suits) {
|
switch(num_suits) {
|
||||||
case 3:
|
case 3:
|
||||||
return produce_state<3,4,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<3,4,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 4:
|
case 4:
|
||||||
return produce_state<4,4,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<4,4,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 5:
|
case 5:
|
||||||
return produce_state<5,4,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<5,4,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 6:
|
case 6:
|
||||||
return produce_state<6,4,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<6,4,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
}
|
}
|
||||||
case 5:
|
case 5:
|
||||||
switch(num_suits) {
|
switch(num_suits) {
|
||||||
case 3:
|
case 3:
|
||||||
return produce_state<3,5,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<3,5,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 4:
|
case 4:
|
||||||
return produce_state<4,5,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<4,5,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 5:
|
case 5:
|
||||||
return produce_state<5,5,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<5,5,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 6:
|
case 6:
|
||||||
return produce_state<6,5,4>(deck, actions, turn, draw_pile_break);
|
return produce_state<6,5,4>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
}
|
}
|
||||||
case 6:
|
case 6:
|
||||||
switch(num_suits) {
|
switch(num_suits) {
|
||||||
case 3:
|
case 3:
|
||||||
return produce_state<3,6,3>(deck, actions, turn, draw_pile_break);
|
return produce_state<3,6,3>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 4:
|
case 4:
|
||||||
return produce_state<4,6,3>(deck, actions, turn, draw_pile_break);
|
return produce_state<4,6,3>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 5:
|
case 5:
|
||||||
return produce_state<5,6,3>(deck, actions, turn, draw_pile_break);
|
return produce_state<5,6,3>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
case 6:
|
case 6:
|
||||||
return produce_state<6,6,3>(deck, actions, turn, draw_pile_break);
|
return produce_state<6,6,3>(deck, actions, turn, draw_pile_break, score_goal);
|
||||||
default:
|
default:
|
||||||
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
throw std::runtime_error("Invalid number of suits: " + std::to_string(num_suits));
|
||||||
}
|
}
|
||||||
|
|
25
src/main.cpp
25
src/main.cpp
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi {
|
||||||
void analyze_game_and_start_cli(std::variant<int, const char*> game_id, int turn) {
|
void analyze_game_and_start_cli(std::variant<int, const char*> game_id, int turn, std::optional<uint8_t> score_goal) {
|
||||||
auto game = Download::get_game(game_id, turn);
|
auto game = Download::get_game(game_id, turn, 0, score_goal);
|
||||||
if (game == nullptr) {
|
if (game == nullptr) {
|
||||||
if(game_id.index() == 0) {
|
if(game_id.index() == 0) {
|
||||||
std::cout << "Failed to download game " << std::get<int>(game_id) << " from hanab.live." << std::endl;
|
std::cout << "Failed to download game " << std::get<int>(game_id) << " from hanab.live." << std::endl;
|
||||||
|
@ -41,17 +41,19 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
void print_usage(const char* program_name) {
|
void print_usage(const char* program_name) {
|
||||||
std::cout << "Usage: " << program_name << "(GAME_ID | GAME_FILE) TURN" << std::endl;
|
std::cout << "Usage: " << program_name << "(GAME_ID | GAME_FILE) TURN [SCORE_GOAL]" << std::endl;
|
||||||
std::cout << " GAME_ID A game id from hanab.live" << std::endl;
|
std::cout << " GAME_ID A game id from hanab.live" << std::endl;
|
||||||
std::cout << " GAME_FILE A path to a file describing the game in hanab.live json format." << std::endl;
|
std::cout << " GAME_FILE A path to a file describing the game in hanab.live json format." << std::endl;
|
||||||
std::cout << " TURN Turn number of state to analyze. Turn 1 means no actions have been taken." << std::endl;
|
std::cout << " TURN Turn number of state to analyze. Turn 1 means no actions have been taken." << std::endl;
|
||||||
|
std::cout << " TURN Turn number of state to analyze. Turn 1 means no actions have been taken." << std::endl;
|
||||||
|
std::cout << " SCORE_GOAL Score that counts as a win, i.e. is optimized for achieving." << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
if(argc == 3) {
|
if(argc == 3 or argc == 4) {
|
||||||
std::string game_str(argv[1]);
|
std::string game_str(argv[1]);
|
||||||
std::string turn_str (argv[2]);
|
std::string turn_str (argv[2]);
|
||||||
|
|
||||||
|
@ -63,10 +65,21 @@ int main(int argc, char *argv[]) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint8_t> score_goal = std::nullopt;
|
||||||
|
if (argc == 4) {
|
||||||
|
std::string score_goal_str(argv[3]);
|
||||||
|
try {
|
||||||
|
score_goal = std::stoi(score_goal_str);
|
||||||
|
} catch(std::invalid_argument&) {
|
||||||
|
std::cout << "Could not parse score goal number " << score_goal_str;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Hanabi::analyze_game_and_start_cli(std::stoi(game_str), turn);
|
Hanabi::analyze_game_and_start_cli(std::stoi(game_str), turn, score_goal);
|
||||||
} catch(std::invalid_argument&) {
|
} catch(std::invalid_argument&) {
|
||||||
Hanabi::analyze_game_and_start_cli(game_str.c_str(), turn);
|
Hanabi::analyze_game_and_start_cli(game_str.c_str(), turn, score_goal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
Loading…
Reference in a new issue