chiark / gitweb /
Fix completion checking in Killer Solo.
[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     } else if (keycode == 32) {        /* space / CURSOR_SELECT2 */
321         keyevent = keycode;
322     }
323
324     if (keyevent >= 0) {
325         if (shift && keyevent >= 0x100)
326             keyevent |= MOD_SHFT;
327
328         if (ctrl) {
329             if (keyevent >= 0x100)
330                 keyevent |= MOD_CTRL;
331             else
332                 keyevent &= 0x1F;
333         }
334
335         midend_process_key(me, 0, 0, keyevent);
336         update_undo_redo();
337     }
338 }
339
340 /*
341  * Helper function called from several places to update the permalinks
342  * whenever a new game is created.
343  */
344 static void update_permalinks(void)
345 {
346     char *desc, *seed;
347     desc = midend_get_game_id(me);
348     seed = midend_get_random_seed(me);
349     js_update_permalinks(desc, seed);
350     sfree(desc);
351     sfree(seed);
352 }
353
354 /*
355  * Callback from the midend when the game ids change, so we can update
356  * the permalinks.
357  */
358 static void ids_changed(void *ignored)
359 {
360     update_permalinks();
361 }
362
363 /* ----------------------------------------------------------------------
364  * Implementation of the drawing API by calling Javascript canvas
365  * drawing functions. (Well, half of it; the other half is on the JS
366  * side.)
367  */
368 static void js_start_draw(void *handle)
369 {
370     js_canvas_start_draw();
371 }
372
373 static void js_clip(void *handle, int x, int y, int w, int h)
374 {
375     js_canvas_clip_rect(x, y, w, h);
376 }
377
378 static void js_unclip(void *handle)
379 {
380     js_canvas_unclip();
381 }
382
383 static void js_draw_text(void *handle, int x, int y, int fonttype,
384                          int fontsize, int align, int colour, char *text)
385 {
386     char fontstyle[80];
387     int halign;
388
389     sprintf(fontstyle, "%dpx %s", fontsize,
390             fonttype == FONT_FIXED ? "monospace" : "sans-serif");
391
392     if (align & ALIGN_VCENTRE)
393         y += js_canvas_find_font_midpoint(fontsize, fontstyle);
394
395     if (align & ALIGN_HCENTRE)
396         halign = 1;
397     else if (align & ALIGN_HRIGHT)
398         halign = 2;
399     else
400         halign = 0;
401
402     js_canvas_draw_text(x, y, halign, colour_strings[colour], fontstyle, text);
403 }
404
405 static void js_draw_rect(void *handle, int x, int y, int w, int h, int colour)
406 {
407     js_canvas_draw_rect(x, y, w, h, colour_strings[colour]);
408 }
409
410 static void js_draw_line(void *handle, int x1, int y1, int x2, int y2,
411                          int colour)
412 {
413     js_canvas_draw_line(x1, y1, x2, y2, 1, colour_strings[colour]);
414 }
415
416 static void js_draw_thick_line(void *handle, float thickness,
417                                float x1, float y1, float x2, float y2,
418                                int colour)
419 {
420     js_canvas_draw_line(x1, y1, x2, y2, thickness, colour_strings[colour]);
421 }
422
423 static void js_draw_poly(void *handle, int *coords, int npoints,
424                          int fillcolour, int outlinecolour)
425 {
426     js_canvas_draw_poly(coords, npoints,
427                         fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
428                         colour_strings[outlinecolour]);
429 }
430
431 static void js_draw_circle(void *handle, int cx, int cy, int radius,
432                            int fillcolour, int outlinecolour)
433 {
434     js_canvas_draw_circle(cx, cy, radius,
435                           fillcolour >= 0 ? colour_strings[fillcolour] : NULL,
436                           colour_strings[outlinecolour]);
437 }
438
439 struct blitter {
440     int id;                            /* allocated on the js side */
441     int w, h;                          /* easier to retain here */
442 };
443
444 static blitter *js_blitter_new(void *handle, int w, int h)
445 {
446     blitter *bl = snew(blitter);
447     bl->w = w;
448     bl->h = h;
449     bl->id = js_canvas_new_blitter(w, h);
450     return bl;
451 }
452
453 static void js_blitter_free(void *handle, blitter *bl)
454 {
455     js_canvas_free_blitter(bl->id);
456     sfree(bl);
457 }
458
459 static void trim_rect(int *x, int *y, int *w, int *h)
460 {
461     int x0, x1, y0, y1;
462
463     /*
464      * Reduce the size of the copied rectangle to stop it going
465      * outside the bounds of the canvas.
466      */
467
468     /* Transform from x,y,w,h form into coordinates of all edges */
469     x0 = *x;
470     y0 = *y;
471     x1 = *x + *w;
472     y1 = *y + *h;
473
474     /* Clip each coordinate at both extremes of the canvas */
475     x0 = (x0 < 0 ? 0 : x0 > canvas_w ? canvas_w : x0);
476     x1 = (x1 < 0 ? 0 : x1 > canvas_w ? canvas_w : x1);
477     y0 = (y0 < 0 ? 0 : y0 > canvas_h ? canvas_h : y0);
478     y1 = (y1 < 0 ? 0 : y1 > canvas_h ? canvas_h : y1); 
479
480     /* Transform back into x,y,w,h to return */
481     *x = x0;
482     *y = y0;
483     *w = x1 - x0;
484     *h = y1 - y0;
485 }
486
487 static void js_blitter_save(void *handle, blitter *bl, int x, int y)
488 {
489     int w = bl->w, h = bl->h;
490     trim_rect(&x, &y, &w, &h);
491     if (w > 0 && h > 0)
492         js_canvas_copy_to_blitter(bl->id, x, y, w, h);
493 }
494
495 static void js_blitter_load(void *handle, blitter *bl, int x, int y)
496 {
497     int w = bl->w, h = bl->h;
498     trim_rect(&x, &y, &w, &h);
499     if (w > 0 && h > 0)
500         js_canvas_copy_from_blitter(bl->id, x, y, w, h);
501 }
502
503 static void js_draw_update(void *handle, int x, int y, int w, int h)
504 {
505     trim_rect(&x, &y, &w, &h);
506     if (w > 0 && h > 0)
507         js_canvas_draw_update(x, y, w, h);
508 }
509
510 static void js_end_draw(void *handle)
511 {
512     js_canvas_end_draw();
513 }
514
515 static void js_status_bar(void *handle, char *text)
516 {
517     js_canvas_set_statusbar(text);
518 }
519
520 static char *js_text_fallback(void *handle, const char *const *strings,
521                               int nstrings)
522 {
523     return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
524 }
525
526 const struct drawing_api js_drawing = {
527     js_draw_text,
528     js_draw_rect,
529     js_draw_line,
530     js_draw_poly,
531     js_draw_circle,
532     js_draw_update,
533     js_clip,
534     js_unclip,
535     js_start_draw,
536     js_end_draw,
537     js_status_bar,
538     js_blitter_new,
539     js_blitter_free,
540     js_blitter_save,
541     js_blitter_load,
542     NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
543     NULL, NULL,                        /* line_width, line_dotted */
544     js_text_fallback,
545     js_draw_thick_line,
546 };
547
548 /* ----------------------------------------------------------------------
549  * Presets and game-configuration dialog support.
550  */
551 static game_params **presets;
552 static int npresets;
553 int have_presets_dropdown;
554
555 void select_appropriate_preset(void)
556 {
557     if (have_presets_dropdown) {
558         int preset = midend_which_preset(me);
559         js_select_preset(preset < 0 ? -1 : preset);
560     }
561 }
562
563 static config_item *cfg = NULL;
564 static int cfg_which;
565
566 /*
567  * Set up a dialog box. This is pretty easy on the C side; most of the
568  * work is done in JS.
569  */
570 static void cfg_start(int which)
571 {
572     char *title;
573     int i;
574
575     cfg = midend_get_config(me, which, &title);
576     cfg_which = which;
577
578     js_dialog_init(title);
579     sfree(title);
580
581     for (i = 0; cfg[i].type != C_END; i++) {
582         switch (cfg[i].type) {
583           case C_STRING:
584             js_dialog_string(i, cfg[i].name, cfg[i].sval);
585             break;
586           case C_BOOLEAN:
587             js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
588             break;
589           case C_CHOICES:
590             js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
591             break;
592         }
593     }
594
595     js_dialog_launch();
596 }
597
598 /*
599  * Callbacks from JS when the OK button is clicked, to return the
600  * final state of each control.
601  */
602 void dlg_return_sval(int index, const char *val)
603 {
604     sfree(cfg[index].sval);
605     cfg[index].sval = dupstr(val);
606 }
607 void dlg_return_ival(int index, int val)
608 {
609     cfg[index].ival = val;
610 }
611
612 /*
613  * Called when the user clicks OK or Cancel. use_results will be TRUE
614  * or FALSE respectively, in those cases. We terminate the dialog box,
615  * unless the user selected an invalid combination of parameters.
616  */
617 static void cfg_end(int use_results)
618 {
619     if (use_results) {
620         /*
621          * User hit OK.
622          */
623         char *err = midend_set_config(me, cfg_which, cfg);
624
625         if (err) {
626             /*
627              * The settings were unacceptable, so leave the config box
628              * open for the user to adjust them and try again.
629              */
630             js_error_box(err);
631         } else {
632             /*
633              * New settings are fine; start a new game and close the
634              * dialog.
635              */
636             select_appropriate_preset();
637             midend_new_game(me);
638             resize();
639             midend_redraw(me);
640             free_cfg(cfg);
641             js_dialog_cleanup();
642         }
643     } else {
644         /*
645          * User hit Cancel. Close the dialog, but also we must still
646          * reselect the right element of the dropdown list.
647          *
648          * (Because: imagine you have a preset selected, and then you
649          * select Custom from the list, but change your mind and hit
650          * Esc. The Custom option will now still be selected in the
651          * list, whereas obviously it should show the preset you still
652          * _actually_ have selected. Worse still, it'll be the visible
653          * rather than invisible Custom option - see the comment in
654          * js_add_preset in emcclib.js - so you won't even be able to
655          * select Custom without a faffy workaround.)
656          */
657         select_appropriate_preset();
658
659         free_cfg(cfg);
660         js_dialog_cleanup();
661     }
662 }
663
664 /* ----------------------------------------------------------------------
665  * Called from JS when a command is given to the puzzle by clicking a
666  * button or control of some sort.
667  */
668 void command(int n)
669 {
670     switch (n) {
671       case 0:                          /* specific game ID */
672         cfg_start(CFG_DESC);
673         break;
674       case 1:                          /* random game seed */
675         cfg_start(CFG_SEED);
676         break;
677       case 2:                          /* game parameter dropdown changed */
678         {
679             int i = js_get_selected_preset();
680             if (i < 0) {
681                 /*
682                  * The user selected 'Custom', so launch the config
683                  * box.
684                  */
685                 if (thegame.can_configure) /* (double-check just in case) */
686                     cfg_start(CFG_SETTINGS);
687             } else {
688                 /*
689                  * The user selected a preset, so just switch straight
690                  * to that.
691                  */
692                 assert(i < npresets);
693                 midend_set_params(me, presets[i]);
694                 midend_new_game(me);
695                 resize();
696                 midend_redraw(me);
697                 update_undo_redo();
698                 js_focus_canvas();
699                 select_appropriate_preset(); /* sort out Custom/Customise */
700             }
701         }
702         break;
703       case 3:                          /* OK clicked in a config box */
704         cfg_end(TRUE);
705         update_undo_redo();
706         break;
707       case 4:                          /* Cancel clicked in a config box */
708         cfg_end(FALSE);
709         update_undo_redo();
710         break;
711       case 5:                          /* New Game */
712         midend_process_key(me, 0, 0, 'n');
713         update_undo_redo();
714         js_focus_canvas();
715         break;
716       case 6:                          /* Restart */
717         midend_restart_game(me);
718         update_undo_redo();
719         js_focus_canvas();
720         break;
721       case 7:                          /* Undo */
722         midend_process_key(me, 0, 0, 'u');
723         update_undo_redo();
724         js_focus_canvas();
725         break;
726       case 8:                          /* Redo */
727         midend_process_key(me, 0, 0, 'r');
728         update_undo_redo();
729         js_focus_canvas();
730         break;
731       case 9:                          /* Solve */
732         if (thegame.can_solve) {
733             char *msg = midend_solve(me);
734             if (msg)
735                 js_error_box(msg);
736         }
737         update_undo_redo();
738         js_focus_canvas();
739         break;
740     }
741 }
742
743 /* ----------------------------------------------------------------------
744  * Setup function called at page load time. It's called main() because
745  * that's the most convenient thing in Emscripten, but it's not main()
746  * in the usual sense of bounding the program's entire execution.
747  * Instead, this function returns once the initial puzzle is set up
748  * and working, and everything thereafter happens by means of JS event
749  * handlers sending us callbacks.
750  */
751 int main(int argc, char **argv)
752 {
753     char *param_err;
754     float *colours;
755     int i;
756
757     /*
758      * Instantiate a midend.
759      */
760     me = midend_new(NULL, &thegame, &js_drawing, NULL);
761
762     /*
763      * Chuck in the HTML fragment ID if we have one (trimming the
764      * leading # off the front first). If that's invalid, we retain
765      * the error message and will display it at the end, after setting
766      * up a random puzzle as usual.
767      */
768     if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
769         param_err = midend_game_id(me, argv[1] + 1);
770     else
771         param_err = NULL;
772
773     /*
774      * Create either a random game or the specified one, and set the
775      * canvas size appropriately.
776      */
777     midend_new_game(me);
778     resize();
779
780     /*
781      * Create a status bar, if needed.
782      */
783     if (midend_wants_statusbar(me))
784         js_canvas_make_statusbar();
785
786     /*
787      * Set up the game-type dropdown with presets and/or the Custom
788      * option.
789      */
790     npresets = midend_num_presets(me);
791     if (npresets == 0) {
792         /*
793          * This puzzle doesn't have selectable game types at all.
794          * Completely remove the drop-down list from the page.
795          */
796         js_remove_type_dropdown();
797         have_presets_dropdown = FALSE;
798     } else {
799         presets = snewn(npresets, game_params *);
800         for (i = 0; i < npresets; i++) {
801             char *name;
802             midend_fetch_preset(me, i, &name, &presets[i]);
803             js_add_preset(name);
804         }
805         if (thegame.can_configure)
806             js_add_preset(NULL);   /* the 'Custom' entry in the dropdown */
807
808         have_presets_dropdown = TRUE;
809
810         /*
811          * Now ensure the appropriate element of the presets menu
812          * starts off selected, in case it isn't the first one in the
813          * list (e.g. Slant).
814          */
815         select_appropriate_preset();
816     }
817
818     /*
819      * Remove the Solve button if the game doesn't support it.
820      */
821     if (!thegame.can_solve)
822         js_remove_solve_button();
823
824     /*
825      * Retrieve the game's colours, and convert them into #abcdef type
826      * hex ID strings.
827      */
828     colours = midend_colours(me, &ncolours);
829     colour_strings = snewn(ncolours, char *);
830     for (i = 0; i < ncolours; i++) {
831         char col[40];
832         sprintf(col, "#%02x%02x%02x",
833                 (unsigned)(0.5 + 255 * colours[i*3+0]),
834                 (unsigned)(0.5 + 255 * colours[i*3+1]),
835                 (unsigned)(0.5 + 255 * colours[i*3+2]));
836         colour_strings[i] = dupstr(col);
837     }
838
839     /*
840      * Request notification when the game ids change (e.g. if the user
841      * presses 'n', and also when Mines supersedes its game
842      * description), so that we can proactively update the permalink.
843      */
844     midend_request_id_changes(me, ids_changed, NULL);
845
846     /*
847      * Draw the puzzle's initial state, and set up the permalinks and
848      * undo/redo greying out.
849      */
850     midend_redraw(me);
851     update_permalinks();
852     update_undo_redo();
853
854     /*
855      * If we were given an erroneous game ID in argv[1], now's the
856      * time to put up the error box about it, after we've fully set up
857      * a random puzzle. Then when the user clicks 'ok', we have a
858      * puzzle for them.
859      */
860     if (param_err)
861         js_error_box(param_err);
862
863     /*
864      * Done. Return to JS, and await callbacks!
865      */
866     return 0;
867 }