improve cli interface: easier handling, more input validation
This commit is contained in:
parent
a8818418e9
commit
74fe5513da
4 changed files with 192 additions and 64 deletions
|
@ -29,20 +29,23 @@ namespace Hanabi {
|
|||
}
|
||||
|
||||
Card parse_card(std::string card_str) {
|
||||
if (card_str == "trash") {
|
||||
return Cards::trash;
|
||||
}
|
||||
if(card_str.size() != 2) {
|
||||
return unknown_card;
|
||||
return Cards::unknown;
|
||||
}
|
||||
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;
|
||||
return Cards::unknown;
|
||||
}
|
||||
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;
|
||||
return Cards::unknown;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,27 +55,51 @@ namespace Hanabi {
|
|||
return true;
|
||||
}
|
||||
std::cout << "Choose drawn card: " << std::endl;
|
||||
unsigned num_trash = 0;
|
||||
for(const auto &[card_multiplicity, probability]: next_states) {
|
||||
if (game->is_trash(card_multiplicity.card)) {
|
||||
std::cout << "kt " << std::endl;
|
||||
num_trash += card_multiplicity.multiplicity;
|
||||
} else {
|
||||
std::cout << card_multiplicity.card << " ";
|
||||
std::cout << card_multiplicity.card << " (" << card_multiplicity.multiplicity;
|
||||
std::cout << " copie(s) in draw) " << probability << std::endl;
|
||||
}
|
||||
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) {
|
||||
if (num_trash > 0) {
|
||||
std::cout << Cards::trash << " (" << num_trash << " copie(s) in draw" << std::endl;
|
||||
}
|
||||
|
||||
std::stringstream prompt;
|
||||
prompt << "draw? [";
|
||||
if (num_trash > 0) {
|
||||
prompt << Cards::trash;
|
||||
} else {
|
||||
prompt << next_states.front().first.card;
|
||||
}
|
||||
prompt << "] ";
|
||||
const std::string card_str = read_line_memory_safe(prompt.str().c_str());
|
||||
const Card drawn_card = [&card_str, &num_trash, &next_states](){
|
||||
if (card_str.empty()) {
|
||||
if (num_trash > 0) {
|
||||
return Cards::trash;
|
||||
}
|
||||
return next_states.front().first.card;
|
||||
}
|
||||
return parse_card(card_str);
|
||||
}();
|
||||
|
||||
if (drawn_card == Cards::unknown) {
|
||||
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;
|
||||
auto selected_draw_it = std::find_if(next_states.begin(), next_states.end(), [&drawn_card, &game](const std::pair<CardMultiplicity, std::optional<probability_t>>& pair) {
|
||||
return (game->is_trash(pair.first.card) and drawn_card == Cards::trash) or pair.first.card == drawn_card;
|
||||
});
|
||||
if (selected_draw_it == next_states.end()){
|
||||
std::cout << "That card is not in the draw pile, aborting." << std::endl;
|
||||
return false;
|
||||
};
|
||||
game->rotate_next_draw(drawn_card);
|
||||
game->rotate_next_draw(selected_draw_it->first.card);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -81,12 +108,14 @@ namespace Hanabi {
|
|||
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;
|
||||
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: discard trash from hand." << 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;
|
||||
std::cout << "id: display id of state. Has no inherent meaning, useful for debugging." << std::endl;
|
||||
std::cout << "evaluate: evaluate current game state recursively. Potentially runtime-expensive." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -97,15 +126,29 @@ namespace Hanabi {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (prompt.starts_with("evaluate")) {
|
||||
std::cout << "Evaluating current game state, this might take a while." << std::endl;
|
||||
game->evaluate_state();
|
||||
std::cout << "Evaluated state." << std::endl;
|
||||
}
|
||||
|
||||
if (prompt.starts_with("revert")) {
|
||||
std::cout << "Reverting one turn" << std::endl;
|
||||
game->revert();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prompt.starts_with("id")) {
|
||||
std::cout << game->unique_id() << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prompt.starts_with("play")) {
|
||||
const Card card = parse_card(prompt.substr(5,2));
|
||||
if (card == unknown_card) {
|
||||
if (prompt.length() < 7) {
|
||||
std::cout << "No card specified." << std::endl;
|
||||
}
|
||||
if (card == Cards::unknown) {
|
||||
std::cout << "Could not parse card " << prompt.substr(5,2) << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
@ -121,24 +164,31 @@ namespace Hanabi {
|
|||
}
|
||||
|
||||
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;
|
||||
const auto hand = game->cur_hand();
|
||||
hand_index_t trash_index = invalid_hand_idx;
|
||||
for(hand_index_t index = 0; index < hand.size(); index++) {
|
||||
if (game->is_trash(hand[index])) {
|
||||
trash_index = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trash_index == invalid_hand_idx) {
|
||||
std::cout << "No trash in hand found, discarding not supported." << 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)) {
|
||||
if (game->num_clues() == max_num_clues) {
|
||||
std::cout << "You cannot discard at " << max_num_clues << " clues." << std::endl;
|
||||
continue;
|
||||
}
|
||||
game->discard(index);
|
||||
if (!ask_for_card_and_rotate_draw(game, trash_index, false)) {
|
||||
continue;
|
||||
}
|
||||
game->discard(trash_index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prompt.starts_with("clue")) {
|
||||
game->clue();
|
||||
game->give_clue();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace Download {
|
|||
switch(actions[i].type) {
|
||||
case Hanabi::ActionType::color_clue:
|
||||
case Hanabi::ActionType::rank_clue:
|
||||
game->clue();
|
||||
game->give_clue();
|
||||
break;
|
||||
case Hanabi::ActionType::discard:
|
||||
index = game->find_card_in_hand(deck[actions[i].target]);
|
||||
|
|
91
game_state.h
91
game_state.h
|
@ -39,8 +39,11 @@ namespace Hanabi {
|
|||
constexpr size_t max_card_duplicity = 3;
|
||||
constexpr clue_t max_num_clues = 8;
|
||||
constexpr uint8_t not_in_starting_hand = std::numeric_limits<uint8_t>::max();
|
||||
constexpr hand_index_t invalid_hand_idx = std::numeric_limits<hand_index_t>::max();
|
||||
|
||||
constexpr std::array<std::string, 6> suit_initials{"r", "y", "g", "b", "p", "t"};
|
||||
// We might want to change these at runtime to adapt to other variants.
|
||||
// However, a global variable is used so that we can have an output operator for cards reading from here
|
||||
static std::array<char, 6> suit_initials = {'r', 'y', 'g', 'b', 'p', 't'};
|
||||
|
||||
struct Card {
|
||||
suit_t suit;
|
||||
|
@ -53,7 +56,48 @@ namespace Hanabi {
|
|||
|
||||
inline const Card operator++(int);
|
||||
|
||||
auto operator<=>(const Card &) const = default;
|
||||
inline bool operator==(const Card &other) const;
|
||||
};
|
||||
|
||||
namespace Cards {
|
||||
static constexpr Card r0 = {0, 5};
|
||||
static constexpr Card r1 = {0, 4};
|
||||
static constexpr Card r2 = {0, 3};
|
||||
static constexpr Card r3 = {0, 2};
|
||||
static constexpr Card r4 = {0, 1};
|
||||
static constexpr Card r5 = {0, 0};
|
||||
static constexpr Card y0 = {1, 5};
|
||||
static constexpr Card y1 = {1, 4};
|
||||
static constexpr Card y2 = {1, 3};
|
||||
static constexpr Card y3 = {1, 2};
|
||||
static constexpr Card y4 = {1, 1};
|
||||
static constexpr Card y5 = {1, 0};
|
||||
static constexpr Card g0 = {2, 5};
|
||||
static constexpr Card g1 = {2, 4};
|
||||
static constexpr Card g2 = {2, 3};
|
||||
static constexpr Card g3 = {2, 2};
|
||||
static constexpr Card g4 = {2, 1};
|
||||
static constexpr Card g5 = {2, 0};
|
||||
static constexpr Card b0 = {3, 5};
|
||||
static constexpr Card b1 = {3, 4};
|
||||
static constexpr Card b2 = {3, 3};
|
||||
static constexpr Card b3 = {3, 2};
|
||||
static constexpr Card b4 = {3, 1};
|
||||
static constexpr Card b5 = {3, 0};
|
||||
static constexpr Card p0 = {4, 5};
|
||||
static constexpr Card p1 = {4, 4};
|
||||
static constexpr Card p2 = {4, 3};
|
||||
static constexpr Card p3 = {4, 2};
|
||||
static constexpr Card p4 = {4, 1};
|
||||
static constexpr Card p5 = {4, 0};
|
||||
static constexpr Card t0 = {5, 5};
|
||||
static constexpr Card t1 = {5, 4};
|
||||
static constexpr Card t2 = {5, 3};
|
||||
static constexpr Card t3 = {5, 2};
|
||||
static constexpr Card t4 = {5, 1};
|
||||
static constexpr Card t5 = {5, 0};
|
||||
static constexpr Card unknown = {std::numeric_limits<suit_t>::max(), 0};
|
||||
static constexpr Card trash = {std::numeric_limits<suit_t>::max(), 1};
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -70,19 +114,6 @@ namespace Hanabi {
|
|||
|
||||
inline std::ostream &operator<<(std::ostream &os, const Card &card);
|
||||
|
||||
constexpr Card r0 = {0, 5};
|
||||
constexpr Card r1 = {0, 4};
|
||||
constexpr Card r2 = {0, 3};
|
||||
constexpr Card r3 = {0, 2};
|
||||
constexpr Card r4 = {0, 1};
|
||||
constexpr Card r5 = {0, 0};
|
||||
constexpr Card y0 = {1, 5};
|
||||
constexpr Card y1 = {1, 4};
|
||||
constexpr Card y2 = {1, 3};
|
||||
constexpr Card y3 = {1, 2};
|
||||
constexpr Card y4 = {1, 1};
|
||||
constexpr Card y5 = {1, 0};
|
||||
constexpr Card unknown_card = {0, 6};
|
||||
|
||||
/**
|
||||
* To store:
|
||||
|
@ -162,19 +193,23 @@ inline std::ostream& operator<<(std::ostream& os, const Action& action);
|
|||
|
||||
class HanabiStateIF {
|
||||
public:
|
||||
virtual void clue() = 0;
|
||||
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;
|
||||
|
||||
virtual void revert() = 0;
|
||||
|
||||
[[nodiscard]] virtual hand_index_t find_card_in_hand(const Card& card) const = 0;
|
||||
[[nodiscard]] virtual player_t turn() const = 0;
|
||||
[[nodiscard]] virtual clue_t num_clues() 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;
|
||||
[[nodiscard]] virtual bool is_playable(const Card& card) const = 0;
|
||||
[[nodiscard]] virtual size_t draw_pile_size() 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 std::unordered_map<unsigned long, probability_t>& position_tablebase() const = 0;
|
||||
|
@ -182,8 +217,8 @@ public:
|
|||
virtual void init_backtracking_information() = 0;
|
||||
virtual probability_t evaluate_state() = 0;
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
|
@ -203,7 +238,7 @@ public:
|
|||
HanabiState() = default;
|
||||
explicit HanabiState(const std::vector<Card>& deck);
|
||||
|
||||
void clue() final;
|
||||
void give_clue() final;
|
||||
void discard(hand_index_t index) final;
|
||||
void play(hand_index_t index) final;
|
||||
|
||||
|
@ -211,10 +246,15 @@ public:
|
|||
|
||||
void revert() final;
|
||||
|
||||
[[nodiscard]] player_t turn() const final;
|
||||
[[nodiscard]] clue_t num_clues() const final;
|
||||
[[nodiscard]] std::vector<std::vector<Card>> hands() const final;
|
||||
[[nodiscard]] std::vector<Card> cur_hand() const final;
|
||||
[[nodiscard]] size_t draw_pile_size() 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]] size_t draw_pile_size() const final;
|
||||
[[nodiscard]] bool is_relative_state_initialized() const final;
|
||||
|
||||
[[nodiscard]] std::uint64_t enumerated_states() const final;
|
||||
|
@ -224,6 +264,7 @@ public:
|
|||
probability_t evaluate_state() final;
|
||||
|
||||
std::optional<probability_t> lookup() const;
|
||||
std::uint64_t unique_id() const 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;
|
||||
|
@ -237,7 +278,7 @@ private:
|
|||
struct BacktrackAction {
|
||||
explicit BacktrackAction(
|
||||
ActionType action_type,
|
||||
Card discarded_or_played = unknown_card,
|
||||
Card discarded_or_played = Cards::unknown,
|
||||
hand_index_t index = 0,
|
||||
bool was_on_8_clues = false
|
||||
);
|
||||
|
@ -299,8 +340,6 @@ private:
|
|||
static constexpr player_t draw_pile = num_players;
|
||||
static constexpr player_t trash_or_play_stack = num_players + 1;
|
||||
|
||||
std::uint64_t unique_id() const;
|
||||
|
||||
// Usual game state
|
||||
player_t _turn{};
|
||||
clue_t _num_clues{};
|
||||
|
|
|
@ -40,8 +40,16 @@ namespace Hanabi {
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool Card::operator==(const Card &other) const {
|
||||
return suit == other.suit and rank == other.rank;
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &os, const Card &card) {
|
||||
os << suit_initials[card.suit] << 5 - card.rank;
|
||||
if (card == Cards::trash) {
|
||||
os << "kt";
|
||||
} else {
|
||||
os << suit_initials[card.suit] << 5 - card.rank;
|
||||
}
|
||||
return os;
|
||||
}
|
||||
|
||||
|
@ -118,11 +126,11 @@ namespace Hanabi {
|
|||
}
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
void HanabiState<num_suits, num_players, hand_size>::clue() {
|
||||
void HanabiState<num_suits, num_players, hand_size>::give_clue() {
|
||||
ASSERT(_num_clues > 0);
|
||||
--_num_clues;
|
||||
|
||||
_actions_log.emplace(ActionType::clue, unknown_card, 0);
|
||||
_actions_log.emplace(ActionType::clue, Cards::unknown, 0);
|
||||
incr_turn();
|
||||
}
|
||||
|
||||
|
@ -469,6 +477,37 @@ namespace Hanabi {
|
|||
}
|
||||
}
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
player_t HanabiState<num_suits, num_players, hand_size>::turn() const {
|
||||
return _turn;
|
||||
}
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
clue_t HanabiState<num_suits, num_players, hand_size>::num_clues() const {
|
||||
return _num_clues;
|
||||
};
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
std::vector<std::vector<Card>> HanabiState<num_suits, num_players, hand_size>::hands() const {
|
||||
std::vector<std::vector<Card>> hands;
|
||||
for(player_t player = 0; player < num_players; player++) {
|
||||
hands.push_back({});
|
||||
for(const Card& card: _hands[player]) {
|
||||
hands.back().push_back(card);
|
||||
}
|
||||
}
|
||||
return hands;
|
||||
}
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
std::vector<Card> HanabiState<num_suits, num_players, hand_size>::cur_hand() const {
|
||||
std::vector<Card> hand;
|
||||
for(const Card& card: _hands[_turn]) {
|
||||
hand.push_back(card);
|
||||
}
|
||||
return hand;
|
||||
}
|
||||
|
||||
template<suit_t num_suits, player_t num_players, hand_index_t hand_size>
|
||||
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;
|
||||
|
@ -551,9 +590,9 @@ namespace Hanabi {
|
|||
}
|
||||
|
||||
if(_num_clues > 0) {
|
||||
clue();
|
||||
give_clue();
|
||||
const std::optional<probability_t> prob = lookup();
|
||||
const Action action = {ActionType::clue, unknown_card};
|
||||
const Action action = {ActionType::clue, Cards::unknown};
|
||||
reasonable_actions.emplace_back(action, prob);
|
||||
revert_clue();
|
||||
}
|
||||
|
@ -648,7 +687,7 @@ namespace Hanabi {
|
|||
|
||||
// Last option is to stall
|
||||
if(_num_clues > 0) {
|
||||
clue();
|
||||
give_clue();
|
||||
const probability_t probability_stall = evaluate_state();
|
||||
revert_clue();
|
||||
best_probability = std::max(best_probability, probability_stall);
|
||||
|
|
Loading…
Reference in a new issue