From daa19e408f329f1250c4ec544e85db28eceed91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Sun, 6 Aug 2023 15:02:50 +0200 Subject: [PATCH] working version done --- CMakeLists.txt | 1 + download.h | 15 ++++---- game_state.h | 6 ++-- game_state.hpp | 93 +++++++++++++++++++++++++++++++------------------- main.cpp | 24 ++++++++----- 5 files changed, 86 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f5abd0..b21b5f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(dynamic_program CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") find_package(cpr) FIND_PACKAGE( Boost 1.81 COMPONENTS program_options REQUIRED ) diff --git a/download.h b/download.h index f20d034..8f2d123 100644 --- a/download.h +++ b/download.h @@ -9,6 +9,7 @@ #include #include "game_state.h" +#include "myassert.h" // This helper function deduces the type and assigns the value with the matching key template @@ -71,10 +72,10 @@ namespace Download { std::vector parse_deck(const boost::json::value &deck_json) { auto deck = boost::json::value_to>(deck_json); for (auto &card: deck) { - assert(card.rank < 5); - assert(card.rank >= 0); - assert(card.suit < 6); - assert(card.suit >= 0); + ASSERT(card.rank < 5); + ASSERT(card.rank >= 0); + ASSERT(card.suit < 6); + ASSERT(card.suit >= 0); } return deck; } @@ -118,12 +119,12 @@ namespace Download { break; case Hanabi::ActionType::discard: index = game.find_card_in_hand(deck[actions[i].target]); - assert(index != -1); + ASSERT(index != -1); game.discard(index); break; case Hanabi::ActionType::play: index = game.find_card_in_hand(deck[actions[i].target]); - assert(index != -1); + ASSERT(index != -1); game.play(index); break; case Hanabi::ActionType::vote_terminate: @@ -146,7 +147,7 @@ namespace Download { const std::vector deck = parse_deck(game_json.at("deck")); const std::vector actions = parse_actions(game_json.at("actions")); const size_t num_players_js = game_json.at("players").as_array().size(); - assert (num_players_js == num_players); + ASSERT (num_players_js == num_players); auto game = produce_state(deck, actions, turn); game.normalize_draw_and_positions(); diff --git a/game_state.h b/game_state.h index 43ba301..5e894f5 100644 --- a/game_state.h +++ b/game_state.h @@ -182,14 +182,16 @@ public: std::array, num_players> _hands{}; // CardArray _card_positions{}; std::list _draw_pile{}; - std::uint8_t endgame_turns_left; + std::uint8_t _endgame_turns_left; - static constexpr uint8_t no_endgame = std::numeric_limits::max(); + static constexpr uint8_t no_endgame = std::numeric_limits::max() - 1; // further statistics that we might want to keep track of uint8_t _pace{}; uint8_t _score{}; + std::uint64_t _enumerated_states {}; + auto operator<=>(const HanabiState &) const = default; }; diff --git a/game_state.hpp b/game_state.hpp index d9a4ef7..82e5a46 100644 --- a/game_state.hpp +++ b/game_state.hpp @@ -1,6 +1,6 @@ -#include #include #include +#include "myassert.h" namespace Hanabi { @@ -66,6 +66,7 @@ namespace Hanabi { _hands(), // _card_positions(draw_pile), _draw_pile(), + _endgame_turns_left(no_endgame), _pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)), _score(0) { std::ranges::fill(_stacks, starting_card_rank); @@ -78,12 +79,12 @@ namespace Hanabi { } incr_turn(); } - assert(_turn == 0); + ASSERT(_turn == 0); } template BacktrackAction HanabiState::clue() { - assert(_num_clues > 0); + ASSERT(_num_clues > 0); --_num_clues; incr_turn(); @@ -94,19 +95,16 @@ namespace Hanabi { template void HanabiState::incr_turn() { _turn = (_turn + 1) % num_players; - if(endgame_turns_left != no_endgame) { - endgame_turns_left--; + if(_endgame_turns_left != no_endgame) { + _endgame_turns_left--; } } template void HanabiState::decr_turn() { _turn = (_turn + num_players - 1) % num_players; - if (endgame_turns_left != no_endgame) { - endgame_turns_left++; - if(endgame_turns_left == num_players) { - endgame_turns_left = no_endgame; - } + if (_endgame_turns_left != no_endgame) { + _endgame_turns_left++; } } @@ -123,9 +121,9 @@ namespace Hanabi { template BacktrackAction HanabiState::play( std::uint8_t index) { - assert(index < _hands[_turn].size()); + ASSERT(index < _hands[_turn].size()); const Card card = _hands[_turn][index]; - assert(card.rank == _stacks[card.suit] - 1); + ASSERT(card.rank == _stacks[card.suit] - 1); --_stacks[card.suit]; _score++; @@ -145,8 +143,8 @@ namespace Hanabi { template BacktrackAction HanabiState::discard(std::uint8_t index) { - assert(index < _hands[_turn].size()); - assert(_num_clues != max_num_clues); + ASSERT(index < _hands[_turn].size()); + ASSERT(_num_clues != max_num_clues); _num_clues++; _pace--; @@ -195,7 +193,7 @@ namespace Hanabi { template std::uint8_t HanabiState::draw(uint8_t index) { - assert(index < _hands[_turn].size()); + ASSERT(index < _hands[_turn].size()); const Card& discarded = _hands[_turn][index]; if (_stacks[discarded.suit] > discarded.rank) { @@ -208,7 +206,7 @@ namespace Hanabi { const CardMultiplicity draw = _draw_pile.front(); _draw_pile.pop_front(); - assert(draw.multiplicity > 0); + ASSERT(draw.multiplicity > 0); if (draw.multiplicity > 1) { _draw_pile.push_back(draw); @@ -224,7 +222,8 @@ namespace Hanabi { } if(_draw_pile.empty()) { - endgame_turns_left = num_players; + // Note the +1, since we will immediately decrement this when moving to the next player + _endgame_turns_left = num_players + 1; } return draw.multiplicity; } @@ -233,9 +232,9 @@ namespace Hanabi { template void HanabiState::revert_draw(std::uint8_t index, Card discarded_card) { - if (endgame_turns_left == no_endgame) { + if (_endgame_turns_left == num_players + 1 || _endgame_turns_left == no_endgame) { // Put the card that is currently in hand back into the draw pile - assert(index < _hands[_turn].size()); + ASSERT(index < _hands[_turn].size()); const Card &drawn = _hands[_turn][index]; if (_stacks[drawn.suit] > drawn.rank) { // _card_positions[drawn] = draw_pile; @@ -249,8 +248,8 @@ namespace Hanabi { _draw_pile.push_back({drawn, 1}); } _weighted_draw_pile_size++; + _endgame_turns_left = no_endgame; } - endgame_turns_left = no_endgame; _hands[_turn][index] = discarded_card; if (_stacks[discarded_card.suit] > discarded_card.rank) { // _card_positions[discarded_card] = _turn; @@ -309,11 +308,11 @@ namespace Hanabi { decr_turn(); switch (action.type) { case ActionType::clue: - assert(_num_clues < max_num_clues); + ASSERT(_num_clues < max_num_clues); _num_clues++; break; case ActionType::discard: - assert(_num_clues > 0); + ASSERT(_num_clues > 0); _num_clues--; _pace++; revert_draw(action.index, action.discarded); @@ -338,11 +337,11 @@ namespace Hanabi { template double HanabiState::backtrack(size_t depth) { - std::cout << *this << std::endl; +// std::cout << *this << std::endl; if (_score == 5 * num_suits) { return 1; } - if(_pace < 0 || endgame_turns_left == 0) { + if(_pace < 0 || _endgame_turns_left == 0) { return 0; } @@ -355,13 +354,21 @@ namespace Hanabi { // First, check for playables for(std::uint8_t index = 0; index < hand_size; index++) { if(is_playable(hand[index])) { - std::cout << std::string("---------------------", depth) << "playing " << hand[index] << std::endl; +// std::cout << std::string("---------------------", depth) << "playing " << hand[index] << std::endl; if (_draw_pile.empty()) { auto copy = *this; BacktrackAction action = play(index); const double probability_for_this_play = backtrack(depth + 1); revert(action); - assert(same_up_to_discard_permutation(*this, copy)); + if (!same_up_to_discard_permutation(*this, copy)) { + std::cout << "detected faulty backtrack" << std::endl; + std::cout << *this << std::endl; + std::cout << copy << std::endl; + ASSERT(this->_hands == copy._hands); + ASSERT(this->_draw_pile == copy._draw_pile); + ASSERT(this->_endgame_turns_left == copy._endgame_turns_left); + ASSERT(false); + } UPDATE_PROBABILITY(probability_for_this_play); } else { double sum_of_probabilities = 0; @@ -372,10 +379,10 @@ namespace Hanabi { sum_of_probabilities += backtrack(depth + 1) * action.multiplicity; sum_of_mults += action.multiplicity; revert(action); - assert(same_up_to_discard_permutation(*this, copy)); - assert(sum_of_mults <= _weighted_draw_pile_size); + ASSERT(same_up_to_discard_permutation(*this, copy)); + ASSERT(sum_of_mults <= _weighted_draw_pile_size); } - assert(sum_of_mults == _weighted_draw_pile_size); + ASSERT(sum_of_mults == _weighted_draw_pile_size); const double probability_for_this_play = sum_of_probabilities / _weighted_draw_pile_size; UPDATE_PROBABILITY(probability_for_this_play); } @@ -386,14 +393,22 @@ namespace Hanabi { if(_pace > 0) { for(std::uint8_t index = 0; index < hand_size; index++) { if (is_trash(hand[index])) { - std::cout << std::string("---------------------------", depth) << "discarding " << hand[index] << std::endl; +// std::cout << std::string("---------------------------", depth) << "discarding " << hand[index] << std::endl; double sum_of_probabilities = 0; if (_draw_pile.empty()) { auto copy = *this; BacktrackAction action = discard(index); const double probability_for_this_discard = backtrack(depth + 1); revert(action); - assert(same_up_to_discard_permutation(*this, copy)); + if (!same_up_to_discard_permutation(*this, copy)) { + std::cout << "detected faulty backtrack" << std::endl; + std::cout << *this << std::endl; + std::cout << copy << std::endl; + ASSERT(this->_hands == copy._hands); + ASSERT(this->_draw_pile == copy._draw_pile); + ASSERT(this->_endgame_turns_left == copy._endgame_turns_left); + ASSERT(false); + } UPDATE_PROBABILITY(probability_for_this_discard); } else { uint8_t sum_of_mults = 0; @@ -403,9 +418,17 @@ namespace Hanabi { sum_of_probabilities += backtrack(depth + 1) * action.multiplicity; sum_of_mults += action.multiplicity; revert(action); - assert(same_up_to_discard_permutation(*this, copy)); + if (!same_up_to_discard_permutation(*this, copy)) { + std::cout << "detected faulty backtrack" << std::endl; + std::cout << *this << std::endl; + std::cout << copy << std::endl; + ASSERT(this->_hands == copy._hands); + ASSERT(this->_draw_pile == copy._draw_pile); + ASSERT(this->_endgame_turns_left == copy._endgame_turns_left); + ASSERT(false); + } } - assert(sum_of_mults == _weighted_draw_pile_size); + ASSERT(sum_of_mults == _weighted_draw_pile_size); const double probability_discard = sum_of_probabilities / _weighted_draw_pile_size; UPDATE_PROBABILITY(probability_discard); } @@ -418,12 +441,12 @@ namespace Hanabi { // Last option is to stall if(_num_clues > 0) { - std::cout << std::string("--------------------", depth) << "stalling " << std::endl; +// std::cout << std::string("--------------------", depth) << "stalling " << std::endl; auto copy = *this; BacktrackAction action = clue(); const double probability_stall = backtrack(depth + 1); revert(action); - assert(same_up_to_discard_permutation(*this, copy)); + ASSERT(same_up_to_discard_permutation(*this, copy)); UPDATE_PROBABILITY(probability_stall); } diff --git a/main.cpp b/main.cpp index 6b5c2bc..33a7466 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,8 @@ #include "game_state.h" #include "download.h" +#include "myassert.h" + namespace Hanabi { @@ -32,17 +34,18 @@ void test_game() { std::cout << state << std::endl; std::cout << state2 << std::endl; - assert(state._hands == state2._hands); - assert(state._draw_pile == state2._draw_pile); -// assert(state._card_positions == state2._card_positions); - assert(state == state2); + ASSERT(state._hands == state2._hands); + ASSERT(state._draw_pile == state2._draw_pile); +// ASSERT(state._card_positions == state2._card_positions); + ASSERT(state == state2); } -void download() { - auto game = Download::get_game<4,3,5>("1004480.json", 30); -// std::cout << game << std::endl; +void download(int turn) { + auto game = Download::get_game<6,5,4>(996518, turn); + std::cout << "Analysing state: " << game << std::endl; auto res = game.backtrack(1); std::cout << "Probability with optimal play: " << res << std::endl; + std::cout << "Enumerated " << game._enumerated_states << " states" << std::endl; } void print_sizes() { @@ -58,9 +61,12 @@ void print_sizes() { } -int main() { +int main(int argc, char *argv[]) { // Hanabi::test_game(); - Hanabi::download(); + if(argc == 2) { + std::string turn (argv[1]); + Hanabi::download(std::stoi(turn)); + } return 0; } \ No newline at end of file