main.cpp 32 KB

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