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