main.cpp 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696
  1. #include "door.h"
  2. #include "space.h"
  3. #include <chrono> // chrono::system_clock
  4. #include <ctime> // localtime
  5. #include <iomanip> // put_time
  6. #include <iostream>
  7. #include <random>
  8. #include <string>
  9. #include "db.h"
  10. #include "deck.h"
  11. #include "version.h"
  12. #include <algorithm> // transform
  13. void string_toupper(std::string &str) {
  14. std::transform(str.begin(), str.end(), str.begin(), ::toupper);
  15. }
  16. bool replace(std::string &str, const std::string &from, const std::string &to) {
  17. size_t start_pos = str.find(from);
  18. if (start_pos == std::string::npos)
  19. return false;
  20. str.replace(start_pos, from.length(), to);
  21. return true;
  22. }
  23. bool replace(std::string &str, const char *from, const char *to) {
  24. size_t start_pos = str.find(from);
  25. if (start_pos == std::string::npos)
  26. return false;
  27. str.replace(start_pos, strlen(from), to);
  28. return true;
  29. }
  30. door::ANSIColor from_string(std::string colorCode);
  31. std::function<std::ofstream &(void)> get_logger;
  32. unsigned long score = 0;
  33. int hand = 1;
  34. int total_hands = 3;
  35. int card_number = 28;
  36. int current_streak = 0;
  37. int best_streak = 0;
  38. int active_card = 23;
  39. std::chrono::_V2::system_clock::time_point play_day;
  40. /*
  41. Cards:
  42. 4 layers deep.
  43. https://en.wikipedia.org/wiki/Code_page_437
  44. using: \xb0, 0xb1, 0xb2, 0xdb
  45. OR: \u2591, \u2592, \u2593, \u2588
  46. Like so:
  47. ##### #####
  48. ##### #####
  49. ##### #####
  50. Cards: (Black on White, or Red on White)
  51. 8D### TH###
  52. ##D## ##H##
  53. ###D8 ###HT
  54. D, H = Red, Clubs, Spades = Black.
  55. ^ Where D = Diamonds, H = Hearts
  56. ♥, ♦, ♣, ♠
  57. \x03, \x04, \x05, \x06
  58. \u2665, \u2666, \u2663, \u2660
  59. Card layout. Actual cards are 3 lines thick.
  60. ░░░░░ ░░░░░ ░░░░░
  61. ░░░░░ ░░░░░ ░░░░░
  62. ▒▒▒▒▒░▒▒▒▒▒ #####░##### #####░#####
  63. ▒▒▒▒▒ ▒▒▒▒▒ ##### ##### ##### #####
  64. ▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▓ #####=#####=##### #####=#####=#####
  65. ▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓ ##### ##### ##### ##### ##### #####
  66. █████▓█████▓█████▓#####=#####=#####=#####=#####=#####=#####
  67. █████ █████ █████ ##### ##### ##### ##### ##### ##### #####
  68. █████ █████ █████ ##### ##### ##### ##### ##### ##### #####
  69. #####
  70. Player Information ##### Time in: xx Time out: xx
  71. Name: ##### Playing Day: November 3rd
  72. Hand Score : Current Streak: N
  73. Todays Score : XX Cards Remaining Longest Streak: NN
  74. Monthly Score: Playing Hand X of X Most Won: xxx Lost: xxx
  75. [4] Lf [6] Rt [Space] Play Card [Enter] Draw [D]one [H]elp [R]edraw
  76. ►, ◄, ▲, ▼
  77. \x10, \x11, \x1e, \x1f
  78. \u25ba, \u25c4, \u25b2, \u25bc
  79. The Name is <- 6 to the left.
  80. # is back of card. = is upper card showing between.
  81. There's no fancy anything here. Cards overlap the last
  82. line of the previous line/card.
  83. */
  84. // The idea is that this would be defined elsewhere (maybe)
  85. int user_score = 0;
  86. // NOTE: When run as a door, we always detect CP437 (because that's what Enigma
  87. // is defaulting to)
  88. void adjust_score(int by) { user_score += by; }
  89. door::Panel make_timeout(int mx, int my) {
  90. door::ANSIColor yellowred =
  91. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::RED, door::ATTR::BOLD);
  92. std::string line_text("Sorry, you've been inactive for too long.");
  93. int msgWidth = line_text.length() + (2 * 3); // + padding * 2
  94. door::Panel timeout((mx - (msgWidth)) / 2, my / 2 + 4, msgWidth);
  95. // place.setTitle(std::make_unique<door::Line>(title), 1);
  96. timeout.setStyle(door::BorderStyle::DOUBLE);
  97. timeout.setColor(yellowred);
  98. door::Line base(line_text);
  99. base.setColor(yellowred);
  100. std::string pad1(3, ' ');
  101. /*
  102. std::string pad1(3, '\xb0');
  103. if (door::unicode) {
  104. std::string unicode;
  105. door::cp437toUnicode(pad1.c_str(), unicode);
  106. pad1 = unicode;
  107. }
  108. */
  109. base.setPadding(pad1, yellowred);
  110. // base.setColor(door::ANSIColor(door::COLOR::GREEN, door::COLOR::BLACK));
  111. std::unique_ptr<door::Line> stuff = std::make_unique<door::Line>(base);
  112. timeout.addLine(std::make_unique<door::Line>(base));
  113. return timeout;
  114. }
  115. door::Panel make_notime(int mx, int my) {
  116. door::ANSIColor yellowred =
  117. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::RED, door::ATTR::BOLD);
  118. std::string line_text("Sorry, you've used up all your time for today.");
  119. int msgWidth = line_text.length() + (2 * 3); // + padding * 2
  120. door::Panel timeout((mx - (msgWidth)) / 2, my / 2 + 4, msgWidth);
  121. timeout.setStyle(door::BorderStyle::DOUBLE);
  122. timeout.setColor(yellowred);
  123. door::Line base(line_text);
  124. base.setColor(yellowred);
  125. std::string pad1(3, ' ');
  126. /*
  127. std::string pad1(3, '\xb0');
  128. if (door::unicode) {
  129. std::string unicode;
  130. door::cp437toUnicode(pad1.c_str(), unicode);
  131. pad1 = unicode;
  132. }
  133. */
  134. base.setPadding(pad1, yellowred);
  135. std::unique_ptr<door::Line> stuff = std::make_unique<door::Line>(base);
  136. timeout.addLine(std::make_unique<door::Line>(base));
  137. return timeout;
  138. }
  139. door::Menu make_main_menu(void) {
  140. door::Menu m(5, 5, 25);
  141. door::Line mtitle(SPACEACE " Main Menu");
  142. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  143. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  144. door::ATTR::BOLD);
  145. m.setColor(border_color);
  146. mtitle.setColor(title_color);
  147. mtitle.setPadding(" ", title_color);
  148. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  149. // m.setColorizer(true,
  150. m.setRender(true, door::Menu::makeRender(
  151. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  152. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  153. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  154. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD)));
  155. // m.setColorizer(false,
  156. m.setRender(false, door::Menu::makeRender(
  157. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  158. door::ATTR::BOLD),
  159. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  160. door::ATTR::BOLD),
  161. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  162. door::ATTR::BOLD),
  163. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE,
  164. door::ATTR::BOLD)));
  165. m.addSelection('P', "Play Cards");
  166. m.addSelection('S', "View Scores");
  167. m.addSelection('C', "Configure");
  168. m.addSelection('H', "Help");
  169. m.addSelection('A', "About this game");
  170. m.addSelection('Q', "Quit");
  171. return m;
  172. }
  173. door::renderFunction statusValue(door::ANSIColor status,
  174. door::ANSIColor value) {
  175. door::renderFunction rf = [status,
  176. value](const std::string &txt) -> door::Render {
  177. door::Render r(txt);
  178. door::ColorOutput co;
  179. co.pos = 0;
  180. co.len = 0;
  181. co.c = status;
  182. size_t pos = txt.find(':');
  183. if (pos == std::string::npos) {
  184. // failed to find :, render digits/numbers in value color
  185. int tpos = 0;
  186. for (char const &c : txt) {
  187. if (std::isdigit(c)) {
  188. if (co.c != value) {
  189. r.outputs.push_back(co);
  190. co.reset();
  191. co.pos = tpos;
  192. co.c = value;
  193. }
  194. } else {
  195. if (co.c != status) {
  196. r.outputs.push_back(co);
  197. co.reset();
  198. co.pos = tpos;
  199. co.c = status;
  200. }
  201. }
  202. co.len++;
  203. tpos++;
  204. }
  205. if (co.len != 0)
  206. r.outputs.push_back(co);
  207. } else {
  208. pos++; // Have : in status color
  209. co.len = pos;
  210. r.outputs.push_back(co);
  211. co.reset();
  212. co.pos = pos;
  213. co.c = value;
  214. co.len = txt.length() - pos;
  215. r.outputs.push_back(co);
  216. }
  217. return r;
  218. };
  219. return rf;
  220. }
  221. /*
  222. door::renderFunction rStatus = [](const std::string &txt) -> door::Render {
  223. door::Render r(txt);
  224. door::ColorOutput co;
  225. // default colors STATUS: value
  226. door::ANSIColor status(door::COLOR::BLUE, door::ATTR::BOLD);
  227. door::ANSIColor value(door::COLOR::YELLOW, door::ATTR::BOLD);
  228. co.pos = 0;
  229. co.len = 0;
  230. co.c = status;
  231. size_t pos = txt.find(':');
  232. if (pos == std::string::npos) {
  233. // failed to find - use entire string as status color.
  234. co.len = txt.length();
  235. r.outputs.push_back(co);
  236. } else {
  237. pos++; // Have : in status color
  238. co.len = pos;
  239. r.outputs.push_back(co);
  240. co.reset();
  241. co.pos = pos;
  242. co.c = value;
  243. co.len = txt.length() - pos;
  244. r.outputs.push_back(co);
  245. }
  246. return r;
  247. };
  248. */
  249. std::string return_current_time_and_date() {
  250. auto now = std::chrono::system_clock::now();
  251. auto in_time_t = std::chrono::system_clock::to_time_t(now);
  252. std::stringstream ss;
  253. // ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %X");
  254. ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %r");
  255. return ss.str();
  256. }
  257. int press_a_key(door::Door &door) {
  258. door << door::reset << "Press a key to continue...";
  259. int r = door.sleep_key(door.inactivity);
  260. door << door::nl;
  261. return r;
  262. }
  263. door::Menu make_config_menu(void) {
  264. door::Menu m(5, 5, 31);
  265. door::Line mtitle(SPACEACE " Configuration Menu");
  266. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  267. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  268. door::ATTR::BOLD);
  269. m.setColor(border_color);
  270. mtitle.setColor(title_color);
  271. mtitle.setPadding(" ", title_color);
  272. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  273. // m.setColorizer(true,
  274. m.setRender(true, door::Menu::makeRender(
  275. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  276. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  277. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  278. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD)));
  279. // m.setColorizer(false,
  280. m.setRender(false, door::Menu::makeRender(
  281. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  282. door::ATTR::BOLD),
  283. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  284. door::ATTR::BOLD),
  285. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  286. door::ATTR::BOLD),
  287. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE,
  288. door::ATTR::BOLD)));
  289. m.addSelection('D', "Deck Colors");
  290. m.addSelection('Q', "Quit");
  291. return m;
  292. }
  293. // all the possible deck colors
  294. vector<std::string> deck_colors = {std::string("All"), std::string("Blue"),
  295. std::string("Cyan"), std::string("Green"),
  296. std::string("Magenta"), std::string("Red")};
  297. /**
  298. * @brief menu render that sets the text color based on the color found in the
  299. * text itself.
  300. *
  301. * @param c1 [] brackets
  302. * @param c2 text within brackets
  303. * @param c3 base color give (we set the FG, we use the BG)
  304. * @return door::renderFunction
  305. */
  306. door::renderFunction makeColorRender(door::ANSIColor c1, door::ANSIColor c2,
  307. door::ANSIColor c3) {
  308. door::renderFunction render = [c1, c2,
  309. c3](const std::string &txt) -> door::Render {
  310. door::Render r(txt);
  311. bool option = true;
  312. door::ColorOutput co;
  313. // I need this mutable
  314. door::ANSIColor textColor = c3;
  315. // Color update:
  316. {
  317. std::string found;
  318. for (auto &dc : deck_colors) {
  319. if (txt.find(dc) != string::npos) {
  320. found = dc;
  321. break;
  322. }
  323. }
  324. if (!found.empty()) {
  325. if (found == "All") {
  326. // handle this some other way.
  327. textColor.setFg(door::COLOR::WHITE);
  328. } else {
  329. door::ANSIColor c = from_string(found);
  330. textColor.setFg(c.getFg());
  331. }
  332. }
  333. }
  334. co.pos = 0;
  335. co.len = 0;
  336. co.c = c1;
  337. int tpos = 0;
  338. for (char const &c : txt) {
  339. if (option) {
  340. if (c == '[' or c == ']') {
  341. if (co.c != c1)
  342. if (co.len != 0) {
  343. r.outputs.push_back(co);
  344. co.reset();
  345. co.pos = tpos;
  346. }
  347. co.c = c1;
  348. if (c == ']')
  349. option = false;
  350. } else {
  351. if (co.c != c2)
  352. if (co.len != 0) {
  353. r.outputs.push_back(co);
  354. co.reset();
  355. co.pos = tpos;
  356. }
  357. co.c = c2;
  358. }
  359. } else {
  360. if (co.c != textColor)
  361. if (co.len != 0) {
  362. r.outputs.push_back(co);
  363. co.reset();
  364. co.pos = tpos;
  365. }
  366. co.c = textColor;
  367. }
  368. co.len++;
  369. tpos++;
  370. }
  371. if (co.len != 0) {
  372. r.outputs.push_back(co);
  373. }
  374. return r;
  375. };
  376. return render;
  377. }
  378. door::Menu make_deck_menu(void) {
  379. door::Menu m(5, 5, 31);
  380. door::Line mtitle(SPACEACE " Deck Menu");
  381. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  382. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  383. door::ATTR::BOLD);
  384. m.setColor(border_color);
  385. mtitle.setColor(title_color);
  386. mtitle.setPadding(" ", title_color);
  387. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  388. m.setRender(true, makeColorRender(
  389. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  390. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  391. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD)));
  392. m.setRender(false, makeColorRender(
  393. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  394. door::ATTR::BOLD),
  395. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  396. door::ATTR::BOLD),
  397. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  398. door::ATTR::BOLD)));
  399. // build the menu options from the colors. First character = single letter
  400. // option trigger.
  401. for (auto iter = deck_colors.begin(); iter != deck_colors.end(); ++iter) {
  402. char c = (*iter)[0];
  403. m.addSelection(c, (*iter).c_str());
  404. }
  405. /*
  406. m.addSelection('A', "All");
  407. m.addSelection('B', "Blue");
  408. m.addSelection('C', "Cyan");
  409. m.addSelection('G', "Green");
  410. m.addSelection('M', "Magenta");
  411. m.addSelection('R', "Red");
  412. */
  413. return m;
  414. }
  415. // DOES THIS WORK?
  416. bool iequals(const string &a, const string &b) {
  417. unsigned int sz = a.size();
  418. if (b.size() != sz)
  419. return false;
  420. for (unsigned int i = 0; i < sz; ++i)
  421. if (tolower(a[i]) != tolower(b[i]))
  422. return false;
  423. return true;
  424. }
  425. // convert a string to an option
  426. // an option to the string to store
  427. // This needs to be updated to use deck_colors.
  428. door::ANSIColor from_string(std::string colorCode) {
  429. std::map<std::string, door::ANSIColor> codeMap = {
  430. {std::string("BLUE"), door::ANSIColor(door::COLOR::BLUE)},
  431. {std::string("RED"), door::ANSIColor(door::COLOR::RED)},
  432. {std::string("CYAN"), door::ANSIColor(door::COLOR::CYAN)},
  433. {std::string("GREEN"), door::ANSIColor(door::COLOR::GREEN)},
  434. {std::string("MAGENTA"), door::ANSIColor(door::COLOR::MAGENTA)}};
  435. std::string code = colorCode;
  436. string_toupper(code);
  437. auto iter = codeMap.find(code);
  438. if (iter != codeMap.end()) {
  439. return iter->second;
  440. }
  441. // And if it doesn't match, and isn't ALL ... ?
  442. // if (code.compare("ALL") == 0) {
  443. std::random_device dev;
  444. std::mt19937_64 rng(dev());
  445. std::uniform_int_distribution<size_t> idDist(0, codeMap.size() - 1);
  446. iter = codeMap.begin();
  447. std::advance(iter, idDist(rng));
  448. return iter->second;
  449. // }
  450. }
  451. // This does not seem to be working. I keep getting zero.
  452. int opt_from_string(std::string colorCode) {
  453. for (std::size_t pos = 0; pos != deck_colors.size(); ++pos) {
  454. // if (caseInsensitiveStringCompare(colorCode, deck_colors[pos]) == 0) {
  455. if (iequals(colorCode, deck_colors[pos])) {
  456. return pos;
  457. }
  458. }
  459. return 0;
  460. }
  461. std::string from_color_option(int opt) { return deck_colors[opt]; }
  462. int configure(door::Door &door, DBData &db) {
  463. auto menu = make_config_menu();
  464. int r = 0;
  465. while (r >= 0) {
  466. r = menu.choose(door);
  467. if (r > 0) {
  468. door << door::reset << door::cls;
  469. char c = menu.which(r - 1);
  470. if (c == 'D') {
  471. // Ok, deck colors
  472. // get default
  473. std::string key("DeckColor");
  474. std::string currentDefault = db.getSetting(key, std::string("ALL"));
  475. int currentOpt = opt_from_string(currentDefault);
  476. door << door::reset << door::cls;
  477. auto deck = make_deck_menu();
  478. deck.defaultSelection(currentOpt);
  479. int newOpt = deck.choose(door);
  480. door << door::reset << door::cls;
  481. if (newOpt >= 0) {
  482. newOpt--;
  483. std::string newColor = from_color_option(newOpt);
  484. if (newOpt != currentOpt) {
  485. door.log() << key << " was " << currentDefault << ", " << currentOpt
  486. << ". Now " << newColor << ", " << newOpt << std::endl;
  487. db.setSetting(key, newColor);
  488. }
  489. }
  490. }
  491. if (c == 'Q') {
  492. return r;
  493. }
  494. }
  495. }
  496. return r;
  497. }
  498. door::Panel make_score_panel(door::Door &door) {
  499. const int W = 25;
  500. door::Panel p(W);
  501. p.setStyle(door::BorderStyle::NONE);
  502. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  503. door::ATTR::BOLD);
  504. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  505. door::ATTR::BOLD);
  506. door::renderFunction svRender = statusValue(statusColor, valueColor);
  507. // or use renderStatus as defined above.
  508. // We'll stick with these for now.
  509. {
  510. std::string userString = "Name: ";
  511. userString += door.username;
  512. door::Line username(userString, W);
  513. username.setRender(svRender);
  514. p.addLine(std::make_unique<door::Line>(username));
  515. }
  516. {
  517. door::updateFunction scoreUpdate = [](void) -> std::string {
  518. std::string text = "Score: ";
  519. text.append(std::to_string(score));
  520. return text;
  521. };
  522. std::string scoreString = scoreUpdate();
  523. door::Line score(scoreString, W);
  524. score.setRender(svRender);
  525. score.setUpdater(scoreUpdate);
  526. p.addLine(std::make_unique<door::Line>(score));
  527. }
  528. {
  529. door::updateFunction timeUpdate = [&door](void) -> std::string {
  530. std::stringstream ss;
  531. std::string text;
  532. ss << "Time used: " << setw(3) << door.time_used << " / " << setw(3)
  533. << door.time_left;
  534. text = ss.str();
  535. return text;
  536. };
  537. std::string timeString = timeUpdate();
  538. door::Line time(timeString, W);
  539. time.setRender(svRender);
  540. time.setUpdater(timeUpdate);
  541. p.addLine(std::make_unique<door::Line>(time));
  542. }
  543. {
  544. door::updateFunction handUpdate = [](void) -> std::string {
  545. std::string text = "Playing Hand ";
  546. text.append(std::to_string(hand));
  547. text.append(" of ");
  548. text.append(std::to_string(total_hands));
  549. return text;
  550. };
  551. std::string handString = handUpdate();
  552. door::Line hands(handString, W);
  553. hands.setRender(svRender);
  554. hands.setUpdater(handUpdate);
  555. p.addLine(std::make_unique<door::Line>(hands));
  556. }
  557. return p;
  558. }
  559. door::Panel make_streak_panel(void) {
  560. const int W = 20;
  561. door::Panel p(W);
  562. p.setStyle(door::BorderStyle::NONE);
  563. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  564. door::ATTR::BOLD);
  565. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  566. door::ATTR::BOLD);
  567. door::renderFunction svRender = statusValue(statusColor, valueColor);
  568. {
  569. std::string text = "Playing: ";
  570. auto in_time_t = std::chrono::system_clock::to_time_t(play_day);
  571. std::stringstream ss;
  572. ss << std::put_time(std::localtime(&in_time_t), "%B %d");
  573. text.append(ss.str());
  574. door::Line current(text, W);
  575. current.setRender(svRender);
  576. p.addLine(std::make_unique<door::Line>(current));
  577. }
  578. {
  579. door::updateFunction currentUpdate = [](void) -> std::string {
  580. std::string text = "Current Streak: ";
  581. text.append(std::to_string(current_streak));
  582. return text;
  583. };
  584. std::string currentString = currentUpdate();
  585. door::Line current(currentString, W);
  586. current.setRender(svRender);
  587. current.setUpdater(currentUpdate);
  588. p.addLine(std::make_unique<door::Line>(current));
  589. }
  590. {
  591. door::updateFunction currentUpdate = [](void) -> std::string {
  592. std::string text = "Longest Streak: ";
  593. text.append(std::to_string(best_streak));
  594. return text;
  595. };
  596. std::string currentString = currentUpdate();
  597. door::Line current(currentString, W);
  598. current.setRender(svRender);
  599. current.setUpdater(currentUpdate);
  600. p.addLine(std::make_unique<door::Line>(current));
  601. }
  602. return p;
  603. }
  604. door::Panel make_left_panel(void) {
  605. const int W = 13;
  606. door::Panel p(W);
  607. p.setStyle(door::BorderStyle::NONE);
  608. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  609. door::ATTR::BOLD);
  610. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  611. door::ATTR::BOLD);
  612. door::renderFunction svRender = statusValue(statusColor, valueColor);
  613. {
  614. door::updateFunction cardsleftUpdate = [](void) -> std::string {
  615. std::string text = "Cards left:";
  616. text.append(std::to_string(51 - card_number));
  617. return text;
  618. };
  619. std::string cardsleftString = "Cards left:--";
  620. door::Line cardsleft(cardsleftString, W);
  621. cardsleft.setRender(svRender);
  622. cardsleft.setUpdater(cardsleftUpdate);
  623. p.addLine(std::make_unique<door::Line>(cardsleft));
  624. }
  625. return p;
  626. }
  627. door::renderFunction commandLineRender(door::ANSIColor bracket,
  628. door::ANSIColor inner,
  629. door::ANSIColor outer) {
  630. door::renderFunction rf = [bracket, inner,
  631. outer](const std::string &txt) -> door::Render {
  632. door::Render r(txt);
  633. door::ColorOutput co;
  634. co.pos = 0;
  635. co.len = 0;
  636. co.c = outer;
  637. bool inOuter = true;
  638. int tpos = 0;
  639. for (char const &c : txt) {
  640. if (inOuter) {
  641. // we're in the outer text
  642. if (co.c != outer) {
  643. if (co.len != 0) {
  644. r.outputs.push_back(co);
  645. co.reset();
  646. co.pos = tpos;
  647. }
  648. co.c = outer;
  649. }
  650. // check for [
  651. if (c == '[') {
  652. if (co.len != 0) {
  653. r.outputs.push_back(co);
  654. co.reset();
  655. co.pos = tpos;
  656. }
  657. inOuter = false;
  658. co.c = bracket;
  659. }
  660. } else {
  661. // We're not in the outer.
  662. if (co.c != inner) {
  663. if (co.len != 0) {
  664. r.outputs.push_back(co);
  665. co.reset();
  666. co.pos = tpos;
  667. }
  668. co.c = inner;
  669. }
  670. if (c == ']') {
  671. if (co.len != 0) {
  672. r.outputs.push_back(co);
  673. co.reset();
  674. co.pos = tpos;
  675. }
  676. inOuter = true;
  677. co.c = bracket;
  678. }
  679. }
  680. co.len++;
  681. tpos++;
  682. }
  683. if (co.len != 0)
  684. r.outputs.push_back(co);
  685. return r;
  686. };
  687. return rf;
  688. }
  689. door::Panel make_command_panel(void) {
  690. const int W = 76;
  691. door::Panel p(W);
  692. p.setStyle(door::BorderStyle::NONE);
  693. std::string commands;
  694. if (door::unicode) {
  695. commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] "
  696. "Draw [Q]uit "
  697. "[R]edraw [H]elp";
  698. } else {
  699. commands =
  700. "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit "
  701. "[R]edraw [H]elp";
  702. }
  703. door::ANSIColor bracketColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  704. door::ATTR::BOLD);
  705. door::ANSIColor innerColor(door::COLOR::CYAN, door::COLOR::BLUE,
  706. door::ATTR::BOLD);
  707. door::ANSIColor outerColor(door::COLOR::GREEN, door::COLOR::BLUE,
  708. door::ATTR::BOLD);
  709. door::renderFunction cmdRender =
  710. commandLineRender(bracketColor, innerColor, outerColor);
  711. door::Line cmd(commands, W);
  712. cmd.setRender(cmdRender);
  713. p.addLine(std::make_unique<door::Line>(cmd));
  714. return p;
  715. }
  716. door::Panel make_tripeaks(void) {
  717. std::string tripeaksText(" " SPACEACE
  718. " - Tri-Peaks Solitaire v" SPACEACE_VERSION " ");
  719. door::Panel spaceAceTriPeaks(tripeaksText.size());
  720. spaceAceTriPeaks.setStyle(door::BorderStyle::SINGLE);
  721. spaceAceTriPeaks.setColor(
  722. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLACK));
  723. spaceAceTriPeaks.addLine(
  724. std::make_unique<door::Line>(tripeaksText, tripeaksText.size()));
  725. return spaceAceTriPeaks;
  726. }
  727. int play_cards(door::Door &door, DBData &db, std::mt19937 &rng) {
  728. int mx = door.width;
  729. int my = door.height;
  730. // init these values:
  731. card_number = 28;
  732. active_card = 23;
  733. score = 0;
  734. play_day = std::chrono::system_clock::now();
  735. // cards color --
  736. // configured by the player.
  737. door::ANSIColor deck_color;
  738. // std::string key("DeckColor");
  739. const char *key = "DeckColor";
  740. std::string currentDefault = db.getSetting(key, "ALL");
  741. door.log() << key << " shows as " << currentDefault << std::endl;
  742. deck_color = from_string(currentDefault);
  743. int height = 3;
  744. Deck d(deck_color, height);
  745. door::Panel *c;
  746. // This displays the cards in the upper left corner.
  747. // We want them center, and down some.
  748. const int space = 3;
  749. // int cards_dealt_width = 59; int cards_dealt_height = 9;
  750. int game_width;
  751. int game_height = 20; // 13; // 9;
  752. {
  753. int cx, cy, level;
  754. cardgo(27, cx, cy, level);
  755. game_width = cx + 5; // card width
  756. }
  757. int off_x = (mx - game_width) / 2;
  758. int off_y = (my - game_height) / 2;
  759. // The idea is to see the cards with <<Something Unique to the card game>>,
  760. // Year, Month, Day, and game (like 1 of 3).
  761. // This will make the games the same/fair for everyone.
  762. door::Panel spaceAceTriPeaks = make_tripeaks();
  763. int tp_off_x = (mx - spaceAceTriPeaks.getWidth()) / 2;
  764. spaceAceTriPeaks.set(tp_off_x, off_y);
  765. off_y += 3;
  766. // figure out what date we're playing / what game we're playing
  767. std::seed_seq s1{2021, 2, 27, 1};
  768. cards deck1 = card_shuffle(s1, 1);
  769. cards state = card_states();
  770. door::Panel score_panel = make_score_panel(door);
  771. door::Panel streak_panel = make_streak_panel();
  772. door::Panel left_panel = make_left_panel();
  773. door::Panel cmd_panel = make_command_panel();
  774. {
  775. int off_yp = off_y + 11;
  776. int cxp, cyp, levelp;
  777. int left_panel_x, right_panel_x;
  778. // find position of card, to position the panels
  779. cardgo(18, cxp, cyp, levelp);
  780. left_panel_x = cxp;
  781. cardgo(15, cxp, cyp, levelp);
  782. right_panel_x = cxp;
  783. score_panel.set(left_panel_x + off_x, off_yp);
  784. streak_panel.set(right_panel_x + off_x, off_yp);
  785. cmd_panel.set(left_panel_x + off_x, off_yp + 5);
  786. }
  787. bool dealing = true;
  788. int r = 0;
  789. while ((r >= 0) and (r != 'Q')) {
  790. // REDRAW everything
  791. door << door::reset << door::cls;
  792. door << spaceAceTriPeaks;
  793. {
  794. // step 1:
  795. // draw the deck "source"
  796. int cx, cy, level;
  797. cardgo(29, cx, cy, level);
  798. if (card_number == 51)
  799. level = 0; // out of cards!
  800. c = d.back(level);
  801. c->set(cx + off_x, cy + off_y);
  802. // p3 is heigh below
  803. left_panel.set(cx + off_x, cy + off_y + height);
  804. door << score_panel << left_panel << streak_panel << cmd_panel;
  805. door << *c;
  806. if (dealing)
  807. std::this_thread::sleep_for(std::chrono::seconds(1));
  808. }
  809. // I tried setting the cursor before the delay and before displaying the
  810. // card. It is very hard to see / just about useless. Not worth the effort.
  811. for (int x = 0; x < (dealing ? 28 : 29); x++) {
  812. int cx, cy, level;
  813. cardgo(x, cx, cy, level);
  814. // This is hardly visible.
  815. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  816. if (dealing)
  817. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  818. if (dealing) {
  819. c = d.back(level);
  820. c->set(cx + off_x, cy + off_y);
  821. door << *c;
  822. } else {
  823. // redrawing -- draw the cards with their correct "state"
  824. int s = state.at(x);
  825. switch (s) {
  826. case 0:
  827. c = d.back(level);
  828. c->set(cx + off_x, cy + off_y);
  829. door << *c;
  830. break;
  831. case 1:
  832. // cardgo(x, space, height, cx, cy, level);
  833. if (x == 28)
  834. c = d.card(deck1.at(card_number));
  835. else
  836. c = d.card(deck1.at(x));
  837. c->set(cx + off_x, cy + off_y);
  838. door << *c;
  839. break;
  840. case 2:
  841. // no card to draw. :)
  842. break;
  843. }
  844. }
  845. }
  846. if (dealing)
  847. for (int x = 18; x < 29; x++) {
  848. int cx, cy, level;
  849. state.at(x) = 1;
  850. cardgo(x, cx, cy, level);
  851. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  852. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  853. c = d.card(deck1.at(x));
  854. c->set(cx + off_x, cy + off_y);
  855. door << *c;
  856. }
  857. {
  858. int cx, cy, level;
  859. cardgo(active_card, cx, cy, level);
  860. c = d.marker(1);
  861. c->set(cx + off_x + 2, cy + off_y + 2);
  862. door << *c;
  863. }
  864. dealing = false;
  865. left_panel.update(door);
  866. door << door::reset;
  867. bool now_what = true;
  868. while (now_what) {
  869. // time might have updated, so update score panel too.
  870. score_panel.update(door);
  871. r = door.sleep_key(door.inactivity);
  872. if (r > 0) {
  873. // They didn't timeout/expire. They didn't press a function key.
  874. if (r < 0x1000)
  875. r = std::toupper(r);
  876. switch (r) {
  877. case '\x0d':
  878. if (card_number < 51) {
  879. card_number++;
  880. current_streak = 0;
  881. streak_panel.update(door);
  882. // Ok, deal next card from the pile.
  883. int cx, cy, level;
  884. if (card_number == 51) {
  885. cardgo(29, cx, cy, level);
  886. level = 0; // out of cards
  887. c = d.back(level);
  888. c->set(cx + off_x, cy + off_y);
  889. door << *c;
  890. }
  891. cardgo(28, cx, cy, level);
  892. c = d.card(deck1.at(card_number));
  893. c->set(cx + off_x, cy + off_y);
  894. door << *c;
  895. // update the cards left_panel
  896. left_panel.update(door);
  897. }
  898. break;
  899. case 'R':
  900. now_what = false;
  901. break;
  902. case 'Q':
  903. now_what = false;
  904. break;
  905. case ' ':
  906. // can we play this card?
  907. /*
  908. get_logger() << "can_play( " << active_card << ":"
  909. << deck1.at(active_card) << "/"
  910. << d.is_rank(deck1.at(active_card)) << " , "
  911. << card_number << "/" << d.is_rank(deck1.at(card_number))
  912. << ") = "
  913. << d.can_play(deck1.at(active_card),
  914. deck1.at(card_number))
  915. << std::endl;
  916. */
  917. if (d.can_play(deck1.at(active_card), deck1.at(card_number))) {
  918. // if (true) {
  919. // yes we can.
  920. ++current_streak;
  921. if (current_streak > best_streak)
  922. best_streak = current_streak;
  923. streak_panel.update(door);
  924. // play card!
  925. state.at(active_card) = 2;
  926. {
  927. // swap the active card with card_number (play card)
  928. int temp = deck1.at(active_card);
  929. deck1.at(active_card) = deck1.at(card_number);
  930. deck1.at(card_number) = temp;
  931. // active_card is -- invalidated here! find "new" card.
  932. int cx, cy, level;
  933. // erase/clear active_card
  934. std::vector<int> check = d.unblocks(active_card);
  935. bool left = false, right = false;
  936. for (const int c : check) {
  937. std::pair<int, int> blockers = d.blocks[c];
  938. if (blockers.first == active_card)
  939. right = true;
  940. if (blockers.second == active_card)
  941. left = true;
  942. }
  943. d.remove_card(door, active_card, off_x, off_y, left, right);
  944. /* // old way of doing this that leaves holes.
  945. cardgo(active_card, cx, cy, level);
  946. c = d.back(0);
  947. c->set(cx + off_x, cy + off_y);
  948. door << *c;
  949. */
  950. // redraw play card #28. (Which is the "old" active_card)
  951. cardgo(28, cx, cy, level);
  952. c = d.card(deck1.at(card_number));
  953. c->set(cx + off_x, cy + off_y);
  954. door << *c;
  955. // Did we unhide a card here?
  956. // std::vector<int> check = d.unblocks(active_card);
  957. /*
  958. get_logger() << "active_card = " << active_card
  959. << " unblocks: " << check.size() << std::endl;
  960. */
  961. int new_card_shown = -1;
  962. if (!check.empty()) {
  963. for (const int to_check : check) {
  964. std::pair<int, int> blockers = d.blocks[to_check];
  965. /*
  966. get_logger()
  967. << "Check: " << to_check << " " << blockers.first << ","
  968. << blockers.second << " " << state.at(blockers.first)
  969. << "," << state.at(blockers.second) << std::endl;
  970. */
  971. if ((state.at(blockers.first) == 2) and
  972. (state.at(blockers.second) == 2)) {
  973. // WOOT! Card uncovered.
  974. /*
  975. get_logger() << "showing: " << to_check << std::endl;
  976. */
  977. state.at(to_check) = 1;
  978. cardgo(to_check, cx, cy, level);
  979. c = d.card(deck1.at(to_check));
  980. c->set(cx + off_x, cy + off_y);
  981. door << *c;
  982. new_card_shown = to_check;
  983. }
  984. }
  985. } else {
  986. // this would be a "top" card. Should set status = 4 and
  987. // display something here?
  988. get_logger() << "top card cleared?" << std::endl;
  989. // display something at active_card position
  990. int cx, cy, level;
  991. cardgo(active_card, cx, cy, level);
  992. door << door::Goto(cx + off_x, cy + off_y) << door::reset
  993. << "clear";
  994. }
  995. // Find new "number" for active_card to be.
  996. if (new_card_shown != -1) {
  997. active_card = new_card_shown;
  998. } else {
  999. // active_card++;
  1000. int new_active = active_card - 1;
  1001. while (new_active >= 0) {
  1002. if (state.at(new_active) == 1)
  1003. break;
  1004. --new_active;
  1005. }
  1006. if (new_active >= 0) {
  1007. active_card = new_active;
  1008. } else {
  1009. // ok, we failed to find a card that way, look the other way.
  1010. new_active = active_card + 1;
  1011. while (new_active <= 28) {
  1012. if (state.at(new_active) == 1)
  1013. break;
  1014. ++new_active;
  1015. }
  1016. if (new_active < 28) {
  1017. active_card = new_active;
  1018. } else {
  1019. get_logger() << "This looks like END OF GAME." << std::endl;
  1020. }
  1021. }
  1022. }
  1023. // update the active_card marker!
  1024. cardgo(active_card, cx, cy, level);
  1025. c = d.marker(1);
  1026. c->set(cx + off_x + 2, cy + off_y + 2);
  1027. door << *c;
  1028. }
  1029. }
  1030. break;
  1031. case XKEY_LEFT_ARROW:
  1032. case '4': {
  1033. int new_active = find_next(true, state, active_card);
  1034. /*
  1035. int new_active = active_card - 1;
  1036. while (new_active >= 0) {
  1037. if (state.at(new_active) == 1)
  1038. break;
  1039. --new_active;
  1040. }*/
  1041. if (new_active >= 0) {
  1042. int cx, cy, level;
  1043. cardgo(active_card, cx, cy, level);
  1044. c = d.marker(0);
  1045. c->set(cx + off_x + 2, cy + off_y + 2);
  1046. door << *c;
  1047. active_card = new_active;
  1048. cardgo(active_card, cx, cy, level);
  1049. c = d.marker(1);
  1050. c->set(cx + off_x + 2, cy + off_y + 2);
  1051. door << *c;
  1052. }
  1053. } break;
  1054. case XKEY_RIGHT_ARROW:
  1055. case '6': {
  1056. int new_active = find_next(false, state, active_card);
  1057. /*
  1058. int new_active = active_card + 1;
  1059. while (new_active < 28) {
  1060. if (state.at(new_active) == 1)
  1061. break;
  1062. ++new_active;
  1063. }
  1064. */
  1065. if (new_active >= 0) { //(new_active < 28) {
  1066. int cx, cy, level;
  1067. cardgo(active_card, cx, cy, level);
  1068. c = d.marker(0);
  1069. c->set(cx + off_x + 2, cy + off_y + 2);
  1070. door << *c;
  1071. active_card = new_active;
  1072. cardgo(active_card, cx, cy, level);
  1073. c = d.marker(1);
  1074. c->set(cx + off_x + 2, cy + off_y + 2);
  1075. door << *c;
  1076. }
  1077. }
  1078. break;
  1079. }
  1080. } else
  1081. now_what = false;
  1082. }
  1083. }
  1084. return r;
  1085. }
  1086. door::Panel make_about(void) {
  1087. const int W = 60;
  1088. door::Panel about(W);
  1089. about.setStyle(door::BorderStyle::DOUBLE_SINGLE);
  1090. about.setColor(door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  1091. door::ATTR::BOLD));
  1092. about.addLine(std::make_unique<door::Line>("About This Door", W));
  1093. about.addLine(std::make_unique<door::Line>(
  1094. "---------------------------------", W,
  1095. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE, door::ATTR::BOLD)));
  1096. /*
  1097. 123456789012345678901234567890123456789012345678901234567890-60
  1098. This door was written by Bugz.
  1099. It is written in c++, only supports Linux, and replaces
  1100. opendoors.
  1101. It's written in c++, and replaces the outdated opendoors
  1102. library.
  1103. */
  1104. about.addLine(
  1105. std::make_unique<door::Line>(SPACEACE " v" SPACEACE_VERSION, W));
  1106. std::string copyright = SPACEACE_COPYRIGHT;
  1107. if (door::unicode) {
  1108. replace(copyright, "(C)", "\u00a9");
  1109. }
  1110. about.addLine(std::make_unique<door::Line>(copyright, W));
  1111. about.addLine(std::make_unique<door::Line>("", W));
  1112. about.addLine(
  1113. std::make_unique<door::Line>("This door was written by Bugz.", W));
  1114. about.addLine(std::make_unique<door::Line>("", W));
  1115. about.addLine(std::make_unique<door::Line>(
  1116. "It is written in c++, only support Linux, and replaces", W));
  1117. about.addLine(std::make_unique<door::Line>("opendoors.", W));
  1118. /*
  1119. door::updateFunction updater = [](void) -> std::string {
  1120. std::string text = "Currently: ";
  1121. text.append(return_current_time_and_date());
  1122. return text;
  1123. };
  1124. std::string current = updater();
  1125. door::Line active(current, 60);
  1126. active.setUpdater(updater);
  1127. active.setRender(statusValue(
  1128. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  1129. door::ATTR::BOLD), door::ANSIColor(door::COLOR::YELLOW,
  1130. door::COLOR::BLUE, door::ATTR::BOLD)));
  1131. about.addLine(std::make_unique<door::Line>(active));
  1132. */
  1133. return about;
  1134. }
  1135. void display_starfield(door::Door &door, std::mt19937 &rng) {
  1136. door << door::reset << door::cls;
  1137. int mx = door.width;
  1138. int my = door.height;
  1139. // display starfield
  1140. const char *stars[2];
  1141. stars[0] = ".";
  1142. if (door::unicode) {
  1143. stars[1] = "\u2219"; // "\u00b7";
  1144. } else {
  1145. stars[1] = "\xf9"; // "\xfa";
  1146. };
  1147. {
  1148. // Make uniform random distribution between 1 and MAX screen size X/Y
  1149. std::uniform_int_distribution<int> uni_x(1, mx);
  1150. std::uniform_int_distribution<int> uni_y(1, my);
  1151. door::ANSIColor white(door::COLOR::WHITE);
  1152. door::ANSIColor dark(door::COLOR::BLACK, door::ATTR::BRIGHT);
  1153. // 10 is too many, 100 is too few. 40 looks ok.
  1154. int MAX_STARS = ((mx * my) / 40);
  1155. // door.log() << "Generating starmap using " << mx << "," << my << " : "
  1156. // << MAX_STARS << " stars." << std::endl;
  1157. for (int x = 0; x < MAX_STARS; x++) {
  1158. door::Goto star_at(uni_x(rng), uni_y(rng));
  1159. door << star_at;
  1160. if (x % 5 < 2)
  1161. door << dark;
  1162. else
  1163. door << white;
  1164. if (x % 2 == 0)
  1165. door << stars[0];
  1166. else
  1167. door << stars[1];
  1168. }
  1169. }
  1170. }
  1171. void display_space_ace(door::Door &door) {
  1172. int mx = door.width;
  1173. int my = door.height;
  1174. // space_ace is 72 chars wide, 6 high
  1175. int sa_x = (mx - 72) / 2;
  1176. int sa_y = (my - 6) / 2;
  1177. // output the SpaceAce logo -- centered!
  1178. for (const auto s : space) {
  1179. door::Goto sa_at(sa_x, sa_y);
  1180. door << sa_at;
  1181. if (door::unicode) {
  1182. std::string unicode;
  1183. door::cp437toUnicode(s, unicode);
  1184. door << unicode; // << door::nl;
  1185. } else
  1186. door << s; // << door::nl;
  1187. sa_y++;
  1188. }
  1189. // pause 5 seconds so they can enjoy our awesome logo -- if they want.
  1190. door.sleep_key(5);
  1191. }
  1192. void display_starfield_space_ace(door::Door &door, std::mt19937 &rng) {
  1193. // mx = door.width;
  1194. // my = door.height;
  1195. display_starfield(door, rng);
  1196. display_space_ace(door);
  1197. door << door::reset;
  1198. }
  1199. int main(int argc, char *argv[]) {
  1200. door::Door door("space-ace", argc, argv);
  1201. // store the door log so we can easily access it.
  1202. get_logger = [&door]() -> ofstream & { return door.log(); };
  1203. DBData spacedb;
  1204. spacedb.setUser(door.username);
  1205. // retrieve lastcall
  1206. time_t last_call = std::stol(spacedb.getSetting("LastCall", "0"));
  1207. // store now as lastcall
  1208. time_t now =
  1209. std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  1210. spacedb.setSetting("LastCall", std::to_string(now));
  1211. // Have they used this door before?
  1212. if (last_call != 0) {
  1213. door << "Welcome Back!" << door::nl;
  1214. auto nowClock = std::chrono::system_clock::from_time_t(now);
  1215. auto lastClock = std::chrono::system_clock::from_time_t(last_call);
  1216. auto delta = nowClock - lastClock;
  1217. // int days = chrono::duration_cast<chrono::days>(delta).count(); //
  1218. // c++ 20
  1219. int hours = chrono::duration_cast<chrono::hours>(delta).count();
  1220. int days = hours / 24;
  1221. int minutes = chrono::duration_cast<chrono::minutes>(delta).count();
  1222. int secs = chrono::duration_cast<chrono::seconds>(delta).count();
  1223. if (days > 1) {
  1224. door << "It's been " << days << " days since you last played."
  1225. << door::nl;
  1226. } else {
  1227. if (hours > 1) {
  1228. door << "It's been " << hours << " hours since you last played."
  1229. << door::nl;
  1230. } else {
  1231. if (minutes > 1) {
  1232. door << "It's been " << minutes << " minutes since you last played."
  1233. << door::nl;
  1234. } else {
  1235. door << "It's been " << secs << " seconds since you last played."
  1236. << door::nl;
  1237. door << "It's like you never left." << door::nl;
  1238. }
  1239. }
  1240. }
  1241. press_a_key(door);
  1242. }
  1243. /*
  1244. // example: saving the door.log() for global use.
  1245. std::function<std::ofstream &(void)> get_logger;
  1246. get_logger = [&door]() -> ofstream & { return door.log(); };
  1247. if (get_logger) {
  1248. get_logger() << "MEOW" << std::endl;
  1249. get_logger() << "hey! It works!" << std::endl;
  1250. }
  1251. get_logger = nullptr; // before door destruction
  1252. */
  1253. // https://stackoverflow.com/questions/5008804/generating-random-integer-from-a-range
  1254. std::random_device rd; // only used once to initialise (seed) engine
  1255. std::mt19937 rng(rd());
  1256. // random-number engine used (Mersenne-Twister in this case)
  1257. // std::uniform_int_distribution<int> uni(min, max); // guaranteed
  1258. // unbiased
  1259. int mx, my; // Max screen width/height
  1260. if (door.width == 0) {
  1261. // screen detect failed, use sensible defaults
  1262. door.width = mx = 80;
  1263. door.height = my = 23;
  1264. } else {
  1265. mx = door.width;
  1266. my = door.height;
  1267. }
  1268. // We assume here that the width and height aren't something crazy like
  1269. // 10x15. :P (or 24x923!)
  1270. display_starfield_space_ace(door, rng);
  1271. // for testing inactivity timeout
  1272. // door.inactivity = 10;
  1273. door::Panel timeout = make_timeout(mx, my);
  1274. door::Menu m = make_main_menu();
  1275. door::Panel about = make_about(); // 8 lines
  1276. // center the about box
  1277. about.set((mx - 60) / 2, (my - 9) / 2);
  1278. int r = 0;
  1279. while ((r >= 0) and (r != 6)) {
  1280. // starfield + menu ?
  1281. display_starfield(door, rng);
  1282. r = m.choose(door);
  1283. // need to reset the colors. (whoops!)
  1284. door << door::reset << door::cls; // door::nl;
  1285. // OK! The screen is blank at this point!
  1286. switch (r) {
  1287. case 1: // play game
  1288. r = play_cards(door, spacedb, rng);
  1289. break;
  1290. case 2: // view scores
  1291. door << "Show scores goes here!" << door::nl;
  1292. r = press_a_key(door);
  1293. break;
  1294. case 3: // configure
  1295. r = configure(door, spacedb);
  1296. // r = press_a_key(door);
  1297. break;
  1298. case 4: // help
  1299. door << "Help! Need some help here..." << door::nl;
  1300. r = press_a_key(door);
  1301. break;
  1302. case 5: // about
  1303. display_starfield(door, rng);
  1304. door << about << door::nl;
  1305. r = press_a_key(door);
  1306. break;
  1307. case 6: // quit
  1308. break;
  1309. }
  1310. }
  1311. if (r < 0) {
  1312. TIMEOUT:
  1313. if (r == -1) {
  1314. door.log() << "TIMEOUT" << std::endl;
  1315. door << timeout << door::reset << door::nl << door::nl;
  1316. } else {
  1317. if (r == -3) {
  1318. door.log() << "OUTTA TIME" << std::endl;
  1319. door::Panel notime = make_notime(mx, my);
  1320. door << notime << door::reset << door::nl;
  1321. }
  1322. }
  1323. return 0;
  1324. }
  1325. door << door::nl;
  1326. /*
  1327. // magic time!
  1328. door << door::reset << door::nl << "Press another key...";
  1329. int x;
  1330. for (x = 0; x < 60; ++x) {
  1331. r = door.sleep_key(1);
  1332. if (r == -1) {
  1333. // ok! Expected timeout!
  1334. // PROBLEM: regular "local" terminal loses current attributes
  1335. // when cursor is save / restored.
  1336. door << door::SaveCursor;
  1337. if (about.update(door)) {
  1338. // ok I need to "fix" the cursor position.
  1339. // it has moved.
  1340. }
  1341. door << door::RestoreCursor << door::reset;
  1342. } else {
  1343. if (r < 0)
  1344. goto TIMEOUT;
  1345. if (r >= 0)
  1346. break;
  1347. }
  1348. }
  1349. if (x == 60)
  1350. goto TIMEOUT;
  1351. */
  1352. #ifdef NNY
  1353. // configured by the player.
  1354. door::ANSIColor deck_color;
  1355. // RED, BLUE, GREEN, MAGENTA, CYAN
  1356. std::uniform_int_distribution<int> rand_color(0, 4);
  1357. switch (rand_color(rng)) {
  1358. case 0:
  1359. deck_color = door::ANSIColor(door::COLOR::RED);
  1360. break;
  1361. case 1:
  1362. deck_color = door::ANSIColor(door::COLOR::BLUE);
  1363. break;
  1364. case 2:
  1365. deck_color = door::ANSIColor(door::COLOR::GREEN);
  1366. break;
  1367. case 3:
  1368. deck_color = door::ANSIColor(door::COLOR::MAGENTA);
  1369. break;
  1370. case 4:
  1371. deck_color = door::ANSIColor(door::COLOR::CYAN);
  1372. break;
  1373. default:
  1374. deck_color = door::ANSIColor(door::COLOR::BLUE, door::ATTR::BLINK);
  1375. break;
  1376. }
  1377. int height = 3;
  1378. Deck d(deck_color, height);
  1379. door::Panel *c;
  1380. door << door::reset << door::cls;
  1381. // This displays the cards in the upper left corner.
  1382. // We want them center, and down some.
  1383. int space = 3;
  1384. // int cards_dealt_width = 59; int cards_dealt_height = 9;
  1385. int game_width;
  1386. {
  1387. int cx, cy, level;
  1388. cardgo(27, space, height, cx, cy, level);
  1389. game_width = cx + 5; // card width
  1390. }
  1391. int off_x = (mx - game_width) / 2;
  1392. int off_y = (my - 9) / 2;
  1393. // The idea is to see the cards with <<Something Unique to the card
  1394. // game>>, Year, Month, Day, and game (like 1 of 3). This will make the
  1395. // games the same/fair for everyone.
  1396. std::seed_seq s1{2021, 2, 27, 1};
  1397. cards deck1 = card_shuffle(s1, 1);
  1398. cards state = card_states();
  1399. // I tried setting the cursor before the delay and before displaying the
  1400. // card. It is very hard to see / just about useless. Not worth the
  1401. // effort.
  1402. for (int x = 0; x < 28; x++) {
  1403. int cx, cy, level;
  1404. cardgo(x, space, height, cx, cy, level);
  1405. // This is hardly visible.
  1406. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  1407. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  1408. c = d.back(level);
  1409. c->set(cx + off_x, cy + off_y);
  1410. door << *c;
  1411. }
  1412. /*
  1413. std::this_thread::sleep_for(
  1414. std::chrono::seconds(1)); // 3 secs seemed too long!
  1415. */
  1416. for (int x = 18; x < 28; x++) {
  1417. int cx, cy, level;
  1418. // usleep(1000 * 20);
  1419. state.at(x) = 1;
  1420. cardgo(x, space, height, cx, cy, level);
  1421. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  1422. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  1423. c = d.card(deck1.at(x));
  1424. c->set(cx + off_x, cy + off_y);
  1425. door << *c;
  1426. }
  1427. door << door::reset;
  1428. door << door::nl << door::nl;
  1429. r = door.sleep_key(door.inactivity);
  1430. if (r < 0)
  1431. goto TIMEOUT;
  1432. #endif
  1433. /*
  1434. door::Panel *p = d.back(2);
  1435. p->set(10, 10);
  1436. door << *p;
  1437. door::Panel *d8 = d.card(8);
  1438. d8->set(20, 8);
  1439. door << *d8;
  1440. r = door.sleep_key(door.inactivity);
  1441. if (r < 0)
  1442. goto TIMEOUT;
  1443. */
  1444. // door << door::reset << door::cls;
  1445. display_starfield(door, rng);
  1446. door << m << door::reset << door::nl;
  1447. // Normal DOOR exit goes here...
  1448. door << door::nl << "Returning you to the BBS, please wait..." << door::nl;
  1449. get_logger = nullptr;
  1450. return 0;
  1451. }