main.cpp 49 KB

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