選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

tty.cpp 12KB

5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
5年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  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. }