/*------------ -------------- -------- --- ----- --- -- - -
* 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;
}