add cli interface

This commit is contained in:
Maximilian Keßler 2023-08-12 00:04:02 +02:00
parent a4e9560753
commit a8818418e9
Signed by: max
GPG key ID: BCC5A619923C0BA5
6 changed files with 193 additions and 31 deletions

View file

@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25)
project(dynamic_program CXX) project(dynamic_program CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wpedantic -Werror -lreadline")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
find_package(cpr) find_package(cpr)
@ -11,7 +11,7 @@ FIND_PACKAGE( Boost 1.81 COMPONENTS program_options REQUIRED )
include_directories(.) include_directories(.)
INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} ) INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} )
add_executable(dynamic_program main.cpp game_state.h) add_executable(dynamic_program main.cpp game_state.h cli_interface.cpp)
target_link_libraries(dynamic_program cpr) target_link_libraries(dynamic_program cpr)
TARGET_LINK_LIBRARIES(dynamic_program Boost::program_options ) TARGET_LINK_LIBRARIES(dynamic_program Boost::program_options )

155
cli_interface.cpp Normal file
View file

@ -0,0 +1,155 @@
#include <cstdio>
#include <readline/readline.h>
#include <iostream>
#include <memory>
#include <cmath>
#include "game_state.h"
namespace Hanabi {
std::ostream& operator<<(std::ostream& os, const std::optional<probability_t>& prob) {
if (prob.has_value()) {
os << prob.value() << " ~ " << std::setprecision(5) << boost::rational_cast<double>(prob.value()) * 100 << "%";
} else {
os << "unknown";
}
return os;
}
std::string read_line_memory_safe(const char *prompt) {
char *line = readline(prompt);
std::string ret;
if (line == nullptr) {
ret = "";
} else {
ret = std::string(line);
}
free(line);
return ret;
}
Card parse_card(std::string card_str) {
if(card_str.size() != 2) {
return unknown_card;
}
constexpr std::array<char, 6> color_initials = {'r', 'y', 'g', 'b', 'p', 't'};
auto it = std::find(color_initials.begin(), color_initials.end(), card_str[0]);
if (it == color_initials.end()) {
return unknown_card;
}
const suit_t suit = std::distance(color_initials.begin(), it);
try {
const rank_t rank = 5 - std::stoi(card_str.substr(1, 1));
return Card {suit, rank};
} catch(std::invalid_argument&) {
return unknown_card;
}
}
bool ask_for_card_and_rotate_draw(const std::shared_ptr<HanabiStateIF>& game, hand_index_t index, bool play) {
const auto next_states = game->possible_next_states(index, play);
if (next_states.size() <= 1) {
return true;
}
std::cout << "Choose drawn card: " << std::endl;
for(const auto &[card_multiplicity, probability]: next_states) {
if (game->is_trash(card_multiplicity.card)) {
std::cout << "kt " << std::endl;
} else {
std::cout << card_multiplicity.card << " ";
}
std::cout << "(" << card_multiplicity.multiplicity << " copie(s) in draw) " << probability << std::endl;
}
const std::string card_str = read_line_memory_safe("draw? ");
const Card drawn_card = parse_card(card_str);
if (drawn_card == unknown_card) {
std::cout << "Could not parse card " << card_str << std::endl;
return false;
}
if (std::find_if(next_states.begin(), next_states.end(), [&drawn_card](const std::pair<CardMultiplicity, std::optional<probability_t>>& pair) {
return pair.first.card.suit == drawn_card.suit and pair.first.card.rank == drawn_card.rank;
}) == next_states.end()) {
std::cout << "That card is not in the draw pile" << std::endl;
};
game->rotate_next_draw(drawn_card);
return true;
}
[[noreturn]] void cli(const std::shared_ptr<HanabiStateIF>& game) {
while (true) {
const std::string prompt = read_line_memory_safe("> ");
if (prompt.starts_with("help")) {
std::cout << "state: print information on current game state" << std::endl;
std::cout << "clue: give a clue" << std::endl;
std::cout << "play <card>: play specified card" << std::endl;
std::cout << "discard <card>: discard specified card" << std::endl;
std::cout << "revert: revert last turn of game" << std::endl;
std::cout << "actions: display list of reasonable actions to take and their winning chances" << std::endl;
continue;
}
if (prompt.starts_with("state")) {
std::cout << *game << std::endl;
const std::optional<probability_t> prob = game->lookup();
std::cout << "Winning chance: " << prob << std::endl;
continue;
}
if (prompt.starts_with("revert")) {
std::cout << "Reverting one turn" << std::endl;
game->revert();
continue;
}
if (prompt.starts_with("play")) {
const Card card = parse_card(prompt.substr(5,2));
if (card == unknown_card) {
std::cout << "Could not parse card " << prompt.substr(5,2) << std::endl;
continue;
}
const hand_index_t index = game->find_card_in_hand(card);
if (index == hand_index_t(-1)) {
std::cout << "This card is not in the current players hand, aborting." << std::endl;
}
if (!ask_for_card_and_rotate_draw(game, index, true)) {
continue;
}
game->play(index);
continue;
}
if (prompt.starts_with("discard")) {
const Card card = parse_card(prompt.substr(8,2));
if (card == unknown_card) {
std::cout << "Could not parse card " << prompt.substr(5,2) << std::endl;
continue;
}
const hand_index_t index = game->find_card_in_hand(card);
if (index == hand_index_t(-1)) {
std::cout << "This card is not in the current players hand, aborting." << std::endl;
}
if (!ask_for_card_and_rotate_draw(game, index, false)) {
continue;
}
game->discard(index);
continue;
}
if (prompt.starts_with("clue")) {
game->clue();
continue;
}
if (prompt.starts_with("actions")) {
for (const auto &[action, probability] : game->get_reasonable_actions()) {
std::cout << action << ": " << probability << std::endl;
}
continue;
}
std::cout << "Unrecognized command. Type 'help' for a list of available commands." << std::endl;
}
}
}

12
cli_interface.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef DYNAMIC_PROGRAM_CLI_INTERFACE_H
#define DYNAMIC_PROGRAM_CLI_INTERFACE_H
#include <memory>
#include "game_state.h"
namespace Hanabi {
[[noreturn]] [[noreturn]] void cli(const std::shared_ptr<HanabiStateIF>& game);
}
#endif //DYNAMIC_PROGRAM_CLI_INTERFACE_H

View file

@ -8,6 +8,7 @@
#include <cstddef> #include <cstddef>
#include <unordered_map> #include <unordered_map>
#include <bitset> #include <bitset>
#include <vector>
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
@ -48,9 +49,9 @@ namespace Hanabi {
bool in_starting_hand; bool in_starting_hand;
bool initial_trash; bool initial_trash;
Card &operator++(); inline Card &operator++();
const Card operator++(int); inline const Card operator++(int);
auto operator<=>(const Card &) const = default; auto operator<=>(const Card &) const = default;
}; };
@ -67,7 +68,7 @@ namespace std {
namespace Hanabi { namespace Hanabi {
std::ostream &operator<<(std::ostream &os, const Card &card); inline std::ostream &operator<<(std::ostream &os, const Card &card);
constexpr Card r0 = {0, 5}; constexpr Card r0 = {0, 5};
constexpr Card r1 = {0, 4}; constexpr Card r1 = {0, 4};
@ -99,7 +100,7 @@ std::ostream& operator<<(std::ostream &os, const Stacks<num_suits> &stacks);
struct CardMultiplicity { struct CardMultiplicity {
Card card; Card card;
std::uint8_t multiplicity; unsigned multiplicity;
auto operator<=>(const CardMultiplicity &) const = default; auto operator<=>(const CardMultiplicity &) const = default;
}; };
@ -150,7 +151,7 @@ struct Action {
Card card {}; Card card {};
}; };
std::ostream& operator<<(std::ostream& os, const Action& action); inline std::ostream& operator<<(std::ostream& os, const Action& action);
/** Would like to have 2 versions: /** Would like to have 2 versions:
* All: * All:
@ -181,8 +182,10 @@ public:
virtual void init_backtracking_information() = 0; virtual void init_backtracking_information() = 0;
virtual probability_t evaluate_state() = 0; virtual probability_t evaluate_state() = 0;
virtual std::optional<probability_t> lookup() 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,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;
@ -192,6 +195,8 @@ protected:
friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&); friend std::ostream& operator<<(std::ostream&, HanabiStateIF const&);
}; };
inline std::ostream &operator<<(std::ostream &os, HanabiStateIF const &hanabi_state);
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:
@ -221,7 +226,7 @@ public:
std::optional<probability_t> lookup() const; std::optional<probability_t> lookup() const;
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,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;
@ -274,7 +279,7 @@ private:
unsigned long discard_and_potentially_update(hand_index_t index); unsigned long discard_and_potentially_update(hand_index_t index);
unsigned long play_and_potentially_update(hand_index_t index); unsigned long play_and_potentially_update(hand_index_t index);
unsigned long draw(hand_index_t index); unsigned draw(hand_index_t index);
void revert_draw(hand_index_t index, Card discarded_card); void revert_draw(hand_index_t index, Card discarded_card);
void revert_clue(); void revert_clue();

View file

@ -244,7 +244,7 @@ namespace Hanabi {
} }
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>
unsigned long HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index) { unsigned HanabiState<num_suits, num_players, hand_size>::draw(uint8_t index) {
ASSERT(index < _hands[_turn].size()); ASSERT(index < _hands[_turn].size());
// update card position of the card we are about to discard // update card position of the card we are about to discard
@ -470,18 +470,17 @@ namespace Hanabi {
} }
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>
std::vector<std::pair<CardMultiplicity, probability_t>> HanabiState<num_suits, num_players, hand_size>::possible_next_states(hand_index_t index, bool play) { std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> HanabiState<num_suits, num_players, hand_size>::possible_next_states(hand_index_t index, bool play) {
std::vector<std::pair<CardMultiplicity, probability_t>> next_states; std::vector<std::pair<CardMultiplicity, std::optional<probability_t>>> next_states;
do_for_each_potential_draw(index, play, [this, &next_states](unsigned long multiplicity){ do_for_each_potential_draw(index, play, [this, &next_states, &index](unsigned multiplicity){
auto prob = lookup(); auto prob = lookup();
ASSERT(lookup().has_value());
// bit hacky to get drawn card here // bit hacky to get drawn card here
decr_turn(); decr_turn();
const CardMultiplicity drawn_card = {_hands[_turn], multiplicity}; const CardMultiplicity drawn_card = {_hands[_turn][index], multiplicity};
incr_turn(); incr_turn();
next_states.emplace_back(drawn_card, prob.value()); next_states.emplace_back(drawn_card, prob);
}); });
return next_states; return next_states;
} }

View file

@ -11,6 +11,7 @@
#include "game_state.h" #include "game_state.h"
#include "download.h" #include "download.h"
#include "myassert.h" #include "myassert.h"
#include "cli_interface.h"
namespace Hanabi { namespace Hanabi {
@ -23,20 +24,6 @@ namespace Hanabi {
std::cout << "Probability with optimal play: " << res << std::endl; std::cout << "Probability with optimal play: " << res << std::endl;
std::cout << "Enumerated " << game->enumerated_states() << " states" << std::endl; std::cout << "Enumerated " << game->enumerated_states() << " states" << std::endl;
std::cout << "Visited " << game->position_tablebase().size() << " unique game states. " << std::endl; std::cout << "Visited " << game->position_tablebase().size() << " unique game states. " << std::endl;
game->rotate_next_draw(r1);
game->discard(0);
game->rotate_next_draw(r1);
game->play(game->find_card_in_hand(y3));
game->clue();
game->rotate_next_draw(r1);
game->play(game->find_card_in_hand(y4));
std::cout << *game << std::endl;
for (const auto &[action, probability] : game->get_reasonable_actions()) { for (const auto &[action, probability] : game->get_reasonable_actions()) {
std::cout << action; std::cout << action;
if(probability.has_value()) { if(probability.has_value()) {
@ -52,6 +39,10 @@ namespace Hanabi {
biggest_key = std::max(biggest_key, key); biggest_key = std::max(biggest_key, key);
} }
std::cout << "Biggest key generated is " << biggest_key << std::endl; std::cout << "Biggest key generated is " << biggest_key << std::endl;
auto game_shared = std::shared_ptr<HanabiStateIF>(game.release());
auto states = game_shared->possible_next_states(0, false);
cli(game_shared);
} }
void print_sizes() { void print_sizes() {