working version done

This commit is contained in:
Maximilian Keßler 2023-08-06 15:02:50 +02:00
parent 34f8bf2444
commit daa19e408f
Signed by: max
GPG key ID: BCC5A619923C0BA5
5 changed files with 86 additions and 53 deletions

View file

@ -3,6 +3,7 @@ project(dynamic_program CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror") 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(cpr)
FIND_PACKAGE( Boost 1.81 COMPONENTS program_options REQUIRED ) FIND_PACKAGE( Boost 1.81 COMPONENTS program_options REQUIRED )

View file

@ -9,6 +9,7 @@
#include <variant> #include <variant>
#include "game_state.h" #include "game_state.h"
#include "myassert.h"
// This helper function deduces the type and assigns the value with the matching key // This helper function deduces the type and assigns the value with the matching key
template<class T> template<class T>
@ -71,10 +72,10 @@ namespace Download {
std::vector<Hanabi::Card> parse_deck(const boost::json::value &deck_json) { std::vector<Hanabi::Card> parse_deck(const boost::json::value &deck_json) {
auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(deck_json); auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(deck_json);
for (auto &card: deck) { for (auto &card: deck) {
assert(card.rank < 5); ASSERT(card.rank < 5);
assert(card.rank >= 0); ASSERT(card.rank >= 0);
assert(card.suit < 6); ASSERT(card.suit < 6);
assert(card.suit >= 0); ASSERT(card.suit >= 0);
} }
return deck; return deck;
} }
@ -118,12 +119,12 @@ namespace Download {
break; break;
case Hanabi::ActionType::discard: case Hanabi::ActionType::discard:
index = game.find_card_in_hand(deck[actions[i].target]); index = game.find_card_in_hand(deck[actions[i].target]);
assert(index != -1); ASSERT(index != -1);
game.discard(index); game.discard(index);
break; break;
case Hanabi::ActionType::play: case Hanabi::ActionType::play:
index = game.find_card_in_hand(deck[actions[i].target]); index = game.find_card_in_hand(deck[actions[i].target]);
assert(index != -1); ASSERT(index != -1);
game.play(index); game.play(index);
break; break;
case Hanabi::ActionType::vote_terminate: case Hanabi::ActionType::vote_terminate:
@ -146,7 +147,7 @@ namespace Download {
const std::vector<Hanabi::Card> deck = parse_deck(game_json.at("deck")); const std::vector<Hanabi::Card> deck = parse_deck(game_json.at("deck"));
const std::vector<Action> actions = parse_actions(game_json.at("actions")); const std::vector<Action> actions = parse_actions(game_json.at("actions"));
const size_t num_players_js = game_json.at("players").as_array().size(); 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<num_suits, num_players, hand_size>(deck, actions, turn); auto game = produce_state<num_suits, num_players, hand_size>(deck, actions, turn);
game.normalize_draw_and_positions(); game.normalize_draw_and_positions();

View file

@ -182,14 +182,16 @@ public:
std::array<std::array<Card, hand_size>, num_players> _hands{}; std::array<std::array<Card, hand_size>, num_players> _hands{};
// CardArray<num_suits, player_t> _card_positions{}; // CardArray<num_suits, player_t> _card_positions{};
std::list<CardMultiplicity> _draw_pile{}; std::list<CardMultiplicity> _draw_pile{};
std::uint8_t endgame_turns_left; std::uint8_t _endgame_turns_left;
static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max(); static constexpr uint8_t no_endgame = std::numeric_limits<uint8_t>::max() - 1;
// further statistics that we might want to keep track of // further statistics that we might want to keep track of
uint8_t _pace{}; uint8_t _pace{};
uint8_t _score{}; uint8_t _score{};
std::uint64_t _enumerated_states {};
auto operator<=>(const HanabiState &) const = default; auto operator<=>(const HanabiState &) const = default;
}; };

View file

@ -1,6 +1,6 @@
#include <cassert>
#include <algorithm> #include <algorithm>
#include <iterator> #include <iterator>
#include "myassert.h"
namespace Hanabi { namespace Hanabi {
@ -66,6 +66,7 @@ namespace Hanabi {
_hands(), _hands(),
// _card_positions(draw_pile), // _card_positions(draw_pile),
_draw_pile(), _draw_pile(),
_endgame_turns_left(no_endgame),
_pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)), _pace(deck.size() - 5 * num_suits - num_players * (hand_size - 1)),
_score(0) { _score(0) {
std::ranges::fill(_stacks, starting_card_rank); std::ranges::fill(_stacks, starting_card_rank);
@ -78,12 +79,12 @@ namespace Hanabi {
} }
incr_turn(); incr_turn();
} }
assert(_turn == 0); ASSERT(_turn == 0);
} }
template<size_t num_suits, player_t num_players, size_t hand_size> template<size_t num_suits, player_t num_players, size_t hand_size>
BacktrackAction HanabiState<num_suits, num_players, hand_size>::clue() { BacktrackAction HanabiState<num_suits, num_players, hand_size>::clue() {
assert(_num_clues > 0); ASSERT(_num_clues > 0);
--_num_clues; --_num_clues;
incr_turn(); incr_turn();
@ -94,19 +95,16 @@ namespace Hanabi {
template<size_t num_suits, player_t num_players, size_t hand_size> template<size_t num_suits, player_t num_players, size_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::incr_turn() { void HanabiState<num_suits, num_players, hand_size>::incr_turn() {
_turn = (_turn + 1) % num_players; _turn = (_turn + 1) % num_players;
if(endgame_turns_left != no_endgame) { if(_endgame_turns_left != no_endgame) {
endgame_turns_left--; _endgame_turns_left--;
} }
} }
template<size_t num_suits, player_t num_players, size_t hand_size> template<size_t num_suits, player_t num_players, size_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::decr_turn() { void HanabiState<num_suits, num_players, hand_size>::decr_turn() {
_turn = (_turn + num_players - 1) % num_players; _turn = (_turn + num_players - 1) % num_players;
if (endgame_turns_left != no_endgame) { if (_endgame_turns_left != no_endgame) {
endgame_turns_left++; _endgame_turns_left++;
if(endgame_turns_left == num_players) {
endgame_turns_left = no_endgame;
}
} }
} }
@ -123,9 +121,9 @@ namespace Hanabi {
template<std::size_t num_suits, player_t num_players, std::size_t hand_size> template<std::size_t num_suits, player_t num_players, std::size_t hand_size>
BacktrackAction HanabiState<num_suits, num_players, hand_size>::play( BacktrackAction HanabiState<num_suits, num_players, hand_size>::play(
std::uint8_t index) { std::uint8_t index) {
assert(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
const Card card = _hands[_turn][index]; const Card card = _hands[_turn][index];
assert(card.rank == _stacks[card.suit] - 1); ASSERT(card.rank == _stacks[card.suit] - 1);
--_stacks[card.suit]; --_stacks[card.suit];
_score++; _score++;
@ -145,8 +143,8 @@ namespace Hanabi {
template<std::size_t num_suits, player_t num_players, std::size_t hand_size> template<std::size_t num_suits, player_t num_players, std::size_t hand_size>
BacktrackAction HanabiState<num_suits, num_players, hand_size>::discard(std::uint8_t index) { BacktrackAction HanabiState<num_suits, num_players, hand_size>::discard(std::uint8_t index) {
assert(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
assert(_num_clues != max_num_clues); ASSERT(_num_clues != max_num_clues);
_num_clues++; _num_clues++;
_pace--; _pace--;
@ -195,7 +193,7 @@ namespace Hanabi {
template<std::size_t num_suits, player_t num_players, std::size_t hand_size> template<std::size_t num_suits, player_t num_players, std::size_t hand_size>
std::uint8_t HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index) { std::uint8_t HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index) {
assert(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
const Card& discarded = _hands[_turn][index]; const Card& discarded = _hands[_turn][index];
if (_stacks[discarded.suit] > discarded.rank) { if (_stacks[discarded.suit] > discarded.rank) {
@ -208,7 +206,7 @@ namespace Hanabi {
const CardMultiplicity draw = _draw_pile.front(); const CardMultiplicity draw = _draw_pile.front();
_draw_pile.pop_front(); _draw_pile.pop_front();
assert(draw.multiplicity > 0); ASSERT(draw.multiplicity > 0);
if (draw.multiplicity > 1) { if (draw.multiplicity > 1) {
_draw_pile.push_back(draw); _draw_pile.push_back(draw);
@ -224,7 +222,8 @@ namespace Hanabi {
} }
if(_draw_pile.empty()) { 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; return draw.multiplicity;
} }
@ -233,9 +232,9 @@ namespace Hanabi {
template<std::size_t num_suits, player_t num_players, std::size_t hand_size> template<std::size_t num_suits, player_t num_players, std::size_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::revert_draw(std::uint8_t index, Card discarded_card) { void HanabiState<num_suits, num_players, hand_size>::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 // 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]; const Card &drawn = _hands[_turn][index];
if (_stacks[drawn.suit] > drawn.rank) { if (_stacks[drawn.suit] > drawn.rank) {
// _card_positions[drawn] = draw_pile; // _card_positions[drawn] = draw_pile;
@ -249,8 +248,8 @@ namespace Hanabi {
_draw_pile.push_back({drawn, 1}); _draw_pile.push_back({drawn, 1});
} }
_weighted_draw_pile_size++; _weighted_draw_pile_size++;
_endgame_turns_left = no_endgame;
} }
endgame_turns_left = no_endgame;
_hands[_turn][index] = discarded_card; _hands[_turn][index] = discarded_card;
if (_stacks[discarded_card.suit] > discarded_card.rank) { if (_stacks[discarded_card.suit] > discarded_card.rank) {
// _card_positions[discarded_card] = _turn; // _card_positions[discarded_card] = _turn;
@ -309,11 +308,11 @@ namespace Hanabi {
decr_turn(); decr_turn();
switch (action.type) { switch (action.type) {
case ActionType::clue: case ActionType::clue:
assert(_num_clues < max_num_clues); ASSERT(_num_clues < max_num_clues);
_num_clues++; _num_clues++;
break; break;
case ActionType::discard: case ActionType::discard:
assert(_num_clues > 0); ASSERT(_num_clues > 0);
_num_clues--; _num_clues--;
_pace++; _pace++;
revert_draw(action.index, action.discarded); revert_draw(action.index, action.discarded);
@ -338,11 +337,11 @@ namespace Hanabi {
template<std::size_t num_suits, player_t num_players, std::size_t hand_size> template<std::size_t num_suits, player_t num_players, std::size_t hand_size>
double HanabiState<num_suits, num_players, hand_size>::backtrack(size_t depth) { double HanabiState<num_suits, num_players, hand_size>::backtrack(size_t depth) {
std::cout << *this << std::endl; // std::cout << *this << std::endl;
if (_score == 5 * num_suits) { if (_score == 5 * num_suits) {
return 1; return 1;
} }
if(_pace < 0 || endgame_turns_left == 0) { if(_pace < 0 || _endgame_turns_left == 0) {
return 0; return 0;
} }
@ -355,13 +354,21 @@ namespace Hanabi {
// First, check for playables // First, check for playables
for(std::uint8_t index = 0; index < hand_size; index++) { for(std::uint8_t index = 0; index < hand_size; index++) {
if(is_playable(hand[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()) { if (_draw_pile.empty()) {
auto copy = *this; auto copy = *this;
BacktrackAction action = play(index); BacktrackAction action = play(index);
const double probability_for_this_play = backtrack(depth + 1); const double probability_for_this_play = backtrack(depth + 1);
revert(action); 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); UPDATE_PROBABILITY(probability_for_this_play);
} else { } else {
double sum_of_probabilities = 0; double sum_of_probabilities = 0;
@ -372,10 +379,10 @@ namespace Hanabi {
sum_of_probabilities += backtrack(depth + 1) * action.multiplicity; sum_of_probabilities += backtrack(depth + 1) * action.multiplicity;
sum_of_mults += action.multiplicity; sum_of_mults += action.multiplicity;
revert(action); revert(action);
assert(same_up_to_discard_permutation(*this, copy)); 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); ASSERT(sum_of_mults == _weighted_draw_pile_size);
const double probability_for_this_play = sum_of_probabilities / _weighted_draw_pile_size; const double probability_for_this_play = sum_of_probabilities / _weighted_draw_pile_size;
UPDATE_PROBABILITY(probability_for_this_play); UPDATE_PROBABILITY(probability_for_this_play);
} }
@ -386,14 +393,22 @@ namespace Hanabi {
if(_pace > 0) { if(_pace > 0) {
for(std::uint8_t index = 0; index < hand_size; index++) { for(std::uint8_t index = 0; index < hand_size; index++) {
if (is_trash(hand[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; double sum_of_probabilities = 0;
if (_draw_pile.empty()) { if (_draw_pile.empty()) {
auto copy = *this; auto copy = *this;
BacktrackAction action = discard(index); BacktrackAction action = discard(index);
const double probability_for_this_discard = backtrack(depth + 1); const double probability_for_this_discard = backtrack(depth + 1);
revert(action); 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); UPDATE_PROBABILITY(probability_for_this_discard);
} else { } else {
uint8_t sum_of_mults = 0; uint8_t sum_of_mults = 0;
@ -403,9 +418,17 @@ namespace Hanabi {
sum_of_probabilities += backtrack(depth + 1) * action.multiplicity; sum_of_probabilities += backtrack(depth + 1) * action.multiplicity;
sum_of_mults += action.multiplicity; sum_of_mults += action.multiplicity;
revert(action); 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; const double probability_discard = sum_of_probabilities / _weighted_draw_pile_size;
UPDATE_PROBABILITY(probability_discard); UPDATE_PROBABILITY(probability_discard);
} }
@ -418,12 +441,12 @@ namespace Hanabi {
// Last option is to stall // Last option is to stall
if(_num_clues > 0) { if(_num_clues > 0) {
std::cout << std::string("--------------------", depth) << "stalling " << std::endl; // std::cout << std::string("--------------------", depth) << "stalling " << std::endl;
auto copy = *this; auto copy = *this;
BacktrackAction action = clue(); BacktrackAction action = clue();
const double probability_stall = backtrack(depth + 1); const double probability_stall = backtrack(depth + 1);
revert(action); revert(action);
assert(same_up_to_discard_permutation(*this, copy)); ASSERT(same_up_to_discard_permutation(*this, copy));
UPDATE_PROBABILITY(probability_stall); UPDATE_PROBABILITY(probability_stall);
} }

View file

@ -10,6 +10,8 @@
#include "game_state.h" #include "game_state.h"
#include "download.h" #include "download.h"
#include "myassert.h"
namespace Hanabi { namespace Hanabi {
@ -32,17 +34,18 @@ void test_game() {
std::cout << state << std::endl; std::cout << state << std::endl;
std::cout << state2 << std::endl; std::cout << state2 << std::endl;
assert(state._hands == state2._hands); ASSERT(state._hands == state2._hands);
assert(state._draw_pile == state2._draw_pile); ASSERT(state._draw_pile == state2._draw_pile);
// assert(state._card_positions == state2._card_positions); // ASSERT(state._card_positions == state2._card_positions);
assert(state == state2); ASSERT(state == state2);
} }
void download() { void download(int turn) {
auto game = Download::get_game<4,3,5>("1004480.json", 30); auto game = Download::get_game<6,5,4>(996518, turn);
// std::cout << game << std::endl; std::cout << "Analysing state: " << game << std::endl;
auto res = game.backtrack(1); auto res = game.backtrack(1);
std::cout << "Probability with optimal play: " << res << std::endl; std::cout << "Probability with optimal play: " << res << std::endl;
std::cout << "Enumerated " << game._enumerated_states << " states" << std::endl;
} }
void print_sizes() { void print_sizes() {
@ -58,9 +61,12 @@ void print_sizes() {
} }
int main() { int main(int argc, char *argv[]) {
// Hanabi::test_game(); // Hanabi::test_game();
Hanabi::download(); if(argc == 2) {
std::string turn (argv[1]);
Hanabi::download(std::stoi(turn));
}
return 0; return 0;
} }