reformat code
This commit is contained in:
parent
43b4bec7c6
commit
ea881c5e6a
20 changed files with 2076 additions and 1644 deletions
|
@ -6,13 +6,14 @@
|
||||||
#include "hanabi_types.hpp"
|
#include "hanabi_types.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
enum class GameStateSpecType {
|
{
|
||||||
turn = 0,
|
enum class GameStateSpecType
|
||||||
draw_pile_size = 1,
|
{
|
||||||
};
|
turn = 0, draw_pile_size = 1, };
|
||||||
|
|
||||||
struct CLIParms {
|
struct CLIParms
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* The data source for the game to be analysed.
|
* The data source for the game to be analysed.
|
||||||
* If of type int, assumed to be a game ID from hanab.live
|
* If of type int, assumed to be a game ID from hanab.live
|
||||||
|
@ -20,19 +21,19 @@ namespace Hanabi {
|
||||||
* see https://raw.githubusercontent.com/Hanabi-Live/hanabi-live/main/misc/example_game_with_comments.jsonc
|
* see https://raw.githubusercontent.com/Hanabi-Live/hanabi-live/main/misc/example_game_with_comments.jsonc
|
||||||
* for a format specification.
|
* for a format specification.
|
||||||
*/
|
*/
|
||||||
std::variant<int, std::string> game {};
|
std::variant<int, std::string> game{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Definition of a 'winning' game, i.e. what score (number of cards played) is considered
|
* Definition of a 'winning' game, i.e. what score (number of cards played) is considered
|
||||||
* to be winning.
|
* to be winning.
|
||||||
* If std::nullopt, then the maximum score of the game will be used.
|
* If std::nullopt, then the maximum score of the game will be used.
|
||||||
*/
|
*/
|
||||||
boost::optional<uint8_t> score_goal {};
|
boost::optional<uint8_t> score_goal{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether game_state_spec denotes a turn number or draw pile size.
|
* Whether game_state_spec denotes a turn number or draw pile size.
|
||||||
*/
|
*/
|
||||||
GameStateSpecType game_state_spec_type { GameStateSpecType::draw_pile_size };
|
GameStateSpecType game_state_spec_type{GameStateSpecType::draw_pile_size};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Either a turn number or a draw pile size, depending on game_state_spec_type.
|
* Either a turn number or a draw pile size, depending on game_state_spec_type.
|
||||||
|
@ -42,36 +43,36 @@ namespace Hanabi {
|
||||||
* (since this is also the case on hanab.live)
|
* (since this is also the case on hanab.live)
|
||||||
* Thus, a turn number of 0 is undefined and a turn number of 1 corresponds to no actions taken in the game.
|
* Thus, a turn number of 0 is undefined and a turn number of 1 corresponds to no actions taken in the game.
|
||||||
*/
|
*/
|
||||||
unsigned game_state_spec { 5 };
|
unsigned game_state_spec{5};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to launch an interactive exploration shell for the game state after performing analysis.
|
* Whether to launch an interactive exploration shell for the game state after performing analysis.
|
||||||
*/
|
*/
|
||||||
boost::optional<bool> interactive {};
|
boost::optional<bool> interactive{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, deactivates non-essential output (to cout).
|
* If true, deactivates non-essential output (to cout).
|
||||||
*/
|
*/
|
||||||
bool quiet { false };
|
bool quiet{false};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If this holds std::monostate, then all clue numbers are evaluated.
|
* If this holds std::monostate, then all clue numbers are evaluated.
|
||||||
* Otherwise, the specified clue modifier is applied (relative to actual number of clues).
|
* Otherwise, the specified clue modifier is applied (relative to actual number of clues).
|
||||||
* Thus, setting this to 0 has no effect.
|
* Thus, setting this to 0 has no effect.
|
||||||
*/
|
*/
|
||||||
std::variant<std::monostate, clue_t> clue_spec {static_cast<clue_t>(0)};
|
std::variant<std::monostate, clue_t> clue_spec{static_cast<clue_t>(0)};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, then all states corresponding to smaller draw pile sizes
|
* If true, then all states corresponding to smaller draw pile sizes
|
||||||
*/
|
*/
|
||||||
bool recursive { false };
|
bool recursive{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get an output stream that is std::cout or a Null-Stream.
|
* @brief Get an output stream that is std::cout or a Null-Stream.
|
||||||
* @param quiet If true, NullStream is returned, otherwise std::cout
|
* @param quiet If true, NullStream is returned, otherwise std::cout
|
||||||
*/
|
*/
|
||||||
std::ostream & quiet_ostream(bool quiet);
|
std::ostream &quiet_ostream(bool quiet);
|
||||||
|
|
||||||
constexpr int download_failed = 1;
|
constexpr int download_failed = 1;
|
||||||
constexpr int state_unreachable = 2;
|
constexpr int state_unreachable = 2;
|
||||||
|
@ -88,6 +89,6 @@ namespace Hanabi {
|
||||||
/**
|
/**
|
||||||
* @brief Execute parsed parameters.
|
* @brief Execute parsed parameters.
|
||||||
*/
|
*/
|
||||||
int run_cli(CLIParms const & parms);
|
int run_cli(CLIParms const &parms);
|
||||||
}
|
}
|
||||||
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
#endif //DYNAMIC_PROGRAM_COMMAND_LINE_INTERFACE_H
|
||||||
|
|
|
@ -9,11 +9,12 @@
|
||||||
|
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Download {
|
namespace Download
|
||||||
|
{
|
||||||
|
|
||||||
std::optional<boost::json::object> download_game_json(int game_id);
|
std::optional<boost::json::object> download_game_json(int game_id);
|
||||||
|
|
||||||
std::optional<boost::json::object> open_game_json(const char *filename);
|
std::optional<boost::json::object> open_game_json(char const *filename);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create game object from given source
|
* @brief Create game object from given source
|
||||||
|
@ -27,7 +28,7 @@ namespace Download {
|
||||||
*/
|
*/
|
||||||
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal);
|
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal);
|
||||||
|
|
||||||
Hanabi::Game get_game(const std::string& filename, std::optional<uint8_t> score_goal);
|
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal);
|
||||||
|
|
||||||
} // namespace Download
|
} // namespace Download
|
||||||
|
|
||||||
|
|
|
@ -21,53 +21,73 @@ namespace Hanabi
|
||||||
bool operator==(const CardMultiplicity &) const = default;
|
bool operator==(const CardMultiplicity &) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HanabiStateIF {
|
class HanabiStateIF
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
virtual void give_clue() = 0;
|
virtual void give_clue() = 0;
|
||||||
|
|
||||||
virtual void discard(hand_index_t index) = 0;
|
virtual void discard(hand_index_t index) = 0;
|
||||||
|
|
||||||
virtual void play(hand_index_t index) = 0;
|
virtual void play(hand_index_t index) = 0;
|
||||||
|
|
||||||
virtual void rotate_next_draw(const Card& card) = 0;
|
virtual void rotate_next_draw(const Card & card) = 0;
|
||||||
|
|
||||||
virtual ActionType last_action_type() const = 0;
|
virtual ActionType last_action_type() const = 0;
|
||||||
|
|
||||||
virtual void revert() = 0;
|
virtual void revert() = 0;
|
||||||
|
|
||||||
virtual void modify_clues(clue_t change) = 0;
|
virtual void modify_clues(clue_t change) = 0;
|
||||||
|
|
||||||
virtual void set_clues(clue_t clues) = 0;
|
virtual void set_clues(clue_t clues) = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual player_t turn() const = 0;
|
[[nodiscard]] virtual player_t turn() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual clue_t num_clues() const = 0;
|
[[nodiscard]] virtual clue_t num_clues() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual unsigned score() const = 0;
|
[[nodiscard]] virtual unsigned score() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::vector<std::vector<Card>> hands() const = 0;
|
[[nodiscard]] virtual std::vector<std::vector<Card>> hands() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::vector<Card> cur_hand() const = 0;
|
[[nodiscard]] virtual std::vector<Card> cur_hand() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual size_t draw_pile_size() const = 0;
|
[[nodiscard]] virtual size_t draw_pile_size() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool is_trash(const Card& card) const = 0;
|
[[nodiscard]] virtual bool is_trash(const Card & card) const = 0;
|
||||||
[[nodiscard]] virtual bool is_playable(const Card& card) const = 0;
|
|
||||||
|
[[nodiscard]] virtual bool is_playable(const Card & card) const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual bool is_relative_state_initialized() const = 0;
|
[[nodiscard]] virtual bool is_relative_state_initialized() const = 0;
|
||||||
[[nodiscard]] virtual hand_index_t find_card_in_hand(const Card& card) const = 0;
|
|
||||||
|
[[nodiscard]] virtual hand_index_t find_card_in_hand(const Card & card) const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::uint64_t enumerated_states() const = 0;
|
[[nodiscard]] virtual std::uint64_t enumerated_states() const = 0;
|
||||||
[[nodiscard]] virtual const std::unordered_map<unsigned long, probability_t>& position_tablebase() const = 0;
|
|
||||||
|
[[nodiscard]] virtual const std::unordered_map<unsigned long, probability_t> & position_tablebase() const = 0;
|
||||||
|
|
||||||
virtual void init_backtracking_information() = 0;
|
virtual void init_backtracking_information() = 0;
|
||||||
|
|
||||||
virtual probability_t evaluate_state() = 0;
|
virtual probability_t evaluate_state() = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::optional<probability_t> lookup() const = 0;
|
[[nodiscard]] virtual std::optional<probability_t> lookup() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::uint64_t unique_id() const = 0;
|
[[nodiscard]] virtual std::uint64_t unique_id() const = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const = 0;
|
[[nodiscard]] virtual std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const = 0;
|
||||||
|
|
||||||
virtual std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() = 0;
|
virtual std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() = 0;
|
||||||
virtual std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> possible_next_states(hand_index_t index, bool play) = 0;
|
|
||||||
|
virtual std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>>
|
||||||
|
possible_next_states(hand_index_t index, bool play) = 0;
|
||||||
|
|
||||||
virtual ~HanabiStateIF() = default;
|
virtual ~HanabiStateIF() = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void print(std::ostream& os) const = 0;
|
virtual void print(std::ostream & os) const = 0;
|
||||||
|
|
||||||
friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&);
|
friend std::ostream & operator<<(std::ostream &, HanabiStateIF const &);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state);
|
std::ostream & operator<<(std::ostream & os, HanabiStateIF const & hanabi_state);
|
||||||
|
|
||||||
struct GameInfo
|
struct GameInfo
|
||||||
{
|
{
|
||||||
|
@ -77,16 +97,19 @@ namespace Hanabi
|
||||||
Hanabi::player_t num_players;
|
Hanabi::player_t num_players;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Game : private GameInfo {
|
struct Game : private GameInfo
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
Game(std::unique_ptr<HanabiStateIF> state, GameInfo game_info);
|
Game(std::unique_ptr<HanabiStateIF> state, GameInfo game_info);
|
||||||
|
|
||||||
[[nodiscard]] unsigned cur_turn() const;
|
[[nodiscard]] unsigned cur_turn() const;
|
||||||
|
|
||||||
void make_turn();
|
void make_turn();
|
||||||
|
|
||||||
void revert_turn();
|
void revert_turn();
|
||||||
|
|
||||||
bool goto_draw_pile_size(size_t draw_pile_break);
|
bool goto_draw_pile_size(size_t draw_pile_break);
|
||||||
|
|
||||||
bool goto_turn(size_t turn);
|
bool goto_turn(size_t turn);
|
||||||
|
|
||||||
[[nodiscard]] bool holds_state() const;
|
[[nodiscard]] bool holds_state() const;
|
||||||
|
|
|
@ -18,104 +18,131 @@
|
||||||
|
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
|
|
||||||
template<size_t num_suits>
|
template<size_t num_suits>
|
||||||
using Stacks = std::array<rank_t, num_suits>;
|
using Stacks = std::array<rank_t, num_suits>;
|
||||||
|
|
||||||
template<size_t num_suits>
|
template<size_t num_suits>
|
||||||
std::ostream &operator<<(std::ostream &os, const Stacks<num_suits> &stacks);
|
std::ostream & operator<<(std::ostream & os, const Stacks<num_suits> & stacks);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
struct InnerCardArray {
|
struct InnerCardArray
|
||||||
|
{
|
||||||
template<size_t N>
|
template<size_t N>
|
||||||
using array_t = std::array<T, N>;
|
using array_t = std::array<T, N>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct InnerCardArray<bool> {
|
struct InnerCardArray<bool>
|
||||||
|
{
|
||||||
template<size_t N>
|
template<size_t N>
|
||||||
using array_t = std::bitset<N>;
|
using array_t = std::bitset<N>;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <suit_t num_suits, typename T> struct CardArray {
|
template<suit_t num_suits, typename T>
|
||||||
|
struct CardArray
|
||||||
|
{
|
||||||
using value_type = T;
|
using value_type = T;
|
||||||
|
|
||||||
CardArray() = default;
|
CardArray() = default;
|
||||||
|
|
||||||
explicit CardArray(value_type default_val);
|
explicit CardArray(value_type default_val);
|
||||||
|
|
||||||
void fill(value_type val);
|
void fill(value_type val);
|
||||||
|
|
||||||
const value_type &operator[](const Card &card) const;
|
const value_type & operator[](const Card & card) const;
|
||||||
|
|
||||||
value_type &operator[](const Card &card);
|
value_type & operator[](const Card & card);
|
||||||
|
|
||||||
auto operator<=>(const CardArray &) const = default;
|
auto operator<=>(const CardArray &) const = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using inner_array_t = typename InnerCardArray<T>::template array_t<starting_card_rank>;
|
using inner_array_t = typename InnerCardArray<T>::template array_t<starting_card_rank>;
|
||||||
std::array<inner_array_t , num_suits> _array {};
|
std::array<inner_array_t, num_suits> _array{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// A game mimics a game state together with a list of actions and allows to traverse the game
|
// A game mimics a game state together with a list of actions and allows to traverse the game
|
||||||
// history by making and reverting the stored actions.
|
// history by making and reverting the stored actions.
|
||||||
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>
|
||||||
class HanabiState : public HanabiStateIF {
|
class HanabiState : public HanabiStateIF
|
||||||
public:
|
{
|
||||||
|
public:
|
||||||
HanabiState() = default;
|
HanabiState() = default;
|
||||||
explicit HanabiState(const std::vector<Card>& deck, uint8_t score_goal = 5 * num_suits);
|
|
||||||
|
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;
|
||||||
|
|
||||||
void play(hand_index_t index) final;
|
void play(hand_index_t index) final;
|
||||||
|
|
||||||
void rotate_next_draw(const Card& card) final;
|
void rotate_next_draw(const Card & card) final;
|
||||||
|
|
||||||
ActionType last_action_type() const final;
|
ActionType last_action_type() const final;
|
||||||
|
|
||||||
void revert() final;
|
void revert() final;
|
||||||
|
|
||||||
void modify_clues(clue_t change) final;
|
void modify_clues(clue_t change) final;
|
||||||
|
|
||||||
void set_clues(clue_t clues) final;
|
void set_clues(clue_t clues) final;
|
||||||
|
|
||||||
[[nodiscard]] player_t turn() const final;
|
[[nodiscard]] player_t turn() const final;
|
||||||
|
|
||||||
[[nodiscard]] clue_t num_clues() const final;
|
[[nodiscard]] clue_t num_clues() const final;
|
||||||
|
|
||||||
[[nodiscard]] unsigned score() const final;
|
[[nodiscard]] unsigned score() const final;
|
||||||
|
|
||||||
[[nodiscard]] std::vector<std::vector<Card>> hands() const final;
|
[[nodiscard]] std::vector<std::vector<Card>> hands() const final;
|
||||||
|
|
||||||
[[nodiscard]] std::vector<Card> cur_hand() const final;
|
[[nodiscard]] std::vector<Card> cur_hand() const final;
|
||||||
|
|
||||||
[[nodiscard]] size_t draw_pile_size() const final;
|
[[nodiscard]] size_t draw_pile_size() const final;
|
||||||
|
|
||||||
[[nodiscard]] hand_index_t find_card_in_hand(const Card& card) const final;
|
[[nodiscard]] hand_index_t find_card_in_hand(const Card & card) const final;
|
||||||
[[nodiscard]] bool is_trash(const Card& card) const final;
|
|
||||||
[[nodiscard]] bool is_playable(const Card& card) const final;
|
[[nodiscard]] bool is_trash(const Card & card) const final;
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_playable(const Card & card) const final;
|
||||||
|
|
||||||
[[nodiscard]] bool is_relative_state_initialized() const final;
|
[[nodiscard]] bool is_relative_state_initialized() const final;
|
||||||
|
|
||||||
[[nodiscard]] std::uint64_t enumerated_states() const final;
|
[[nodiscard]] std::uint64_t enumerated_states() const final;
|
||||||
[[nodiscard]] const std::unordered_map<unsigned long, probability_t>& position_tablebase() const final;
|
|
||||||
|
[[nodiscard]] const std::unordered_map<unsigned long, probability_t> & position_tablebase() const final;
|
||||||
|
|
||||||
void init_backtracking_information() final;
|
void init_backtracking_information() final;
|
||||||
|
|
||||||
probability_t evaluate_state() final;
|
probability_t evaluate_state() final;
|
||||||
|
|
||||||
[[nodiscard]] std::optional<probability_t> lookup() const final;
|
[[nodiscard]] std::optional<probability_t> lookup() const final;
|
||||||
|
|
||||||
[[nodiscard]] std::uint64_t unique_id() const final;
|
[[nodiscard]] std::uint64_t unique_id() const final;
|
||||||
|
|
||||||
[[nodiscard]] std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const final;
|
[[nodiscard]] std::pair<std::vector<std::uint64_t>, std::vector<Card>> dump_unique_id_parts() const final;
|
||||||
|
|
||||||
std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() final;
|
std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions() final;
|
||||||
std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> possible_next_states(hand_index_t index, bool play) final;
|
|
||||||
|
std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>>
|
||||||
|
possible_next_states(hand_index_t index, bool play) final;
|
||||||
|
|
||||||
auto operator<=>(const HanabiState &) const = default;
|
auto operator<=>(const HanabiState &) const = default;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void print(std::ostream& os) const final;
|
void print(std::ostream & os) const final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct BacktrackAction {
|
struct BacktrackAction
|
||||||
|
{
|
||||||
explicit BacktrackAction(
|
explicit BacktrackAction(
|
||||||
ActionType action_type,
|
ActionType action_type
|
||||||
Card discarded_or_played = Cards::unknown,
|
, Card discarded_or_played = Cards::unknown
|
||||||
hand_index_t index = 0,
|
, hand_index_t index = 0
|
||||||
bool was_on_8_clues = false,
|
, bool was_on_8_clues = false
|
||||||
bool strike = false
|
, bool strike = false
|
||||||
);
|
);
|
||||||
|
|
||||||
ActionType action_type{};
|
ActionType action_type{};
|
||||||
|
@ -126,50 +153,55 @@ private:
|
||||||
|
|
||||||
// Indicates whether before the action was taken, we had 8 clues.
|
// Indicates whether before the action was taken, we had 8 clues.
|
||||||
// This is important so that we know if we go back to 7 or 8 clues when we revert playing a 5
|
// This is important so that we know if we go back to 7 or 8 clues when we revert playing a 5
|
||||||
bool was_on_8_clues {false};
|
bool was_on_8_clues{false};
|
||||||
|
|
||||||
// Indicates whether playing this card triggered a bomb.
|
// Indicates whether playing this card triggered a bomb.
|
||||||
// This cannot be deduced just from the stacks since we cannot differentiate between a card
|
// This cannot be deduced just from the stacks since we cannot differentiate between a card
|
||||||
// having been played correctly or the top card of the draw pile being bombed.
|
// having been played correctly or the top card of the draw pile being bombed.
|
||||||
bool strike {false};
|
bool strike{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
// This keeps track of the representation of the gamestate relative to some starting state
|
// This keeps track of the representation of the gamestate relative to some starting state
|
||||||
// and is used for id calculation
|
// and is used for id calculation
|
||||||
struct RelativeRepresentationData {
|
struct RelativeRepresentationData
|
||||||
|
{
|
||||||
static constexpr player_t draw_pile = num_players;
|
static constexpr player_t draw_pile = num_players;
|
||||||
static constexpr player_t discard_pile = num_players + 1;
|
static constexpr player_t discard_pile = num_players + 1;
|
||||||
static constexpr player_t play_stack = num_players + 2;
|
static constexpr player_t play_stack = num_players + 2;
|
||||||
enum CardPosition : uint8_t {
|
enum CardPosition : uint8_t
|
||||||
hand = 0,
|
{
|
||||||
played = 1,
|
hand = 0, played = 1, discarded = 2
|
||||||
discarded = 2
|
|
||||||
};
|
};
|
||||||
// List of unique non-trash cards in draw pile
|
// List of unique non-trash cards in draw pile
|
||||||
boost::container::static_vector<Card, 30> good_cards_draw;
|
boost::container::static_vector<Card, 30> good_cards_draw;
|
||||||
|
|
||||||
// Card positions of these cards. Indexes correspond to the cards stored in _good_cards_draw vector
|
// Card positions of these cards. Indexes correspond to the cards stored in _good_cards_draw vector
|
||||||
boost::container::static_vector<boost::container::static_vector<player_t, max_card_duplicity>, 30> card_positions_draw;
|
boost::container::static_vector<boost::container::static_vector<player_t, max_card_duplicity>
|
||||||
|
, 30> card_positions_draw;
|
||||||
|
|
||||||
// This will indicate whether cards that were in hands initially still are in hand
|
// This will indicate whether cards that were in hands initially still are in hand
|
||||||
// The first n bits are used and cards are assumed to have been marked with their indices in this bitset
|
// The first n bits are used and cards are assumed to have been marked with their indices in this bitset
|
||||||
boost::container::static_vector<CardPosition, num_players * hand_size> card_positions_hands {};
|
boost::container::static_vector<CardPosition, num_players * hand_size> card_positions_hands{};
|
||||||
|
|
||||||
// Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities
|
// Note this is not the same as _good_cards_draw.size(), since this accounts for multiplicities
|
||||||
std::uint8_t initial_draw_pile_size { 0 };
|
std::uint8_t initial_draw_pile_size{0};
|
||||||
|
|
||||||
// Whether we initialized the values above and marked cards accordingly
|
// Whether we initialized the values above and marked cards accordingly
|
||||||
bool initialized { false };
|
bool initialized{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
unsigned long discard_and_potentially_update(hand_index_t index, bool cycle = false);
|
unsigned long discard_and_potentially_update(hand_index_t index, bool cycle = false);
|
||||||
|
|
||||||
unsigned long play_and_potentially_update(hand_index_t index, bool cycle = false);
|
unsigned long play_and_potentially_update(hand_index_t index, bool cycle = false);
|
||||||
|
|
||||||
unsigned draw(hand_index_t index, bool cycle = false, bool played = true);
|
unsigned draw(hand_index_t index, bool cycle = false, bool played = true);
|
||||||
|
|
||||||
void revert_draw(hand_index_t index, Card discarded_card, bool cycle = false, bool played = true);
|
void revert_draw(hand_index_t index, Card discarded_card, bool cycle = false, bool played = true);
|
||||||
|
|
||||||
void revert_clue();
|
void revert_clue();
|
||||||
|
|
||||||
void revert_discard(bool cycle = false);
|
void revert_discard(bool cycle = false);
|
||||||
|
|
||||||
void revert_play(bool cycle = false);
|
void revert_play(bool cycle = false);
|
||||||
|
|
||||||
|
|
||||||
|
@ -179,6 +211,7 @@ private:
|
||||||
void do_for_each_potential_draw(hand_index_t index, bool play, Function f);
|
void do_for_each_potential_draw(hand_index_t index, bool play, Function f);
|
||||||
|
|
||||||
void incr_turn();
|
void incr_turn();
|
||||||
|
|
||||||
void decr_turn();
|
void decr_turn();
|
||||||
|
|
||||||
void check_draw_pile_integrity() const;
|
void check_draw_pile_integrity() const;
|
||||||
|
@ -208,19 +241,24 @@ private:
|
||||||
// Lookup table for states. Uses the ids calculated using the relative representation
|
// Lookup table for states. Uses the ids calculated using the relative representation
|
||||||
std::unordered_map<unsigned long, probability_t> _position_tablebase;
|
std::unordered_map<unsigned long, probability_t> _position_tablebase;
|
||||||
|
|
||||||
std::uint64_t _enumerated_states {};
|
std::uint64_t _enumerated_states{};
|
||||||
};
|
};
|
||||||
|
|
||||||
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>
|
||||||
bool same_up_to_discard_permutation(HanabiState<num_suits, num_players, hand_size> state1, HanabiState<num_suits, num_players, hand_size> state2) {
|
bool same_up_to_discard_permutation(
|
||||||
auto comp = [](CardMultiplicity &m1, CardMultiplicity &m2) -> bool {
|
HanabiState<num_suits, num_players, hand_size> state1, HanabiState<num_suits
|
||||||
|
, num_players
|
||||||
|
, hand_size> state2
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto comp = [](CardMultiplicity & m1, CardMultiplicity & m2) -> bool {
|
||||||
return m1.card.suit < m2.card.suit || (m1.card.suit == m2.card.suit and m1.card.rank < m2.card.rank) ||
|
return m1.card.suit < m2.card.suit || (m1.card.suit == m2.card.suit and m1.card.rank < m2.card.rank) ||
|
||||||
(m1.card.suit == m2.card.suit and m1.card.rank == m2.card.rank and m1.multiplicity < m2.multiplicity);
|
(m1.card.suit == m2.card.suit and m1.card.rank == m2.card.rank and m1.multiplicity < m2.multiplicity);
|
||||||
};
|
};
|
||||||
state1._draw_pile.sort(comp);
|
state1._draw_pile.sort(comp);
|
||||||
state2._draw_pile.sort(comp);
|
state2._draw_pile.sort(comp);
|
||||||
return state1 == state2;
|
return state1 == state2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -8,7 +8,8 @@
|
||||||
|
|
||||||
#include <boost/rational.hpp>
|
#include <boost/rational.hpp>
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
|
|
||||||
using rank_t = std::uint8_t;
|
using rank_t = std::uint8_t;
|
||||||
using suit_t = std::uint8_t;
|
using suit_t = std::uint8_t;
|
||||||
|
@ -32,11 +33,12 @@ namespace Hanabi {
|
||||||
using probability_t = double;
|
using probability_t = double;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::ostream& print_probability(std::ostream& os, const rational_probability& prob);
|
std::ostream & print_probability(std::ostream & os, const rational_probability & prob);
|
||||||
std::ostream& print_probability(std::ostream& os, double prob);
|
|
||||||
|
std::ostream & print_probability(std::ostream & os, double prob);
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::ostream& print_probability(std::ostream& os, const std::optional<T>& prob);
|
std::ostream & print_probability(std::ostream & os, const std::optional<T> & prob);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We will generally assume that stacks are played from n to 0
|
* We will generally assume that stacks are played from n to 0
|
||||||
|
@ -57,7 +59,8 @@ namespace Hanabi {
|
||||||
// Note that this is therefore not static so that we have external linking
|
// Note that this is therefore not static so that we have external linking
|
||||||
inline std::array<char, 6> suit_initials = {'r', 'y', 'g', 'b', 'p', 't'};
|
inline std::array<char, 6> suit_initials = {'r', 'y', 'g', 'b', 'p', 't'};
|
||||||
|
|
||||||
struct Card {
|
struct Card
|
||||||
|
{
|
||||||
suit_t suit;
|
suit_t suit;
|
||||||
rank_t rank;
|
rank_t rank;
|
||||||
|
|
||||||
|
@ -71,32 +74,37 @@ namespace Hanabi {
|
||||||
* @brief Compares cards *only* regarding suit and rank.
|
* @brief Compares cards *only* regarding suit and rank.
|
||||||
* This is inlined as this is a runtime critical function when backtracking.
|
* This is inlined as this is a runtime critical function when backtracking.
|
||||||
*/
|
*/
|
||||||
inline bool operator==(const Card &other) const;
|
inline bool operator==(const Card & other) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class ActionType : std::uint8_t {
|
enum class ActionType : std::uint8_t
|
||||||
play = 0,
|
{
|
||||||
discard = 1,
|
play = 0
|
||||||
clue = 2,
|
, discard = 1
|
||||||
color_clue = 2,
|
, clue = 2
|
||||||
rank_clue = 3,
|
, color_clue = 2
|
||||||
end_game = 4,
|
, rank_clue = 3
|
||||||
vote_terminate_players = 5,
|
, end_game = 4
|
||||||
vote_terminate = 10,
|
, vote_terminate_players = 5
|
||||||
};
|
, vote_terminate = 10
|
||||||
|
, };
|
||||||
|
|
||||||
struct Action {
|
struct Action
|
||||||
ActionType type {};
|
{
|
||||||
Card card {};
|
ActionType type{};
|
||||||
|
Card card{};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Output utilities for Cards and Actions
|
// Output utilities for Cards and Actions
|
||||||
std::string to_string(const Card &card);
|
std::string to_string(const Card & card);
|
||||||
std::ostream &operator<<(std::ostream &os, const Card & card);
|
|
||||||
std::ostream& operator<<(std::ostream& os, const Action& action);
|
std::ostream & operator<<(std::ostream & os, const Card & card);
|
||||||
|
|
||||||
|
std::ostream & operator<<(std::ostream & os, const Action & action);
|
||||||
|
|
||||||
|
|
||||||
namespace Cards {
|
namespace Cards
|
||||||
|
{
|
||||||
static constexpr Card r0 = {0, 5};
|
static constexpr Card r0 = {0, 5};
|
||||||
static constexpr Card r1 = {0, 4};
|
static constexpr Card r1 = {0, 4};
|
||||||
static constexpr Card r2 = {0, 3};
|
static constexpr Card r2 = {0, 3};
|
||||||
|
@ -139,15 +147,20 @@ namespace Hanabi {
|
||||||
|
|
||||||
//// INLINE SECTION
|
//// INLINE SECTION
|
||||||
|
|
||||||
bool Card::operator==(const Card &other) const {
|
bool Card::operator==(const Card & other) const
|
||||||
|
{
|
||||||
return suit == other.suit and rank == other.rank;
|
return suit == other.suit and rank == other.rank;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::ostream& print_probability(std::ostream& os, const std::optional<T>& prob) {
|
std::ostream & print_probability(std::ostream & os, const std::optional<T> & prob)
|
||||||
if (prob.has_value()) {
|
{
|
||||||
|
if (prob.has_value())
|
||||||
|
{
|
||||||
return print_probability(os, prob.value());
|
return print_probability(os, prob.value());
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
os << "unknown";
|
os << "unknown";
|
||||||
}
|
}
|
||||||
return os;
|
return os;
|
||||||
|
|
|
@ -4,7 +4,9 @@
|
||||||
#ifdef NDEBUG
|
#ifdef NDEBUG
|
||||||
#define ASSERT(x) do { (void)sizeof(x);} while (0)
|
#define ASSERT(x) do { (void)sizeof(x);} while (0)
|
||||||
#else
|
#else
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#define ASSERT(x) assert(x)
|
#define ASSERT(x) assert(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,27 @@
|
||||||
|
|
||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
|
||||||
namespace NullBuffer {
|
namespace NullBuffer
|
||||||
|
{
|
||||||
|
|
||||||
class NullBuffer final : public std::streambuf {
|
class NullBuffer final : public std::streambuf
|
||||||
public:
|
{
|
||||||
int overflow(int c) override { return c; }
|
public:
|
||||||
};
|
int overflow(int c) override
|
||||||
|
{ return c; }
|
||||||
|
};
|
||||||
|
|
||||||
class NullStream final : public std::ostream {
|
class NullStream final : public std::ostream
|
||||||
public:
|
{
|
||||||
NullStream() : std::ostream(&_m_sb) {}
|
public:
|
||||||
private:
|
NullStream() : std::ostream(&_m_sb)
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
NullBuffer _m_sb;
|
NullBuffer _m_sb;
|
||||||
};
|
};
|
||||||
|
|
||||||
NullStream null_stream;
|
NullStream null_stream;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,22 +5,26 @@
|
||||||
|
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
// These are overloads that the boost/json library uses for parsing.
|
// These are overloads that the boost/json library uses for parsing.
|
||||||
// They convert a Card from/to json.
|
// They convert a Card from/to json.
|
||||||
// This has to be in the same namespace as Hanabi::Card.
|
// This has to be in the same namespace as Hanabi::Card.
|
||||||
Card tag_invoke(boost::json::value_to_tag<Card>, boost::json::value const &jv);
|
Card tag_invoke(boost::json::value_to_tag<Card>, boost::json::value const & jv);
|
||||||
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card);
|
|
||||||
|
void tag_invoke(boost::json::value_from_tag, boost::json::value & jv, Hanabi::Card const & card);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Parsing {
|
namespace Parsing
|
||||||
|
{
|
||||||
/**
|
/**
|
||||||
* Represents a single action (turn) in a Hanab game.
|
* Represents a single action (turn) in a Hanab game.
|
||||||
* Note that this is slightly differen than Hanabi::Action,
|
* Note that this is slightly differen than Hanabi::Action,
|
||||||
* since this uses indices for specifying the discarded/played cards.
|
* since this uses indices for specifying the discarded/played cards.
|
||||||
* We only want to work with this type while parsing, converting to Hanabi::Action after.
|
* We only want to work with this type while parsing, converting to Hanabi::Action after.
|
||||||
*/
|
*/
|
||||||
struct HanabLiveAction {
|
struct HanabLiveAction
|
||||||
|
{
|
||||||
Hanabi::ActionType type{};
|
Hanabi::ActionType type{};
|
||||||
/**
|
/**
|
||||||
* In case the action is of type discard or play,
|
* In case the action is of type discard or play,
|
||||||
|
@ -30,28 +34,26 @@ namespace Parsing {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Overload for parsing from json to HanabLiveAction
|
// Overload for parsing from json to HanabLiveAction
|
||||||
HanabLiveAction tag_invoke(boost::json::value_to_tag<HanabLiveAction>, boost::json::value const &jv);
|
HanabLiveAction tag_invoke(boost::json::value_to_tag<HanabLiveAction>, boost::json::value const & jv);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @brief Parse deck from hanab.live format
|
* @brief Parse deck from hanab.live format
|
||||||
* @return List of cards (in order) and number of suits
|
* @return List of cards (in order) and number of suits
|
||||||
*/
|
*/
|
||||||
std::pair<std::vector<Hanabi::Card>, Hanabi::suit_t> parse_deck(const boost::json::value &deck_json);
|
std::pair<std::vector<Hanabi::Card>, Hanabi::suit_t> parse_deck(const boost::json::value & deck_json);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Parse actions from hanab.live format.
|
* @brief Parse actions from hanab.live format.
|
||||||
* @return List of actions
|
* @return List of actions
|
||||||
*/
|
*/
|
||||||
std::vector<HanabLiveAction> parse_actions(const boost::json::value &action_json);
|
std::vector<HanabLiveAction> parse_actions(const boost::json::value & action_json);
|
||||||
|
|
||||||
std::vector<Hanabi::Action> convert_actions(
|
std::vector<Hanabi::Action> convert_actions(
|
||||||
std::vector<HanabLiveAction> const & hanab_live_actions,
|
std::vector<HanabLiveAction> const & hanab_live_actions, std::vector<Hanabi::Card> const & deck
|
||||||
std::vector<Hanabi::Card> const & deck
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Hanabi::GameInfo parse_game(boost::json::object const & game_json);
|
Hanabi::GameInfo parse_game(boost::json::object const & game_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //DYNAMIC_PROGRAM_PARSE_GAME_H
|
#endif //DYNAMIC_PROGRAM_PARSE_GAME_H
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
void cli(Game const & game);
|
void cli(Game const & game);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,23 +2,29 @@
|
||||||
|
|
||||||
#include "download.h"
|
#include "download.h"
|
||||||
|
|
||||||
void check_games(unsigned num_players, unsigned max_draw_pile_size, unsigned first_game = 0, unsigned last_game = 9999) {
|
void check_games(unsigned num_players, unsigned max_draw_pile_size, unsigned first_game = 0, unsigned last_game = 9999)
|
||||||
|
{
|
||||||
std::vector<std::vector<Hanabi::probability_t>> winning_percentages(last_game + 2);
|
std::vector<std::vector<Hanabi::probability_t>> winning_percentages(last_game + 2);
|
||||||
|
|
||||||
for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) {
|
for (size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++)
|
||||||
|
{
|
||||||
Hanabi::probability_t total_chance = 0;
|
Hanabi::probability_t total_chance = 0;
|
||||||
const std::string output_fname = "games_" + std::to_string(num_players) + "p_draw_size_" + std::to_string(draw_pile_size) + ".txt";
|
const std::string output_fname =
|
||||||
std::ofstream file (output_fname);
|
"games_" + std::to_string(num_players) + "p_draw_size_" + std::to_string(draw_pile_size) + ".txt";
|
||||||
for(size_t game_id = first_game; game_id <= last_game; game_id++) {
|
std::ofstream file(output_fname);
|
||||||
|
for (size_t game_id = first_game; game_id <= last_game; game_id++)
|
||||||
|
{
|
||||||
const std::string input_fname = "json/" + std::to_string(num_players) + "p/" + std::to_string(game_id) + ".json";
|
const std::string input_fname = "json/" + std::to_string(num_players) + "p/" + std::to_string(game_id) + ".json";
|
||||||
auto game = Download::get_game(input_fname.c_str(), 50, draw_pile_size);
|
auto game = Download::get_game(input_fname.c_str(), 50, draw_pile_size);
|
||||||
const Hanabi::probability_t chance = game->evaluate_state();
|
const Hanabi::probability_t chance = game->evaluate_state();
|
||||||
winning_percentages[game_id].push_back(chance);
|
winning_percentages[game_id].push_back(chance);
|
||||||
if(chance != 1) {
|
if (chance != 1)
|
||||||
|
{
|
||||||
file << "Game " << game_id << ": " << chance << std::endl;
|
file << "Game " << game_id << ": " << chance << std::endl;
|
||||||
file << *game << std::endl << std::endl;
|
file << *game << std::endl << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << "Finished game " << game_id << " with draw pile size " << draw_pile_size << ": " << chance << std::endl;
|
std::cout << "Finished game " << game_id << " with draw pile size " << draw_pile_size << ": " << chance
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
total_chance += chance;
|
total_chance += chance;
|
||||||
}
|
}
|
||||||
|
@ -27,22 +33,26 @@ void check_games(unsigned num_players, unsigned max_draw_pile_size, unsigned fir
|
||||||
file << "Total chance found over " << last_game - first_game + 1 << " many games: " << total_average << std::endl;
|
file << "Total chance found over " << last_game - first_game + 1 << " many games: " << total_average << std::endl;
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
const std::string results_file_name {"results_" + std::to_string(num_players) + "p.txt"};
|
const std::string results_file_name{"results_" + std::to_string(num_players) + "p.txt"};
|
||||||
std::ofstream results_file (results_file_name);
|
std::ofstream results_file(results_file_name);
|
||||||
results_file << "game_id, ";
|
results_file << "game_id, ";
|
||||||
for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) {
|
for (size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++)
|
||||||
|
{
|
||||||
results_file << std::to_string(draw_pile_size) << ", ";
|
results_file << std::to_string(draw_pile_size) << ", ";
|
||||||
}
|
}
|
||||||
results_file << "\n";
|
results_file << "\n";
|
||||||
for(size_t game_id = first_game; game_id <= last_game; game_id++) {
|
for (size_t game_id = first_game; game_id <= last_game; game_id++)
|
||||||
|
{
|
||||||
results_file << game_id << ", ";
|
results_file << game_id << ", ";
|
||||||
for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) {
|
for (size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++)
|
||||||
|
{
|
||||||
results_file << winning_percentages[game_id][draw_pile_size] << ", ";
|
results_file << winning_percentages[game_id][draw_pile_size] << ", ";
|
||||||
}
|
}
|
||||||
results_file << std::endl;
|
results_file << std::endl;
|
||||||
}
|
}
|
||||||
results_file << "total, ";
|
results_file << "total, ";
|
||||||
for(size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++) {
|
for (size_t draw_pile_size = 0; draw_pile_size <= max_draw_pile_size; draw_pile_size++)
|
||||||
|
{
|
||||||
results_file << winning_percentages.back()[draw_pile_size] << ", ";
|
results_file << winning_percentages.back()[draw_pile_size] << ", ";
|
||||||
}
|
}
|
||||||
results_file << std::endl;
|
results_file << std::endl;
|
||||||
|
|
|
@ -8,7 +8,8 @@
|
||||||
|
|
||||||
namespace bpo = boost::program_options;
|
namespace bpo = boost::program_options;
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
std::optional<T> convert_optional(boost::optional<T> val)
|
std::optional<T> convert_optional(boost::optional<T> val)
|
||||||
|
@ -17,10 +18,10 @@ namespace Hanabi {
|
||||||
{
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return { std::move(val.value()) };
|
return {std::move(val.value())};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& quiet_ostream(bool const quiet)
|
std::ostream & quiet_ostream(bool const quiet)
|
||||||
{
|
{
|
||||||
if (quiet)
|
if (quiet)
|
||||||
{
|
{
|
||||||
|
@ -50,7 +51,7 @@ namespace Hanabi {
|
||||||
quiet_os.precision(10);
|
quiet_os.precision(10);
|
||||||
|
|
||||||
// Load game, either from file or from hanab.live
|
// Load game, either from file or from hanab.live
|
||||||
Game game = [&parms]{
|
Game game = [&parms] {
|
||||||
if (std::holds_alternative<int>(parms.game))
|
if (std::holds_alternative<int>(parms.game))
|
||||||
{
|
{
|
||||||
return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal));
|
return Download::get_game(std::get<int>(parms.game), convert_optional(parms.score_goal));
|
||||||
|
@ -63,9 +64,12 @@ namespace Hanabi {
|
||||||
|
|
||||||
if (not game.holds_state())
|
if (not game.holds_state())
|
||||||
{
|
{
|
||||||
if(std::holds_alternative<int>(parms.game)) {
|
if (std::holds_alternative<int>(parms.game))
|
||||||
|
{
|
||||||
std::cout << "Failed to download game " << std::get<int>(parms.game) << " from hanab.live." << std::endl;
|
std::cout << "Failed to download game " << std::get<int>(parms.game) << " from hanab.live." << std::endl;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
std::cout << "Failed to open file " << std::get<std::string>(parms.game) << "." << std::endl;
|
std::cout << "Failed to open file " << std::get<std::string>(parms.game) << "." << std::endl;
|
||||||
}
|
}
|
||||||
return download_failed;
|
return download_failed;
|
||||||
|
@ -102,7 +106,8 @@ namespace Hanabi {
|
||||||
throw std::logic_error("Invalid game state specification type encountered");
|
throw std::logic_error("Invalid game state specification type encountered");
|
||||||
}
|
}
|
||||||
std::cout << parms.game_state_spec << " cannot be reached with specified replay." << std::endl;
|
std::cout << parms.game_state_spec << " cannot be reached with specified replay." << std::endl;
|
||||||
std::cout << "Replay ends at turn " << game.cur_turn() << " with score of " << game.state->score() << "." << std::endl;
|
std::cout << "Replay ends at turn " << game.cur_turn() << " with score of " << game.state->score() << "."
|
||||||
|
<< std::endl;
|
||||||
return state_unreachable;
|
return state_unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,13 +130,16 @@ namespace Hanabi {
|
||||||
// (except for rare cases, where there is a forced win that does not need stalling).
|
// (except for rare cases, where there is a forced win that does not need stalling).
|
||||||
size_t const max_draw_pile_size = game.state->draw_pile_size();
|
size_t const max_draw_pile_size = game.state->draw_pile_size();
|
||||||
bool printed_replay_end_msg = false;
|
bool printed_replay_end_msg = false;
|
||||||
for(size_t remaining_cards = 1; remaining_cards <= max_draw_pile_size; remaining_cards++) {
|
for (size_t remaining_cards = 1; remaining_cards <= max_draw_pile_size; remaining_cards++)
|
||||||
|
{
|
||||||
if (!game.goto_draw_pile_size(remaining_cards))
|
if (!game.goto_draw_pile_size(remaining_cards))
|
||||||
{
|
{
|
||||||
if (not printed_replay_end_msg)
|
if (not printed_replay_end_msg)
|
||||||
{
|
{
|
||||||
std::cout << "Draw pile size of " << game.state->draw_pile_size() -1 << " or lower cannot be obtained with the specified replay:" << std::endl;
|
std::cout << "Draw pile size of " << game.state->draw_pile_size() - 1
|
||||||
std::cout << "Replay ends at turn " << game.cur_turn() << " with score of " << game.state->score() << "." << std::endl;
|
<< " or lower cannot be obtained with the specified replay:" << std::endl;
|
||||||
|
std::cout << "Replay ends at turn " << game.cur_turn() << " with score of " << game.state->score() << "."
|
||||||
|
<< std::endl;
|
||||||
printed_replay_end_msg = true;
|
printed_replay_end_msg = true;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
@ -142,21 +150,25 @@ namespace Hanabi {
|
||||||
// When modifying the game state, we want to reset to the actual number of clues
|
// When modifying the game state, we want to reset to the actual number of clues
|
||||||
// to ensure that actions taken are legal.
|
// to ensure that actions taken are legal.
|
||||||
clue_t const original_num_clues = game.state->num_clues();
|
clue_t const original_num_clues = game.state->num_clues();
|
||||||
for(clue_t num_clues = 0; num_clues <= 8; num_clues++) {
|
for (clue_t num_clues = 0; num_clues <= 8; num_clues++)
|
||||||
|
{
|
||||||
game.state->set_clues(num_clues);
|
game.state->set_clues(num_clues);
|
||||||
probability_t const result = game.state->evaluate_state();
|
probability_t const result = game.state->evaluate_state();
|
||||||
std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues
|
std::cout << "Probability with " << remaining_cards << " cards left in deck and " << +num_clues
|
||||||
<< " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos;
|
<< " clues (" << std::showpos << +(num_clues - original_num_clues) << "): " << std::noshowpos;
|
||||||
print_probability(std::cout, result) << std::endl;
|
print_probability(std::cout, result) << std::endl;
|
||||||
std::cout << *game.state << std::endl;
|
std::cout << *game.state << std::endl;
|
||||||
auto const [a,b] = game.state->dump_unique_id_parts();
|
auto const [a, b] = game.state->dump_unique_id_parts();
|
||||||
for (auto elem : a) {
|
for (auto elem: a)
|
||||||
|
{
|
||||||
std::cout << elem << ", ";
|
std::cout << elem << ", ";
|
||||||
}
|
}
|
||||||
std::cout << "-> " << game.state->unique_id() << std::endl;
|
std::cout << "-> " << game.state->unique_id() << std::endl;
|
||||||
}
|
}
|
||||||
game.state->set_clues(original_num_clues);
|
game.state->set_clues(original_num_clues);
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
probability_t const result = game.state->evaluate_state();
|
probability_t const result = game.state->evaluate_state();
|
||||||
std::cout << "Probability with " << remaining_cards << " cards left in deck: ";
|
std::cout << "Probability with " << remaining_cards << " cards left in deck: ";
|
||||||
print_probability(std::cout, result) << std::endl;
|
print_probability(std::cout, result) << std::endl;
|
||||||
|
@ -200,27 +212,29 @@ namespace Hanabi {
|
||||||
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. "
|
("turn,t", bpo::value<unsigned>(&parms.game_state_spec), "Turn number of state to analyze. "
|
||||||
"Turn 1 means no actions have been taken. ")
|
"Turn 1 means no actions have been taken. ")
|
||||||
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
("draw-pile-size,d", bpo::value<unsigned>(&parms.game_state_spec), "Draw pile size of state to analyze.")
|
||||||
("score-goal,s", bpo::value<boost::optional<uint8_t>>(&parms.score_goal),
|
("score-goal,s"
|
||||||
"Score that counts as a win, i.e. is optimized for achieving. If unspecified, the maximum possible "
|
, bpo::value<boost::optional<uint8_t>>(&parms.score_goal)
|
||||||
|
, "Score that counts as a win, i.e. is optimized for achieving. If unspecified, the maximum possible "
|
||||||
"score will be used.")
|
"score will be used.")
|
||||||
("clue-modifier,c", bpo::value<int>(), "Optional relative modification to the number of clues applied to "
|
("clue-modifier,c", bpo::value<int>(), "Optional relative modification to the number of clues applied to "
|
||||||
"selected game state. If unspecified, has value 0 and thus no effect.")
|
"selected game state. If unspecified, has value 0 and thus no effect.")
|
||||||
("interactive,i", bpo::value<boost::optional<bool>>(&parms.interactive),
|
("interactive,i"
|
||||||
"After computation, drop into interactive shell to explore game. If unspecified, a reasonable default "
|
, bpo::value<boost::optional<bool>>(&parms.interactive)
|
||||||
|
, "After computation, drop into interactive shell to explore game. If unspecified, a reasonable default "
|
||||||
"is chosen depending on other options.")
|
"is chosen depending on other options.")
|
||||||
("recursive,r", "If specified, also analyzes all game states with fewer cards in the draw pile than the "
|
("recursive,r", "If specified, also analyzes all game states with fewer cards in the draw pile than the "
|
||||||
"specified one, considering smaller draw pile sizes first. Also useful for infinite analysis "
|
"specified one, considering smaller draw pile sizes first. Also useful for infinite analysis "
|
||||||
"(specifying this with a complex base state")
|
"(specifying this with a complex base state")
|
||||||
("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their "
|
("all-clues", "Whenever evaluating a game state, evaluate it with all clue counts and output their "
|
||||||
"probabilities.")
|
"probabilities.")
|
||||||
("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.")
|
("quiet,q", "Deactivate all non-essential prints. Useful if output is parsed by another program.");
|
||||||
;
|
|
||||||
|
|
||||||
bpo::variables_map vm;
|
bpo::variables_map vm;
|
||||||
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
|
bpo::store(bpo::parse_command_line(argc, argv, desc), vm);
|
||||||
bpo::notify(vm);
|
bpo::notify(vm);
|
||||||
|
|
||||||
if (vm.count("help")) {
|
if (vm.count("help"))
|
||||||
|
{
|
||||||
std::cout << "This program performs endgame analysis of Hanabi. It calculates optimum strategies\n"
|
std::cout << "This program performs endgame analysis of Hanabi. It calculates optimum strategies\n"
|
||||||
"(and their winning percentages assuming a random draw pile distribution) under the\n"
|
"(and their winning percentages assuming a random draw pile distribution) under the\n"
|
||||||
"assumption that all players know their hands at all times during the game.\n"
|
"assumption that all players know their hands at all times during the game.\n"
|
||||||
|
@ -238,7 +252,8 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse file or game id specification, ensuring at most one is given
|
// Parse file or game id specification, ensuring at most one is given
|
||||||
if (vm.count("file") + vm.count("game") != 1) {
|
if (vm.count("file") + vm.count("game") != 1)
|
||||||
|
{
|
||||||
std::cout << "Exactly one option of 'file' and 'id' has to be given." << std::endl;
|
std::cout << "Exactly one option of 'file' and 'id' has to be given." << std::endl;
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -253,7 +268,8 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse game state options (turn or draw), ensuring at most one is given.
|
// Parse game state options (turn or draw), ensuring at most one is given.
|
||||||
if (vm.count("draw-pile-size") + vm.count("turn") != 1) {
|
if (vm.count("draw-pile-size") + vm.count("turn") != 1)
|
||||||
|
{
|
||||||
std::cout << "Conflicting options --draw and --turn." << std::endl;
|
std::cout << "Conflicting options --draw and --turn." << std::endl;
|
||||||
std::cout << "Use '--help' to print a help message." << std::endl;
|
std::cout << "Use '--help' to print a help message." << std::endl;
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
|
@ -7,20 +7,25 @@
|
||||||
#include "download.h"
|
#include "download.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Download {
|
namespace Download
|
||||||
|
{
|
||||||
|
|
||||||
std::optional<boost::json::object> download_game_json(int game_id) {
|
std::optional<boost::json::object> download_game_json(int game_id)
|
||||||
|
{
|
||||||
std::string request_str = "https://hanab.live/export/" + std::to_string(game_id);
|
std::string request_str = "https://hanab.live/export/" + std::to_string(game_id);
|
||||||
cpr::Response r = cpr::Get(cpr::Url(request_str));
|
cpr::Response r = cpr::Get(cpr::Url(request_str));
|
||||||
if (r.header["content-type"] != "application/json; charset=utf-8") {
|
if (r.header["content-type"] != "application/json; charset=utf-8")
|
||||||
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
return boost::json::parse(r.text).as_object();
|
return boost::json::parse(r.text).as_object();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<boost::json::object> open_game_json(const char *filename) {
|
std::optional<boost::json::object> open_game_json(const char *filename)
|
||||||
|
{
|
||||||
std::ifstream file(filename);
|
std::ifstream file(filename);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open())
|
||||||
|
{
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
std::string game_json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
std::string game_json((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||||
|
@ -30,7 +35,8 @@ namespace Download {
|
||||||
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal)
|
Hanabi::Game get_game(int game_id, std::optional<uint8_t> score_goal)
|
||||||
{
|
{
|
||||||
std::optional<boost::json::object> const game_json = download_game_json(game_id);
|
std::optional<boost::json::object> const game_json = download_game_json(game_id);
|
||||||
if (!game_json.has_value() or game_json.value().empty()) {
|
if (!game_json.has_value() or game_json.value().empty())
|
||||||
|
{
|
||||||
return {nullptr, {}};
|
return {nullptr, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +47,8 @@ namespace Download {
|
||||||
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal)
|
Hanabi::Game get_game(std::string const & filename, std::optional<uint8_t> score_goal)
|
||||||
{
|
{
|
||||||
std::optional<boost::json::object> const game_json = open_game_json(filename.c_str());
|
std::optional<boost::json::object> const game_json = open_game_json(filename.c_str());
|
||||||
if (!game_json.has_value() or game_json.value().empty()) {
|
if (!game_json.has_value() or game_json.value().empty())
|
||||||
|
{
|
||||||
return {nullptr, {}};
|
return {nullptr, {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,27 +2,29 @@
|
||||||
|
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state)
|
std::ostream & operator<<(std::ostream & os, HanabiStateIF const & hanabi_state)
|
||||||
{
|
{
|
||||||
hanabi_state.print(os);
|
hanabi_state.print(os);
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
Game::Game(std::unique_ptr<HanabiStateIF> state, Hanabi::GameInfo game_info):
|
Game::Game(std::unique_ptr<HanabiStateIF> state, Hanabi::GameInfo game_info) :
|
||||||
GameInfo(std::move(game_info)), state(std::move(state)), next_action(0)
|
GameInfo(std::move(game_info)), state(std::move(state)), next_action(0)
|
||||||
{
|
{
|
||||||
// If there is a 'Null' action that only signals the game's end, we want to get rid of it now,
|
// If there is a 'Null' action that only signals the game's end, we want to get rid of it now,
|
||||||
// as this will mess with our moves.
|
// as this will mess with our moves.
|
||||||
if(not this->actions.empty()) {
|
if (not this->actions.empty())
|
||||||
switch(this->actions.back().type) {
|
{
|
||||||
|
switch (this->actions.back().type)
|
||||||
|
{
|
||||||
case ActionType::vote_terminate:
|
case ActionType::vote_terminate:
|
||||||
case ActionType::vote_terminate_players:
|
case ActionType::vote_terminate_players:
|
||||||
case ActionType::end_game:
|
case ActionType::end_game:
|
||||||
this->actions.pop_back();
|
this->actions.pop_back();
|
||||||
default:
|
default:;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +41,8 @@ namespace Hanabi {
|
||||||
state->rotate_next_draw(next_draw);
|
state->rotate_next_draw(next_draw);
|
||||||
Action const & action = actions[next_action];
|
Action const & action = actions[next_action];
|
||||||
std::uint8_t index;
|
std::uint8_t index;
|
||||||
switch(action.type) {
|
switch (action.type)
|
||||||
|
{
|
||||||
case Hanabi::ActionType::color_clue:
|
case Hanabi::ActionType::color_clue:
|
||||||
case Hanabi::ActionType::rank_clue:
|
case Hanabi::ActionType::rank_clue:
|
||||||
state->give_clue();
|
state->give_clue();
|
||||||
|
@ -56,8 +59,7 @@ namespace Hanabi {
|
||||||
break;
|
break;
|
||||||
case Hanabi::ActionType::vote_terminate_players:
|
case Hanabi::ActionType::vote_terminate_players:
|
||||||
case Hanabi::ActionType::vote_terminate:
|
case Hanabi::ActionType::vote_terminate:
|
||||||
case Hanabi::ActionType::end_game:
|
case Hanabi::ActionType::end_game:;
|
||||||
;
|
|
||||||
}
|
}
|
||||||
++next_action;
|
++next_action;
|
||||||
}
|
}
|
||||||
|
@ -71,12 +73,17 @@ namespace Hanabi {
|
||||||
bool Game::goto_turn(size_t turn)
|
bool Game::goto_turn(size_t turn)
|
||||||
{
|
{
|
||||||
size_t const cur_turn = next_action + 1;
|
size_t const cur_turn = next_action + 1;
|
||||||
if (cur_turn >= turn) {
|
if (cur_turn >= turn)
|
||||||
for(size_t i = 0; i < cur_turn - turn; i++) {
|
{
|
||||||
|
for (size_t i = 0; i < cur_turn - turn; i++)
|
||||||
|
{
|
||||||
revert_turn();
|
revert_turn();
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
while(next_action < actions.size() and next_action + 1 < turn) {
|
else
|
||||||
|
{
|
||||||
|
while (next_action < actions.size() and next_action + 1 < turn)
|
||||||
|
{
|
||||||
make_turn();
|
make_turn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,10 +97,13 @@ namespace Hanabi {
|
||||||
|
|
||||||
bool Game::goto_draw_pile_size(size_t draw_pile_break)
|
bool Game::goto_draw_pile_size(size_t draw_pile_break)
|
||||||
{
|
{
|
||||||
while (state->draw_pile_size() > draw_pile_break and next_action < actions.size()) {
|
while (state->draw_pile_size() > draw_pile_break and next_action < actions.size())
|
||||||
|
{
|
||||||
make_turn();
|
make_turn();
|
||||||
}
|
}
|
||||||
while(state->draw_pile_size() < draw_pile_break or (state->draw_pile_size() == draw_pile_break and state->last_action_type() == ActionType::clue)) {
|
while (state->draw_pile_size() < draw_pile_break or
|
||||||
|
(state->draw_pile_size() == draw_pile_break and state->last_action_type() == ActionType::clue))
|
||||||
|
{
|
||||||
revert_turn();
|
revert_turn();
|
||||||
}
|
}
|
||||||
return state->draw_pile_size() == draw_pile_break;
|
return state->draw_pile_size() == draw_pile_break;
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
#include "hanabi_types.hpp"
|
#include "hanabi_types.hpp"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
std::ostream &operator<<(std::ostream &os, Action const& action) {
|
{
|
||||||
switch(action.type) {
|
std::ostream & operator<<(std::ostream & os, Action const & action)
|
||||||
|
{
|
||||||
|
switch (action.type)
|
||||||
|
{
|
||||||
case ActionType::play:
|
case ActionType::play:
|
||||||
os << "play " + to_string(action.card);
|
os << "play " + to_string(action.card);
|
||||||
break;
|
break;
|
||||||
|
@ -19,25 +22,32 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string to_string(const Hanabi::Card &card) {
|
std::string to_string(const Hanabi::Card & card)
|
||||||
if (card == Hanabi::Cards::trash) {
|
{
|
||||||
|
if (card == Hanabi::Cards::trash)
|
||||||
|
{
|
||||||
return "kt";
|
return "kt";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return Hanabi::suit_initials[card.suit] + std::to_string(5 - card.rank);
|
return Hanabi::suit_initials[card.suit] + std::to_string(5 - card.rank);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream &operator<<(std::ostream &os, const Card &card) {
|
std::ostream & operator<<(std::ostream & os, const Card & card)
|
||||||
|
{
|
||||||
os << to_string(card);
|
os << to_string(card);
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& print_probability(std::ostream& os, const rational_probability & prob) {
|
std::ostream & print_probability(std::ostream & os, const rational_probability & prob)
|
||||||
|
{
|
||||||
os << prob << " ~ " << std::setprecision(5) << boost::rational_cast<double>(prob) * 100 << "%";
|
os << prob << " ~ " << std::setprecision(5) << boost::rational_cast<double>(prob) * 100 << "%";
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ostream& print_probability(std::ostream& os, double prob) {
|
std::ostream & print_probability(std::ostream & os, double prob)
|
||||||
|
{
|
||||||
os << std::setprecision(5) << prob;
|
os << std::setprecision(5) << prob;
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
#include "command_line_interface.h"
|
#include "command_line_interface.h"
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
std::optional<Hanabi::CLIParms> parms = Hanabi::parse_parms(argc, argv);
|
std::optional<Hanabi::CLIParms> parms = Hanabi::parse_parms(argc, argv);
|
||||||
if (parms.has_value())
|
if (parms.has_value())
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,77 +1,83 @@
|
||||||
#include "game_state.h"
|
#include "game_state.h"
|
||||||
#include "game_interface.h"
|
#include "game_interface.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
std::unique_ptr<Hanabi::HanabiStateIF> make_game_state(
|
std::unique_ptr<Hanabi::HanabiStateIF> make_game_state(
|
||||||
std::size_t num_suits,
|
std::size_t num_suits, Hanabi::player_t num_players, std::vector<Hanabi::Card> const & deck, std::optional<
|
||||||
Hanabi::player_t num_players,
|
uint8_t> score_goal
|
||||||
std::vector<Hanabi::Card> const &deck,
|
)
|
||||||
std::optional<uint8_t> score_goal)
|
|
||||||
{
|
{
|
||||||
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
|
uint8_t actual_score_goal = score_goal.value_or(5 * num_suits);
|
||||||
switch(num_players) {
|
switch (num_players)
|
||||||
|
{
|
||||||
case 2:
|
case 2:
|
||||||
switch(num_suits) {
|
switch (num_suits)
|
||||||
|
{
|
||||||
case 3:
|
case 3:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,2,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 2, 5>(deck, actual_score_goal));
|
||||||
case 4:
|
case 4:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,2,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 2, 5>(deck, actual_score_goal));
|
||||||
case 5:
|
case 5:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,2,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 2, 5>(deck, actual_score_goal));
|
||||||
case 6:
|
case 6:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,2,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 2, 5>(deck, actual_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 std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,3,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 3, 5>(deck, actual_score_goal));
|
||||||
case 4:
|
case 4:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,3,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 3, 5>(deck, actual_score_goal));
|
||||||
case 5:
|
case 5:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,3,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 3, 5>(deck, actual_score_goal));
|
||||||
case 6:
|
case 6:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,3,5>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 3, 5>(deck, actual_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 std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,4,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 4, 4>(deck, actual_score_goal));
|
||||||
case 4:
|
case 4:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,4,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 4, 4>(deck, actual_score_goal));
|
||||||
case 5:
|
case 5:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,4,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 4, 4>(deck, actual_score_goal));
|
||||||
case 6:
|
case 6:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,4,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 4, 4>(deck, actual_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 std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,5,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 5, 4>(deck, actual_score_goal));
|
||||||
case 4:
|
case 4:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,5,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 5, 4>(deck, actual_score_goal));
|
||||||
case 5:
|
case 5:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,5,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 5, 4>(deck, actual_score_goal));
|
||||||
case 6:
|
case 6:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,5,4>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 5, 4>(deck, actual_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 std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3,6,3>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<3, 6, 3>(deck, actual_score_goal));
|
||||||
case 4:
|
case 4:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4,6,3>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<4, 6, 3>(deck, actual_score_goal));
|
||||||
case 5:
|
case 5:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5,6,3>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<5, 6, 3>(deck, actual_score_goal));
|
||||||
case 6:
|
case 6:
|
||||||
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6,6,3>(deck, actual_score_goal));
|
return std::unique_ptr<Hanabi::HanabiStateIF>(new Hanabi::HanabiState<6, 6, 3>(deck, actual_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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,43 +2,52 @@
|
||||||
|
|
||||||
#include "myassert.h"
|
#include "myassert.h"
|
||||||
|
|
||||||
namespace Parsing {
|
namespace Parsing
|
||||||
|
{
|
||||||
// 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>
|
||||||
void extract(boost::json::object const &obj, T &t, std::string_view key) {
|
void extract(boost::json::object const & obj, T & t, std::string_view key)
|
||||||
|
{
|
||||||
t = value_to<T>(obj.at(key));
|
t = value_to<T>(obj.at(key));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
Card tag_invoke(boost::json::value_to_tag<Card>,
|
{
|
||||||
boost::json::value const &jv) {
|
Card tag_invoke(
|
||||||
|
boost::json::value_to_tag<Card>, boost::json::value const & jv
|
||||||
|
)
|
||||||
|
{
|
||||||
Hanabi::Card card{};
|
Hanabi::Card card{};
|
||||||
boost::json::object const &obj = jv.as_object();
|
boost::json::object const & obj = jv.as_object();
|
||||||
Parsing::extract(obj, card.rank, "rank");
|
Parsing::extract(obj, card.rank, "rank");
|
||||||
Parsing::extract(obj, card.suit, "suitIndex");
|
Parsing::extract(obj, card.suit, "suitIndex");
|
||||||
card.rank = 5 - card.rank;
|
card.rank = 5 - card.rank;
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, Hanabi::Card const &card) {
|
void tag_invoke(boost::json::value_from_tag, boost::json::value & jv, Hanabi::Card const & card)
|
||||||
jv = {{"suitIndex", card.suit},
|
{
|
||||||
{"rank", card.rank}};
|
jv = {{ "suitIndex", card.suit}
|
||||||
|
, {"rank" , card.rank}};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Parsing {
|
namespace Parsing
|
||||||
|
{
|
||||||
|
|
||||||
HanabLiveAction tag_invoke(boost::json::value_to_tag<HanabLiveAction>, boost::json::value const &jv) {
|
HanabLiveAction tag_invoke(boost::json::value_to_tag<HanabLiveAction>, boost::json::value const & jv)
|
||||||
|
{
|
||||||
HanabLiveAction action{};
|
HanabLiveAction action{};
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
boost::json::object const &obj = jv.as_object();
|
boost::json::object const & obj = jv.as_object();
|
||||||
|
|
||||||
extract(obj, action.target, "target");
|
extract(obj, action.target, "target");
|
||||||
extract(obj, type, "type");
|
extract(obj, type, "type");
|
||||||
|
|
||||||
action.type = static_cast<Hanabi::ActionType>(type);
|
action.type = static_cast<Hanabi::ActionType>(type);
|
||||||
switch (action.type) {
|
switch (action.type)
|
||||||
|
{
|
||||||
case Hanabi::ActionType::color_clue:
|
case Hanabi::ActionType::color_clue:
|
||||||
case Hanabi::ActionType::rank_clue:
|
case Hanabi::ActionType::rank_clue:
|
||||||
action.type = Hanabi::ActionType::clue;
|
action.type = Hanabi::ActionType::clue;
|
||||||
|
@ -58,35 +67,39 @@ namespace Parsing {
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::vector<Hanabi::Card>, Hanabi::suit_t> parse_deck(const boost::json::value &deck_json) {
|
std::pair<std::vector<Hanabi::Card>, Hanabi::suit_t> 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);
|
||||||
}
|
}
|
||||||
Hanabi::suit_t num_suits = 0;
|
Hanabi::suit_t num_suits = 0;
|
||||||
for(const auto& card: deck) {
|
for (const auto & card: deck)
|
||||||
|
{
|
||||||
num_suits = std::max(num_suits, card.suit);
|
num_suits = std::max(num_suits, card.suit);
|
||||||
}
|
}
|
||||||
return {deck, num_suits + 1};
|
return {deck, num_suits + 1};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<HanabLiveAction> parse_actions(const boost::json::value &action_json)
|
std::vector<HanabLiveAction> parse_actions(const boost::json::value & action_json)
|
||||||
{
|
{
|
||||||
return boost::json::value_to<std::vector<HanabLiveAction>>(action_json);
|
return boost::json::value_to<std::vector<HanabLiveAction>>(action_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Hanabi::Action> convert_actions(std::vector<HanabLiveAction> const & hanab_live_actions, std::vector<Hanabi::Card> const & deck)
|
std::vector<Hanabi::Action>
|
||||||
|
convert_actions(std::vector<HanabLiveAction> const & hanab_live_actions, std::vector<Hanabi::Card> const & deck)
|
||||||
{
|
{
|
||||||
std::vector<Hanabi::Action> actions;
|
std::vector<Hanabi::Action> actions;
|
||||||
std::transform(
|
std::transform(
|
||||||
hanab_live_actions.begin(),
|
hanab_live_actions.begin()
|
||||||
hanab_live_actions.end(),
|
, hanab_live_actions.end()
|
||||||
std::back_inserter(actions),
|
, std::back_inserter(actions)
|
||||||
[&deck](HanabLiveAction const & action){
|
, [&deck](HanabLiveAction const & action) {
|
||||||
return Hanabi::Action {action.type, deck[action.target]};
|
return Hanabi::Action{action.type, deck[action.target]};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return actions;
|
return actions;
|
||||||
|
|
|
@ -11,15 +11,20 @@
|
||||||
|
|
||||||
#include "game_state.h"
|
#include "game_state.h"
|
||||||
|
|
||||||
namespace Hanabi {
|
namespace Hanabi
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
std::string read_line_memory_safe(const char *prompt) {
|
std::string read_line_memory_safe(const char *prompt)
|
||||||
|
{
|
||||||
char *line = readline(prompt);
|
char *line = readline(prompt);
|
||||||
std::string ret;
|
std::string ret;
|
||||||
if (line == nullptr) {
|
if (line == nullptr)
|
||||||
|
{
|
||||||
ret = "";
|
ret = "";
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
ret = std::string(line);
|
ret = std::string(line);
|
||||||
}
|
}
|
||||||
free(line);
|
free(line);
|
||||||
|
@ -27,25 +32,17 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr static std::array<std::string, 13> cli_commands = {
|
constexpr static std::array<std::string, 13> cli_commands = {
|
||||||
"play",
|
"play", "clue", "discard", "opt", "state", "id", "revert", "actions", "evaluate", "help", "quit", "set-initials"
|
||||||
"clue",
|
, "dump-id-parts",
|
||||||
"discard",
|
|
||||||
"opt",
|
|
||||||
"state",
|
|
||||||
"id",
|
|
||||||
"revert",
|
|
||||||
"actions",
|
|
||||||
"evaluate",
|
|
||||||
"help",
|
|
||||||
"quit",
|
|
||||||
"set-initials",
|
|
||||||
"dump-id-parts",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
char * cli_commands_generator(const char *text, int state) {
|
char *cli_commands_generator(const char *text, int state)
|
||||||
std::string text_str (text);
|
{
|
||||||
for(auto& command : cli_commands) {
|
std::string text_str(text);
|
||||||
if (command.starts_with(text_str) && state-- <= 0) {
|
for (auto & command: cli_commands)
|
||||||
|
{
|
||||||
|
if (command.starts_with(text_str) && state-- <= 0)
|
||||||
|
{
|
||||||
return strdup(command.c_str());
|
return strdup(command.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,39 +57,50 @@ namespace Hanabi {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Card parse_card(std::string card_str) {
|
Card parse_card(std::string card_str)
|
||||||
if (card_str == "trash" or card_str == "kt") {
|
{
|
||||||
|
if (card_str == "trash" or card_str == "kt")
|
||||||
|
{
|
||||||
return Cards::trash;
|
return Cards::trash;
|
||||||
}
|
}
|
||||||
if(card_str.size() != 2) {
|
if (card_str.size() != 2)
|
||||||
|
{
|
||||||
return Cards::unknown;
|
return Cards::unknown;
|
||||||
}
|
}
|
||||||
auto it = std::find(suit_initials.begin(), suit_initials.end(), card_str[0]);
|
auto it = std::find(suit_initials.begin(), suit_initials.end(), card_str[0]);
|
||||||
if (it == suit_initials.end()) {
|
if (it == suit_initials.end())
|
||||||
|
{
|
||||||
return Cards::unknown;
|
return Cards::unknown;
|
||||||
}
|
}
|
||||||
const suit_t suit = std::distance(suit_initials.begin(), it);
|
const suit_t suit = std::distance(suit_initials.begin(), it);
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
const rank_t rank = 5 - std::stoi(card_str.substr(1, 1));
|
const rank_t rank = 5 - std::stoi(card_str.substr(1, 1));
|
||||||
return Card {suit, rank};
|
return Card{suit, rank};
|
||||||
} catch(std::invalid_argument&) {
|
}
|
||||||
|
catch (std::invalid_argument &)
|
||||||
|
{
|
||||||
return Cards::unknown;
|
return Cards::unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int representation_length(const rational_probability& probability) {
|
int representation_length(const rational_probability & probability)
|
||||||
|
{
|
||||||
return 1 + static_cast<int>(std::ceil(std::log10(probability.denominator()))) + \
|
return 1 + static_cast<int>(std::ceil(std::log10(probability.denominator()))) + \
|
||||||
static_cast<int>(std::ceil(std::log10(probability.numerator())));
|
static_cast<int>(std::ceil(std::log10(probability.numerator())));
|
||||||
}
|
}
|
||||||
|
|
||||||
int representation_length(const double probability) {
|
int representation_length(const double probability)
|
||||||
|
{
|
||||||
return static_cast<int>(std::ceil(std::log10(probability)));
|
return static_cast<int>(std::ceil(std::log10(probability)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ask_for_card_and_rotate_draw(HanabiStateIF & state, hand_index_t index, bool play) {
|
bool ask_for_card_and_rotate_draw(HanabiStateIF & state, hand_index_t index, bool play)
|
||||||
|
{
|
||||||
const auto next_states = state.possible_next_states(index, play);
|
const auto next_states = state.possible_next_states(index, play);
|
||||||
|
|
||||||
if (next_states.size() <= 1) {
|
if (next_states.size() <= 1)
|
||||||
|
{
|
||||||
// No need to ask for anything if draw contains only one possible type of card
|
// No need to ask for anything if draw contains only one possible type of card
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -102,30 +110,37 @@ namespace Hanabi {
|
||||||
|
|
||||||
std::cout << "Choose drawn card: " << std::endl;
|
std::cout << "Choose drawn card: " << std::endl;
|
||||||
int max_rational_digit_len = 0;
|
int max_rational_digit_len = 0;
|
||||||
for(const auto &[card_multiplicity, probability]: next_states) {
|
for (const auto & [card_multiplicity, probability]: next_states)
|
||||||
|
{
|
||||||
// If the card is played, we can treat it as a trash draw as well
|
// If the card is played, we can treat it as a trash draw as well
|
||||||
if (state.is_trash(card_multiplicity.card) or (play and state.cur_hand()[index] == card_multiplicity.card)) {
|
if (state.is_trash(card_multiplicity.card) or (play and state.cur_hand()[index] == card_multiplicity.card))
|
||||||
|
{
|
||||||
states_to_show.front().first.multiplicity += card_multiplicity.multiplicity;
|
states_to_show.front().first.multiplicity += card_multiplicity.multiplicity;
|
||||||
states_to_show.front().second = probability;
|
states_to_show.front().second = probability;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
states_to_show.emplace_back(card_multiplicity, probability);
|
states_to_show.emplace_back(card_multiplicity, probability);
|
||||||
}
|
}
|
||||||
if (probability.has_value()) {
|
if (probability.has_value())
|
||||||
|
{
|
||||||
max_rational_digit_len = std::max(max_rational_digit_len, representation_length(probability.value()));
|
max_rational_digit_len = std::max(max_rational_digit_len, representation_length(probability.value()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get rid of the trash collecting entry at the front
|
// Get rid of the trash collecting entry at the front
|
||||||
if (states_to_show.front().first.multiplicity == 0) {
|
if (states_to_show.front().first.multiplicity == 0)
|
||||||
|
{
|
||||||
states_to_show.front() = std::move(states_to_show.back());
|
states_to_show.front() = std::move(states_to_show.back());
|
||||||
states_to_show.pop_back();
|
states_to_show.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ranges::sort(states_to_show, [](const auto &left, const auto &right) {
|
std::ranges::sort(states_to_show, [](const auto & left, const auto & right) {
|
||||||
return left.second > right.second;
|
return left.second > right.second;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const auto &[card_multiplicity, probability]: states_to_show) {
|
for (const auto & [card_multiplicity, probability]: states_to_show)
|
||||||
|
{
|
||||||
std::cout << card_multiplicity.card << " (" << card_multiplicity.multiplicity;
|
std::cout << card_multiplicity.card << " (" << card_multiplicity.multiplicity;
|
||||||
std::cout << " copie(s) in draw) " << std::setw(max_rational_digit_len);
|
std::cout << " copie(s) in draw) " << std::setw(max_rational_digit_len);
|
||||||
print_probability(std::cout, probability) << std::endl;
|
print_probability(std::cout, probability) << std::endl;
|
||||||
|
@ -135,22 +150,28 @@ namespace Hanabi {
|
||||||
prompt << "draw? [" << states_to_show.front().first.card << "] ";
|
prompt << "draw? [" << states_to_show.front().first.card << "] ";
|
||||||
const std::string card_str = read_line_memory_safe(prompt.str().c_str());
|
const std::string card_str = read_line_memory_safe(prompt.str().c_str());
|
||||||
|
|
||||||
const Card drawn_card = [&card_str, &states_to_show](){
|
const Card drawn_card = [&card_str, &states_to_show]() {
|
||||||
if (card_str.empty()) {
|
if (card_str.empty())
|
||||||
|
{
|
||||||
return states_to_show.front().first.card;
|
return states_to_show.front().first.card;
|
||||||
}
|
}
|
||||||
return parse_card(card_str);
|
return parse_card(card_str);
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (drawn_card == Cards::unknown) {
|
if (drawn_card == Cards::unknown)
|
||||||
|
{
|
||||||
std::cout << "Could not parse card " << card_str << std::endl;
|
std::cout << "Could not parse card " << card_str << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto selected_draw_it = std::find_if(next_states.begin(), next_states.end(), [&drawn_card, &state](const std::pair<CardMultiplicity, std::optional<probability_t>>& pair) {
|
auto selected_draw_it = std::find_if(next_states.begin(), next_states.end(), [&drawn_card, &state](
|
||||||
|
const std::pair<CardMultiplicity
|
||||||
|
, std::optional<probability_t>> & pair
|
||||||
|
) {
|
||||||
return (state.is_trash(pair.first.card) and drawn_card == Cards::trash) or pair.first.card == drawn_card;
|
return (state.is_trash(pair.first.card) and drawn_card == Cards::trash) or pair.first.card == drawn_card;
|
||||||
});
|
});
|
||||||
if (selected_draw_it == next_states.end()){
|
if (selected_draw_it == next_states.end())
|
||||||
|
{
|
||||||
std::cout << "That card is not in the draw pile, aborting." << std::endl;
|
std::cout << "That card is not in the draw pile, aborting." << std::endl;
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
@ -158,11 +179,13 @@ namespace Hanabi {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void signal_handler(int) {
|
void signal_handler(int)
|
||||||
|
{
|
||||||
std::cout << "Use 'quit' to exit the interactive shell." << std::endl << "> ";
|
std::cout << "Use 'quit' to exit the interactive shell." << std::endl << "> ";
|
||||||
}
|
}
|
||||||
|
|
||||||
void cli(Game const & game) {
|
void cli(Game const & game)
|
||||||
|
{
|
||||||
std::signal(SIGINT, signal_handler);
|
std::signal(SIGINT, signal_handler);
|
||||||
// Set up GNU readline
|
// Set up GNU readline
|
||||||
rl_attempted_completion_function = cli_command_completion;
|
rl_attempted_completion_function = cli_command_completion;
|
||||||
|
@ -171,64 +194,82 @@ namespace Hanabi {
|
||||||
// Tracks the depth of the replay the user explores. We have to ensure that we don't revert too much.
|
// Tracks the depth of the replay the user explores. We have to ensure that we don't revert too much.
|
||||||
unsigned depth = 0;
|
unsigned depth = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true)
|
||||||
|
{
|
||||||
const std::string prompt = read_line_memory_safe("> ");
|
const std::string prompt = read_line_memory_safe("> ");
|
||||||
add_history(prompt.c_str());
|
add_history(prompt.c_str());
|
||||||
|
|
||||||
if (prompt.starts_with("help")) {
|
if (prompt.starts_with("help"))
|
||||||
|
{
|
||||||
std::cout << "state: print information on current game state." << std::endl;
|
std::cout << "state: print information on current game state." << std::endl;
|
||||||
std::cout << "clue: give a clue." << std::endl;
|
std::cout << "clue: give a clue." << std::endl;
|
||||||
std::cout << "play <card>: play specified card." << std::endl;
|
std::cout << "play <card>: play specified card." << std::endl;
|
||||||
std::cout << "discard: discard trash from hand." << std::endl;
|
std::cout << "discard: discard trash from hand." << std::endl;
|
||||||
std::cout << "opt: take optimal action. In case of ties, prefers plays and discards in that order." << std::endl;
|
std::cout
|
||||||
|
<< "opt: take optimal action. In case of ties, prefers plays and discards in that order."
|
||||||
|
<< std::endl;
|
||||||
std::cout << "revert <turns>: revert specified number of turns (default 1)." << std::endl;
|
std::cout << "revert <turns>: revert specified number of turns (default 1)." << std::endl;
|
||||||
std::cout << "actions: display list of reasonable actions to take and their winning chances." << std::endl;
|
std::cout << "actions: display list of reasonable actions to take and their winning chances."
|
||||||
std::cout << "evaluate: evaluate current game state recursively. Potentially runtime-expensive." << std::endl;
|
<< std::endl;
|
||||||
|
std::cout << "evaluate: evaluate current game state recursively. Potentially runtime-expensive."
|
||||||
|
<< std::endl;
|
||||||
std::cout << "set-initials <chars>: Set initials for the suits." << std::endl;
|
std::cout << "set-initials <chars>: Set initials for the suits." << std::endl;
|
||||||
std::cout << "(q)uit: Quit this interactive shell." << std::endl;
|
std::cout << "(q)uit: Quit this interactive shell." << std::endl;
|
||||||
std::cout << "id: display id of state. Has no inherent meaning, useful for debugging." << std::endl;
|
std::cout << "id: display id of state. Has no inherent meaning, useful for debugging."
|
||||||
std::cout << "dump-id-parts: Dump parts used to calculate the id of the state as well as the cards associated to them." << std::endl;
|
<< std::endl;
|
||||||
|
std::cout
|
||||||
|
<< "dump-id-parts: Dump parts used to calculate the id of the state as well as the cards associated to them."
|
||||||
|
<< std::endl;
|
||||||
std::cout << "help: Display this help message." << std::endl;
|
std::cout << "help: Display this help message." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("dump-id-parts")) {
|
if (prompt.starts_with("dump-id-parts"))
|
||||||
for (const auto val: game.state->dump_unique_id_parts().first) {
|
{
|
||||||
|
for (const auto val: game.state->dump_unique_id_parts().first)
|
||||||
|
{
|
||||||
std::cout << val << ", ";
|
std::cout << val << ", ";
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
for (const auto card: game.state->dump_unique_id_parts().second) {
|
for (const auto card: game.state->dump_unique_id_parts().second)
|
||||||
|
{
|
||||||
std::cout << card << " ";
|
std::cout << card << " ";
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("quit") or prompt == "q") {
|
if (prompt.starts_with("quit") or prompt == "q")
|
||||||
|
{
|
||||||
std::cout << "Quitting." << std::endl;
|
std::cout << "Quitting." << std::endl;
|
||||||
clear_history();
|
clear_history();
|
||||||
std::signal(SIGINT, SIG_DFL);
|
std::signal(SIGINT, SIG_DFL);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("set-initials")) {
|
if (prompt.starts_with("set-initials"))
|
||||||
if (prompt.length() < 16) {
|
{
|
||||||
|
if (prompt.length() < 16)
|
||||||
|
{
|
||||||
std::cout << "At least 3 initials need to be specified" << std::endl;
|
std::cout << "At least 3 initials need to be specified" << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const std::string new_initials = prompt.substr(13);
|
const std::string new_initials = prompt.substr(13);
|
||||||
for(size_t i = 0; i < std::min(size_t(6), new_initials.length()); i++) {
|
for (size_t i = 0; i < std::min(size_t(6), new_initials.length()); i++)
|
||||||
|
{
|
||||||
suit_initials[i] = new_initials[i];
|
suit_initials[i] = new_initials[i];
|
||||||
}
|
}
|
||||||
std::cout << "Updated initials to ";
|
std::cout << "Updated initials to ";
|
||||||
for(const char c: suit_initials) {
|
for (const char c: suit_initials)
|
||||||
|
{
|
||||||
std::cout << c;
|
std::cout << c;
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("state")) {
|
if (prompt.starts_with("state"))
|
||||||
|
{
|
||||||
std::cout << *game.state << std::endl;
|
std::cout << *game.state << std::endl;
|
||||||
const std::optional<probability_t> prob = game.state->lookup();
|
const std::optional<probability_t> prob = game.state->lookup();
|
||||||
std::cout << "Winning chance: ";
|
std::cout << "Winning chance: ";
|
||||||
|
@ -236,60 +277,75 @@ namespace Hanabi {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("evaluate")) {
|
if (prompt.starts_with("evaluate"))
|
||||||
|
{
|
||||||
std::cout << "Evaluating current game state, this might take a while." << std::endl;
|
std::cout << "Evaluating current game state, this might take a while." << std::endl;
|
||||||
game.state->evaluate_state();
|
game.state->evaluate_state();
|
||||||
std::cout << "Evaluated state." << std::endl;
|
std::cout << "Evaluated state." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("revert")) {
|
if (prompt.starts_with("revert"))
|
||||||
if (depth == 0) {
|
{
|
||||||
|
if (depth == 0)
|
||||||
|
{
|
||||||
std::cout << "Cannot revert more than base state." << std::endl;
|
std::cout << "Cannot revert more than base state." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
unsigned turns_to_revert = 1;
|
unsigned turns_to_revert = 1;
|
||||||
if(prompt.length() > 7) {
|
if (prompt.length() > 7)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
turns_to_revert = std::stoi(prompt.substr(7));
|
turns_to_revert = std::stoi(prompt.substr(7));
|
||||||
} catch(const std::invalid_argument&) {
|
}
|
||||||
|
catch (const std::invalid_argument &)
|
||||||
|
{
|
||||||
std::cout << "Could not parse number of turns to revert." << std::endl;
|
std::cout << "Could not parse number of turns to revert." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (turns_to_revert > depth) {
|
if (turns_to_revert > depth)
|
||||||
|
{
|
||||||
turns_to_revert = depth;
|
turns_to_revert = depth;
|
||||||
std::cout << "Only revererting " << depth << " turns, since this is already the base state." << std::endl;
|
std::cout << "Only revererting " << depth << " turns, since this is already the base state." << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << "Reverting " << turns_to_revert << " turn(s)." << std::endl;
|
std::cout << "Reverting " << turns_to_revert << " turn(s)." << std::endl;
|
||||||
while(turns_to_revert--) {
|
while (turns_to_revert--)
|
||||||
|
{
|
||||||
game.state->revert();
|
game.state->revert();
|
||||||
depth--;
|
depth--;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("id")) {
|
if (prompt.starts_with("id"))
|
||||||
|
{
|
||||||
std::cout << game.state->unique_id() << std::endl;
|
std::cout << game.state->unique_id() << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("play")) {
|
if (prompt.starts_with("play"))
|
||||||
const Card card = parse_card(prompt.substr(5,2));
|
{
|
||||||
if (prompt.length() < 7) {
|
const Card card = parse_card(prompt.substr(5, 2));
|
||||||
|
if (prompt.length() < 7)
|
||||||
|
{
|
||||||
std::cout << "No card specified." << std::endl;
|
std::cout << "No card specified." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (card == Cards::unknown) {
|
if (card == Cards::unknown)
|
||||||
std::cout << "Could not parse card " << prompt.substr(5,2) << std::endl;
|
{
|
||||||
|
std::cout << "Could not parse card " << prompt.substr(5, 2) << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const hand_index_t index = game.state->find_card_in_hand(card);
|
const hand_index_t index = game.state->find_card_in_hand(card);
|
||||||
if (index == hand_index_t(-1)) {
|
if (index == hand_index_t(-1))
|
||||||
|
{
|
||||||
std::cout << "This card is not in the current players hand, aborting." << std::endl;
|
std::cout << "This card is not in the current players hand, aborting." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ask_for_card_and_rotate_draw(*game.state, index, true)) {
|
if (!ask_for_card_and_rotate_draw(*game.state, index, true))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
game.state->play(index);
|
game.state->play(index);
|
||||||
|
@ -297,24 +353,30 @@ namespace Hanabi {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("discard")) {
|
if (prompt.starts_with("discard"))
|
||||||
|
{
|
||||||
const auto hand = game.state->cur_hand();
|
const auto hand = game.state->cur_hand();
|
||||||
hand_index_t trash_index = invalid_hand_idx;
|
hand_index_t trash_index = invalid_hand_idx;
|
||||||
for(hand_index_t index = 0; index < hand.size(); index++) {
|
for (hand_index_t index = 0; index < hand.size(); index++)
|
||||||
if (game.state->is_trash(hand[index])) {
|
{
|
||||||
|
if (game.state->is_trash(hand[index]))
|
||||||
|
{
|
||||||
trash_index = index;
|
trash_index = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (trash_index == invalid_hand_idx) {
|
if (trash_index == invalid_hand_idx)
|
||||||
|
{
|
||||||
std::cout << "No trash in hand found, discarding not supported." << std::endl;
|
std::cout << "No trash in hand found, discarding not supported." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (game.state->num_clues() == max_num_clues) {
|
if (game.state->num_clues() == max_num_clues)
|
||||||
|
{
|
||||||
std::cout << "You cannot discard at " << max_num_clues << " clues." << std::endl;
|
std::cout << "You cannot discard at " << max_num_clues << " clues." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!ask_for_card_and_rotate_draw(*game.state, trash_index, false)) {
|
if (!ask_for_card_and_rotate_draw(*game.state, trash_index, false))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
game.state->discard(trash_index);
|
game.state->discard(trash_index);
|
||||||
|
@ -322,8 +384,10 @@ namespace Hanabi {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("clue")) {
|
if (prompt.starts_with("clue"))
|
||||||
if (game.state->num_clues() == 0) {
|
{
|
||||||
|
if (game.state->num_clues() == 0)
|
||||||
|
{
|
||||||
std::cout << "You cannot give a clue at 0 clues." << std::endl;
|
std::cout << "You cannot give a clue at 0 clues." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -332,50 +396,60 @@ namespace Hanabi {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("actions")) {
|
if (prompt.starts_with("actions"))
|
||||||
|
{
|
||||||
auto reasonable_actions = game.state->get_reasonable_actions();
|
auto reasonable_actions = game.state->get_reasonable_actions();
|
||||||
int max_rational_digit_len = std::accumulate(
|
int max_rational_digit_len = std::accumulate(
|
||||||
reasonable_actions.begin(),
|
reasonable_actions.begin(), reasonable_actions.end(), 0, [](
|
||||||
reasonable_actions.end(),
|
int old, const std::pair<Action
|
||||||
0,
|
, std::optional<probability_t>> & pair
|
||||||
[](int old, const std::pair<Action, std::optional<probability_t>>& pair){
|
) {
|
||||||
return std::max(old, representation_length(pair.second.value_or(0)));
|
return std::max(old, representation_length(pair.second.value_or(0)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
for (const auto &[action, probability] : reasonable_actions) {
|
for (const auto & [action, probability]: reasonable_actions)
|
||||||
|
{
|
||||||
std::cout.setf(std::ios_base::left, std::ios_base::adjustfield);
|
std::cout.setf(std::ios_base::left, std::ios_base::adjustfield);
|
||||||
std::cout << std::setw(7) << action << ": ";
|
std::cout << std::setw(7) << action << ": ";
|
||||||
std::cout.setf(std::ios_base::right, std::ios_base::adjustfield);
|
std::cout.setf(std::ios_base::right, std::ios_base::adjustfield);
|
||||||
std::cout << std::setw(max_rational_digit_len);
|
std::cout << std::setw(max_rational_digit_len);
|
||||||
print_probability(std::cout, probability) << std::endl;
|
print_probability(std::cout, probability) << std::endl;
|
||||||
}
|
}
|
||||||
if(reasonable_actions.empty()) {
|
if (reasonable_actions.empty())
|
||||||
|
{
|
||||||
std::cout << "Game is over, no actions to take." << std::endl;
|
std::cout << "Game is over, no actions to take." << std::endl;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prompt.starts_with("opt")) {
|
if (prompt.starts_with("opt"))
|
||||||
|
{
|
||||||
const auto reasonable_actions = game.state->get_reasonable_actions();
|
const auto reasonable_actions = game.state->get_reasonable_actions();
|
||||||
if(reasonable_actions.empty()) {
|
if (reasonable_actions.empty())
|
||||||
|
{
|
||||||
std::cout << "Game is over, no actions to take." << std::endl;
|
std::cout << "Game is over, no actions to take." << std::endl;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Action best_action;
|
Action best_action;
|
||||||
std::optional<probability_t> best_probability;
|
std::optional<probability_t> best_probability;
|
||||||
for (const auto &[action, probability] : game.state->get_reasonable_actions()) {
|
for (const auto & [action, probability]: game.state->get_reasonable_actions())
|
||||||
if (!best_probability.has_value() or (probability.has_value() and probability.value() > best_probability.value())) {
|
{
|
||||||
|
if (!best_probability.has_value() or
|
||||||
|
(probability.has_value() and probability.value() > best_probability.value()))
|
||||||
|
{
|
||||||
best_action = action;
|
best_action = action;
|
||||||
best_probability = probability;
|
best_probability = probability;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hand_index_t index = 0;
|
hand_index_t index = 0;
|
||||||
switch(best_action.type) {
|
switch (best_action.type)
|
||||||
|
{
|
||||||
case ActionType::play:
|
case ActionType::play:
|
||||||
std::cout << "Playing " << best_action.card << std::endl;
|
std::cout << "Playing " << best_action.card << std::endl;
|
||||||
index = game.state->find_card_in_hand(best_action.card);
|
index = game.state->find_card_in_hand(best_action.card);
|
||||||
if(!ask_for_card_and_rotate_draw(*game.state,index,true)) {
|
if (!ask_for_card_and_rotate_draw(*game.state, index, true))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
game.state->play(game.state->find_card_in_hand(best_action.card));
|
game.state->play(game.state->find_card_in_hand(best_action.card));
|
||||||
|
@ -383,7 +457,8 @@ namespace Hanabi {
|
||||||
case ActionType::discard:
|
case ActionType::discard:
|
||||||
std::cout << "Discarding" << std::endl;
|
std::cout << "Discarding" << std::endl;
|
||||||
index = game.state->find_card_in_hand(best_action.card);
|
index = game.state->find_card_in_hand(best_action.card);
|
||||||
if(!ask_for_card_and_rotate_draw(*game.state, index, false)) {
|
if (!ask_for_card_and_rotate_draw(*game.state, index, false))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
game.state->discard(game.state->find_card_in_hand(best_action.card));
|
game.state->discard(game.state->find_card_in_hand(best_action.card));
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
#include "download.h"
|
#include "download.h"
|
||||||
#include "game_state.h"
|
#include "game_state.h"
|
||||||
|
|
||||||
void test() {
|
void test()
|
||||||
|
{
|
||||||
{
|
{
|
||||||
auto game = Download::get_game("in/1005195", 43);
|
auto game = Download::get_game("in/1005195", 43);
|
||||||
auto res = game->evaluate_state();
|
auto res = game->evaluate_state();
|
||||||
if (!(res == Hanabi::probability_t(7, 8))) {
|
if (!(res == Hanabi::probability_t(7, 8)))
|
||||||
|
{
|
||||||
std::cerr << "Test " << ("1005195") << " failed." << std::endl;
|
std::cerr << "Test " << ("1005195") << " failed." << std::endl;
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
std::cout << "Test " << ("1005195") << " succeeded." << std::endl;
|
std::cout << "Test " << ("1005195") << " succeeded." << std::endl;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue