mainloop_net.h 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. #ifndef __MAINLOOP_H
  2. #define __MAINLOOP_H
  3. #include <functional>
  4. #include "logging.h"
  5. #include "spectator.h"
  6. #include "serialize.h"
  7. namespace mainloop {
  8. struct drawing_context_t {
  9. unsigned int view_w;
  10. unsigned int view_h;
  11. int voff_off_x;
  12. int voff_off_y;
  13. unsigned int px;
  14. unsigned int py;
  15. unsigned int centerx;
  16. unsigned int centery;
  17. unsigned int lightradius;
  18. unsigned int hlx;
  19. unsigned int hly;
  20. unsigned int rangemin;
  21. unsigned int rangemax;
  22. unsigned int num_messages;
  23. bool do_hud;
  24. bool do_center_view;
  25. bool do_fade_colors;
  26. drawing_context_t() :
  27. view_w(0), view_h(0),
  28. voff_off_x(0), voff_off_y(0), px(0), py(0), lightradius(1000),
  29. hlx(std::numeric_limits<unsigned int>::max()),
  30. hly(hlx),
  31. rangemin(0),
  32. rangemax(hlx),
  33. num_messages(3),
  34. do_hud(true),
  35. do_center_view(false),
  36. do_fade_colors(true)
  37. {}
  38. };
  39. struct screen_params_t {
  40. unsigned int w;
  41. unsigned int h;
  42. unsigned int w2;
  43. unsigned int h2;
  44. screen_params_t() : w(0), h(0), w2(0), h2(0) {}
  45. };
  46. template <typename GAME, typename GAMESTATE, typename GAMEOPTIONS, typename SCREEN, unsigned int SAVEFILE_VERSION = 23>
  47. struct Main {
  48. GAME game;
  49. SCREEN& screen;
  50. std::vector<std::string> messages;
  51. GAMEOPTIONS options;
  52. GAMESTATE state;
  53. Main(SCREEN& s, bool debug, size_t n_skin, bool fullwidth) :
  54. game(debug, n_skin),
  55. screen(s)
  56. {
  57. // HACK!
  58. state.fullwidth = fullwidth;
  59. }
  60. bool load(const std::string& filename) {
  61. try {
  62. serialize::Source s(filename);
  63. unsigned int ver;
  64. serialize::read(s, ver);
  65. if (ver != SAVEFILE_VERSION)
  66. return false;
  67. serialize::read(s, options);
  68. // HACK!!
  69. state.render.set_ui_symbol(options.menu_theme);
  70. serialize::read(s, state);
  71. game.load(s);
  72. } catch (std::exception& e) {
  73. return false;
  74. }
  75. return true;
  76. }
  77. void save(const std::string& filename) {
  78. // HACK!!
  79. options.menu_theme = state.render.ui_symbol_index;
  80. serialize::Sink s(filename);
  81. serialize::write(s, SAVEFILE_VERSION);
  82. serialize::write(s, options);
  83. serialize::write(s, state);
  84. game.save(s);
  85. }
  86. void clobber_savefile(const std::string& filename) {
  87. // HACK!!
  88. options.menu_theme = state.render.ui_symbol_index;
  89. serialize::Sink s(filename);
  90. serialize::write(s, SAVEFILE_VERSION);
  91. serialize::write(s, options);
  92. }
  93. bool start(const std::string& savefile, std::string& window, const screen_params_t& sp) {
  94. if (load(savefile)) {
  95. return false;
  96. }
  97. std::string code;
  98. unsigned int seed;
  99. {
  100. window += "\n\3Do you want to enter a replay code? (Y/N)\2"_m;
  101. maudit::keypress k = state.render.draw_window(screen, window);
  102. if (k.letter == 'Y' || k.letter == 'y') {
  103. window += "\n\3Enter a replay code (case insensitive):\2 "_m;
  104. enter_text(window, code, false);
  105. seed = rcode::decode<unsigned int>(code) & 0xFFFFFFFF;
  106. } else {
  107. seed = (::time(NULL) & 0xFFFFFFFF);
  108. }
  109. }
  110. state.rng.init(seed);
  111. state.neigh.init(sp.w, sp.h);
  112. state.grid.init(sp.w, sp.h);
  113. state.path.init(sp.w, sp.h);
  114. state.render.init(sp.w2, sp.h2);
  115. state.camap.init();
  116. state.moon.init(seed);
  117. state.designs_counts = designs().counts;
  118. state.species_counts = species().counts;
  119. state.bonus_designs_a_counts = designs().bonus_a_counts;
  120. state.bonus_designs_b_counts = designs().bonus_b_counts;
  121. state.features.init();
  122. state.items.init();
  123. state.monsters.init();
  124. state.ticks = 1;
  125. game.init(state, screen.address, seed);
  126. screen.clear();
  127. game.generate(state, [this](const std::string& msg) { screen.io.write(msg + "\r\n"); });
  128. return true;
  129. }
  130. void regenerate() {
  131. game.dispose(state);
  132. state.neigh.clear();
  133. state.grid.clear();
  134. state.render.clear();
  135. state.camap.clear();
  136. state.features.clear();
  137. state.items.clear();
  138. state.monsters.clear();
  139. screen.clear();
  140. game.generate(state, [this](const std::string& msg) { screen.io.write(msg + "\r\n"); });
  141. }
  142. void draw(bool dead) {
  143. if (spectator::screens<SCREEN>().get_messages(screen, messages)) {
  144. for (const std::string& msg : messages) {
  145. state.render.do_message(std::string("Chat message: "_m) + msg, true);
  146. }
  147. messages.clear();
  148. }
  149. drawing_context_t ctx;
  150. ctx.view_w = screen.w;
  151. ctx.view_h = screen.h;
  152. ctx.do_center_view = options.center_view;
  153. ctx.do_fade_colors = !options.no_fade_colors;
  154. game.drawing_context(ctx, state);
  155. if (ctx.do_hud) {
  156. game.draw_hud(state);
  157. }
  158. ctx.num_messages = (dead ? 7 : 3);
  159. state.render.draw(screen,
  160. state.ticks,
  161. ctx,
  162. state.fullwidth,
  163. std::bind(&GAME::set_skin, &game, std::ref(state),
  164. std::placeholders::_1, std::placeholders::_2));
  165. }
  166. bool process(size_t& oldticks, bool& done, bool& dead, bool& regen, bool& need_input, bool& draw) {
  167. if (state.ticks == oldticks) {
  168. need_input = true;
  169. return false;
  170. }
  171. oldticks = state.ticks;
  172. game.process_world(state, done, dead, regen, need_input, draw);
  173. if (state.ticks != oldticks) {
  174. need_input = false;
  175. }
  176. return true;
  177. }
  178. void pump_event(bool need_input, bool& done, bool& dead, bool& regen) {
  179. if (need_input) {
  180. grender::Grid::keypress k = state.render.wait_for_key(screen);
  181. game.handle_input(state, options, done, dead, regen, k);
  182. while (state.window_stack.size() > 0) {
  183. k = state.render.draw_window(screen,
  184. state.window_stack.back().message,
  185. state.window_stack.back().allow_tab);
  186. game.handle_input(state, options, done, dead, regen, k);
  187. }
  188. }
  189. }
  190. bool check_done(bool done, bool dead, const std::string& name, const std::string& savefile) {
  191. if (done) {
  192. if (dead) {
  193. game.endgame(state, name, screen.address, game.game_seed);
  194. clobber_savefile(savefile);
  195. } else {
  196. save(savefile);
  197. }
  198. return true;
  199. }
  200. return false;
  201. }
  202. void goodbye_message(bool dead) {
  203. while (1) {
  204. grender::Grid::keypress k = state.render.wait_for_key(screen);
  205. if (k.letter == ' ')
  206. break;
  207. }
  208. screen.reset_color();
  209. if (dead) {
  210. game.goodbye_message(state, [this](const std::string& msg) { screen.io.write(msg + "\r\n"); });
  211. state.render.wait_for_key(screen);
  212. }
  213. }
  214. void enter_text(std::string& prompt, std::string& out, bool secret) {
  215. while (1) {
  216. maudit::keypress k = state.render.draw_window(screen, prompt);
  217. // HACK
  218. // Disable parens to prevent cheating achievements in the highscore table.
  219. if (k.letter >= ' ' && k.letter <= '~' && k.letter != '(' && k.letter != ')' && out.size() < 75) {
  220. out += k.letter;
  221. prompt += (secret ? '*' : k.letter);
  222. continue;
  223. } else if (k.letter == '\x7F' || k.letter == '\x08' || k.key == maudit::keycode::del) {
  224. if (out.size() > 0) {
  225. out.resize(out.size() - 1);
  226. prompt.resize(prompt.size() - 1);
  227. }
  228. } else if (k.letter == '\n') {
  229. return;
  230. } else {
  231. continue;
  232. }
  233. }
  234. }
  235. std::string _mainloop_start(bool singleplayer,
  236. std::string& name, unsigned int& seed, bool& new_game) {
  237. screen_params_t sp;
  238. game.make_screen(sp, state);
  239. // //
  240. name.clear();
  241. std::string pass;
  242. std::string window = "\nWelcome!\n"_m;
  243. window +=
  244. "\n\1The name and passcode will identify your savefile.\n"
  245. "If a name/passcode combination doesn't exist, then a new savefile will be created.\n"
  246. "\n\3Enter your name:\2 "_m;
  247. enter_text(window, name, false);
  248. if (!singleplayer) {
  249. window += "\n\3Enter a passcode:\2 "_m;
  250. enter_text(window, pass, true);
  251. }
  252. std::string savefile;
  253. {
  254. size_t h = std::hash<std::string>()(name) + std::hash<std::string>()(pass);
  255. std::ostringstream tmp;
  256. tmp << h << ".sav";
  257. savefile = tmp.str();
  258. }
  259. // //
  260. new_game = start(savefile, window, sp);
  261. seed = game.game_seed;
  262. return savefile;
  263. }
  264. void _mainloop_main(const std::string& name, const std::string& savefile, bool& dead) {
  265. size_t oldticks = 0;
  266. state.render.do_message("Press '?' twice for detailed instructions."_m);
  267. state.render.do_message("Press '?' for help on controls."_m);
  268. draw(dead);
  269. bool done = false;
  270. dead = false;
  271. bool regen = false;
  272. while (1) {
  273. bool need_input = false;
  274. do {
  275. bool do_draw = false;
  276. bool time_passed = process(oldticks, done, dead, regen, need_input, do_draw);
  277. if (regen) {
  278. regenerate();
  279. draw(dead);
  280. regen = false;
  281. }
  282. if (check_done(done, dead, name, savefile)) {
  283. draw(dead);
  284. goodbye_message(dead);
  285. return;
  286. }
  287. if (do_draw) {
  288. draw(dead);
  289. }
  290. if (!time_passed) break;
  291. } while (1);
  292. draw(dead);
  293. pump_event(need_input, done, dead, regen);
  294. if (check_done(done, dead, name, savefile)) {
  295. draw(dead);
  296. goodbye_message(dead);
  297. return;
  298. }
  299. }
  300. }
  301. bool mainloop(bool singleplayer) {
  302. std::string name;
  303. unsigned int seed;
  304. bool new_game;
  305. std::string savefile = _mainloop_start(singleplayer, name, seed, new_game);
  306. bool dead = false;
  307. try {
  308. {
  309. logger::Sink gamelog("game.log");
  310. if (new_game) {
  311. gamelog << nlp::message("START\t%s\t%s\t%d\n", name, rcode::encode(seed), ::time(NULL));
  312. } else {
  313. gamelog << nlp::message("LOAD\t%s\t%s\t%d\n", name, rcode::encode(seed), ::time(NULL));
  314. }
  315. }
  316. if (!singleplayer)
  317. spectator::screens<SCREEN>().add(screen, name);
  318. _mainloop_main(name, savefile, dead);
  319. if (!singleplayer)
  320. spectator::screens<SCREEN>().remove(screen);
  321. {
  322. logger::Sink gamelog("game.log");
  323. if (dead) {
  324. gamelog << nlp::message("DEAD\t%s\t%s\t%d\n", name, rcode::encode(seed), ::time(NULL));
  325. } else {
  326. gamelog << nlp::message("QUIT\t%s\t%s\t%d\n", name, rcode::encode(seed), ::time(NULL));
  327. }
  328. }
  329. return dead;
  330. } catch (...) {
  331. if (!singleplayer)
  332. spectator::screens<SCREEN>().remove(screen);
  333. if (dead) {
  334. clobber_savefile(savefile);
  335. } else {
  336. save(savefile);
  337. }
  338. {
  339. logger::Sink gamelog("game.log");
  340. gamelog << nlp::message("OOPS\t%s\t%s\t%d\n", name, rcode::encode(seed), ::time(NULL));
  341. }
  342. throw;
  343. }
  344. }
  345. };
  346. }
  347. #endif