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