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