working version done
This commit is contained in:
parent
34f8bf2444
commit
daa19e408f
5 changed files with 86 additions and 53 deletions
|
@ -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 )
|
||||
|
|
15
download.h
15
download.h
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
24
main.cpp
24
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;
|
||||
}
|
Loading…
Reference in a new issue