play.cpp 30 KB

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