add cli interface
This commit is contained in:
parent
a4e9560753
commit
a8818418e9
6 changed files with 193 additions and 31 deletions
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25)
|
|||
project(dynamic_program CXX)
|
||||
|
||||
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")
|
||||
|
||||
find_package(cpr)
|
||||
|
@ -11,7 +11,7 @@ FIND_PACKAGE( Boost 1.81 COMPONENTS program_options REQUIRED )
|
|||
include_directories(.)
|
||||
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 Boost::program_options )
|
||||
|
|
155
cli_interface.cpp
Normal file
155
cli_interface.cpp
Normal 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
12
cli_interface.h
Normal 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
|
21
game_state.h
21
game_state.h
|
@ -8,6 +8,7 @@
|
|||
#include <cstddef>
|
||||
#include <unordered_map>
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
@ -48,9 +49,9 @@ namespace Hanabi {
|
|||
bool in_starting_hand;
|
||||
bool initial_trash;
|
||||
|
||||
Card &operator++();
|
||||
inline Card &operator++();
|
||||
|
||||
const Card operator++(int);
|
||||
inline const Card operator++(int);
|
||||
|
||||
auto operator<=>(const Card &) const = default;
|
||||
};
|
||||
|
@ -67,7 +68,7 @@ namespace std {
|
|||
|
||||
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 r1 = {0, 4};
|
||||
|
@ -99,7 +100,7 @@ std::ostream& operator<<(std::ostream &os, const Stacks<num_suits> &stacks);
|
|||
|
||||
struct CardMultiplicity {
|
||||
Card card;
|
||||
std::uint8_t multiplicity;
|
||||
unsigned multiplicity;
|
||||
|
||||
auto operator<=>(const CardMultiplicity &) const = default;
|
||||
};
|
||||
|
@ -150,7 +151,7 @@ struct Action {
|
|||
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:
|
||||
* All:
|
||||
|
@ -181,8 +182,10 @@ public:
|
|||
virtual void init_backtracking_information() = 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<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;
|
||||
|
||||
|
@ -192,6 +195,8 @@ protected:
|
|||
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>
|
||||
class HanabiState : public HanabiStateIF {
|
||||
public:
|
||||
|
@ -221,7 +226,7 @@ public:
|
|||
std::optional<probability_t> lookup() const;
|
||||
|
||||
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;
|
||||
|
||||
|
@ -274,7 +279,7 @@ private:
|
|||
unsigned long discard_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_clue();
|
||||
|
|
|
@ -244,7 +244,7 @@ namespace Hanabi {
|
|||
}
|
||||
|
||||
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());
|
||||
|
||||
// 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>
|
||||
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, probability_t>> next_states;
|
||||
do_for_each_potential_draw(index, play, [this, &next_states](unsigned long multiplicity){
|
||||
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, std::optional<probability_t>>> next_states;
|
||||
do_for_each_potential_draw(index, play, [this, &next_states, &index](unsigned multiplicity){
|
||||
auto prob = lookup();
|
||||
ASSERT(lookup().has_value());
|
||||
|
||||
// bit hacky to get drawn card here
|
||||
decr_turn();
|
||||
const CardMultiplicity drawn_card = {_hands[_turn], multiplicity};
|
||||
const CardMultiplicity drawn_card = {_hands[_turn][index], multiplicity};
|
||||
incr_turn();
|
||||
|
||||
next_states.emplace_back(drawn_card, prob.value());
|
||||
next_states.emplace_back(drawn_card, prob);
|
||||
});
|
||||
return next_states;
|
||||
}
|
||||
|
|
19
main.cpp
19
main.cpp
|
@ -11,6 +11,7 @@
|
|||
#include "game_state.h"
|
||||
#include "download.h"
|
||||
#include "myassert.h"
|
||||
#include "cli_interface.h"
|
||||
|
||||
|
||||
namespace Hanabi {
|
||||
|
@ -23,20 +24,6 @@ namespace Hanabi {
|
|||
std::cout << "Probability with optimal play: " << res << std::endl;
|
||||
std::cout << "Enumerated " << game->enumerated_states() << " 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()) {
|
||||
std::cout << action;
|
||||
if(probability.has_value()) {
|
||||
|
@ -52,6 +39,10 @@ namespace Hanabi {
|
|||
biggest_key = std::max(biggest_key, key);
|
||||
}
|
||||
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() {
|
||||
|
|
Loading…
Reference in a new issue