From 4718698ddcbc659c3c3329b51522b1f6d8625108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Ke=C3=9Fler?= Date: Sat, 12 Aug 2023 09:36:06 +0200 Subject: [PATCH] add history and command completion to readline --- cli_interface.cpp | 78 +++++++++++++++++++++++++++++++++++++++++------ cli_interface.h | 2 +- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/cli_interface.cpp b/cli_interface.cpp index 7afbea9..7040408 100644 --- a/cli_interface.cpp +++ b/cli_interface.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -28,6 +29,37 @@ namespace Hanabi { return ret; } + constexpr static std::array cli_commands = { + "play", + "clue", + "discard", + "id", + "state", + "revert", + "actions", + "evaluate", + "help", + "quit", + }; + + char * cli_commands_generator(const char *text, int state) { + std::string text_str (text); + for(auto& command : cli_commands) { + if (command.starts_with(text_str) && state-- <= 0) { + return strdup(command.c_str()); + } + } + return nullptr; + } + + char ** + cli_command_completion(const char *text, int start, int end) + { + rl_attempted_completion_over = 1; + return rl_completion_matches(text, cli_commands_generator); + } + + Card parse_card(std::string card_str) { if (card_str == "trash") { return Cards::trash; @@ -56,16 +88,18 @@ namespace Hanabi { } std::cout << "Choose drawn card: " << std::endl; unsigned num_trash = 0; + std::optional trash_discard_prob = 0; for(const auto &[card_multiplicity, probability]: next_states) { if (game->is_trash(card_multiplicity.card)) { num_trash += card_multiplicity.multiplicity; + trash_discard_prob = probability; } else { std::cout << card_multiplicity.card << " (" << card_multiplicity.multiplicity; std::cout << " copie(s) in draw) " << probability << std::endl; } } if (num_trash > 0) { - std::cout << Cards::trash << " (" << num_trash << " copie(s) in draw" << std::endl; + std::cout << Cards::trash << " (" << num_trash << " copie(s) in draw) " << trash_discard_prob << std::endl; } std::stringstream prompt; @@ -103,22 +137,33 @@ namespace Hanabi { return true; } - [[noreturn]] void cli(const std::shared_ptr& game) { + void cli(const std::shared_ptr& game) { + rl_attempted_completion_function = cli_command_completion; + using_history(); + unsigned depth = 0; while (true) { const std::string prompt = read_line_memory_safe("> "); + add_history(prompt.c_str()); 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 << "state: print information on current game state." << std::endl; + std::cout << "clue: give a clue." << std::endl; std::cout << "play : 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; + 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 << "evaluate: evaluate current game state recursively. Potentially runtime-expensive." << std::endl; + std::cout << "id: display id of state. Has no inherent meaning, useful for debugging." << std::endl; + std::cout << "quit: Quit this interactive shell." << std::endl; continue; } + if (prompt.starts_with("quit")) { + std::cout << "Quitting." << std::endl; + clear_history(); + break; + } + if (prompt.starts_with("state")) { std::cout << *game << std::endl; const std::optional prob = game->lookup(); @@ -130,11 +175,17 @@ namespace Hanabi { std::cout << "Evaluating current game state, this might take a while." << std::endl; game->evaluate_state(); std::cout << "Evaluated state." << std::endl; + continue; } if (prompt.starts_with("revert")) { + if (depth == 0) { + std::cout << "Cannot revert more than base state." << std::endl; + continue; + } std::cout << "Reverting one turn" << std::endl; game->revert(); + depth--; continue; } @@ -147,6 +198,7 @@ namespace Hanabi { const Card card = parse_card(prompt.substr(5,2)); if (prompt.length() < 7) { std::cout << "No card specified." << std::endl; + continue; } if (card == Cards::unknown) { std::cout << "Could not parse card " << prompt.substr(5,2) << std::endl; @@ -155,11 +207,13 @@ namespace Hanabi { 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; + continue; } if (!ask_for_card_and_rotate_draw(game, index, true)) { continue; } game->play(index); + depth++; continue; } @@ -184,11 +238,17 @@ namespace Hanabi { continue; } game->discard(trash_index); + depth++; continue; } if (prompt.starts_with("clue")) { + if (game->num_clues() == 0) { + std::cout << "You cannot give a clue at 0 clues." << std::endl; + continue; + } game->give_clue(); + depth++; continue; } diff --git a/cli_interface.h b/cli_interface.h index 21e05d6..a3692bb 100644 --- a/cli_interface.h +++ b/cli_interface.h @@ -5,7 +5,7 @@ #include "game_state.h" namespace Hanabi { - [[noreturn]] [[noreturn]] void cli(const std::shared_ptr& game); + void cli(const std::shared_ptr& game); }