/*------------ -------------- -------- --- ----- --- -- - - * Libre Blackjack - tty interactive player * * Copyright (C) 2020 jeremy theler * * This file is part of Libre Blackjack. * * Libre Blackjack 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. * * Libre Blackjack is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Libre Blackjack. If not, see . *------------------- ------------ ---- -------- -- - - - */ #include #include #include #include #include #include #include #include #ifdef HAVE_LIBREADLINE #include #include #endif #include "conf.h" #include "blackjack.h" #include "tty.h" // TODO: make class static std::vector commands; // trim from start (in place) static inline void ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { return !std::isspace(ch); })); } // trim from end (in place) static inline void rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { return !std::isspace(ch); }).base(), s.end()); } // trim from both ends (in place) static inline void trim(std::string &s) { ltrim(s); rtrim(s); } Tty::Tty(Configuration &conf) { Libreblackjack::shortversion(); // Libreblackjack::copyright(); conf.set(&flat_bet, {"flat_bet", "flatbet"}); conf.set(&no_insurance, {"no_insurance", "dont_insure"}); if (commands.size() == 0) { // commands.push_back("help"); commands.push_back("hit"); commands.push_back("stand"); commands.push_back("double"); commands.push_back("split"); commands.push_back("pair"); commands.push_back("yes"); commands.push_back("no"); commands.push_back("quit"); } if (conf.exists("color")) { conf.set(&color, {"color"}); } if (color) { black = "\x1B[0m"; red = "\x1B[31m"; green = "\x1B[32m"; yellow = "\x1B[33m"; blue = "\x1B[34m"; magenta = "\x1B[35m"; cyan = "\x1B[36m"; white = "\x1B[37m"; reset = "\033[0m"; } prompt = cyan + " > " + reset; return; } void Tty::info(Info msg, int intData) { std::string s; // TODO: choose utf8 or other representation switch (msg) { case Info::InvalidBet: if (intData < 0) { // s = "bet_negative"; s = "Your bet is negative (" + std::to_string(intData) + ")"; } else if (intData > 0) { // s = "bet_maximum"; s = "Your bet is larger than the maximum allowed (" + std::to_string(intData) + ")"; } else { // s = "bet_zero"; s = "Your bet is zero"; } break; case Info::NewHand: // s = "new_hand"; std::cout << std::endl; s = "Starting new hand, bankroll " + std::to_string(intData); dealerHand.cards.clear(); break; case Info::Shuffle: // s = "shuffle"; s = "Deck needs to be shuffled."; break; case Info::CardPlayer: switch (currentHand->cards.size()) { case 1: // s = "card_player_first"; s = "Player's first card is " + card[intData].utf8(); break; case 2: // s = "card_player_second"; s = "Player's second card is " + card[intData].utf8(); break; default: // s = "card_player"; s = "Player's card is " + card[intData].utf8(); break; } break; case Info::CardDealer: if (intData != -1) { switch (dealerHand.cards.size()) { case 0: // s = "card_dealer_up"; s = "Dealer's up card is " + card[intData].utf8(); break; default: // s = "card_dealer"; s = "Dealer's card is " + card[intData].utf8(); break; } } else { s = "Dealer's hole card is dealt"; } dealerHand.cards.push_back(intData); break; case Info::CardDealerRevealsHole: // s = "card_dealer_hole"; s = "Dealer's hole card was " + card[intData].utf8(); *(++(dealerHand.cards.begin())) = intData; // renderTable(); break; case Info::DealerBlackjack: // s = "dealer_blackjack"; s = "Dealer has Blackjack"; break; case Info::PlayerWinsInsurance: // s = "player_wins_insurance"; s = "Player wins insurance"; break; case Info::PlayerBlackjackAlso: // s = "player_blackjack_also"; s = "Player also has Blackjack"; renderTable(); break; case Info::PlayerPushes: // s = "player_pushes"; s = "Player pushes"; renderTable(); break; case Info::PlayerLosses: // s = "player_losses"; s = "Player losses"; renderTable(); break; case Info::PlayerBlackjack: // s = "blackjack_player"; s = "Player has Blackjack"; renderTable(); break; case Info::PlayerWins: // s = "player_wins"; s = "Player wins " + std::to_string(intData); renderTable(); break; case Info::NoBlackjacks: // s = "no_blackjacks"; s = "No blackjacks"; break; case Info::PlayerBustsAllHands: // s = "player_busted_all_hands"; if (hands.size() == 1) { s = "Player busted"; } else { s = "Player busted all hands"; } renderTable(); break; case Info::DealerBusts: // s = "no_blackjacks"; s = "Dealer busts!"; renderTable(); break; case Info::Help: std::cout << "help yourself" << std::endl; break; case Info::Bye: // s = "bye"; s = "Bye bye! We'll play Blackjack again next time."; break; case Info::None: break; } if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } std::cout << green << s << reset << std::endl; return; } int Tty::play() { std::string s; switch (actionRequired) { case PlayerActionRequired::Bet: s = "Bet?"; break; case PlayerActionRequired::Insurance: renderTable(); s = "Insurance?"; break; case PlayerActionRequired::Play: renderTable(); s = "Play?"; break; case PlayerActionRequired::None: break; } if (s != "") { if (delay > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); } std::cout << yellow << " <-- " << s << reset << std::endl; } #ifdef HAVE_LIBREADLINE rl_attempted_completion_function = rl_completion; if ((input_buffer = readline(prompt.c_str())) == nullptr) { // EOF means "quit" actionTaken = PlayerActionTaken::Quit; std::cout << std::endl; } else { add_history(input_buffer); actionTaken = PlayerActionTaken::None; // TODO: better solution std::string command = input_buffer; trim(command); // check common commands first if (command == "quit" || command == "q") { actionTaken = PlayerActionTaken::Quit; } else if (command == "help") { actionTaken = PlayerActionTaken::Help; } else if (command == "count" || command == "c") { actionTaken = PlayerActionTaken::Count; } else if (command == "upcard" || command == "u") { actionTaken = PlayerActionTaken::UpcardValue; } else if (command == "bankroll" || command == "b") { actionTaken = PlayerActionTaken::Bankroll; } else if (command == "hands") { actionTaken = PlayerActionTaken::Hands; } if (actionTaken == PlayerActionTaken::None) { switch (actionRequired) { case PlayerActionRequired::Bet: currentBet = std::stoi(input_buffer); actionTaken = PlayerActionTaken::Bet; break; case PlayerActionRequired::Insurance: if (command == "y" || command == "yes") { actionTaken = PlayerActionTaken::Insure; } else if (command == "n" || command == "no") { actionTaken = PlayerActionTaken::DontInsure; } else { // TODO: chosse if we allow not(yes) == no actionTaken = PlayerActionTaken::None; } break; case PlayerActionRequired::Play: // TODO: sort by higher-expected response first if (command == "h" || command =="hit") { actionTaken = PlayerActionTaken::Hit; } else if (command == "s" || command == "stand") { actionTaken = PlayerActionTaken::Stand; } else if (command == "d" || command == "double") { actionTaken = PlayerActionTaken::Stand; } else if (command == "p" || command == "split" || command == "pair") { actionTaken = PlayerActionTaken::Split; } else { actionTaken = PlayerActionTaken::None; } break; case PlayerActionRequired::None: break; } } free(input_buffer); } #else std::cout << prompt; std::cin >> input_buffer; // TODO: check EOF // TODO: esto puede ir en algo comun para tty y stdout if (input_buffer == "hit" || input_buffer == "h") { *command = Command::Hit; } else { *command = Command::None; } #endif return 0; } void Tty::renderTable(void) { std::cout << " -- Dealer's hand: --------" << std::endl; renderHand(&dealerHand); std::cout << " Total: " << dealerHand.total() << std::endl; std::cout << " -- Player's hand --------" << std::endl; for (auto hand : hands) { renderHand(&hand); std::cout << " Total: " << hand.total() << std::endl; } return; } void Tty::renderHand(Hand *hand) { for (unsigned int i = 0; i < hand->cards.size(); i++) { std::cout << " _____ "; } std::cout << std::endl; unsigned int i = 0; for (auto it : hand->cards) { if (it >= 0) { std::cout << "|" << card[it].getNumberASCII() << ((card[it].number != 10)?" ":"") << " | "; } else { std::cout << "|#####| "; } i++; } std::cout << std::endl; i = 0; for (auto it : hand->cards) { if (it >= 0) { std::cout << "| | "; } else { std::cout << "|#####| "; } i++; } std::cout << std::endl; i = 0; for (auto it : hand->cards) { if (it >= 0) { std::cout << "| " << card[it].getSuitUTF8() << " | "; } else { std::cout << "|#####| "; } i++; } std::cout << std::endl; i = 0; for (auto it : hand->cards) { if (it >= 0) { std::cout << "| | "; } else { std::cout << "|#####| "; } i++; } std::cout << std::endl; i = 0; for (auto it : hand->cards) { if (it >= 0) { std::cout << "|___" << ((card[it].number != 10)?"_":"") << card[it].getNumberASCII() << "| "; } else { std::cout << "|#####| "; } i++; } std::cout << std::endl; return; } int Tty::list_index = 0; int Tty::len = 0; char *Tty::rl_command_generator(const char *text, int state) { if (!state) { list_index = 0; len = strlen(text); } for (unsigned int i = list_index; i < commands.size(); i++) { if (commands[i].compare(0, len, text) == 0) { list_index = i+1; return strdup(commands[i].c_str()); } } return NULL; } char **Tty::rl_completion(const char *text, int start, int end) { char **matches = NULL; #ifdef HAVE_LIBREADLINE matches = rl_completion_matches(text, rl_command_generator); #endif return matches; }