play.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847
  1. #include "play.h"
  2. #include "db.h"
  3. #include "deck.h"
  4. #include "version.h"
  5. #include <iomanip> // put_time
  6. #include <sstream>
  7. // configuration settings access
  8. #include "yaml-cpp/yaml.h"
  9. extern YAML::Node config;
  10. #define CHEATER "CHEAT_YOUR_ASS_OFF"
  11. static std::function<std::ofstream &(void)> get_logger;
  12. static int press_a_key(door::Door &door) {
  13. door << door::reset << "Press a key to continue...";
  14. int r = door.sleep_key(door.inactivity);
  15. door << door::nl;
  16. return r;
  17. }
  18. /*
  19. In the future, this will probably check to see if they can play today or not, as
  20. well as displaying a calendar to show what days are available to be played.
  21. For now, it will play today.
  22. */
  23. PlayCards::PlayCards(door::Door &d, DBData &dbd) : door{d}, db{dbd} {
  24. get_logger = [this]() -> ofstream & { return door.log(); };
  25. init_values();
  26. play_day = std::chrono::system_clock::now();
  27. // adjustment
  28. time_t time_play_day = std::chrono::system_clock::to_time_t(play_day);
  29. if (get_logger) {
  30. get_logger() << "before: "
  31. << std::put_time(std::localtime(&time_play_day), "%F %R")
  32. << std::endl;
  33. };
  34. normalizeDate(time_play_day);
  35. play_day = std::chrono::system_clock::from_time_t(time_play_day);
  36. if (get_logger) {
  37. get_logger() << "after: "
  38. << std::put_time(std::localtime(&time_play_day), "%F %R")
  39. << std::endl;
  40. };
  41. spaceAceTriPeaks = make_tripeaks();
  42. score_panel = make_score_panel();
  43. streak_panel = make_streak_panel();
  44. left_panel = make_left_panel();
  45. cmd_panel = make_command_panel();
  46. /*
  47. int mx = door.width;
  48. int my = door.height;
  49. */
  50. }
  51. PlayCards::~PlayCards() { get_logger = nullptr; }
  52. void PlayCards::init_values(void) {
  53. hand = 1;
  54. if (config["hands_per_day"]) {
  55. total_hands = config["hands_per_day"].as<int>();
  56. } else
  57. total_hands = 3;
  58. play_card = 28;
  59. current_streak = 0;
  60. best_streak = 0;
  61. std::string best;
  62. best = db.getSetting("best_streak", "0");
  63. best_streak = std::stoi(best);
  64. select_card = 23;
  65. score = 0;
  66. }
  67. void PlayCards::bonus(void) {
  68. door << door::ANSIColor(door::COLOR::YELLOW, door::ATTR::BOLD) << "BONUS";
  69. }
  70. int PlayCards::play_cards(void) {
  71. init_values();
  72. std::string currentDefault = db.getSetting("DeckColor", "ALL");
  73. get_logger() << "DeckColor shows as " << currentDefault << std::endl;
  74. deck_color = stringToANSIColor(currentDefault);
  75. dp = Deck(deck_color);
  76. // Calculate the game size
  77. int game_width;
  78. int game_height = 20;
  79. {
  80. int cx, cy;
  81. cardPos(27, cx, cy);
  82. game_width = cx + 5;
  83. }
  84. int mx = door.width;
  85. int my = door.height;
  86. off_x = (mx - game_width) / 2;
  87. off_y = (my - game_height) / 2;
  88. // int true_off_y = off_y;
  89. // we can now position things properly centered
  90. int tp_off_x = (mx - spaceAceTriPeaks->getWidth()) / 2;
  91. spaceAceTriPeaks->set(tp_off_x, off_y);
  92. off_y += 3; // adjust for tripeaks panel
  93. next_hand:
  94. play_card = 28;
  95. select_card = 23;
  96. score = 0;
  97. current_streak = 0;
  98. // Use play_day to seed the rng
  99. {
  100. time_t tt = std::chrono::system_clock::to_time_t(play_day);
  101. tm local_tm = *localtime(&tt);
  102. std::seed_seq seq{local_tm.tm_year + 1900, local_tm.tm_mon + 1,
  103. local_tm.tm_mday, hand};
  104. deck = shuffleCards(seq, 1);
  105. state = makeCardStates();
  106. }
  107. /*
  108. door::Panel score_panel = make_score_panel();
  109. door::Panel streak_panel = make_streak_panel();
  110. door::Panel left_panel = make_left_panel();
  111. door::Panel cmd_panel = make_command_panel();
  112. */
  113. {
  114. int off_yp = off_y + 11;
  115. int cxp, cyp;
  116. int left_panel_x, right_panel_x;
  117. // find position of card, to position the panels
  118. cardPos(18, cxp, cyp);
  119. left_panel_x = cxp;
  120. cardPos(15, cxp, cyp);
  121. right_panel_x = cxp;
  122. score_panel->set(left_panel_x + off_x, off_yp);
  123. streak_panel->set(right_panel_x + off_x, off_yp);
  124. cmd_panel->set(left_panel_x + off_x, off_yp + 5);
  125. }
  126. bool dealing = true;
  127. int r = 0;
  128. redraw(dealing);
  129. dealing = false;
  130. left_panel->update(door);
  131. door << door::reset;
  132. door::Panel *c;
  133. bool in_game = true;
  134. bool save_streak = false;
  135. while (in_game) {
  136. // time might have updated, so update score panel too.
  137. score_panel->update(door);
  138. // do the save here -- try to hide the database lag
  139. if (save_streak) {
  140. save_streak = false;
  141. std::string best = std::to_string(best_streak);
  142. db.setSetting("best_streak", best);
  143. }
  144. r = door.sleep_key(door.inactivity);
  145. if (r > 0) {
  146. // not a timeout or expire.
  147. if (r < 0x1000) // not a function key
  148. r = std::toupper(r);
  149. switch (r) {
  150. case '\x0d':
  151. if (play_card < 51) {
  152. play_card++;
  153. current_streak = 0;
  154. streak_panel->update(door);
  155. // update the cards left_panel
  156. left_panel->update(door);
  157. // Ok, deal next card from the pile.
  158. int cx, cy, level;
  159. if (play_card == 51) {
  160. cardPosLevel(29, cx, cy, level);
  161. level = 0; // out of cards
  162. c = dp.back(level);
  163. c->set(cx + off_x, cy + off_y);
  164. door << *c;
  165. }
  166. cardPos(28, cx, cy);
  167. c = dp.card(deck.at(play_card));
  168. c->set(cx + off_x, cy + off_y);
  169. door << *c;
  170. }
  171. break;
  172. case 'R':
  173. redraw(false);
  174. break;
  175. case 'Q':
  176. // possibly prompt here for [N]ext hand or [Q]uit ?
  177. in_game = false;
  178. break;
  179. case ' ':
  180. case '5':
  181. // can we play this card?
  182. /*
  183. get_logger() << "canPlay( " << select_card << ":"
  184. << deck1.at(select_card) << "/"
  185. << d.getRank(deck1.at(select_card)) << " , "
  186. << play_card << "/" <<
  187. d.getRank(deck1.at(play_card))
  188. << ") = "
  189. << d.canPlay(deck1.at(select_card),
  190. deck1.at(play_card))
  191. << std::endl;
  192. */
  193. if (dp.canPlay(deck.at(select_card), deck.at(play_card)) or
  194. config[CHEATER]) {
  195. // if (true) {
  196. // yes we can.
  197. ++current_streak;
  198. if (current_streak > best_streak) {
  199. best_streak = current_streak;
  200. if (!config[CHEATER]) {
  201. save_streak = true;
  202. }
  203. }
  204. streak_panel->update(door);
  205. score += 10;
  206. if (current_streak > 1)
  207. score += current_streak * 5;
  208. score_panel->update(door);
  209. /*
  210. if (get_logger)
  211. get_logger() << "score_panel update : " << score << std::endl;
  212. */
  213. // play card!
  214. state.at(select_card) = 2;
  215. {
  216. // swap the select card with play_card
  217. int temp = deck.at(select_card);
  218. deck.at(select_card) = deck.at(play_card);
  219. deck.at(play_card) = temp;
  220. // select_card is -- invalidated here! find "new" card.
  221. int cx, cy;
  222. // erase/clear select_card
  223. std::vector<int> check = dp.unblocks(select_card);
  224. bool left = false, right = false;
  225. for (const int c : check) {
  226. std::pair<int, int> blockers = dp.blocks[c];
  227. if (blockers.first == select_card)
  228. right = true;
  229. if (blockers.second == select_card)
  230. left = true;
  231. }
  232. dp.removeCard(door, select_card, off_x, off_y, left, right);
  233. /* // old way of doing this that leaves holes.
  234. cardPosLevel(select_card, cx, cy, level);
  235. c = d.back(0);
  236. c->set(cx + off_x, cy + off_y);
  237. door << *c;
  238. */
  239. // redraw play card #28. (Which is the "old" select_card)
  240. cardPos(28, cx, cy);
  241. c = dp.card(deck.at(play_card));
  242. c->set(cx + off_x, cy + off_y);
  243. door << *c;
  244. // Did we unhide a card here?
  245. // std::vector<int> check = d.unblocks(select_card);
  246. /*
  247. get_logger() << "select_card = " << select_card
  248. << " unblocks: " << check.size() << std::endl;
  249. */
  250. int new_card_shown = -1;
  251. if (!check.empty()) {
  252. for (const int to_check : check) {
  253. std::pair<int, int> blockers = dp.blocks[to_check];
  254. /*
  255. get_logger()
  256. << "Check: " << to_check << " " << blockers.first << ","
  257. << blockers.second << " " << state.at(blockers.first)
  258. << "," << state.at(blockers.second) << std::endl;
  259. */
  260. if ((state.at(blockers.first) == 2) and
  261. (state.at(blockers.second) == 2)) {
  262. // WOOT! Card uncovered.
  263. /*
  264. get_logger() << "showing: " << to_check << std::endl;
  265. */
  266. state.at(to_check) = 1;
  267. cardPos(to_check, cx, cy);
  268. c = dp.card(deck.at(to_check));
  269. c->set(cx + off_x, cy + off_y);
  270. door << *c;
  271. new_card_shown = to_check;
  272. }
  273. }
  274. } else {
  275. // top card cleared
  276. // get_logger() << "top card cleared?" << std::endl;
  277. // display something at select_card position
  278. int cx, cy;
  279. cardPos(select_card, cx, cy);
  280. door << door::Goto(cx + off_x, cy + off_y);
  281. bonus();
  282. score += 100;
  283. state.at(select_card) = 3; // handle this in the "redraw"
  284. score_panel->update(door);
  285. }
  286. // Find new "number" for select_card to be.
  287. if (new_card_shown != -1) {
  288. select_card = new_card_shown;
  289. } else {
  290. // select_card++;
  291. int new_select = findClosestActiveCard(state, select_card);
  292. if (new_select != -1) {
  293. select_card = new_select;
  294. } else {
  295. get_logger() << "Winner!" << std::endl;
  296. // bonus for cards left
  297. int bonus = 15 * (51 - play_card);
  298. score += 15 * (51 - play_card);
  299. score_panel->update(door);
  300. door << " BONUS: " << bonus << door::nl;
  301. get_logger() << "SCORE: " << score << std::endl;
  302. // if (!config[CHEATER]) {
  303. time_t right_now = std::chrono::system_clock::to_time_t(
  304. std::chrono::system_clock::now());
  305. db.saveScore(right_now,
  306. std::chrono::system_clock::to_time_t(play_day),
  307. hand, score);
  308. //}
  309. press_a_key(door);
  310. if (hand < total_hands) {
  311. hand++;
  312. // current_streak = 0;
  313. door << door::reset << door::cls;
  314. goto next_hand;
  315. }
  316. r = 'Q';
  317. in_game = false;
  318. }
  319. }
  320. // update the select_card marker!
  321. cardPos(select_card, cx, cy);
  322. c = dp.marker(1);
  323. c->set(cx + off_x + 2, cy + off_y + 2);
  324. door << *c;
  325. }
  326. }
  327. break;
  328. case XKEY_LEFT_ARROW:
  329. case '4': {
  330. int new_select = findNextActiveCard(true, state, select_card);
  331. /*
  332. int new_active = active_card - 1;
  333. while (new_active >= 0) {
  334. if (state.at(new_active) == 1)
  335. break;
  336. --new_active;
  337. }*/
  338. if (new_select >= 0) {
  339. int cx, cy;
  340. cardPos(select_card, cx, cy);
  341. c = dp.marker(0);
  342. c->set(cx + off_x + 2, cy + off_y + 2);
  343. door << *c;
  344. select_card = new_select;
  345. cardPos(select_card, cx, cy);
  346. c = dp.marker(1);
  347. c->set(cx + off_x + 2, cy + off_y + 2);
  348. door << *c;
  349. }
  350. } break;
  351. case XKEY_RIGHT_ARROW:
  352. case '6': {
  353. int new_select = findNextActiveCard(false, state, select_card);
  354. /*
  355. int new_active = active_card + 1;
  356. while (new_active < 28) {
  357. if (state.at(new_active) == 1)
  358. break;
  359. ++new_active;
  360. }
  361. */
  362. if (new_select >= 0) { //(new_active < 28) {
  363. int cx, cy;
  364. cardPos(select_card, cx, cy);
  365. c = dp.marker(0);
  366. c->set(cx + off_x + 2, cy + off_y + 2);
  367. door << *c;
  368. select_card = new_select;
  369. cardPos(select_card, cx, cy);
  370. c = dp.marker(1);
  371. c->set(cx + off_x + 2, cy + off_y + 2);
  372. door << *c;
  373. }
  374. }
  375. break;
  376. }
  377. } else
  378. in_game = false;
  379. }
  380. if (r == 'Q') {
  381. // continue with hand, or quit?
  382. // continue -- eat r & redraw.
  383. // quit, save score and exit (unless score is zero).
  384. }
  385. return r;
  386. }
  387. void PlayCards::redraw(bool dealing) {
  388. door::Panel *c;
  389. door << door::reset << door::cls;
  390. door << *spaceAceTriPeaks;
  391. {
  392. // step 1:
  393. // draw the deck "source"
  394. int cx, cy, level;
  395. cardPosLevel(29, cx, cy, level);
  396. if (play_card == 51)
  397. level = 0; // out of cards!
  398. c = dp.back(level);
  399. c->set(cx + off_x, cy + off_y);
  400. // p3 is heigh below
  401. left_panel->set(cx + off_x, cy + off_y + height);
  402. // how do I update these? (hand >1)
  403. score_panel->update();
  404. left_panel->update();
  405. streak_panel->update();
  406. cmd_panel->update();
  407. door << *score_panel << *left_panel << *streak_panel << *cmd_panel;
  408. door << *c;
  409. if (dealing)
  410. std::this_thread::sleep_for(std::chrono::seconds(1));
  411. }
  412. for (int x = 0; x < (dealing ? 28 : 29); x++) {
  413. int cx, cy, level;
  414. cardPosLevel(x, cx, cy, level);
  415. // This is hardly visible.
  416. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  417. if (dealing)
  418. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  419. if (dealing) {
  420. c = dp.back(level);
  421. c->set(cx + off_x, cy + off_y);
  422. door << *c;
  423. } else {
  424. // redrawing -- draw the cards with their correct "state"
  425. int s = state.at(x);
  426. switch (s) {
  427. case 0:
  428. c = dp.back(level);
  429. c->set(cx + off_x, cy + off_y);
  430. door << *c;
  431. break;
  432. case 1:
  433. // cardPosLevel(x, space, height, cx, cy, level);
  434. if (x == 28)
  435. c = dp.card(deck.at(play_card));
  436. else
  437. c = dp.card(deck.at(x));
  438. c->set(cx + off_x, cy + off_y);
  439. door << *c;
  440. break;
  441. case 2:
  442. // no card to draw. :)
  443. break;
  444. case 3:
  445. // peak cleared, draw bonus
  446. door << door::Goto(cx + off_x, cy + off_y);
  447. bonus();
  448. break;
  449. }
  450. }
  451. }
  452. if (dealing)
  453. for (int x = 18; x < 29; x++) {
  454. int cx, cy;
  455. state.at(x) = 1;
  456. cardPos(x, cx, cy);
  457. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  458. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  459. c = dp.card(deck.at(x));
  460. c->set(cx + off_x, cy + off_y);
  461. door << *c;
  462. }
  463. {
  464. int cx, cy;
  465. cardPos(select_card, cx, cy);
  466. c = dp.marker(1);
  467. c->set(cx + off_x + 2, cy + off_y + 2);
  468. door << *c;
  469. }
  470. }
  471. door::renderFunction PlayCards::statusValue(door::ANSIColor status,
  472. door::ANSIColor value) {
  473. door::renderFunction rf = [status,
  474. value](const std::string &txt) -> door::Render {
  475. door::Render r(txt);
  476. door::ColorOutput co;
  477. co.pos = 0;
  478. co.len = 0;
  479. co.c = status;
  480. size_t pos = txt.find(':');
  481. if (pos == std::string::npos) {
  482. // failed to find :, render digits/numbers in value color
  483. int tpos = 0;
  484. for (char const &c : txt) {
  485. if (std::isdigit(c)) {
  486. if (co.c != value) {
  487. r.outputs.push_back(co);
  488. co.reset();
  489. co.pos = tpos;
  490. co.c = value;
  491. }
  492. } else {
  493. if (co.c != status) {
  494. r.outputs.push_back(co);
  495. co.reset();
  496. co.pos = tpos;
  497. co.c = status;
  498. }
  499. }
  500. co.len++;
  501. tpos++;
  502. }
  503. if (co.len != 0)
  504. r.outputs.push_back(co);
  505. } else {
  506. pos++; // Have : in status color
  507. co.len = pos;
  508. r.outputs.push_back(co);
  509. co.reset();
  510. co.pos = pos;
  511. co.c = value;
  512. co.len = txt.length() - pos;
  513. r.outputs.push_back(co);
  514. }
  515. return r;
  516. };
  517. return rf;
  518. }
  519. std::unique_ptr<door::Panel> PlayCards::make_score_panel() {
  520. const int W = 25;
  521. std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
  522. p->setStyle(door::BorderStyle::NONE);
  523. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  524. door::ATTR::BOLD);
  525. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  526. door::ATTR::BOLD);
  527. door::renderFunction svRender = statusValue(statusColor, valueColor);
  528. // or use renderStatus as defined above.
  529. // We'll stick with these for now.
  530. {
  531. std::string userString = "Name: ";
  532. userString += door.username;
  533. door::Line username(userString, W);
  534. username.setRender(svRender);
  535. p->addLine(std::make_unique<door::Line>(username));
  536. }
  537. {
  538. door::updateFunction scoreUpdate = [this](void) -> std::string {
  539. std::string text = "Score: ";
  540. text.append(std::to_string(score));
  541. return text;
  542. };
  543. std::string scoreString = scoreUpdate();
  544. door::Line scoreline(scoreString, W);
  545. scoreline.setRender(svRender);
  546. scoreline.setUpdater(scoreUpdate);
  547. p->addLine(std::make_unique<door::Line>(scoreline));
  548. }
  549. {
  550. door::updateFunction timeUpdate = [this](void) -> std::string {
  551. std::stringstream ss;
  552. std::string text;
  553. ss << "Time used: " << setw(3) << door.time_used << " / " << setw(3)
  554. << door.time_left;
  555. text = ss.str();
  556. return text;
  557. };
  558. std::string timeString = timeUpdate();
  559. door::Line time(timeString, W);
  560. time.setRender(svRender);
  561. time.setUpdater(timeUpdate);
  562. p->addLine(std::make_unique<door::Line>(time));
  563. }
  564. {
  565. door::updateFunction handUpdate = [this](void) -> std::string {
  566. std::string text = "Playing Hand ";
  567. text.append(std::to_string(hand));
  568. text.append(" of ");
  569. text.append(std::to_string(total_hands));
  570. return text;
  571. };
  572. std::string handString = handUpdate();
  573. door::Line hands(handString, W);
  574. hands.setRender(svRender);
  575. hands.setUpdater(handUpdate);
  576. p->addLine(std::make_unique<door::Line>(hands));
  577. }
  578. return p;
  579. }
  580. std::unique_ptr<door::Panel> PlayCards::make_streak_panel(void) {
  581. const int W = 20;
  582. std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
  583. p->setStyle(door::BorderStyle::NONE);
  584. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  585. door::ATTR::BOLD);
  586. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  587. door::ATTR::BOLD);
  588. door::renderFunction svRender = statusValue(statusColor, valueColor);
  589. {
  590. std::string text = "Playing: ";
  591. auto in_time_t = std::chrono::system_clock::to_time_t(play_day);
  592. std::stringstream ss;
  593. if (config["date_format"]) {
  594. std::string fmt = config["date_format"].as<std::string>();
  595. ss << std::put_time(std::localtime(&in_time_t), fmt.c_str());
  596. } else
  597. ss << std::put_time(std::localtime(&in_time_t), "%B %d");
  598. text.append(ss.str());
  599. door::Line current(text, W);
  600. current.setRender(svRender);
  601. p->addLine(std::make_unique<door::Line>(current));
  602. }
  603. {
  604. door::updateFunction currentUpdate = [this](void) -> std::string {
  605. std::string text = "Current Streak: ";
  606. text.append(std::to_string(current_streak));
  607. return text;
  608. };
  609. std::string currentString = currentUpdate();
  610. door::Line current(currentString, W);
  611. current.setRender(svRender);
  612. current.setUpdater(currentUpdate);
  613. p->addLine(std::make_unique<door::Line>(current));
  614. }
  615. {
  616. door::updateFunction currentUpdate = [this](void) -> std::string {
  617. std::string text = "Longest Streak: ";
  618. text.append(std::to_string(best_streak));
  619. return text;
  620. };
  621. std::string currentString = currentUpdate();
  622. door::Line current(currentString, W);
  623. current.setRender(svRender);
  624. current.setUpdater(currentUpdate);
  625. p->addLine(std::make_unique<door::Line>(current));
  626. }
  627. return p;
  628. }
  629. std::unique_ptr<door::Panel> PlayCards::make_left_panel(void) {
  630. const int W = 13;
  631. std::unique_ptr<door::Panel> p = std::make_unique<door::Panel>(W);
  632. p->setStyle(door::BorderStyle::NONE);
  633. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  634. door::ATTR::BOLD);
  635. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  636. door::ATTR::BOLD);
  637. door::renderFunction svRender = statusValue(statusColor, valueColor);
  638. {
  639. door::updateFunction cardsleftUpdate = [this](void) -> std::string {
  640. std::string text = "Cards left:";
  641. text.append(std::to_string(51 - play_card));
  642. return text;
  643. };
  644. std::string cardsleftString = "Cards left:--";
  645. door::Line cardsleft(cardsleftString, W);
  646. cardsleft.setRender(svRender);
  647. cardsleft.setUpdater(cardsleftUpdate);
  648. p->addLine(std::make_unique<door::Line>(cardsleft));
  649. }
  650. return p;
  651. }
  652. door::renderFunction PlayCards::commandLineRender(door::ANSIColor bracket,
  653. door::ANSIColor inner,
  654. door::ANSIColor outer) {
  655. door::renderFunction rf = [bracket, inner,
  656. outer](const std::string &txt) -> door::Render {
  657. door::Render r(txt);
  658. door::ColorOutput co;
  659. co.pos = 0;
  660. co.len = 0;
  661. co.c = outer;
  662. bool inOuter = true;
  663. int tpos = 0;
  664. for (char const &c : txt) {
  665. if (inOuter) {
  666. // we're in the outer text
  667. if (co.c != outer) {
  668. if (co.len != 0) {
  669. r.outputs.push_back(co);
  670. co.reset();
  671. co.pos = tpos;
  672. }
  673. co.c = outer;
  674. }
  675. // check for [
  676. if (c == '[') {
  677. if (co.len != 0) {
  678. r.outputs.push_back(co);
  679. co.reset();
  680. co.pos = tpos;
  681. }
  682. inOuter = false;
  683. co.c = bracket;
  684. }
  685. } else {
  686. // We're not in the outer.
  687. if (co.c != inner) {
  688. if (co.len != 0) {
  689. r.outputs.push_back(co);
  690. co.reset();
  691. co.pos = tpos;
  692. }
  693. co.c = inner;
  694. }
  695. if (c == ']') {
  696. if (co.len != 0) {
  697. r.outputs.push_back(co);
  698. co.reset();
  699. co.pos = tpos;
  700. }
  701. inOuter = true;
  702. co.c = bracket;
  703. }
  704. }
  705. co.len++;
  706. tpos++;
  707. }
  708. if (co.len != 0)
  709. r.outputs.push_back(co);
  710. return r;
  711. };
  712. return rf;
  713. }
  714. std::unique_ptr<door::Panel> PlayCards::make_command_panel(void) {
  715. const int W = 76;
  716. std::unique_ptr<door::Panel> p = make_unique<door::Panel>(W);
  717. p->setStyle(door::BorderStyle::NONE);
  718. std::string commands;
  719. if (door::unicode) {
  720. commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] "
  721. "Draw [Q]uit "
  722. "[R]edraw [H]elp";
  723. } else {
  724. commands =
  725. "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit "
  726. "[R]edraw [H]elp";
  727. }
  728. door::ANSIColor bracketColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  729. door::ATTR::BOLD);
  730. door::ANSIColor innerColor(door::COLOR::CYAN, door::COLOR::BLUE,
  731. door::ATTR::BOLD);
  732. door::ANSIColor outerColor(door::COLOR::GREEN, door::COLOR::BLUE,
  733. door::ATTR::BOLD);
  734. door::renderFunction cmdRender =
  735. commandLineRender(bracketColor, innerColor, outerColor);
  736. door::Line cmd(commands, W);
  737. cmd.setRender(cmdRender);
  738. p->addLine(std::make_unique<door::Line>(cmd));
  739. return p;
  740. }
  741. std::unique_ptr<door::Panel> PlayCards::make_tripeaks(void) {
  742. std::string tripeaksText(" " SPACEACE
  743. " - Tri-Peaks Solitaire v" SPACEACE_VERSION " ");
  744. std::unique_ptr<door::Panel> spaceAceTriPeaks =
  745. std::make_unique<door::Panel>(tripeaksText.size());
  746. spaceAceTriPeaks->setStyle(door::BorderStyle::SINGLE);
  747. spaceAceTriPeaks->setColor(
  748. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLACK));
  749. spaceAceTriPeaks->addLine(
  750. std::make_unique<door::Line>(tripeaksText, tripeaksText.size()));
  751. return spaceAceTriPeaks;
  752. }