Endgame-Analyzer/include/game_interface.h
Maximilian Keßler 5c4a2bb4f7
Store rationals without denominator
Instead of storing a rational for evey game state,
we just store how many of the factorial(draw pile size) many
game states can be won.
This allows us to save only one 64-bit integer per game state instead of
two and thus reduces memory consumption of the program significantly.
Also, this makes some computations a bit easier, since we do not have to
normalize when recursing - we can just add the numbe of winnable states
for each possible draw.

On the other hand, this means that upon lookup, we have to normalize the
stored values again to retrieve the probabilities.
In particular, one needs to know what the draw pile size of the game
state is in order to interpret the value of the state.
2024-02-09 15:58:15 +01:00

148 lines
4.4 KiB
C++

#ifndef DYNAMIC_PROGRAM_GAME_INTERFACE_H
#define DYNAMIC_PROGRAM_GAME_INTERFACE_H
#include <cstddef>
#include <iosfwd>
#include <vector>
#include <memory>
#include "hanabi_types.hpp"
namespace Hanabi
{
/**
* A card together with an associated multiplicity
*/
struct CardMultiplicity
{
Card card;
unsigned multiplicity;
bool operator==(const CardMultiplicity &) const;
bool operator!=(const CardMultiplicity &) const;
};
struct HanabiStateConfig
{
/** What score to consider as a win. If left empty, automatically replaced by max score on construction. */
std::optional<uint8_t> score_goal;
/** The number of clues gained when a stack is finished or a card is discarded. Usually 1, 1/2 in clue starved. */
clue_t num_clues_gained_on_discard_or_stack_finished {1};
/**
* If set to true, only roughly half of the game states will be stored in the internal lookup table.
* This results in roughly halving memory consumption at the cost of roughly a factor 3-5 in execution speed
* (the slowdown is theoretically bounded to visiting at most 9 times the number of states if this option is activated).
* You should typically never need this, unless you are very specifically short on memory and.
* */
bool save_memory {false};
};
class HanabiStateIF
{
public:
virtual void give_clue() = 0;
virtual void discard(hand_index_t index) = 0;
virtual void play(hand_index_t index) = 0;
virtual void rotate_next_draw(const Card & card) = 0;
[[nodiscard]] virtual ActionType last_action_type() const = 0;
virtual void revert() = 0;
virtual void modify_clues(clue_t change) = 0;
virtual void set_clues(clue_t clues) = 0;
[[nodiscard]] virtual player_t turn() const = 0;
[[nodiscard]] virtual clue_t num_clues() const = 0;
[[nodiscard]] virtual unsigned num_strikes() const = 0;
[[nodiscard]] virtual unsigned score() const = 0;
[[nodiscard]] virtual std::vector<std::vector<Card>> hands() const = 0;
[[nodiscard]] virtual std::vector<Card> cur_hand() const = 0;
[[nodiscard]] virtual size_t draw_pile_size() const = 0;
[[nodiscard]] virtual bool is_trash(const Card & card) const = 0;
/** Returns whether the card is critical, assuming it is not trash already */
[[nodiscard]] virtual bool is_critical(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 hand_index_t find_card_in_hand(const Card & card) const = 0;
[[nodiscard]] virtual std::uint64_t enumerated_states() const = 0;
[[nodiscard]] virtual const map_type<unsigned long, std::uint64_t> & position_tablebase() const = 0;
virtual void init_backtracking_information() = 0;
virtual probability_t evaluate_state() = 0;
[[nodiscard]] virtual std::optional<probability_t> lookup() 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;
virtual std::vector<std::pair<Action, std::optional<probability_t>>> get_reasonable_actions(bool evaluate_all = false, bool reasonable = true) = 0;
virtual std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>>
possible_next_states(hand_index_t index, bool play) = 0;
virtual ~HanabiStateIF() = default;
protected:
virtual void print(std::ostream & os) const = 0;
friend std::ostream & operator<<(std::ostream &, HanabiStateIF const &);
};
std::ostream & operator<<(std::ostream & os, HanabiStateIF const & hanabi_state);
struct GameInfo
{
std::vector<Hanabi::Card> deck;
std::vector<Hanabi::Action> actions;
Hanabi::suit_t num_suits;
Hanabi::player_t num_players;
clue_t num_clues_gained_per_discard_or_stack_finished;
};
struct Game : private GameInfo
{
public:
Game(std::unique_ptr<HanabiStateIF> state, GameInfo game_info);
[[nodiscard]] unsigned cur_turn() const;
[[nodiscard]] unsigned num_turns() const;
void make_turn();
void revert_turn();
bool goto_draw_pile_size(size_t draw_pile_break);
bool goto_turn(size_t turn);
[[nodiscard]] bool holds_state() const;
std::unique_ptr<HanabiStateIF> state;
unsigned next_action;
};
}
#endif //DYNAMIC_PROGRAM_GAME_INTERFACE_H