Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

499 lines
12KB

  1. /*------------ -------------- -------- --- ----- --- -- - -
  2. * Libre Blackjack - tty interactive player
  3. *
  4. * Copyright (C) 2020 jeremy theler
  5. *
  6. * This file is part of Libre Blackjack.
  7. *
  8. * Libre Blackjack is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * Libre Blackjack is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with Libre Blackjack. If not, see <http://www.gnu.org/licenses/>.
  20. *------------------- ------------ ---- -------- -- - - -
  21. */
  22. #include <iostream>
  23. #include <cstring>
  24. #include <thread>
  25. #include <chrono>
  26. #include <algorithm>
  27. #include <functional>
  28. #include <cctype>
  29. #include <locale>
  30. #ifdef HAVE_LIBREADLINE
  31. #include <readline/readline.h>
  32. #include <readline/history.h>
  33. #endif
  34. #include "conf.h"
  35. #include "blackjack.h"
  36. #include "tty.h"
  37. // TODO: make class static
  38. std::vector<std::string> commands;
  39. // trim from start (in place)
  40. static inline void ltrim(std::string &s) {
  41. s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
  42. return !std::isspace(ch);
  43. }));
  44. }
  45. // trim from end (in place)
  46. static inline void rtrim(std::string &s) {
  47. s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
  48. return !std::isspace(ch);
  49. }).base(), s.end());
  50. }
  51. // trim from both ends (in place)
  52. static inline void trim(std::string &s) {
  53. ltrim(s);
  54. rtrim(s);
  55. }
  56. Tty::Tty(Configuration &conf) {
  57. Libreblackjack::shortversion();
  58. // Libreblackjack::copyright();
  59. conf.set(&flat_bet, {"flat_bet", "flatbet"});
  60. conf.set(&no_insurance, {"no_insurance", "dont_insure"});
  61. if (commands.size() == 0) {
  62. // commands.push_back("help");
  63. commands.push_back("hit");
  64. commands.push_back("stand");
  65. commands.push_back("double");
  66. commands.push_back("split");
  67. commands.push_back("pair");
  68. commands.push_back("yes");
  69. commands.push_back("no");
  70. commands.push_back("quit");
  71. }
  72. if (conf.exists("color")) {
  73. conf.set(&color, {"color"});
  74. }
  75. if (color) {
  76. black = "\x1B[0m";
  77. red = "\x1B[31m";
  78. green = "\x1B[32m";
  79. yellow = "\x1B[33m";
  80. blue = "\x1B[34m";
  81. magenta = "\x1B[35m";
  82. cyan = "\x1B[36m";
  83. white = "\x1B[37m";
  84. reset = "\033[0m";
  85. }
  86. prompt = cyan + " > " + reset;
  87. return;
  88. }
  89. void Tty::info(Info msg, int intData) {
  90. std::string s;
  91. // TODO: choose utf8 or other representation
  92. switch (msg) {
  93. case Info::InvalidBet:
  94. if (intData < 0) {
  95. // s = "bet_negative";
  96. s = "Your bet is negative (" + std::to_string(intData) + ")";
  97. } else if (intData > 0) {
  98. // s = "bet_maximum";
  99. s = "Your bet is larger than the maximum allowed (" + std::to_string(intData) + ")";
  100. } else {
  101. // s = "bet_zero";
  102. s = "Your bet is zero";
  103. }
  104. break;
  105. case Info::NewHand:
  106. // s = "new_hand";
  107. std::cout << std::endl;
  108. s = "Starting new hand, bankroll " + std::to_string(intData);
  109. dealerHand.cards.clear();
  110. break;
  111. case Info::Shuffle:
  112. // s = "shuffle";
  113. s = "Deck needs to be shuffled.";
  114. break;
  115. case Info::CardPlayer:
  116. switch (currentHand->cards.size()) {
  117. case 1:
  118. // s = "card_player_first";
  119. s = "Player's first card is " + card[intData].utf8();
  120. break;
  121. case 2:
  122. // s = "card_player_second";
  123. s = "Player's second card is " + card[intData].utf8();
  124. break;
  125. default:
  126. // s = "card_player";
  127. s = "Player's card is " + card[intData].utf8();
  128. break;
  129. }
  130. break;
  131. case Info::CardDealer:
  132. if (intData != -1) {
  133. switch (dealerHand.cards.size()) {
  134. case 0:
  135. // s = "card_dealer_up";
  136. s = "Dealer's up card is " + card[intData].utf8();
  137. break;
  138. default:
  139. // s = "card_dealer";
  140. s = "Dealer's card is " + card[intData].utf8();
  141. break;
  142. }
  143. } else {
  144. s = "Dealer's hole card is dealt";
  145. }
  146. dealerHand.cards.push_back(intData);
  147. break;
  148. case Info::CardDealerRevealsHole:
  149. // s = "card_dealer_hole";
  150. s = "Dealer's hole card was " + card[intData].utf8();
  151. *(++(dealerHand.cards.begin())) = intData;
  152. // renderTable();
  153. break;
  154. case Info::DealerBlackjack:
  155. // s = "dealer_blackjack";
  156. s = "Dealer has Blackjack";
  157. break;
  158. case Info::PlayerWinsInsurance:
  159. // s = "player_wins_insurance";
  160. s = "Player wins insurance";
  161. break;
  162. case Info::PlayerBlackjackAlso:
  163. // s = "player_blackjack_also";
  164. s = "Player also has Blackjack";
  165. renderTable();
  166. break;
  167. case Info::PlayerPushes:
  168. // s = "player_pushes";
  169. s = "Player pushes";
  170. renderTable();
  171. break;
  172. case Info::PlayerLosses:
  173. // s = "player_losses";
  174. s = "Player losses";
  175. renderTable();
  176. break;
  177. case Info::PlayerBlackjack:
  178. // s = "blackjack_player";
  179. s = "Player has Blackjack";
  180. renderTable();
  181. break;
  182. case Info::PlayerWins:
  183. // s = "player_wins";
  184. s = "Player wins " + std::to_string(intData);
  185. renderTable();
  186. break;
  187. case Info::NoBlackjacks:
  188. // s = "no_blackjacks";
  189. s = "No blackjacks";
  190. break;
  191. case Info::PlayerBustsAllHands:
  192. // s = "player_busted_all_hands";
  193. if (hands.size() == 1) {
  194. s = "Player busted";
  195. } else {
  196. s = "Player busted all hands";
  197. }
  198. renderTable();
  199. break;
  200. case Info::DealerBusts:
  201. // s = "no_blackjacks";
  202. s = "Dealer busts!";
  203. renderTable();
  204. break;
  205. case Info::Help:
  206. std::cout << "help yourself" << std::endl;
  207. break;
  208. case Info::Bye:
  209. // s = "bye";
  210. s = "Bye bye! We'll play Blackjack again next time.";
  211. break;
  212. case Info::None:
  213. break;
  214. }
  215. if (delay > 0) {
  216. std::this_thread::sleep_for(std::chrono::milliseconds(delay));
  217. }
  218. std::cout << green << s << reset << std::endl;
  219. return;
  220. }
  221. int Tty::play() {
  222. std::string s;
  223. switch (actionRequired) {
  224. case PlayerActionRequired::Bet:
  225. s = "Bet?";
  226. break;
  227. case PlayerActionRequired::Insurance:
  228. renderTable();
  229. s = "Insurance?";
  230. break;
  231. case PlayerActionRequired::Play:
  232. renderTable();
  233. s = "Play?";
  234. break;
  235. case PlayerActionRequired::None:
  236. break;
  237. }
  238. if (s != "") {
  239. if (delay > 0) {
  240. std::this_thread::sleep_for(std::chrono::milliseconds(delay));
  241. }
  242. std::cout << yellow << " <-- " << s << reset << std::endl;
  243. }
  244. #ifdef HAVE_LIBREADLINE
  245. rl_attempted_completion_function = rl_completion;
  246. if ((input_buffer = readline(prompt.c_str())) == nullptr) {
  247. // EOF means "quit"
  248. actionTaken = PlayerActionTaken::Quit;
  249. std::cout << std::endl;
  250. } else {
  251. add_history(input_buffer);
  252. actionTaken = PlayerActionTaken::None;
  253. // TODO: better solution
  254. std::string command = input_buffer;
  255. trim(command);
  256. // check common commands first
  257. if (command == "quit" || command == "q") {
  258. actionTaken = PlayerActionTaken::Quit;
  259. } else if (command == "help") {
  260. actionTaken = PlayerActionTaken::Help;
  261. } else if (command == "count" || command == "c") {
  262. actionTaken = PlayerActionTaken::Count;
  263. } else if (command == "upcard" || command == "u") {
  264. actionTaken = PlayerActionTaken::UpcardValue;
  265. } else if (command == "bankroll" || command == "b") {
  266. actionTaken = PlayerActionTaken::Bankroll;
  267. } else if (command == "hands") {
  268. actionTaken = PlayerActionTaken::Hands;
  269. }
  270. if (actionTaken == PlayerActionTaken::None) {
  271. switch (actionRequired) {
  272. case PlayerActionRequired::Bet:
  273. currentBet = std::stoi(input_buffer);
  274. actionTaken = PlayerActionTaken::Bet;
  275. break;
  276. case PlayerActionRequired::Insurance:
  277. if (command == "y" || command == "yes") {
  278. actionTaken = PlayerActionTaken::Insure;
  279. } else if (command == "n" || command == "no") {
  280. actionTaken = PlayerActionTaken::DontInsure;
  281. } else {
  282. // TODO: chosse if we allow not(yes) == no
  283. actionTaken = PlayerActionTaken::None;
  284. }
  285. break;
  286. case PlayerActionRequired::Play:
  287. // TODO: sort by higher-expected response first
  288. if (command == "h" || command =="hit") {
  289. actionTaken = PlayerActionTaken::Hit;
  290. } else if (command == "s" || command == "stand") {
  291. actionTaken = PlayerActionTaken::Stand;
  292. } else if (command == "d" || command == "double") {
  293. actionTaken = PlayerActionTaken::Stand;
  294. } else if (command == "p" || command == "split" || command == "pair") {
  295. actionTaken = PlayerActionTaken::Split;
  296. } else {
  297. actionTaken = PlayerActionTaken::None;
  298. }
  299. break;
  300. case PlayerActionRequired::None:
  301. break;
  302. }
  303. }
  304. free(input_buffer);
  305. }
  306. #else
  307. std::cout << prompt;
  308. std::cin >> input_buffer;
  309. // TODO: check EOF
  310. // TODO: esto puede ir en algo comun para tty y stdout
  311. if (input_buffer == "hit" || input_buffer == "h") {
  312. *command = Command::Hit;
  313. } else {
  314. *command = Command::None;
  315. }
  316. #endif
  317. return 0;
  318. }
  319. void Tty::renderTable(void) {
  320. std::cout << " -- Dealer's hand: --------" << std::endl;
  321. renderHand(&dealerHand);
  322. std::cout << " Total: " << dealerHand.total() << std::endl;
  323. std::cout << " -- Player's hand --------" << std::endl;
  324. for (auto hand : hands) {
  325. renderHand(&hand);
  326. std::cout << " Total: " << hand.total() << std::endl;
  327. }
  328. return;
  329. }
  330. void Tty::renderHand(Hand *hand) {
  331. for (unsigned int i = 0; i < hand->cards.size(); i++) {
  332. std::cout << " _____ ";
  333. }
  334. std::cout << std::endl;
  335. unsigned int i = 0;
  336. for (auto it : hand->cards) {
  337. if (it >= 0) {
  338. std::cout << "|" << card[it].getNumberASCII() << ((card[it].number != 10)?" ":"") << " | ";
  339. } else {
  340. std::cout << "|#####| ";
  341. }
  342. i++;
  343. }
  344. std::cout << std::endl;
  345. i = 0;
  346. for (auto it : hand->cards) {
  347. if (it >= 0) {
  348. std::cout << "| | ";
  349. } else {
  350. std::cout << "|#####| ";
  351. }
  352. i++;
  353. }
  354. std::cout << std::endl;
  355. i = 0;
  356. for (auto it : hand->cards) {
  357. if (it >= 0) {
  358. std::cout << "| " << card[it].getSuitUTF8() << " | ";
  359. } else {
  360. std::cout << "|#####| ";
  361. }
  362. i++;
  363. }
  364. std::cout << std::endl;
  365. i = 0;
  366. for (auto it : hand->cards) {
  367. if (it >= 0) {
  368. std::cout << "| | ";
  369. } else {
  370. std::cout << "|#####| ";
  371. }
  372. i++;
  373. }
  374. std::cout << std::endl;
  375. i = 0;
  376. for (auto it : hand->cards) {
  377. if (it >= 0) {
  378. std::cout << "|___" << ((card[it].number != 10)?"_":"") << card[it].getNumberASCII() << "| ";
  379. } else {
  380. std::cout << "|#####| ";
  381. }
  382. i++;
  383. }
  384. std::cout << std::endl;
  385. return;
  386. }
  387. int Tty::list_index = 0;
  388. int Tty::len = 0;
  389. char *Tty::rl_command_generator(const char *text, int state) {
  390. if (!state) {
  391. list_index = 0;
  392. len = strlen(text);
  393. }
  394. for (unsigned int i = list_index; i < commands.size(); i++) {
  395. if (commands[i].compare(0, len, text) == 0) {
  396. list_index = i+1;
  397. return strdup(commands[i].c_str());
  398. }
  399. }
  400. return NULL;
  401. }
  402. char **Tty::rl_completion(const char *text, int start, int end) {
  403. char **matches = NULL;
  404. #ifdef HAVE_LIBREADLINE
  405. matches = rl_completion_matches(text, rl_command_generator);
  406. #endif
  407. return matches;
  408. }