// This file is part of Timmi. // // Timmi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. #include #include #include #include #include #include #include #include #include #include #include #include // Stuff for readline usage const std::string ANSI_CSI("\033["); const std::string ANSI_COLOR_DEFAULT(ANSI_CSI + "m"); const std::string ANSI_COLOR_ACTION(ANSI_CSI + "1m"); const std::string ANSI_COLOR_STATUS(ANSI_CSI + "1;33m"); const std::string ANSI_COLOR_CHAT(ANSI_CSI + "m"); const std::string ANSI_COLOR_OVER(ANSI_CSI + "32m"); const std::string ANSI_COLOR_ERROR(ANSI_CSI + "1;31m"); const std::string ANSI_COLOR_INFO(ANSI_CSI + "32m"); // Wrap member function pointer for binding static ReadlineClient *CLIENT_INSTANCE = 0; int idle_hook_wrapper() { if (CLIENT_INSTANCE != NULL) { return CLIENT_INSTANCE->idle_hook(); } else { return 0; } } static std::string COMPLETION_COMMAND(""); static int COMPLETION_POSITION = 0; char *completion_generator_wrapper(const char *text, int state); char **completion(const char *text, int start, int end) { // Find position in buffer std::istringstream stream(rl_line_buffer); stream >> COMPLETION_COMMAND; // Count number of parts before current stream.str(std::string(rl_line_buffer).substr(0, start)); std::string part(""); COMPLETION_POSITION = 0; while (stream >> part) { COMPLETION_POSITION += 1; } rl_attempted_completion_over = 1; return rl_completion_matches(text, &completion_generator_wrapper); } char *completion_generator_wrapper(const char *text, int state) { // TODO: cleanup if (CLIENT_INSTANCE == NULL) return 0; static std::vector options; static int listpos; std::string current(text); if (state == 0) { options = CLIENT_INSTANCE->completion_hook(COMPLETION_COMMAND, COMPLETION_POSITION); listpos = 0; } while (listpos < options.size() && strtoupper(options.at(listpos).substr(0, current.size())) != strtoupper(current)) { listpos += 1; } if (listpos >= options.size()) { return 0; } char *result = (char *) malloc(options.at(listpos).size() + 1); strcpy(result, options.at(listpos).c_str()); listpos += 1; return result; } // ---------------- // ReadlineClient implementation ReadlineClient::ReadlineClient(std::string name): TimmiClient(name), prompt_visible_(false), first_char_(0), force_status_(false) {} void ReadlineClient::mainloop() { CLIENT_INSTANCE = this; rl_event_hook = &idle_hook_wrapper; rl_attempted_completion_function = &completion; force_status_ = true; rl_initialize(); // Parse initial messages while (players_.size() == 0 || find_player(myid_).name == "") { wait_for_event(); std::string line(""); while (socket_->getline(line)) { handle_line(line); } } prompt_visible_ = true; char *lineptr; while (good() && (lineptr = readline("timmi> "))) { std::string line(lineptr); free(lineptr); simple_iconv(line, "", line, "UTF-8", true); std::string command(""); std::istringstream stream(line); stream >> command; if (command != "") { add_history(line.c_str()); if (command == "help") { show_help(); } else if (command == "hit" || command == "move") { std::string card(""); stream >> card; send_line(command + std::string(" ") + card); while (stream >> card) { send_line(std::string("hit ") + card); } status_valid_ = false; } else if (command == "beat") { std::string tobeat(""), beater(""); while (stream >> tobeat >> beater) { send_line(std::string("beat ") + tobeat + std::string(" ") + beater); } status_valid_ = false; } else { send_line(line); } force_status_ = true; if (command != "help") { socket_->wait_for_event(1000); // Time for server to reply before printing status } } } if (!good()) { std::cout << "Disconnected"; } std::cout << std::endl; } void ReadlineClient::show_help() { std::cout << ANSI_COLOR_INFO << \ "Available generic commands:\n" \ " * chat Send a chat message\n" \ " * start Start the game (if in pregame)\n" \ " * setting id value Set a configuration option\n" \ "\n" \ "Available game commands:\n" \ " * hit card... Hit one or many cards\n" \ " * beat tobeat beater... Beat one or many cards\n" \ " * move card... Move cards by adding one or many\n" \ " * pickup Pick cards up\n" \ " * over Let table go over\n" \ "Commands may be completed by pressing tabulator.\n" \ << ANSI_COLOR_DEFAULT; } std::vector ReadlineClient::completion_hook(std::string command, int pos) const { std::vector options; if (pos == 0) { options.push_back("chat"); options.push_back("help"); if (!ingame_) { options.push_back("start"); options.push_back("setting"); } else if (turn_ == myid_ && table_.size() == 0) { options.push_back("hit"); } else if (turn_ != myid_ && table_.size() != 0) { options.push_back("hit"); options.push_back("over"); } else if (turn_ == myid_ && table_.size() != 0) { options.push_back("beat"); if (one_rank_only(table_, table_.at(0).tobeat.rank)) { options.push_back("move"); } options.push_back("pickup"); } } if (pos == 1 && command == "setting") { options.push_back("CARDS_IN_HAND"); options.push_back("OVER_DELAY"); options.push_back("DECK_COUNT"); options.push_back("ALLOW_JOKERS"); } if (pos >= 1 && (command == "hit" || command == "move")) { int i; for (i = 0; i < hand_.size(); i++) { if (table_.size() == 0 || rank_exists_on_table(table_, hand_.at(i).rank)) { options.push_back(card_to_string(hand_.at(i))); } } } if (pos >= 1 && command == "beat") { if (pos % 2 == 1) { // Odd position == card to beat int i; for (i = 0; i < table_.size(); i++) { if (!is_card(table_.at(i).beater)) { options.push_back(card_to_string(table_.at(i).tobeat)); } } } else { // What is the card to beat? std::istringstream stream(rl_line_buffer); std::string part(""); for (int i = pos; i; i--) { stream >> part; } Card tobeat = string_to_card(part); // What cards can beat it? int i; for (i = 0; i < hand_.size(); i++) { if (can_beat(tobeat, hand_.at(i), trump_.suit)) { options.push_back(card_to_string(hand_.at(i))); } } } } return options; } void ReadlineClient::show_message(std::string message) { if (prompt_visible_) { // Clear message and prompt to show own stuff first_char_ = rl_line_buffer[0]; rl_line_buffer[0] = '\0'; rl_save_prompt(); rl_clear_message(); prompt_visible_ = false; } simple_iconv(message, "UTF-8", message, "", true); std::cout << message << std::endl; } void ReadlineClient::handle_event(const Event &event) { std::ostringstream msg(""); switch (event.eventtype) { case MOVE_PICKUP: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player); msg << " picked up table: " << table_to_string(table_); break; case MOVE_HIT: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player); msg << " hit card " << card_to_string(event.owncard); break; case MOVE_BEAT: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player); msg << " beat card " << card_to_string(event.othercard); msg << " with card " << card_to_string(event.owncard); break; case MOVE_MOVE: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player); msg << " moved cards by adding " << card_to_string(event.owncard); break; case OVER_TIMEOUT: msg << ANSI_COLOR_OVER; msg << "* Table went over: Timeouted"; break; case OVER_CANTADD: msg << ANSI_COLOR_OVER; msg << "* Table went over: No player could add"; break; case OVER_LETGOOVER: msg << ANSI_COLOR_OVER; msg << "* Table went over: Players allowed"; break; case STATUS_JOIN: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player) << " joined the game"; break; case STATUS_DISCONNECTED: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player) << " disconnected"; break; case STATUS_WON: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player) << " ran out of cards and won"; break; case STATUS_OBSERVER: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player) << " changed to observer mode"; break; case STATUS_ACTIVE: msg << ANSI_COLOR_ACTION; msg << "* Player " << describe_player(event.player) << " is now active"; break; default: debug(WARNING, "Invalid event"); } msg << ANSI_COLOR_DEFAULT; show_message(msg.str()); TimmiClient::handle_event(event); } void ReadlineClient::handle_chat(int player, std::string message) { std::ostringstream msg(""); msg << ANSI_COLOR_CHAT; msg << "<" << describe_player(player) << "> " << message; msg << ANSI_COLOR_DEFAULT; show_message(msg.str()); } void ReadlineClient::handle_error(std::string errorcode) { std::ostringstream msg(""); msg << ANSI_COLOR_ERROR; msg << "-!- Error from server: " << errorcode; msg << ANSI_COLOR_DEFAULT; show_message(msg.str()); } void ReadlineClient::handle_winners(std::vector winners) { show_message(ANSI_COLOR_INFO + "--------------"); if (winners.size() != 0) { show_message("-!- Game over, winners in order:"); int i; for (i = 0; i < winners.size(); i++) { std::ostringstream msg(""); msg << "-!- " << i + 1 << ". " << describe_player(winners.at(i)); show_message(msg.str()); } } else { show_message("-!- Game over, no winners"); } show_message("--------------" + ANSI_COLOR_DEFAULT); } void ReadlineClient::show_status() { std::ostringstream msg(""); if (!status_valid_) { wait_for_event(200); do_main(); } if (!status_valid_) { send_line("status"); while (!status_valid_ && wait_for_event()) { do_main(); } } show_message(ANSI_COLOR_STATUS + "--------------"); msg << "Status: "; if (ingame_) { if (turn_ == myid_) { if (table_.size() != 0) { msg << "Your turn to beat"; } else { msg << "Your turn to hit"; } } else { if (table_.size() != 0) { msg << describe_player(turn_) << " is beating"; } else { msg << describe_player(turn_) << " is hitting"; } } if (find_player(myid_).state == OBSERVER) { msg << " (you are observer)"; } } else { msg << "Pregame, you are "; switch (find_player(myid_).state) { case ACTIVE: msg << "active"; break; case DISCONNECTED: msg << "disconnected (WTF?)"; break; case OBSERVER: msg << "observer"; break; } } show_message(msg.str()); msg.str(""); msg << "Players: "; int i; for (i = 0; i < players_.size(); i++) { if (i != 0) { msg << ", "; } msg << describe_player(players_.at(i).id); if (ingame_ && players_.at(i).state == ACTIVE) { msg << " (" << players_.at(i).handsize << " cards"; if (all_beaten(table_) && players_.at(i).id != turn_) { if (players_.at(i).let_go_over) { msg << ", let go over"; } else { msg << ", not over"; } } msg << ")"; } else if (players_.at(i).state == ACTIVE) { msg << " (active)"; } else if (players_.at(i).state == DISCONNECTED) { msg << " (disconnected)"; } else if (players_.at(i).state == OBSERVER) { msg << " (observer)"; } } show_message(msg.str()); if (!ingame_) { msg.str(""); msg << "Settings: "; msg << "CARDS_IN_HAND: " << c_cards_in_hand_; msg << ", OVER_DELAY: " << c_over_delay_; msg << ", DECK_COUNT: " << c_deck_count_; msg << ", ALLOW_JOKERS: " << c_allow_jokers_; show_message(msg.str()); } if (ingame_) { std::vector table(table_); std::sort(table.begin(), table.end(), CMPTable(trump_.suit)); msg.str(""); msg << "Deck: " << decksize_ << " Trump: " << card_to_string(trump_); msg << " Table: " << table_to_string(table); show_message(msg.str()); } if (ingame_ && find_player(myid_).state == ACTIVE) { std::vector hand(hand_); std::sort(hand.begin(), hand.end(), CMPSuit(trump_.suit)); msg.str(""); msg << "Hand: " << hand_to_string(hand); show_message(msg.str()); } std::cout << ANSI_COLOR_DEFAULT; } int ReadlineClient::idle_hook() { std::vector oldtable(table_); std::vector oldhand(hand_); std::vector oldplayers(players_); int olddeck = decksize_; int old_c_cih = c_cards_in_hand_; int old_c_od = c_over_delay_; int old_c_dc = c_deck_count_; int old_c_aj = c_allow_jokers_; std::string line(""); while (socket_->getline(line)) { handle_line(line); } if (force_status_ || !prompt_visible_ || oldtable != table_ || oldhand != hand_ || olddeck != decksize_ || oldplayers != players_ || old_c_cih != c_cards_in_hand_ || old_c_od != c_over_delay_ || old_c_dc != c_deck_count_ || old_c_aj != c_allow_jokers_) { show_status(); force_status_ = false; } if (!prompt_visible_) { // Restore prompt rl_line_buffer[0] = first_char_; rl_restore_prompt(); rl_redisplay(); prompt_visible_ = true; } if (!good()) { rl_done = 1; } return 0; }