main.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551
  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. }
  575. {
  576. door::updateFunction currentUpdate = [](void) -> std::string {
  577. std::string text = "Current Streak: ";
  578. text.append(std::to_string(current_streak));
  579. return text;
  580. };
  581. std::string currentString = currentUpdate();
  582. door::Line current(currentString, W);
  583. current.setRender(svRender);
  584. current.setUpdater(currentUpdate);
  585. p.addLine(std::make_unique<door::Line>(current));
  586. }
  587. {
  588. door::updateFunction currentUpdate = [](void) -> std::string {
  589. std::string text = "Longest Streak: ";
  590. text.append(std::to_string(best_streak));
  591. return text;
  592. };
  593. std::string currentString = currentUpdate();
  594. door::Line current(currentString, W);
  595. current.setRender(svRender);
  596. current.setUpdater(currentUpdate);
  597. p.addLine(std::make_unique<door::Line>(current));
  598. }
  599. return p;
  600. }
  601. door::Panel make_left_panel(void) {
  602. const int W = 13;
  603. door::Panel p(W);
  604. p.setStyle(door::BorderStyle::NONE);
  605. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  606. door::ATTR::BOLD);
  607. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  608. door::ATTR::BOLD);
  609. door::renderFunction svRender = statusValue(statusColor, valueColor);
  610. {
  611. door::updateFunction cardsleftUpdate = [](void) -> std::string {
  612. std::string text = "Cards left:";
  613. text.append(std::to_string(51 - card_number));
  614. return text;
  615. };
  616. std::string cardsleftString = "Cards left:--";
  617. door::Line cardsleft(cardsleftString, W);
  618. cardsleft.setRender(svRender);
  619. cardsleft.setUpdater(cardsleftUpdate);
  620. p.addLine(std::make_unique<door::Line>(cardsleft));
  621. }
  622. return p;
  623. }
  624. door::renderFunction commandLineRender(door::ANSIColor bracket,
  625. door::ANSIColor inner,
  626. door::ANSIColor outer) {
  627. door::renderFunction rf = [bracket, inner,
  628. outer](const std::string &txt) -> door::Render {
  629. door::Render r(txt);
  630. door::ColorOutput co;
  631. co.pos = 0;
  632. co.len = 0;
  633. co.c = outer;
  634. bool inOuter = true;
  635. int tpos = 0;
  636. for (char const &c : txt) {
  637. if (inOuter) {
  638. // we're in the outer text
  639. if (co.c != outer) {
  640. if (co.len != 0) {
  641. r.outputs.push_back(co);
  642. co.reset();
  643. co.pos = tpos;
  644. }
  645. co.c = outer;
  646. }
  647. // check for [
  648. if (c == '[') {
  649. if (co.len != 0) {
  650. r.outputs.push_back(co);
  651. co.reset();
  652. co.pos = tpos;
  653. }
  654. inOuter = false;
  655. co.c = bracket;
  656. }
  657. } else {
  658. // We're not in the outer.
  659. if (co.c != inner) {
  660. if (co.len != 0) {
  661. r.outputs.push_back(co);
  662. co.reset();
  663. co.pos = tpos;
  664. }
  665. co.c = inner;
  666. }
  667. if (c == ']') {
  668. if (co.len != 0) {
  669. r.outputs.push_back(co);
  670. co.reset();
  671. co.pos = tpos;
  672. }
  673. inOuter = true;
  674. co.c = bracket;
  675. }
  676. }
  677. co.len++;
  678. tpos++;
  679. }
  680. if (co.len != 0)
  681. r.outputs.push_back(co);
  682. return r;
  683. };
  684. return rf;
  685. }
  686. door::Panel make_command_panel(void) {
  687. const int W = 76;
  688. door::Panel p(W);
  689. p.setStyle(door::BorderStyle::NONE);
  690. std::string commands;
  691. if (door::unicode) {
  692. commands = "[4/\u25c4] Left [6/\u25ba] Right [Space] Play Card [Enter] "
  693. "Draw [Q]uit "
  694. "[R]edraw [H]elp";
  695. } else {
  696. commands =
  697. "[4/\x11] Left [6/\x10] Right [Space] Play Card [Enter] Draw [Q]uit "
  698. "[R]edraw [H]elp";
  699. }
  700. door::ANSIColor bracketColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  701. door::ATTR::BOLD);
  702. door::ANSIColor innerColor(door::COLOR::CYAN, door::COLOR::BLUE,
  703. door::ATTR::BOLD);
  704. door::ANSIColor outerColor(door::COLOR::GREEN, door::COLOR::BLUE,
  705. door::ATTR::BOLD);
  706. door::renderFunction cmdRender =
  707. commandLineRender(bracketColor, innerColor, outerColor);
  708. door::Line cmd(commands, W);
  709. cmd.setRender(cmdRender);
  710. p.addLine(std::make_unique<door::Line>(cmd));
  711. return p;
  712. }
  713. door::Panel make_tripeaks(void) {
  714. std::string tripeaksText(" " SPACEACE
  715. " - Tri-Peaks Solitaire v" SPACEACE_VERSION " ");
  716. door::Panel spaceAceTriPeaks(tripeaksText.size());
  717. spaceAceTriPeaks.setStyle(door::BorderStyle::SINGLE);
  718. spaceAceTriPeaks.setColor(
  719. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLACK));
  720. spaceAceTriPeaks.addLine(
  721. std::make_unique<door::Line>(tripeaksText, tripeaksText.size()));
  722. return spaceAceTriPeaks;
  723. }
  724. int play_cards(door::Door &door, DBData &db, std::mt19937 &rng) {
  725. int mx = door.width;
  726. int my = door.height;
  727. // init these values:
  728. card_number = 28;
  729. active_card = 23;
  730. score = 0;
  731. // cards color --
  732. // configured by the player.
  733. door::ANSIColor deck_color;
  734. // std::string key("DeckColor");
  735. const char *key = "DeckColor";
  736. std::string currentDefault = db.getSetting(key, "ALL");
  737. door.log() << key << " shows as " << currentDefault << std::endl;
  738. deck_color = from_string(currentDefault);
  739. int height = 3;
  740. Deck d(deck_color, height);
  741. door::Panel *c;
  742. // This displays the cards in the upper left corner.
  743. // We want them center, and down some.
  744. const int space = 3;
  745. // int cards_dealt_width = 59; int cards_dealt_height = 9;
  746. int game_width;
  747. int game_height = 20; // 13; // 9;
  748. {
  749. int cx, cy, level;
  750. cardgo(27, space, height, cx, cy, level);
  751. game_width = cx + 5; // card width
  752. }
  753. int off_x = (mx - game_width) / 2;
  754. int off_y = (my - game_height) / 2;
  755. // The idea is to see the cards with <<Something Unique to the card game>>,
  756. // Year, Month, Day, and game (like 1 of 3).
  757. // This will make the games the same/fair for everyone.
  758. door::Panel spaceAceTriPeaks = make_tripeaks();
  759. int tp_off_x = (mx - spaceAceTriPeaks.getWidth()) / 2;
  760. spaceAceTriPeaks.set(tp_off_x, off_y);
  761. off_y += 3;
  762. // figure out what date we're playing / what game we're playing
  763. std::seed_seq s1{2021, 2, 27, 1};
  764. cards deck1 = card_shuffle(s1, 1);
  765. cards state = card_states();
  766. door::Panel score_panel = make_score_panel(door);
  767. door::Panel streak_panel = make_streak_panel();
  768. door::Panel left_panel = make_left_panel();
  769. door::Panel cmd_panel = make_command_panel();
  770. {
  771. int off_yp = off_y + 11;
  772. int cxp, cyp, levelp;
  773. int left_panel_x, right_panel_x;
  774. // find position of card, to position the panels
  775. cardgo(18, space, height, cxp, cyp, levelp);
  776. left_panel_x = cxp;
  777. cardgo(15, space, height, cxp, cyp, levelp);
  778. right_panel_x = cxp;
  779. score_panel.set(left_panel_x + off_x, off_yp);
  780. streak_panel.set(right_panel_x + off_x, off_yp);
  781. cmd_panel.set(left_panel_x + off_x, off_yp + 5);
  782. }
  783. bool dealing = true;
  784. int r = 0;
  785. while ((r >= 0) and (r != 'Q')) {
  786. // REDRAW everything
  787. door << door::reset << door::cls;
  788. door << spaceAceTriPeaks;
  789. {
  790. // step 1:
  791. // draw the deck "source"
  792. int cx, cy, level;
  793. cardgo(29, space, height, cx, cy, level);
  794. if (card_number == 51)
  795. level = 0; // out of cards!
  796. c = d.back(level);
  797. c->set(cx + off_x, cy + off_y);
  798. // p3 is heigh below
  799. left_panel.set(cx + off_x, cy + off_y + height);
  800. door << score_panel << left_panel << streak_panel << cmd_panel;
  801. door << *c;
  802. if (dealing)
  803. std::this_thread::sleep_for(std::chrono::seconds(1));
  804. }
  805. // I tried setting the cursor before the delay and before displaying the
  806. // card. It is very hard to see / just about useless. Not worth the effort.
  807. for (int x = 0; x < (dealing ? 28 : 29); x++) {
  808. int cx, cy, level;
  809. cardgo(x, space, height, cx, cy, level);
  810. // This is hardly visible.
  811. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  812. if (dealing)
  813. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  814. if (dealing) {
  815. c = d.back(level);
  816. c->set(cx + off_x, cy + off_y);
  817. door << *c;
  818. } else {
  819. // redrawing -- draw the cards with their correct "state"
  820. int s = state.at(x);
  821. switch (s) {
  822. case 0:
  823. c = d.back(level);
  824. c->set(cx + off_x, cy + off_y);
  825. door << *c;
  826. break;
  827. case 1:
  828. // cardgo(x, space, height, cx, cy, level);
  829. if (x == 28)
  830. c = d.card(deck1.at(card_number));
  831. else
  832. c = d.card(deck1.at(x));
  833. c->set(cx + off_x, cy + off_y);
  834. door << *c;
  835. break;
  836. case 2:
  837. // no card to draw. :)
  838. break;
  839. }
  840. }
  841. }
  842. if (dealing)
  843. for (int x = 18; x < 29; x++) {
  844. int cx, cy, level;
  845. state.at(x) = 1;
  846. cardgo(x, space, height, cx, cy, level);
  847. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  848. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  849. c = d.card(deck1.at(x));
  850. c->set(cx + off_x, cy + off_y);
  851. door << *c;
  852. }
  853. {
  854. int cx, cy, level;
  855. cardgo(active_card, space, height, cx, cy, level);
  856. c = d.marker(1);
  857. c->set(cx + off_x + 2, cy + off_y + 2);
  858. door << *c;
  859. }
  860. dealing = false;
  861. left_panel.update(door);
  862. door << door::reset;
  863. bool now_what = true;
  864. while (now_what) {
  865. // time might have updated, so update score panel too.
  866. score_panel.update(door);
  867. r = door.sleep_key(door.inactivity);
  868. if (r > 0) {
  869. // They didn't timeout/expire. They didn't press a function key.
  870. if (r < 0x1000)
  871. r = std::toupper(r);
  872. switch (r) {
  873. case ' ':
  874. if (card_number < 51) {
  875. card_number++;
  876. // Ok, deal next card from the pile.
  877. int cx, cy, level;
  878. if (card_number == 51) {
  879. cardgo(29, space, height, cx, cy, level);
  880. level = 0; // out of cards
  881. c = d.back(level);
  882. c->set(cx + off_x, cy + off_y);
  883. door << *c;
  884. }
  885. cardgo(28, space, height, cx, cy, level);
  886. c = d.card(deck1.at(card_number));
  887. c->set(cx + off_x, cy + off_y);
  888. door << *c;
  889. // update the cards left_panel
  890. left_panel.update(door);
  891. }
  892. break;
  893. case 'R':
  894. now_what = false;
  895. break;
  896. case 'Q':
  897. now_what = false;
  898. break;
  899. case XKEY_LEFT_ARROW:
  900. case '4': {
  901. int new_active = active_card - 1;
  902. while (new_active >= 0) {
  903. if (state.at(new_active) == 1)
  904. break;
  905. --new_active;
  906. }
  907. if (new_active >= 0) {
  908. int cx, cy, level;
  909. cardgo(active_card, space, height, cx, cy, level);
  910. c = d.marker(0);
  911. c->set(cx + off_x + 2, cy + off_y + 2);
  912. door << *c;
  913. active_card = new_active;
  914. cardgo(active_card, space, height, cx, cy, level);
  915. c = d.marker(1);
  916. c->set(cx + off_x + 2, cy + off_y + 2);
  917. door << *c;
  918. }
  919. } break;
  920. case XKEY_RIGHT_ARROW:
  921. case '6': {
  922. int new_active = active_card + 1;
  923. while (new_active < 28) {
  924. if (state.at(new_active) == 1)
  925. break;
  926. ++new_active;
  927. }
  928. if (new_active < 28) {
  929. int cx, cy, level;
  930. cardgo(active_card, space, height, cx, cy, level);
  931. c = d.marker(0);
  932. c->set(cx + off_x + 2, cy + off_y + 2);
  933. door << *c;
  934. active_card = new_active;
  935. cardgo(active_card, space, height, cx, cy, level);
  936. c = d.marker(1);
  937. c->set(cx + off_x + 2, cy + off_y + 2);
  938. door << *c;
  939. }
  940. }
  941. break;
  942. }
  943. } else
  944. now_what = false;
  945. }
  946. }
  947. return r;
  948. }
  949. door::Panel make_about(void) {
  950. const int W = 60;
  951. door::Panel about(W);
  952. about.setStyle(door::BorderStyle::DOUBLE_SINGLE);
  953. about.setColor(door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  954. door::ATTR::BOLD));
  955. about.addLine(std::make_unique<door::Line>("About This Door", W));
  956. about.addLine(std::make_unique<door::Line>(
  957. "---------------------------------", W,
  958. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE, door::ATTR::BOLD)));
  959. /*
  960. 123456789012345678901234567890123456789012345678901234567890-60
  961. This door was written by Bugz.
  962. It is written in c++, only supports Linux, and replaces
  963. opendoors.
  964. It's written in c++, and replaces the outdated opendoors
  965. library.
  966. */
  967. about.addLine(
  968. std::make_unique<door::Line>(SPACEACE " v" SPACEACE_VERSION, W));
  969. std::string copyright = SPACEACE_COPYRIGHT;
  970. if (door::unicode) {
  971. replace(copyright, "(C)", "\u00a9");
  972. }
  973. about.addLine(std::make_unique<door::Line>(copyright, W));
  974. about.addLine(std::make_unique<door::Line>("", W));
  975. about.addLine(
  976. std::make_unique<door::Line>("This door was written by Bugz.", W));
  977. about.addLine(std::make_unique<door::Line>("", W));
  978. about.addLine(std::make_unique<door::Line>(
  979. "It is written in c++, only support Linux, and replaces", W));
  980. about.addLine(std::make_unique<door::Line>("opendoors.", W));
  981. /*
  982. door::updateFunction updater = [](void) -> std::string {
  983. std::string text = "Currently: ";
  984. text.append(return_current_time_and_date());
  985. return text;
  986. };
  987. std::string current = updater();
  988. door::Line active(current, 60);
  989. active.setUpdater(updater);
  990. active.setRender(statusValue(
  991. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  992. door::ATTR::BOLD), door::ANSIColor(door::COLOR::YELLOW,
  993. door::COLOR::BLUE, door::ATTR::BOLD)));
  994. about.addLine(std::make_unique<door::Line>(active));
  995. */
  996. return about;
  997. }
  998. void display_starfield(door::Door &door, std::mt19937 &rng) {
  999. door << door::reset << door::cls;
  1000. int mx = door.width;
  1001. int my = door.height;
  1002. // display starfield
  1003. const char *stars[2];
  1004. stars[0] = ".";
  1005. if (door::unicode) {
  1006. stars[1] = "\u2219"; // "\u00b7";
  1007. } else {
  1008. stars[1] = "\xf9"; // "\xfa";
  1009. };
  1010. {
  1011. // Make uniform random distribution between 1 and MAX screen size X/Y
  1012. std::uniform_int_distribution<int> uni_x(1, mx);
  1013. std::uniform_int_distribution<int> uni_y(1, my);
  1014. door::ANSIColor white(door::COLOR::WHITE);
  1015. door::ANSIColor dark(door::COLOR::BLACK, door::ATTR::BRIGHT);
  1016. // 10 is too many, 100 is too few. 40 looks ok.
  1017. int MAX_STARS = ((mx * my) / 40);
  1018. // door.log() << "Generating starmap using " << mx << "," << my << " : "
  1019. // << MAX_STARS << " stars." << std::endl;
  1020. for (int x = 0; x < MAX_STARS; x++) {
  1021. door::Goto star_at(uni_x(rng), uni_y(rng));
  1022. door << star_at;
  1023. if (x % 5 < 2)
  1024. door << dark;
  1025. else
  1026. door << white;
  1027. if (x % 2 == 0)
  1028. door << stars[0];
  1029. else
  1030. door << stars[1];
  1031. }
  1032. }
  1033. }
  1034. void display_space_ace(door::Door &door) {
  1035. int mx = door.width;
  1036. int my = door.height;
  1037. // space_ace is 72 chars wide, 6 high
  1038. int sa_x = (mx - 72) / 2;
  1039. int sa_y = (my - 6) / 2;
  1040. // output the SpaceAce logo -- centered!
  1041. for (const auto s : space) {
  1042. door::Goto sa_at(sa_x, sa_y);
  1043. door << sa_at;
  1044. if (door::unicode) {
  1045. std::string unicode;
  1046. door::cp437toUnicode(s, unicode);
  1047. door << unicode; // << door::nl;
  1048. } else
  1049. door << s; // << door::nl;
  1050. sa_y++;
  1051. }
  1052. // pause 5 seconds so they can enjoy our awesome logo -- if they want.
  1053. door.sleep_key(5);
  1054. }
  1055. void display_starfield_space_ace(door::Door &door, std::mt19937 &rng) {
  1056. // mx = door.width;
  1057. // my = door.height;
  1058. display_starfield(door, rng);
  1059. display_space_ace(door);
  1060. door << door::reset;
  1061. }
  1062. int main(int argc, char *argv[]) {
  1063. door::Door door("space-ace", argc, argv);
  1064. // store the door log so we can easily access it.
  1065. get_logger = [&door]() -> ofstream & { return door.log(); };
  1066. DBData spacedb;
  1067. spacedb.setUser(door.username);
  1068. // retrieve lastcall
  1069. time_t last_call = std::stol(spacedb.getSetting("LastCall", "0"));
  1070. // store now as lastcall
  1071. time_t now =
  1072. std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  1073. spacedb.setSetting("LastCall", std::to_string(now));
  1074. // Have they used this door before?
  1075. if (last_call != 0) {
  1076. door << "Welcome Back!" << door::nl;
  1077. auto nowClock = std::chrono::system_clock::from_time_t(now);
  1078. auto lastClock = std::chrono::system_clock::from_time_t(last_call);
  1079. auto delta = nowClock - lastClock;
  1080. // int days = chrono::duration_cast<chrono::days>(delta).count(); //
  1081. // c++ 20
  1082. int hours = chrono::duration_cast<chrono::hours>(delta).count();
  1083. int days = hours / 24;
  1084. int minutes = chrono::duration_cast<chrono::minutes>(delta).count();
  1085. int secs = chrono::duration_cast<chrono::seconds>(delta).count();
  1086. if (days > 1) {
  1087. door << "It's been " << days << " days since you last played."
  1088. << door::nl;
  1089. } else {
  1090. if (hours > 1) {
  1091. door << "It's been " << hours << " hours since you last played."
  1092. << door::nl;
  1093. } else {
  1094. if (minutes > 1) {
  1095. door << "It's been " << minutes << " minutes since you last played."
  1096. << door::nl;
  1097. } else {
  1098. door << "It's been " << secs << " seconds since you last played."
  1099. << door::nl;
  1100. door << "It's like you never left." << door::nl;
  1101. }
  1102. }
  1103. }
  1104. press_a_key(door);
  1105. }
  1106. /*
  1107. // example: saving the door.log() for global use.
  1108. std::function<std::ofstream &(void)> get_logger;
  1109. get_logger = [&door]() -> ofstream & { return door.log(); };
  1110. if (get_logger) {
  1111. get_logger() << "MEOW" << std::endl;
  1112. get_logger() << "hey! It works!" << std::endl;
  1113. }
  1114. get_logger = nullptr; // before door destruction
  1115. */
  1116. // https://stackoverflow.com/questions/5008804/generating-random-integer-from-a-range
  1117. std::random_device rd; // only used once to initialise (seed) engine
  1118. std::mt19937 rng(rd());
  1119. // random-number engine used (Mersenne-Twister in this case)
  1120. // std::uniform_int_distribution<int> uni(min, max); // guaranteed
  1121. // unbiased
  1122. int mx, my; // Max screen width/height
  1123. if (door.width == 0) {
  1124. // screen detect failed, use sensible defaults
  1125. door.width = mx = 80;
  1126. door.height = my = 23;
  1127. } else {
  1128. mx = door.width;
  1129. my = door.height;
  1130. }
  1131. // We assume here that the width and height aren't something crazy like
  1132. // 10x15. :P (or 24x923!)
  1133. display_starfield_space_ace(door, rng);
  1134. // for testing inactivity timeout
  1135. // door.inactivity = 10;
  1136. door::Panel timeout = make_timeout(mx, my);
  1137. door::Menu m = make_main_menu();
  1138. door::Panel about = make_about(); // 8 lines
  1139. // center the about box
  1140. about.set((mx - 60) / 2, (my - 9) / 2);
  1141. int r = 0;
  1142. while ((r >= 0) and (r != 6)) {
  1143. // starfield + menu ?
  1144. display_starfield(door, rng);
  1145. r = m.choose(door);
  1146. // need to reset the colors. (whoops!)
  1147. door << door::reset << door::cls; // door::nl;
  1148. // OK! The screen is blank at this point!
  1149. switch (r) {
  1150. case 1: // play game
  1151. r = play_cards(door, spacedb, rng);
  1152. break;
  1153. case 2: // view scores
  1154. door << "Show scores goes here!" << door::nl;
  1155. r = press_a_key(door);
  1156. break;
  1157. case 3: // configure
  1158. r = configure(door, spacedb);
  1159. // r = press_a_key(door);
  1160. break;
  1161. case 4: // help
  1162. door << "Help! Need some help here..." << door::nl;
  1163. r = press_a_key(door);
  1164. break;
  1165. case 5: // about
  1166. display_starfield(door, rng);
  1167. door << about << door::nl;
  1168. r = press_a_key(door);
  1169. break;
  1170. case 6: // quit
  1171. break;
  1172. }
  1173. }
  1174. if (r < 0) {
  1175. TIMEOUT:
  1176. if (r == -1) {
  1177. door.log() << "TIMEOUT" << std::endl;
  1178. door << timeout << door::reset << door::nl << door::nl;
  1179. } else {
  1180. if (r == -3) {
  1181. door.log() << "OUTTA TIME" << std::endl;
  1182. door::Panel notime = make_notime(mx, my);
  1183. door << notime << door::reset << door::nl;
  1184. }
  1185. }
  1186. return 0;
  1187. }
  1188. door << door::nl;
  1189. /*
  1190. // magic time!
  1191. door << door::reset << door::nl << "Press another key...";
  1192. int x;
  1193. for (x = 0; x < 60; ++x) {
  1194. r = door.sleep_key(1);
  1195. if (r == -1) {
  1196. // ok! Expected timeout!
  1197. // PROBLEM: regular "local" terminal loses current attributes
  1198. // when cursor is save / restored.
  1199. door << door::SaveCursor;
  1200. if (about.update(door)) {
  1201. // ok I need to "fix" the cursor position.
  1202. // it has moved.
  1203. }
  1204. door << door::RestoreCursor << door::reset;
  1205. } else {
  1206. if (r < 0)
  1207. goto TIMEOUT;
  1208. if (r >= 0)
  1209. break;
  1210. }
  1211. }
  1212. if (x == 60)
  1213. goto TIMEOUT;
  1214. */
  1215. #ifdef NNY
  1216. // configured by the player.
  1217. door::ANSIColor deck_color;
  1218. // RED, BLUE, GREEN, MAGENTA, CYAN
  1219. std::uniform_int_distribution<int> rand_color(0, 4);
  1220. switch (rand_color(rng)) {
  1221. case 0:
  1222. deck_color = door::ANSIColor(door::COLOR::RED);
  1223. break;
  1224. case 1:
  1225. deck_color = door::ANSIColor(door::COLOR::BLUE);
  1226. break;
  1227. case 2:
  1228. deck_color = door::ANSIColor(door::COLOR::GREEN);
  1229. break;
  1230. case 3:
  1231. deck_color = door::ANSIColor(door::COLOR::MAGENTA);
  1232. break;
  1233. case 4:
  1234. deck_color = door::ANSIColor(door::COLOR::CYAN);
  1235. break;
  1236. default:
  1237. deck_color = door::ANSIColor(door::COLOR::BLUE, door::ATTR::BLINK);
  1238. break;
  1239. }
  1240. int height = 3;
  1241. Deck d(deck_color, height);
  1242. door::Panel *c;
  1243. door << door::reset << door::cls;
  1244. // This displays the cards in the upper left corner.
  1245. // We want them center, and down some.
  1246. int space = 3;
  1247. // int cards_dealt_width = 59; int cards_dealt_height = 9;
  1248. int game_width;
  1249. {
  1250. int cx, cy, level;
  1251. cardgo(27, space, height, cx, cy, level);
  1252. game_width = cx + 5; // card width
  1253. }
  1254. int off_x = (mx - game_width) / 2;
  1255. int off_y = (my - 9) / 2;
  1256. // The idea is to see the cards with <<Something Unique to the card
  1257. // game>>, Year, Month, Day, and game (like 1 of 3). This will make the
  1258. // games the same/fair for everyone.
  1259. std::seed_seq s1{2021, 2, 27, 1};
  1260. cards deck1 = card_shuffle(s1, 1);
  1261. cards state = card_states();
  1262. // I tried setting the cursor before the delay and before displaying the
  1263. // card. It is very hard to see / just about useless. Not worth the
  1264. // effort.
  1265. for (int x = 0; x < 28; x++) {
  1266. int cx, cy, level;
  1267. cardgo(x, space, height, cx, cy, level);
  1268. // This is hardly visible.
  1269. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  1270. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  1271. c = d.back(level);
  1272. c->set(cx + off_x, cy + off_y);
  1273. door << *c;
  1274. }
  1275. /*
  1276. std::this_thread::sleep_for(
  1277. std::chrono::seconds(1)); // 3 secs seemed too long!
  1278. */
  1279. for (int x = 18; x < 28; x++) {
  1280. int cx, cy, level;
  1281. // usleep(1000 * 20);
  1282. state.at(x) = 1;
  1283. cardgo(x, space, height, cx, cy, level);
  1284. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  1285. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  1286. c = d.card(deck1.at(x));
  1287. c->set(cx + off_x, cy + off_y);
  1288. door << *c;
  1289. }
  1290. door << door::reset;
  1291. door << door::nl << door::nl;
  1292. r = door.sleep_key(door.inactivity);
  1293. if (r < 0)
  1294. goto TIMEOUT;
  1295. #endif
  1296. /*
  1297. door::Panel *p = d.back(2);
  1298. p->set(10, 10);
  1299. door << *p;
  1300. door::Panel *d8 = d.card(8);
  1301. d8->set(20, 8);
  1302. door << *d8;
  1303. r = door.sleep_key(door.inactivity);
  1304. if (r < 0)
  1305. goto TIMEOUT;
  1306. */
  1307. // door << door::reset << door::cls;
  1308. display_starfield(door, rng);
  1309. door << m << door::reset << door::nl;
  1310. // Normal DOOR exit goes here...
  1311. door << door::nl << "Returning you to the BBS, please wait..." << door::nl;
  1312. get_logger = nullptr;
  1313. return 0;
  1314. }