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_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 )

View file

@ -9,6 +9,7 @@
#include <variant>
#include "game_state.h"
#include "myassert.h"
// This helper function deduces the type and assigns the value with the matching key
template<class T>
@ -71,10 +72,10 @@ namespace Download {
std::vector<Hanabi::Card> parse_deck(const boost::json::value &deck_json) {
auto deck = boost::json::value_to<std::vector<Hanabi::Card>>(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<Hanabi::Card> deck = parse_deck(game_json.at("deck"));
const std::vector<Action> 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<num_suits, num_players, hand_size>(deck, actions, turn);
game.normalize_draw_and_positions();

View file

@ -182,14 +182,16 @@ public:
std::array<std::array<Card, hand_size>, num_players> _hands{};
// CardArray<num_suits, player_t> _card_positions{};
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
uint8_t _pace{};
uint8_t _score{};
std::uint64_t _enumerated_states {};
auto operator<=>(const HanabiState &) const = default;
};

View file

@ -1,6 +1,6 @@
#include <cassert>
#include <algorithm>
#include <iterator>
#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<size_t num_suits, player_t num_players, size_t hand_size>
BacktrackAction HanabiState<num_suits, num_players, hand_size>::clue() {
assert(_num_clues > 0);
ASSERT(_num_clues > 0);
--_num_clues;
incr_turn();
@ -94,19 +95,16 @@ namespace Hanabi {
template<size_t num_suits, player_t num_players, size_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::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<size_t num_suits, player_t num_players, size_t hand_size>
void HanabiState<num_suits, num_players, hand_size>::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<std::size_t num_suits, player_t num_players, std::size_t hand_size>
BacktrackAction HanabiState<num_suits, num_players, hand_size>::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<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) {
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::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) {
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<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) {
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<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) {
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);
}

View file

@ -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;
}