emcc.c 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167
  1. /*
  2. * emcc.c: the C component of an Emscripten-based web/Javascript front
  3. * end for Puzzles.
  4. *
  5. * The Javascript parts of this system live in emcclib.js and
  6. * emccpre.js. It also depends on being run in the context of a web
  7. * page containing an appropriate collection of bits and pieces (a
  8. * canvas, some buttons and links etc), which is generated for each
  9. * puzzle by the script html/jspage.pl.
  10. */
  11. /*
  12. * Further thoughts on possible enhancements:
  13. *
  14. * - I should think about whether these webified puzzles can support
  15. * touchscreen-based tablet browsers.
  16. *
  17. * - think about making use of localStorage. It might be useful to
  18. * let the user save games into there as an alternative to disk
  19. * files - disk files are all very well for getting the save right
  20. * out of your browser to (e.g.) email to me as a bug report, but
  21. * for just resuming a game you were in the middle of, you'd
  22. * probably rather have a nice simple 'quick save' and 'quick load'
  23. * button pair. Also, that might be a useful place to store
  24. * preferences, if I ever get round to writing a preferences UI.
  25. *
  26. * - this is a downright silly idea, but it does occur to me that if
  27. * I were to write a PDF output driver for the Puzzles printing
  28. * API, then I might be able to implement a sort of 'printing'
  29. * feature in this front end, using data: URIs again. (Ask the user
  30. * exactly what they want printed, then construct an appropriate
  31. * PDF and embed it in a gigantic data: URI. Then they can print
  32. * that using whatever they normally use to print PDFs!)
  33. */
  34. #include <assert.h>
  35. #include <stdio.h>
  36. #include <string.h>
  37. #include <stdarg.h>
  38. #include "puzzles.h"
  39. /*
  40. * Extern references to Javascript functions provided in emcclib.js.
  41. */
  42. extern void js_init_puzzle(void);
  43. extern void js_post_init(void);
  44. extern void js_debug(const char *);
  45. extern void js_error_box(const char *message);
  46. extern void js_remove_type_dropdown(void);
  47. extern void js_remove_solve_button(void);
  48. extern void js_add_preset(int menuid, const char *name, int value);
  49. extern int js_add_preset_submenu(int menuid, const char *name);
  50. extern int js_get_selected_preset(void);
  51. extern void js_select_preset(int n);
  52. extern void js_default_colour(float *output);
  53. extern void js_set_background_colour(const char *bg);
  54. extern void js_get_date_64(unsigned *p);
  55. extern void js_update_permalinks(const char *desc, const char *seed);
  56. extern void js_enable_undo_redo(bool undo, bool redo);
  57. extern void js_update_key_labels(const char *lsk, const char *csk);
  58. extern void js_activate_timer(void);
  59. extern void js_deactivate_timer(void);
  60. extern void js_canvas_start_draw(void);
  61. extern void js_canvas_draw_update(int x, int y, int w, int h);
  62. extern void js_canvas_end_draw(void);
  63. extern void js_canvas_draw_rect(int x, int y, int w, int h,
  64. const char *colour);
  65. extern void js_canvas_clip_rect(int x, int y, int w, int h);
  66. extern void js_canvas_unclip(void);
  67. extern void js_canvas_draw_line(float x1, float y1, float x2, float y2,
  68. int width, const char *colour);
  69. extern void js_canvas_draw_poly(const int *points, int npoints,
  70. const char *fillcolour,
  71. const char *outlinecolour);
  72. extern void js_canvas_draw_circle(int x, int y, int r,
  73. const char *fillcolour,
  74. const char *outlinecolour);
  75. extern int js_canvas_find_font_midpoint(int height, bool monospaced);
  76. extern void js_canvas_draw_text(int x, int y, int halign,
  77. const char *colptr, int height,
  78. bool monospaced, const char *text);
  79. extern int js_canvas_new_blitter(int w, int h);
  80. extern void js_canvas_free_blitter(int id);
  81. extern void js_canvas_copy_to_blitter(int id, int x, int y, int w, int h);
  82. extern void js_canvas_copy_from_blitter(int id, int x, int y, int w, int h);
  83. extern void js_canvas_remove_statusbar(void);
  84. extern void js_canvas_set_statusbar(const char *text);
  85. extern bool js_canvas_get_preferred_size(int *wp, int *hp);
  86. extern void js_canvas_set_size(int w, int h);
  87. extern double js_get_device_pixel_ratio(void);
  88. extern void js_dialog_init(const char *title);
  89. extern void js_dialog_string(int i, const char *title, const char *initvalue);
  90. extern void js_dialog_choices(int i, const char *title, const char *choicelist,
  91. int initvalue);
  92. extern void js_dialog_boolean(int i, const char *title, bool initvalue);
  93. extern void js_dialog_launch(void);
  94. extern void js_dialog_cleanup(void);
  95. extern void js_focus_canvas(void);
  96. extern bool js_savefile_read(void *buf, int len);
  97. extern void js_save_prefs(const char *);
  98. extern void js_load_prefs(midend *);
  99. /*
  100. * These functions are called from JavaScript, so their prototypes
  101. * need to be kept in sync with emccpre.js.
  102. */
  103. bool mouseup(int x, int y, int button);
  104. bool mousedown(int x, int y, int button);
  105. bool mousemove(int x, int y, int buttons);
  106. bool key(int keycode, const char *key, const char *chr, int location,
  107. bool shift, bool ctrl);
  108. void timer_callback(double tplus);
  109. void command(int n);
  110. char *get_text_format(void);
  111. void free_save_file(char *buffer);
  112. char *get_save_file(void);
  113. void free_save_file(char *buffer);
  114. void load_game(void);
  115. void dlg_return_sval(int index, const char *val);
  116. void dlg_return_ival(int index, int val);
  117. void resize_puzzle(int w, int h);
  118. void restore_puzzle_size(int w, int h);
  119. void rescale_puzzle(void);
  120. /*
  121. * Internal forward references.
  122. */
  123. static void save_prefs(midend *me);
  124. /*
  125. * Call JS to get the date, and use that to initialise our random
  126. * number generator to invent the first game seed.
  127. */
  128. void get_random_seed(void **randseed, int *randseedsize)
  129. {
  130. unsigned *ret = snewn(2, unsigned);
  131. js_get_date_64(ret);
  132. *randseed = ret;
  133. *randseedsize = 2*sizeof(unsigned);
  134. }
  135. /*
  136. * Fatal error, called in cases of complete despair such as when
  137. * malloc() has returned NULL.
  138. */
  139. void fatal(const char *fmt, ...)
  140. {
  141. char buf[512];
  142. va_list ap;
  143. strcpy(buf, "puzzle fatal error: ");
  144. va_start(ap, fmt);
  145. vsnprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
  146. va_end(ap);
  147. js_error_box(buf);
  148. }
  149. #ifdef DEBUGGING
  150. void debug_printf(const char *fmt, ...)
  151. {
  152. char buf[512];
  153. va_list ap;
  154. va_start(ap, fmt);
  155. vsnprintf(buf, sizeof(buf), fmt, ap);
  156. va_end(ap);
  157. js_debug(buf);
  158. }
  159. #endif
  160. /*
  161. * Helper function that makes it easy to test strings that might be
  162. * NULL.
  163. */
  164. static int strnullcmp(const char *a, const char *b)
  165. {
  166. if (a == NULL || b == NULL)
  167. return a != NULL ? +1 : b != NULL ? -1 : 0;
  168. return strcmp(a, b);
  169. }
  170. /*
  171. * HTMLish names for the colours allocated by the puzzle.
  172. */
  173. static char **colour_strings;
  174. static int ncolours;
  175. /*
  176. * The global midend object.
  177. */
  178. static midend *me;
  179. /* ----------------------------------------------------------------------
  180. * Timing functions.
  181. */
  182. static bool timer_active = false;
  183. void deactivate_timer(frontend *fe)
  184. {
  185. js_deactivate_timer();
  186. timer_active = false;
  187. }
  188. void activate_timer(frontend *fe)
  189. {
  190. if (!timer_active) {
  191. js_activate_timer();
  192. timer_active = true;
  193. }
  194. }
  195. void timer_callback(double tplus)
  196. {
  197. if (timer_active)
  198. midend_timer(me, tplus);
  199. }
  200. /* ----------------------------------------------------------------------
  201. * Helper functions to resize the canvas, and variables to remember
  202. * its size for other functions (e.g. trimming blitter rectangles).
  203. */
  204. static int canvas_w, canvas_h;
  205. /*
  206. * Called when we resize as a result of changing puzzle settings
  207. * or device pixel ratio.
  208. */
  209. static void resize(void)
  210. {
  211. int w, h;
  212. bool user;
  213. w = h = INT_MAX;
  214. user = js_canvas_get_preferred_size(&w, &h);
  215. midend_size(me, &w, &h, user, js_get_device_pixel_ratio());
  216. js_canvas_set_size(w, h);
  217. canvas_w = w;
  218. canvas_h = h;
  219. }
  220. /* Called from JS when the device pixel ratio changes */
  221. void rescale_puzzle(void)
  222. {
  223. resize();
  224. midend_force_redraw(me);
  225. }
  226. /* Called from JS when the user uses the resize handle */
  227. void resize_puzzle(int w, int h)
  228. {
  229. midend_size(me, &w, &h, true, js_get_device_pixel_ratio());
  230. if (canvas_w != w || canvas_h != h) {
  231. js_canvas_set_size(w, h);
  232. canvas_w = w;
  233. canvas_h = h;
  234. midend_force_redraw(me);
  235. }
  236. }
  237. /* Called from JS when the user uses the restore button */
  238. void restore_puzzle_size(int w, int h)
  239. {
  240. midend_reset_tilesize(me);
  241. resize();
  242. midend_force_redraw(me);
  243. }
  244. /*
  245. * Try to extract a background colour from the canvas's CSS. In case
  246. * it doesn't have a usable one, make up a lightish grey ourselves.
  247. */
  248. void frontend_default_colour(frontend *fe, float *output)
  249. {
  250. output[0] = output[1] = output[2] = 0.9F;
  251. js_default_colour(output);
  252. }
  253. /*
  254. * Helper function called from all over the place to ensure the undo
  255. * and redo buttons get properly enabled and disabled after every move
  256. * or undo or new-game event.
  257. */
  258. static void post_move(void)
  259. {
  260. js_enable_undo_redo(midend_can_undo(me), midend_can_redo(me));
  261. js_update_key_labels(midend_current_key_label(me, CURSOR_SELECT2),
  262. midend_current_key_label(me, CURSOR_SELECT));
  263. }
  264. /*
  265. * Mouse event handlers called from JS.
  266. */
  267. bool mousedown(int x, int y, int button)
  268. {
  269. bool handled;
  270. button = (button == 0 ? LEFT_BUTTON :
  271. button == 1 ? MIDDLE_BUTTON : RIGHT_BUTTON);
  272. handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
  273. post_move();
  274. return handled;
  275. }
  276. bool mouseup(int x, int y, int button)
  277. {
  278. bool handled;
  279. button = (button == 0 ? LEFT_RELEASE :
  280. button == 1 ? MIDDLE_RELEASE : RIGHT_RELEASE);
  281. handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
  282. post_move();
  283. return handled;
  284. }
  285. bool mousemove(int x, int y, int buttons)
  286. {
  287. int button = (buttons & 2 ? MIDDLE_DRAG :
  288. buttons & 4 ? RIGHT_DRAG : LEFT_DRAG);
  289. bool handled;
  290. handled = midend_process_key(me, x, y, button) != PKR_UNUSED;
  291. post_move();
  292. return handled;
  293. }
  294. /*
  295. * Keyboard handler called from JS. Returns true if the key was
  296. * handled and hence the keydown event should be cancelled.
  297. */
  298. bool key(int keycode, const char *key, const char *chr, int location,
  299. bool shift, bool ctrl)
  300. {
  301. /* Key location constants from JavaScript. */
  302. #define DOM_KEY_LOCATION_STANDARD 0
  303. #define DOM_KEY_LOCATION_LEFT 1
  304. #define DOM_KEY_LOCATION_RIGHT 2
  305. #define DOM_KEY_LOCATION_NUMPAD 3
  306. int keyevent = -1;
  307. int process_key_result;
  308. if (!strnullcmp(key, "Backspace") || !strnullcmp(key, "Delete") ||
  309. !strnullcmp(key, "Del"))
  310. keyevent = 127; /* Backspace / Delete */
  311. else if (!strnullcmp(key, "Enter"))
  312. keyevent = 13; /* return */
  313. else if (!strnullcmp(key, "Spacebar"))
  314. keyevent = ' ';
  315. else if (!strnullcmp(key, "Escape"))
  316. keyevent = 27;
  317. else if (!strnullcmp(key, "ArrowLeft") || !strnullcmp(key, "Left"))
  318. keyevent = CURSOR_LEFT;
  319. else if (!strnullcmp(key, "ArrowUp") || !strnullcmp(key, "Up"))
  320. keyevent = CURSOR_UP;
  321. else if (!strnullcmp(key, "ArrowRight") || !strnullcmp(key, "Right"))
  322. keyevent = CURSOR_RIGHT;
  323. else if (!strnullcmp(key, "ArrowDown") || !strnullcmp(key, "Down"))
  324. keyevent = CURSOR_DOWN;
  325. else if (!strnullcmp(key, "SoftLeft"))
  326. /* Left soft key on KaiOS. */
  327. keyevent = CURSOR_SELECT2;
  328. else if (!strnullcmp(key, "End"))
  329. /*
  330. * We interpret Home, End, PgUp and PgDn as numeric keypad
  331. * controls regardless of whether they're the ones on the
  332. * numeric keypad (since we can't tell). The effect of
  333. * this should only be that the non-numeric-pad versions
  334. * of those keys generate directions in 8-way movement
  335. * puzzles like Cube and Inertia.
  336. */
  337. keyevent = MOD_NUM_KEYPAD | '1';
  338. else if (!strnullcmp(key, "PageDown"))
  339. keyevent = MOD_NUM_KEYPAD | '3';
  340. else if (!strnullcmp(key, "Home"))
  341. keyevent = MOD_NUM_KEYPAD | '7';
  342. else if (!strnullcmp(key, "PageUp"))
  343. keyevent = MOD_NUM_KEYPAD | '9';
  344. else if (shift && ctrl && (!strnullcmp(key, "Z") || !strnullcmp(key, "z")))
  345. keyevent = UI_REDO;
  346. else if (key && (unsigned char)key[0] < 0x80 && key[1] == '\0')
  347. /* Key generating a single ASCII character. */
  348. keyevent = key[0];
  349. /*
  350. * In modern browsers (since about 2017), all keys that Puzzles
  351. * cares about should be matched by one of the clauses above. The
  352. * code below that checks keycode and chr should be relavent only
  353. * in older browsers.
  354. */
  355. else if (keycode == 8 || keycode == 46)
  356. keyevent = 127; /* Backspace / Delete */
  357. else if (keycode == 13)
  358. keyevent = 13; /* return */
  359. else if (keycode == 37)
  360. keyevent = CURSOR_LEFT;
  361. else if (keycode == 38)
  362. keyevent = CURSOR_UP;
  363. else if (keycode == 39)
  364. keyevent = CURSOR_RIGHT;
  365. else if (keycode == 40)
  366. keyevent = CURSOR_DOWN;
  367. else if (keycode == 35)
  368. keyevent = MOD_NUM_KEYPAD | '1';
  369. else if (keycode == 34)
  370. keyevent = MOD_NUM_KEYPAD | '3';
  371. else if (keycode == 36)
  372. keyevent = MOD_NUM_KEYPAD | '7';
  373. else if (keycode == 33)
  374. keyevent = MOD_NUM_KEYPAD | '9';
  375. else if (shift && ctrl && (keycode & 0x1F) == 26)
  376. keyevent = UI_REDO;
  377. else if (chr && chr[0] && !chr[1])
  378. keyevent = chr[0] & 0xFF;
  379. else if (keycode >= 96 && keycode < 106)
  380. keyevent = MOD_NUM_KEYPAD | ('0' + keycode - 96);
  381. else if (keycode >= 65 && keycode <= 90)
  382. keyevent = keycode + (shift ? 0 : 32);
  383. else if (keycode >= 48 && keycode <= 57)
  384. keyevent = keycode;
  385. else if (keycode == 32) /* space / CURSOR_SELECT2 */
  386. keyevent = keycode;
  387. if (keyevent >= 0) {
  388. if (shift) keyevent |= MOD_SHFT;
  389. if (ctrl) keyevent |= MOD_CTRL;
  390. if (location == DOM_KEY_LOCATION_NUMPAD) keyevent |= MOD_NUM_KEYPAD;
  391. process_key_result = midend_process_key(me, 0, 0, keyevent);
  392. post_move();
  393. /*
  394. * Treat Backspace specially because that's expected on KaiOS.
  395. * https://developer.kaiostech.com/docs/design-guide/key
  396. */
  397. if (process_key_result == PKR_NO_EFFECT &&
  398. !strnullcmp(key, "Backspace"))
  399. return false;
  400. return process_key_result != PKR_UNUSED;
  401. }
  402. return false; /* Event not handled, because we don't even recognise it. */
  403. }
  404. /*
  405. * Helper function called from several places to update the permalinks
  406. * whenever a new game is created.
  407. */
  408. static void update_permalinks(void)
  409. {
  410. char *desc, *seed;
  411. desc = midend_get_game_id(me);
  412. seed = midend_get_random_seed(me);
  413. js_update_permalinks(desc, seed);
  414. sfree(desc);
  415. sfree(seed);
  416. }
  417. /*
  418. * Callback from the midend when the game ids change, so we can update
  419. * the permalinks.
  420. */
  421. static void ids_changed(void *ignored)
  422. {
  423. update_permalinks();
  424. }
  425. /* ----------------------------------------------------------------------
  426. * Implementation of the drawing API by calling Javascript canvas
  427. * drawing functions. (Well, half of it; the other half is on the JS
  428. * side.)
  429. */
  430. static void js_start_draw(void *handle)
  431. {
  432. js_canvas_start_draw();
  433. }
  434. static void js_clip(void *handle, int x, int y, int w, int h)
  435. {
  436. js_canvas_clip_rect(x, y, w, h);
  437. }
  438. static void js_unclip(void *handle)
  439. {
  440. js_canvas_unclip();
  441. }
  442. static void js_draw_text(void *handle, int x, int y, int fonttype,
  443. int fontsize, int align, int colour,
  444. const char *text)
  445. {
  446. int halign;
  447. if (align & ALIGN_VCENTRE)
  448. y += js_canvas_find_font_midpoint(fontsize, fonttype == FONT_FIXED);
  449. if (align & ALIGN_HCENTRE)
  450. halign = 1;
  451. else if (align & ALIGN_HRIGHT)
  452. halign = 2;
  453. else
  454. halign = 0;
  455. js_canvas_draw_text(x, y, halign, colour_strings[colour],
  456. fontsize, fonttype == FONT_FIXED, text);
  457. }
  458. static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
  459. {
  460. js_canvas_draw_rect(x, y, w, h, colour_strings[colour]);
  461. }
  462. static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
  463. int colour)
  464. {
  465. js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]);
  466. }
  467. static void js_draw_thick_line(void *handle, float thickness,
  468. float x1, float y1, float x2, float y2,
  469. int colour)
  470. {
  471. js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]);
  472. }
  473. static void js_draw_poly(void *handle, const int *coords, int npoints,
  474. int fillcolour, int outlinecolour)
  475. {
  476. js_canvas_draw_poly(coords, npoints,
  477. fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
  478. colour_strings[outlinecolour]);
  479. }
  480. static void js_draw_circle(void *handle, int cx, int cy, int radius,
  481. int fillcolour, int outlinecolour)
  482. {
  483. js_canvas_draw_circle(cx, cy, radius,
  484. fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
  485. colour_strings[outlinecolour]);
  486. }
  487. struct blitter {
  488. int id; /* allocated on the js side */
  489. int w, h; /* easier to retain here */
  490. };
  491. static blitter *js_blitter_new(void *handle, int w, int h)
  492. {
  493. blitter *bl = snew(blitter);
  494. bl->w = w;
  495. bl->h = h;
  496. bl->id = js_canvas_new_blitter(w, h);
  497. return bl;
  498. }
  499. static void js_blitter_free(void *handle, blitter *bl)
  500. {
  501. js_canvas_free_blitter(bl->id);
  502. sfree(bl);
  503. }
  504. static void trim_rect(int *x, int *y, int *w, int *h)
  505. {
  506. int x0, x1, y0, y1;
  507. /*
  508. * Reduce the size of the copied rectangle to stop it going
  509. * outside the bounds of the canvas.
  510. */
  511. /* Transform from x,y,w,h form into coordinates of all edges */
  512. x0 = *x;
  513. y0 = *y;
  514. x1 = *x + *w;
  515. y1 = *y + *h;
  516. /* Clip each coordinate at both extremes of the canvas */
  517. x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0);
  518. x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1);
  519. y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0);
  520. y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1);
  521. /* Transform back into x,y,w,h to return */
  522. *x = x0;
  523. *y = y0;
  524. *w = x1 - x0;
  525. *h = y1 - y0;
  526. }
  527. static void js_blitter_save(void *handle, blitter *bl, int x, int y)
  528. {
  529. int w = bl->w, h = bl->h;
  530. trim_rect(&x, &y, &w, &h);
  531. if (w > 0 && h > 0)
  532. js_canvas_copy_to_blitter(bl->id, x, y, w, h);
  533. }
  534. static void js_blitter_load(void *handle, blitter *bl, int x, int y)
  535. {
  536. int w = bl->w, h = bl->h;
  537. trim_rect(&x, &y, &w, &h);
  538. if (w > 0 && h > 0)
  539. js_canvas_copy_from_blitter(bl->id, x, y, w, h);
  540. }
  541. static void js_draw_update(void *handle, int x, int y, int w, int h)
  542. {
  543. trim_rect(&x, &y, &w, &h);
  544. if (w > 0 && h > 0)
  545. js_canvas_draw_update(x, y, w, h);
  546. }
  547. static void js_end_draw(void *handle)
  548. {
  549. js_canvas_end_draw();
  550. }
  551. static void js_status_bar(void *handle, const char *text)
  552. {
  553. js_canvas_set_statusbar(text);
  554. }
  555. static char *js_text_fallback(void *handle, const char *const *strings,
  556. int nstrings)
  557. {
  558. return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
  559. }
  560. static const struct drawing_api js_drawing = {
  561. js_draw_text,
  562. js_draw_rect,
  563. js_draw_line,
  564. js_draw_poly,
  565. js_draw_circle,
  566. js_draw_update,
  567. js_clip,
  568. js_unclip,
  569. js_start_draw,
  570. js_end_draw,
  571. js_status_bar,
  572. js_blitter_new,
  573. js_blitter_free,
  574. js_blitter_save,
  575. js_blitter_load,
  576. NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
  577. NULL, NULL, /* line_width, line_dotted */
  578. js_text_fallback,
  579. js_draw_thick_line,
  580. };
  581. /* ----------------------------------------------------------------------
  582. * Presets and game-configuration dialog support.
  583. */
  584. static game_params **presets;
  585. static int npresets;
  586. static bool have_presets_dropdown;
  587. static void populate_js_preset_menu(int menuid, struct preset_menu *menu)
  588. {
  589. int i;
  590. for (i = 0; i < menu->n_entries; i++) {
  591. struct preset_menu_entry *entry = &menu->entries[i];
  592. if (entry->params) {
  593. presets[entry->id] = entry->params;
  594. js_add_preset(menuid, entry->title, entry->id);
  595. } else {
  596. int js_submenu = js_add_preset_submenu(menuid, entry->title);
  597. populate_js_preset_menu(js_submenu, entry->submenu);
  598. }
  599. }
  600. }
  601. static void select_appropriate_preset(void)
  602. {
  603. if (have_presets_dropdown) {
  604. int preset = midend_which_preset(me);
  605. js_select_preset(preset < 0 ? -1 : preset);
  606. }
  607. }
  608. static config_item *cfg = NULL;
  609. static int cfg_which;
  610. /*
  611. * Set up a dialog box. This is pretty easy on the C side; most of the
  612. * work is done in JS.
  613. */
  614. static void cfg_start(int which)
  615. {
  616. char *title;
  617. int i;
  618. cfg = midend_get_config(me, which, &title);
  619. cfg_which = which;
  620. js_dialog_init(title);
  621. sfree(title);
  622. for (i = 0; cfg[i].type != C_END; i++) {
  623. switch (cfg[i].type) {
  624. case C_STRING:
  625. js_dialog_string(i, cfg[i].name, cfg[i].u.string.sval);
  626. break;
  627. case C_BOOLEAN:
  628. js_dialog_boolean(i, cfg[i].name, cfg[i].u.boolean.bval);
  629. break;
  630. case C_CHOICES:
  631. js_dialog_choices(i, cfg[i].name, cfg[i].u.choices.choicenames,
  632. cfg[i].u.choices.selected);
  633. break;
  634. }
  635. }
  636. js_dialog_launch();
  637. }
  638. /*
  639. * Callbacks from JS when the OK button is clicked, to return the
  640. * final state of each control.
  641. */
  642. void dlg_return_sval(int index, const char *val)
  643. {
  644. config_item *i = cfg + index;
  645. switch (i->type) {
  646. case C_STRING:
  647. sfree(i->u.string.sval);
  648. i->u.string.sval = dupstr(val);
  649. break;
  650. default:
  651. assert(0 && "Bad type for return_sval");
  652. }
  653. }
  654. void dlg_return_ival(int index, int val)
  655. {
  656. config_item *i = cfg + index;
  657. switch (i->type) {
  658. case C_BOOLEAN:
  659. i->u.boolean.bval = val;
  660. break;
  661. case C_CHOICES:
  662. i->u.choices.selected = val;
  663. break;
  664. default:
  665. assert(0 && "Bad type for return_ival");
  666. }
  667. }
  668. /*
  669. * Called when the user clicks OK or Cancel. use_results will be true
  670. * or false respectively, in those cases. We terminate the dialog box,
  671. * unless the user selected an invalid combination of parameters.
  672. */
  673. static void cfg_end(bool use_results)
  674. {
  675. if (use_results) {
  676. /*
  677. * User hit OK.
  678. */
  679. const char *err = midend_set_config(me, cfg_which, cfg);
  680. if (err) {
  681. /*
  682. * The settings were unacceptable, so leave the config box
  683. * open for the user to adjust them and try again.
  684. */
  685. js_error_box(err);
  686. } else if (cfg_which == CFG_PREFS) {
  687. /*
  688. * Acceptable settings for user preferences: enact them
  689. * without blowing away the current game.
  690. */
  691. resize();
  692. midend_redraw(me);
  693. free_cfg(cfg);
  694. js_dialog_cleanup();
  695. save_prefs(me);
  696. } else {
  697. /*
  698. * Acceptable settings for the remaining configuration
  699. * types: start a new game and close the dialog.
  700. */
  701. select_appropriate_preset();
  702. midend_new_game(me);
  703. resize();
  704. midend_redraw(me);
  705. free_cfg(cfg);
  706. js_dialog_cleanup();
  707. }
  708. } else {
  709. /*
  710. * User hit Cancel. Close the dialog, but also we must still
  711. * reselect the right element of the dropdown list.
  712. *
  713. * (Because: imagine you have a preset selected, and then you
  714. * select Custom from the list, but change your mind and hit
  715. * Esc. The Custom option will now still be selected in the
  716. * list, whereas obviously it should show the preset you still
  717. * _actually_ have selected.)
  718. */
  719. select_appropriate_preset();
  720. free_cfg(cfg);
  721. js_dialog_cleanup();
  722. }
  723. }
  724. /* ----------------------------------------------------------------------
  725. * Called from JS when a command is given to the puzzle by clicking a
  726. * button or control of some sort.
  727. */
  728. void command(int n)
  729. {
  730. switch (n) {
  731. case 0: /* specific game ID */
  732. cfg_start(CFG_DESC);
  733. break;
  734. case 1: /* random game seed */
  735. cfg_start(CFG_SEED);
  736. break;
  737. case 2: /* game parameter dropdown changed */
  738. {
  739. int i = js_get_selected_preset();
  740. if (i < 0) {
  741. /*
  742. * The user selected 'Custom', so launch the config
  743. * box.
  744. */
  745. if (thegame.can_configure) /* (double-check just in case) */
  746. cfg_start(CFG_SETTINGS);
  747. } else {
  748. /*
  749. * The user selected a preset, so just switch straight
  750. * to that.
  751. */
  752. assert(i < npresets);
  753. midend_set_params(me, presets[i]);
  754. midend_new_game(me);
  755. resize();
  756. midend_redraw(me);
  757. post_move();
  758. js_focus_canvas();
  759. select_appropriate_preset();
  760. }
  761. }
  762. break;
  763. case 3: /* OK clicked in a config box */
  764. cfg_end(true);
  765. post_move();
  766. break;
  767. case 4: /* Cancel clicked in a config box */
  768. cfg_end(false);
  769. post_move();
  770. break;
  771. case 5: /* New Game */
  772. midend_process_key(me, 0, 0, UI_NEWGAME);
  773. post_move();
  774. js_focus_canvas();
  775. break;
  776. case 6: /* Restart */
  777. midend_restart_game(me);
  778. post_move();
  779. js_focus_canvas();
  780. break;
  781. case 7: /* Undo */
  782. midend_process_key(me, 0, 0, UI_UNDO);
  783. post_move();
  784. js_focus_canvas();
  785. break;
  786. case 8: /* Redo */
  787. midend_process_key(me, 0, 0, UI_REDO);
  788. post_move();
  789. js_focus_canvas();
  790. break;
  791. case 9: /* Solve */
  792. if (thegame.can_solve) {
  793. const char *msg = midend_solve(me);
  794. if (msg)
  795. js_error_box(msg);
  796. }
  797. post_move();
  798. js_focus_canvas();
  799. break;
  800. case 10: /* user preferences */
  801. cfg_start(CFG_PREFS);
  802. break;
  803. }
  804. }
  805. char *get_text_format(void)
  806. {
  807. return midend_text_format(me);
  808. }
  809. void free_text_format(char *buffer)
  810. {
  811. sfree(buffer);
  812. }
  813. /* ----------------------------------------------------------------------
  814. * Called from JS to prepare a save-game file, and free one after it's
  815. * been used.
  816. */
  817. struct savefile_write_ctx {
  818. char *buffer;
  819. size_t pos;
  820. };
  821. static void savefile_write(void *vctx, const void *buf, int len)
  822. {
  823. struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx;
  824. if (ctx->buffer)
  825. memcpy(ctx->buffer + ctx->pos, buf, len);
  826. ctx->pos += len;
  827. }
  828. char *get_save_file(void)
  829. {
  830. struct savefile_write_ctx ctx;
  831. size_t size;
  832. /* First pass, to count up the size */
  833. ctx.buffer = NULL;
  834. ctx.pos = 0;
  835. midend_serialise(me, savefile_write, &ctx);
  836. size = ctx.pos;
  837. /* Second pass, to actually write out the data. We have to put a
  838. * terminating \0 on the end (which we expect never to show up in
  839. * the actual serialisation format - it's text, not binary) so
  840. * that the Javascript side can easily find out the length. */
  841. ctx.buffer = snewn(size+1, char);
  842. ctx.pos = 0;
  843. midend_serialise(me, savefile_write, &ctx);
  844. assert(ctx.pos == size);
  845. ctx.buffer[ctx.pos] = '\0';
  846. return ctx.buffer;
  847. }
  848. void free_save_file(char *buffer)
  849. {
  850. sfree(buffer);
  851. }
  852. static bool savefile_read(void *vctx, void *buf, int len)
  853. {
  854. return js_savefile_read(buf, len);
  855. }
  856. void load_game(void)
  857. {
  858. const char *err;
  859. /*
  860. * savefile_read_callback in JavaScript was set up by our caller
  861. * as a closure that knows what file we're loading.
  862. */
  863. err = midend_deserialise(me, savefile_read, NULL);
  864. if (err) {
  865. js_error_box(err);
  866. } else {
  867. select_appropriate_preset();
  868. resize();
  869. midend_redraw(me);
  870. update_permalinks();
  871. post_move();
  872. }
  873. }
  874. /* ----------------------------------------------------------------------
  875. * Functions to load and save preferences, calling out to JS to access
  876. * the appropriate localStorage slot.
  877. */
  878. static void save_prefs(midend *me)
  879. {
  880. struct savefile_write_ctx ctx;
  881. size_t size;
  882. /* First pass, to count up the size */
  883. ctx.buffer = NULL;
  884. ctx.pos = 0;
  885. midend_save_prefs(me, savefile_write, &ctx);
  886. size = ctx.pos;
  887. /* Second pass, to actually write out the data. As with
  888. * get_save_file, we append a terminating \0. */
  889. ctx.buffer = snewn(size+1, char);
  890. ctx.pos = 0;
  891. midend_save_prefs(me, savefile_write, &ctx);
  892. assert(ctx.pos == size);
  893. ctx.buffer[ctx.pos] = '\0';
  894. js_save_prefs(ctx.buffer);
  895. sfree(ctx.buffer);
  896. }
  897. struct prefs_read_ctx {
  898. const char *buffer;
  899. size_t pos, len;
  900. };
  901. static bool prefs_read(void *vctx, void *buf, int len)
  902. {
  903. struct prefs_read_ctx *ctx = (struct prefs_read_ctx *)vctx;
  904. if (len < 0)
  905. return false;
  906. if (ctx->len - ctx->pos < len)
  907. return false;
  908. memcpy(buf, ctx->buffer + ctx->pos, len);
  909. ctx->pos += len;
  910. return true;
  911. }
  912. void prefs_load_callback(midend *me, const char *prefs)
  913. {
  914. struct prefs_read_ctx ctx;
  915. ctx.buffer = prefs;
  916. ctx.len = strlen(prefs);
  917. ctx.pos = 0;
  918. midend_load_prefs(me, prefs_read, &ctx);
  919. }
  920. /* ----------------------------------------------------------------------
  921. * Setup function called at page load time. It's called main() because
  922. * that's the most convenient thing in Emscripten, but it's not main()
  923. * in the usual sense of bounding the program's entire execution.
  924. * Instead, this function returns once the initial puzzle is set up
  925. * and working, and everything thereafter happens by means of JS event
  926. * handlers sending us callbacks.
  927. */
  928. int main(int argc, char **argv)
  929. {
  930. const char *param_err;
  931. float *colours;
  932. int i;
  933. /*
  934. * Initialise JavaScript event handlers.
  935. */
  936. js_init_puzzle();
  937. /*
  938. * Instantiate a midend.
  939. */
  940. me = midend_new(NULL, &thegame, &js_drawing, NULL);
  941. js_load_prefs(me);
  942. /*
  943. * Chuck in the HTML fragment ID if we have one (trimming the
  944. * leading # off the front first). If that's invalid, we retain
  945. * the error message and will display it at the end, after setting
  946. * up a random puzzle as usual.
  947. */
  948. if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
  949. param_err = midend_game_id(me, argv[1] + 1);
  950. else
  951. param_err = NULL;
  952. /*
  953. * Create either a random game or the specified one, and set the
  954. * canvas size appropriately.
  955. */
  956. midend_new_game(me);
  957. resize();
  958. /*
  959. * Remove the status bar, if not needed.
  960. */
  961. if (!midend_wants_statusbar(me))
  962. js_canvas_remove_statusbar();
  963. /*
  964. * Set up the game-type dropdown with presets and/or the Custom
  965. * option.
  966. */
  967. {
  968. struct preset_menu *menu = midend_get_presets(me, &npresets);
  969. bool may_configure = false;
  970. presets = snewn(npresets, game_params *);
  971. for (i = 0; i < npresets; i++)
  972. presets[i] = NULL;
  973. populate_js_preset_menu(0, menu);
  974. /*
  975. * Crude hack to allow the "Custom..." item to be hidden on
  976. * KaiOS, where dialogs don't yet work.
  977. */
  978. if (thegame.can_configure && getenv_bool("PUZZLES_ALLOW_CUSTOM", true))
  979. may_configure = true;
  980. if (may_configure)
  981. js_add_preset(0, "Custom...", -1);
  982. have_presets_dropdown = npresets > 1 || may_configure;
  983. if (have_presets_dropdown)
  984. /*
  985. * Now ensure the appropriate element of the presets menu
  986. * starts off selected, in case it isn't the first one in the
  987. * list (e.g. Slant).
  988. */
  989. select_appropriate_preset();
  990. else
  991. js_remove_type_dropdown();
  992. }
  993. /*
  994. * Remove the Solve button if the game doesn't support it.
  995. */
  996. if (!thegame.can_solve)
  997. js_remove_solve_button();
  998. /*
  999. * Retrieve the game's colours, and convert them into #abcdef type
  1000. * hex ID strings.
  1001. */
  1002. colours = midend_colours(me, &ncolours);
  1003. colour_strings = snewn(ncolours, char *);
  1004. for (i = 0; i < ncolours; i++) {
  1005. char col[40];
  1006. sprintf(col, "#%02x%02x%02x",
  1007. (unsigned)(0.5F + 255 * colours[i*3+0]),
  1008. (unsigned)(0.5F + 255 * colours[i*3+1]),
  1009. (unsigned)(0.5F + 255 * colours[i*3+2]));
  1010. colour_strings[i] = dupstr(col);
  1011. }
  1012. /* Put the background colour in a CSS variable. */
  1013. js_set_background_colour(colour_strings[0]);
  1014. /*
  1015. * Request notification when the game ids change (e.g. if the user
  1016. * presses 'n', and also when Mines supersedes its game
  1017. * description), so that we can proactively update the permalink.
  1018. */
  1019. midend_request_id_changes(me, ids_changed, NULL);
  1020. /*
  1021. * Draw the puzzle's initial state, and set up the permalinks and
  1022. * undo/redo greying out.
  1023. */
  1024. midend_redraw(me);
  1025. update_permalinks();
  1026. post_move();
  1027. /*
  1028. * If we were given an erroneous game ID in argv[1], now's the
  1029. * time to put up the error box about it, after we've fully set up
  1030. * a random puzzle. Then when the user clicks 'ok', we have a
  1031. * puzzle for them.
  1032. */
  1033. if (param_err)
  1034. js_error_box(param_err);
  1035. /*
  1036. * Reveal the puzzle!
  1037. */
  1038. js_post_init();
  1039. /*
  1040. * Done. Return to JS, and await callbacks!
  1041. */
  1042. return 0;
  1043. }