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