155 lines
6 KiB
C++
155 lines
6 KiB
C++
|
#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;
|
||
|
}
|
||
|
}
|
||
|
}
|