main.cpp 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  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 "play.h"
  12. #include "version.h"
  13. #include <algorithm> // transform
  14. bool replace(std::string &str, const std::string &from, const std::string &to) {
  15. size_t start_pos = str.find(from);
  16. if (start_pos == std::string::npos)
  17. return false;
  18. str.replace(start_pos, from.length(), to);
  19. return true;
  20. }
  21. bool replace(std::string &str, const char *from, const char *to) {
  22. size_t start_pos = str.find(from);
  23. if (start_pos == std::string::npos)
  24. return false;
  25. str.replace(start_pos, strlen(from), to);
  26. return true;
  27. }
  28. door::ANSIColor from_string(std::string colorCode);
  29. std::function<std::ofstream &(void)> get_logger;
  30. unsigned long score = 0;
  31. int hand = 1;
  32. int total_hands = 3;
  33. int card_number = 28;
  34. int current_streak = 0;
  35. int best_streak = 0;
  36. int active_card = 23;
  37. std::chrono::_V2::system_clock::time_point play_day;
  38. /*
  39. Cards:
  40. 4 layers deep.
  41. https://en.wikipedia.org/wiki/Code_page_437
  42. using: \xb0, 0xb1, 0xb2, 0xdb
  43. OR: \u2591, \u2592, \u2593, \u2588
  44. Like so:
  45. ##### #####
  46. ##### #####
  47. ##### #####
  48. Cards: (Black on White, or Red on White)
  49. 8D### TH###
  50. ##D## ##H##
  51. ###D8 ###HT
  52. D, H = Red, Clubs, Spades = Black.
  53. ^ Where D = Diamonds, H = Hearts
  54. ♥, ♦, ♣, ♠
  55. \x03, \x04, \x05, \x06
  56. \u2665, \u2666, \u2663, \u2660
  57. Card layout. Actual cards are 3 lines thick.
  58. ░░░░░ ░░░░░ ░░░░░
  59. ░░░░░ ░░░░░ ░░░░░
  60. ▒▒▒▒▒░▒▒▒▒▒ #####░##### #####░#####
  61. ▒▒▒▒▒ ▒▒▒▒▒ ##### ##### ##### #####
  62. ▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▓ #####=#####=##### #####=#####=#####
  63. ▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓ ##### ##### ##### ##### ##### #####
  64. █████▓█████▓█████▓#####=#####=#####=#####=#####=#####=#####
  65. █████ █████ █████ ##### ##### ##### ##### ##### ##### #####
  66. █████ █████ █████ ##### ##### ##### ##### ##### ##### #####
  67. #####
  68. Player Information ##### Time in: xx Time out: xx
  69. Name: ##### Playing Day: November 3rd
  70. Hand Score : Current Streak: N
  71. Todays Score : XX Cards Remaining Longest Streak: NN
  72. Monthly Score: Playing Hand X of X Most Won: xxx Lost: xxx
  73. [4] Lf [6] Rt [Space] Play Card [Enter] Draw [D]one [H]elp [R]edraw
  74. ►, ◄, ▲, ▼
  75. \x10, \x11, \x1e, \x1f
  76. \u25ba, \u25c4, \u25b2, \u25bc
  77. The Name is <- 6 to the left.
  78. # is back of card. = is upper card showing between.
  79. There's no fancy anything here. Cards overlap the last
  80. line of the previous line/card.
  81. */
  82. door::Panel make_timeout(int mx, int my) {
  83. door::ANSIColor yellowred =
  84. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::RED, door::ATTR::BOLD);
  85. std::string line_text("Sorry, you've been inactive for too long.");
  86. int msgWidth = line_text.length() + (2 * 3); // + padding * 2
  87. door::Panel timeout((mx - (msgWidth)) / 2, my / 2 + 4, msgWidth);
  88. // place.setTitle(std::make_unique<door::Line>(title), 1);
  89. timeout.setStyle(door::BorderStyle::DOUBLE);
  90. timeout.setColor(yellowred);
  91. door::Line base(line_text);
  92. base.setColor(yellowred);
  93. std::string pad1(3, ' ');
  94. /*
  95. std::string pad1(3, '\xb0');
  96. if (door::unicode) {
  97. std::string unicode;
  98. door::cp437toUnicode(pad1.c_str(), unicode);
  99. pad1 = unicode;
  100. }
  101. */
  102. base.setPadding(pad1, yellowred);
  103. // base.setColor(door::ANSIColor(door::COLOR::GREEN, door::COLOR::BLACK));
  104. std::unique_ptr<door::Line> stuff = std::make_unique<door::Line>(base);
  105. timeout.addLine(std::make_unique<door::Line>(base));
  106. return timeout;
  107. }
  108. door::Panel make_notime(int mx, int my) {
  109. door::ANSIColor yellowred =
  110. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::RED, door::ATTR::BOLD);
  111. std::string line_text("Sorry, you've used up all your time for today.");
  112. int msgWidth = line_text.length() + (2 * 3); // + padding * 2
  113. door::Panel timeout((mx - (msgWidth)) / 2, my / 2 + 4, msgWidth);
  114. timeout.setStyle(door::BorderStyle::DOUBLE);
  115. timeout.setColor(yellowred);
  116. door::Line base(line_text);
  117. base.setColor(yellowred);
  118. std::string pad1(3, ' ');
  119. /*
  120. std::string pad1(3, '\xb0');
  121. if (door::unicode) {
  122. std::string unicode;
  123. door::cp437toUnicode(pad1.c_str(), unicode);
  124. pad1 = unicode;
  125. }
  126. */
  127. base.setPadding(pad1, yellowred);
  128. std::unique_ptr<door::Line> stuff = std::make_unique<door::Line>(base);
  129. timeout.addLine(std::make_unique<door::Line>(base));
  130. return timeout;
  131. }
  132. door::Menu make_main_menu(void) {
  133. door::Menu m(5, 5, 25);
  134. door::Line mtitle(SPACEACE " Main Menu");
  135. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  136. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  137. door::ATTR::BOLD);
  138. m.setColor(border_color);
  139. mtitle.setColor(title_color);
  140. mtitle.setPadding(" ", title_color);
  141. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  142. // m.setColorizer(true,
  143. m.setRender(true, door::Menu::makeRender(
  144. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  145. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  146. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  147. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD)));
  148. // m.setColorizer(false,
  149. m.setRender(false, door::Menu::makeRender(
  150. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  151. door::ATTR::BOLD),
  152. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  153. door::ATTR::BOLD),
  154. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  155. door::ATTR::BOLD),
  156. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE,
  157. door::ATTR::BOLD)));
  158. m.addSelection('P', "Play Cards");
  159. m.addSelection('S', "View Scores");
  160. m.addSelection('C', "Configure");
  161. m.addSelection('H', "Help");
  162. m.addSelection('A', "About this game");
  163. m.addSelection('Q', "Quit");
  164. return m;
  165. }
  166. door::renderFunction statusValue(door::ANSIColor status,
  167. door::ANSIColor value) {
  168. door::renderFunction rf = [status,
  169. value](const std::string &txt) -> door::Render {
  170. door::Render r(txt);
  171. door::ColorOutput co;
  172. co.pos = 0;
  173. co.len = 0;
  174. co.c = status;
  175. size_t pos = txt.find(':');
  176. if (pos == std::string::npos) {
  177. // failed to find :, render digits/numbers in value color
  178. int tpos = 0;
  179. for (char const &c : txt) {
  180. if (std::isdigit(c)) {
  181. if (co.c != value) {
  182. r.outputs.push_back(co);
  183. co.reset();
  184. co.pos = tpos;
  185. co.c = value;
  186. }
  187. } else {
  188. if (co.c != status) {
  189. r.outputs.push_back(co);
  190. co.reset();
  191. co.pos = tpos;
  192. co.c = status;
  193. }
  194. }
  195. co.len++;
  196. tpos++;
  197. }
  198. if (co.len != 0)
  199. r.outputs.push_back(co);
  200. } else {
  201. pos++; // Have : in status color
  202. co.len = pos;
  203. r.outputs.push_back(co);
  204. co.reset();
  205. co.pos = pos;
  206. co.c = value;
  207. co.len = txt.length() - pos;
  208. r.outputs.push_back(co);
  209. }
  210. return r;
  211. };
  212. return rf;
  213. }
  214. /*
  215. door::renderFunction rStatus = [](const std::string &txt) -> door::Render {
  216. door::Render r(txt);
  217. door::ColorOutput co;
  218. // default colors STATUS: value
  219. door::ANSIColor status(door::COLOR::BLUE, door::ATTR::BOLD);
  220. door::ANSIColor value(door::COLOR::YELLOW, door::ATTR::BOLD);
  221. co.pos = 0;
  222. co.len = 0;
  223. co.c = status;
  224. size_t pos = txt.find(':');
  225. if (pos == std::string::npos) {
  226. // failed to find - use entire string as status color.
  227. co.len = txt.length();
  228. r.outputs.push_back(co);
  229. } else {
  230. pos++; // Have : in status color
  231. co.len = pos;
  232. r.outputs.push_back(co);
  233. co.reset();
  234. co.pos = pos;
  235. co.c = value;
  236. co.len = txt.length() - pos;
  237. r.outputs.push_back(co);
  238. }
  239. return r;
  240. };
  241. */
  242. std::string return_current_time_and_date() {
  243. auto now = std::chrono::system_clock::now();
  244. auto in_time_t = std::chrono::system_clock::to_time_t(now);
  245. std::stringstream ss;
  246. // ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %X");
  247. ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %r");
  248. return ss.str();
  249. }
  250. int press_a_key(door::Door &door) {
  251. door << door::reset << "Press a key to continue...";
  252. int r = door.sleep_key(door.inactivity);
  253. door << door::nl;
  254. return r;
  255. }
  256. door::Menu make_config_menu(void) {
  257. door::Menu m(5, 5, 31);
  258. door::Line mtitle(SPACEACE " Configuration Menu");
  259. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  260. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  261. door::ATTR::BOLD);
  262. m.setColor(border_color);
  263. mtitle.setColor(title_color);
  264. mtitle.setPadding(" ", title_color);
  265. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  266. // m.setColorizer(true,
  267. m.setRender(true, door::Menu::makeRender(
  268. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  269. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  270. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  271. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD)));
  272. // m.setColorizer(false,
  273. m.setRender(false, door::Menu::makeRender(
  274. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  275. door::ATTR::BOLD),
  276. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  277. door::ATTR::BOLD),
  278. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  279. door::ATTR::BOLD),
  280. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE,
  281. door::ATTR::BOLD)));
  282. m.addSelection('D', "Deck Colors");
  283. m.addSelection('Q', "Quit");
  284. return m;
  285. }
  286. /*
  287. // all the possible deck colors
  288. vector<std::string> deck_colors = {std::string("All"), std::string("Blue"),
  289. std::string("Cyan"), std::string("Green"),
  290. std::string("Magenta"), std::string("Red")};
  291. */
  292. door::Menu make_deck_menu(void) {
  293. door::Menu m(5, 5, 31);
  294. door::Line mtitle(SPACEACE " Deck Menu");
  295. door::ANSIColor border_color(door::COLOR::CYAN, door::COLOR::BLUE);
  296. door::ANSIColor title_color(door::COLOR::CYAN, door::COLOR::BLUE,
  297. door::ATTR::BOLD);
  298. m.setColor(border_color);
  299. mtitle.setColor(title_color);
  300. mtitle.setPadding(" ", title_color);
  301. m.setTitle(std::make_unique<door::Line>(mtitle), 1);
  302. m.setRender(true, makeColorRender(
  303. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD),
  304. door::ANSIColor(door::COLOR::BLUE, door::ATTR::BOLD),
  305. door::ANSIColor(door::COLOR::CYAN, door::ATTR::BOLD)));
  306. m.setRender(false, makeColorRender(
  307. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  308. door::ATTR::BOLD),
  309. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  310. door::ATTR::BOLD),
  311. door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  312. door::ATTR::BOLD)));
  313. // build the menu options from the colors. First character = single letter
  314. // option trigger.
  315. for (auto iter = deck_colors.begin(); iter != deck_colors.end(); ++iter) {
  316. char c = (*iter)[0];
  317. m.addSelection(c, (*iter).c_str());
  318. }
  319. /*
  320. m.addSelection('A', "All");
  321. m.addSelection('B', "Blue");
  322. m.addSelection('C', "Cyan");
  323. m.addSelection('G', "Green");
  324. m.addSelection('M', "Magenta");
  325. m.addSelection('R', "Red");
  326. */
  327. return m;
  328. }
  329. // DOES THIS WORK?
  330. bool iequals(const string &a, const string &b) {
  331. unsigned int sz = a.size();
  332. if (b.size() != sz)
  333. return false;
  334. for (unsigned int i = 0; i < sz; ++i)
  335. if (tolower(a[i]) != tolower(b[i]))
  336. return false;
  337. return true;
  338. }
  339. // This does not seem to be working. I keep getting zero.
  340. int opt_from_string(std::string colorCode) {
  341. for (std::size_t pos = 0; pos != deck_colors.size(); ++pos) {
  342. // if (caseInsensitiveStringCompare(colorCode, deck_colors[pos]) == 0) {
  343. if (iequals(colorCode, deck_colors[pos])) {
  344. return pos;
  345. }
  346. }
  347. return 0;
  348. }
  349. int configure(door::Door &door, DBData &db) {
  350. auto menu = make_config_menu();
  351. int r = 0;
  352. while (r >= 0) {
  353. r = menu.choose(door);
  354. if (r > 0) {
  355. door << door::reset << door::cls;
  356. char c = menu.which(r - 1);
  357. if (c == 'D') {
  358. // Ok, deck colors
  359. // get default
  360. std::string key("DeckColor");
  361. std::string currentDefault = db.getSetting(key, std::string("ALL"));
  362. int currentOpt = opt_from_string(currentDefault);
  363. door << door::reset << door::cls;
  364. auto deck = make_deck_menu();
  365. deck.defaultSelection(currentOpt);
  366. int newOpt = deck.choose(door);
  367. door << door::reset << door::cls;
  368. if (newOpt >= 0) {
  369. newOpt--;
  370. std::string newColor = from_color_option(newOpt);
  371. if (newOpt != currentOpt) {
  372. door.log() << key << " was " << currentDefault << ", " << currentOpt
  373. << ". Now " << newColor << ", " << newOpt << std::endl;
  374. db.setSetting(key, newColor);
  375. }
  376. }
  377. }
  378. if (c == 'Q') {
  379. return r;
  380. }
  381. }
  382. }
  383. return r;
  384. }
  385. door::Panel make_score_panel(door::Door &door) {
  386. const int W = 25;
  387. door::Panel p(W);
  388. p.setStyle(door::BorderStyle::NONE);
  389. door::ANSIColor statusColor(door::COLOR::WHITE, door::COLOR::BLUE,
  390. door::ATTR::BOLD);
  391. door::ANSIColor valueColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  392. door::ATTR::BOLD);
  393. door::renderFunction svRender = statusValue(statusColor, valueColor);
  394. // or use renderStatus as defined above.
  395. // We'll stick with these for now.
  396. {
  397. std::string userString = "Name: ";
  398. userString += door.username;
  399. door::Line username(userString, W);
  400. username.setRender(svRender);
  401. p.addLine(std::make_unique<door::Line>(username));
  402. }
  403. {
  404. door::updateFunction scoreUpdate = [](void) -> std::string {
  405. std::string text = "Score: ";
  406. text.append(std::to_string(score));
  407. return text;
  408. };
  409. std::string scoreString = scoreUpdate();
  410. door::Line score(scoreString, W);
  411. score.setRender(svRender);
  412. score.setUpdater(scoreUpdate);
  413. p.addLine(std::make_unique<door::Line>(score));
  414. }
  415. {
  416. door::updateFunction timeUpdate = [&door](void) -> std::string {
  417. std::stringstream ss;
  418. std::string text;
  419. ss << "Time used: " << setw(3) << door.time_used << " / " << setw(3)
  420. << door.time_left;
  421. text = ss.str();
  422. return text;
  423. };
  424. std::string timeString = timeUpdate();
  425. door::Line time(timeString, W);
  426. time.setRender(svRender);
  427. time.setUpdater(timeUpdate);
  428. p.addLine(std::make_unique<door::Line>(time));
  429. }
  430. {
  431. door::updateFunction handUpdate = [](void) -> std::string {
  432. std::string text = "Playing Hand ";
  433. text.append(std::to_string(hand));
  434. text.append(" of ");
  435. text.append(std::to_string(total_hands));
  436. return text;
  437. };
  438. std::string handString = handUpdate();
  439. door::Line hands(handString, W);
  440. hands.setRender(svRender);
  441. hands.setUpdater(handUpdate);
  442. p.addLine(std::make_unique<door::Line>(hands));
  443. }
  444. return p;
  445. }
  446. door::Panel make_about(void) {
  447. const int W = 60;
  448. door::Panel about(W);
  449. about.setStyle(door::BorderStyle::DOUBLE_SINGLE);
  450. about.setColor(door::ANSIColor(door::COLOR::YELLOW, door::COLOR::BLUE,
  451. door::ATTR::BOLD));
  452. about.addLine(std::make_unique<door::Line>("About This Door", W));
  453. about.addLine(std::make_unique<door::Line>(
  454. "---------------------------------", W,
  455. door::ANSIColor(door::COLOR::CYAN, door::COLOR::BLUE, door::ATTR::BOLD)));
  456. /*
  457. 123456789012345678901234567890123456789012345678901234567890-60
  458. This door was written by Bugz.
  459. It is written in c++, only supports Linux, and replaces
  460. opendoors.
  461. It's written in c++, and replaces the outdated opendoors
  462. library.
  463. */
  464. about.addLine(
  465. std::make_unique<door::Line>(SPACEACE " v" SPACEACE_VERSION, W));
  466. std::string copyright = SPACEACE_COPYRIGHT;
  467. if (door::unicode) {
  468. replace(copyright, "(C)", "\u00a9");
  469. }
  470. about.addLine(std::make_unique<door::Line>(copyright, W));
  471. about.addLine(std::make_unique<door::Line>("", W));
  472. about.addLine(
  473. std::make_unique<door::Line>("This door was written by Bugz.", W));
  474. about.addLine(std::make_unique<door::Line>("", W));
  475. about.addLine(std::make_unique<door::Line>(
  476. "It is written in c++, only support Linux, and replaces", W));
  477. about.addLine(std::make_unique<door::Line>("opendoors.", W));
  478. /*
  479. door::updateFunction updater = [](void) -> std::string {
  480. std::string text = "Currently: ";
  481. text.append(return_current_time_and_date());
  482. return text;
  483. };
  484. std::string current = updater();
  485. door::Line active(current, 60);
  486. active.setUpdater(updater);
  487. active.setRender(statusValue(
  488. door::ANSIColor(door::COLOR::WHITE, door::COLOR::BLUE,
  489. door::ATTR::BOLD), door::ANSIColor(door::COLOR::YELLOW,
  490. door::COLOR::BLUE, door::ATTR::BOLD)));
  491. about.addLine(std::make_unique<door::Line>(active));
  492. */
  493. return about;
  494. }
  495. void display_starfield(door::Door &door, std::mt19937 &rng) {
  496. door << door::reset << door::cls;
  497. int mx = door.width;
  498. int my = door.height;
  499. // display starfield
  500. const char *stars[2];
  501. stars[0] = ".";
  502. if (door::unicode) {
  503. stars[1] = "\u2219"; // "\u00b7";
  504. } else {
  505. stars[1] = "\xf9"; // "\xfa";
  506. };
  507. {
  508. // Make uniform random distribution between 1 and MAX screen size X/Y
  509. std::uniform_int_distribution<int> uni_x(1, mx);
  510. std::uniform_int_distribution<int> uni_y(1, my);
  511. door::ANSIColor white(door::COLOR::WHITE);
  512. door::ANSIColor dark(door::COLOR::BLACK, door::ATTR::BRIGHT);
  513. // 10 is too many, 100 is too few. 40 looks ok.
  514. int MAX_STARS = ((mx * my) / 40);
  515. // door.log() << "Generating starmap using " << mx << "," << my << " : "
  516. // << MAX_STARS << " stars." << std::endl;
  517. for (int x = 0; x < MAX_STARS; x++) {
  518. door::Goto star_at(uni_x(rng), uni_y(rng));
  519. door << star_at;
  520. if (x % 5 < 2)
  521. door << dark;
  522. else
  523. door << white;
  524. if (x % 2 == 0)
  525. door << stars[0];
  526. else
  527. door << stars[1];
  528. }
  529. }
  530. }
  531. void display_space_ace(door::Door &door) {
  532. int mx = door.width;
  533. int my = door.height;
  534. // space_ace is 72 chars wide, 6 high
  535. int sa_x = (mx - 72) / 2;
  536. int sa_y = (my - 6) / 2;
  537. // output the SpaceAce logo -- centered!
  538. for (const auto s : space) {
  539. door::Goto sa_at(sa_x, sa_y);
  540. door << sa_at;
  541. if (door::unicode) {
  542. std::string unicode;
  543. door::cp437toUnicode(s, unicode);
  544. door << unicode; // << door::nl;
  545. } else
  546. door << s; // << door::nl;
  547. sa_y++;
  548. }
  549. // pause 5 seconds so they can enjoy our awesome logo -- if they want.
  550. door.sleep_key(5);
  551. }
  552. void display_starfield_space_ace(door::Door &door, std::mt19937 &rng) {
  553. // mx = door.width;
  554. // my = door.height;
  555. display_starfield(door, rng);
  556. display_space_ace(door);
  557. door << door::reset;
  558. }
  559. int main(int argc, char *argv[]) {
  560. door::Door door("space-ace", argc, argv);
  561. // store the door log so we can easily access it.
  562. get_logger = [&door]() -> ofstream & { return door.log(); };
  563. DBData spacedb;
  564. spacedb.setUser(door.username);
  565. // retrieve lastcall
  566. time_t last_call = std::stol(spacedb.getSetting("LastCall", "0"));
  567. // store now as lastcall
  568. time_t now =
  569. std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
  570. spacedb.setSetting("LastCall", std::to_string(now));
  571. // Have they used this door before?
  572. if (last_call != 0) {
  573. door << "Welcome Back!" << door::nl;
  574. auto nowClock = std::chrono::system_clock::from_time_t(now);
  575. auto lastClock = std::chrono::system_clock::from_time_t(last_call);
  576. auto delta = nowClock - lastClock;
  577. // int days = chrono::duration_cast<chrono::days>(delta).count(); //
  578. // c++ 20
  579. int hours = chrono::duration_cast<chrono::hours>(delta).count();
  580. int days = hours / 24;
  581. int minutes = chrono::duration_cast<chrono::minutes>(delta).count();
  582. int secs = chrono::duration_cast<chrono::seconds>(delta).count();
  583. if (days > 1) {
  584. door << "It's been " << days << " days since you last played."
  585. << door::nl;
  586. } else {
  587. if (hours > 1) {
  588. door << "It's been " << hours << " hours since you last played."
  589. << door::nl;
  590. } else {
  591. if (minutes > 1) {
  592. door << "It's been " << minutes << " minutes since you last played."
  593. << door::nl;
  594. } else {
  595. door << "It's been " << secs << " seconds since you last played."
  596. << door::nl;
  597. door << "It's like you never left." << door::nl;
  598. }
  599. }
  600. }
  601. press_a_key(door);
  602. }
  603. /*
  604. // example: saving the door.log() for global use.
  605. std::function<std::ofstream &(void)> get_logger;
  606. get_logger = [&door]() -> ofstream & { return door.log(); };
  607. if (get_logger) {
  608. get_logger() << "MEOW" << std::endl;
  609. get_logger() << "hey! It works!" << std::endl;
  610. }
  611. get_logger = nullptr; // before door destruction
  612. */
  613. // https://stackoverflow.com/questions/5008804/generating-random-integer-from-a-range
  614. std::random_device rd; // only used once to initialise (seed) engine
  615. std::mt19937 rng(rd());
  616. // random-number engine used (Mersenne-Twister in this case)
  617. // std::uniform_int_distribution<int> uni(min, max); // guaranteed
  618. // unbiased
  619. int mx, my; // Max screen width/height
  620. if (door.width == 0) {
  621. // screen detect failed, use sensible defaults
  622. door.width = mx = 80;
  623. door.height = my = 23;
  624. } else {
  625. mx = door.width;
  626. my = door.height;
  627. }
  628. // We assume here that the width and height aren't something crazy like
  629. // 10x15. :P (or 24x923!)
  630. display_starfield_space_ace(door, rng);
  631. // for testing inactivity timeout
  632. // door.inactivity = 10;
  633. door::Panel timeout = make_timeout(mx, my);
  634. door::Menu m = make_main_menu();
  635. door::Panel about = make_about(); // 8 lines
  636. // center the about box
  637. about.set((mx - 60) / 2, (my - 9) / 2);
  638. int r = 0;
  639. while ((r >= 0) and (r != 6)) {
  640. // starfield + menu ?
  641. display_starfield(door, rng);
  642. r = m.choose(door);
  643. // need to reset the colors. (whoops!)
  644. door << door::reset << door::cls; // door::nl;
  645. // OK! The screen is blank at this point!
  646. switch (r) {
  647. case 1: // play game
  648. {
  649. PlayCards pc(door, spacedb, rng);
  650. r = pc.play_cards();
  651. // r = play_cards(door, spacedb, rng);
  652. }; break;
  653. case 2: // view scores
  654. door << "Show scores goes here!" << door::nl;
  655. r = press_a_key(door);
  656. break;
  657. case 3: // configure
  658. r = configure(door, spacedb);
  659. // r = press_a_key(door);
  660. break;
  661. case 4: // help
  662. door << "Help! Need some help here..." << door::nl;
  663. r = press_a_key(door);
  664. break;
  665. case 5: // about
  666. display_starfield(door, rng);
  667. door << about << door::nl;
  668. r = press_a_key(door);
  669. break;
  670. case 6: // quit
  671. break;
  672. }
  673. }
  674. if (r < 0) {
  675. TIMEOUT:
  676. if (r == -1) {
  677. door.log() << "TIMEOUT" << std::endl;
  678. door << timeout << door::reset << door::nl << door::nl;
  679. } else {
  680. if (r == -3) {
  681. door.log() << "OUTTA TIME" << std::endl;
  682. door::Panel notime = make_notime(mx, my);
  683. door << notime << door::reset << door::nl;
  684. }
  685. }
  686. return 0;
  687. }
  688. door << door::nl;
  689. /*
  690. // magic time!
  691. door << door::reset << door::nl << "Press another key...";
  692. int x;
  693. for (x = 0; x < 60; ++x) {
  694. r = door.sleep_key(1);
  695. if (r == -1) {
  696. // ok! Expected timeout!
  697. // PROBLEM: regular "local" terminal loses current attributes
  698. // when cursor is save / restored.
  699. door << door::SaveCursor;
  700. if (about.update(door)) {
  701. // ok I need to "fix" the cursor position.
  702. // it has moved.
  703. }
  704. door << door::RestoreCursor << door::reset;
  705. } else {
  706. if (r < 0)
  707. goto TIMEOUT;
  708. if (r >= 0)
  709. break;
  710. }
  711. }
  712. if (x == 60)
  713. goto TIMEOUT;
  714. */
  715. #ifdef NNY
  716. // configured by the player.
  717. door::ANSIColor deck_color;
  718. // RED, BLUE, GREEN, MAGENTA, CYAN
  719. std::uniform_int_distribution<int> rand_color(0, 4);
  720. switch (rand_color(rng)) {
  721. case 0:
  722. deck_color = door::ANSIColor(door::COLOR::RED);
  723. break;
  724. case 1:
  725. deck_color = door::ANSIColor(door::COLOR::BLUE);
  726. break;
  727. case 2:
  728. deck_color = door::ANSIColor(door::COLOR::GREEN);
  729. break;
  730. case 3:
  731. deck_color = door::ANSIColor(door::COLOR::MAGENTA);
  732. break;
  733. case 4:
  734. deck_color = door::ANSIColor(door::COLOR::CYAN);
  735. break;
  736. default:
  737. deck_color = door::ANSIColor(door::COLOR::BLUE, door::ATTR::BLINK);
  738. break;
  739. }
  740. int height = 3;
  741. Deck d(deck_color, height);
  742. door::Panel *c;
  743. door << door::reset << door::cls;
  744. // This displays the cards in the upper left corner.
  745. // We want them center, and down some.
  746. int space = 3;
  747. // int cards_dealt_width = 59; int cards_dealt_height = 9;
  748. int game_width;
  749. {
  750. int cx, cy, level;
  751. cardgo(27, space, height, cx, cy, level);
  752. game_width = cx + 5; // card width
  753. }
  754. int off_x = (mx - game_width) / 2;
  755. int off_y = (my - 9) / 2;
  756. // The idea is to see the cards with <<Something Unique to the card
  757. // game>>, Year, Month, Day, and game (like 1 of 3). This will make the
  758. // games the same/fair for everyone.
  759. std::seed_seq s1{2021, 2, 27, 1};
  760. cards deck1 = card_shuffle(s1, 1);
  761. cards state = card_states();
  762. // I tried setting the cursor before the delay and before displaying the
  763. // card. It is very hard to see / just about useless. Not worth the
  764. // effort.
  765. for (int x = 0; x < 28; x++) {
  766. int cx, cy, level;
  767. cardgo(x, space, height, cx, cy, level);
  768. // This is hardly visible.
  769. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  770. std::this_thread::sleep_for(std::chrono::milliseconds(75));
  771. c = d.back(level);
  772. c->set(cx + off_x, cy + off_y);
  773. door << *c;
  774. }
  775. /*
  776. std::this_thread::sleep_for(
  777. std::chrono::seconds(1)); // 3 secs seemed too long!
  778. */
  779. for (int x = 18; x < 28; x++) {
  780. int cx, cy, level;
  781. // usleep(1000 * 20);
  782. state.at(x) = 1;
  783. cardgo(x, space, height, cx, cy, level);
  784. // door << door::Goto(cx + off_x - 1, cy + off_y + 1);
  785. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  786. c = d.card(deck1.at(x));
  787. c->set(cx + off_x, cy + off_y);
  788. door << *c;
  789. }
  790. door << door::reset;
  791. door << door::nl << door::nl;
  792. r = door.sleep_key(door.inactivity);
  793. if (r < 0)
  794. goto TIMEOUT;
  795. #endif
  796. /*
  797. door::Panel *p = d.back(2);
  798. p->set(10, 10);
  799. door << *p;
  800. door::Panel *d8 = d.card(8);
  801. d8->set(20, 8);
  802. door << *d8;
  803. r = door.sleep_key(door.inactivity);
  804. if (r < 0)
  805. goto TIMEOUT;
  806. */
  807. // door << door::reset << door::cls;
  808. display_starfield(door, rng);
  809. door << m << door::reset << door::nl;
  810. // Normal DOOR exit goes here...
  811. door << door::nl << "Returning you to the BBS, please wait..." << door::nl;
  812. get_logger = nullptr;
  813. return 0;
  814. }