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