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