chiark / gitweb /
Make sure the right element of the game-type dropdown starts off
[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     /*
420      * Reduce the size of the copied rectangle to stop it going
421      * outside the bounds of the canvas.
422      */
423     if (*x < 0) {
424         *w += *x;
425         *x = 0;
426     }
427     if (*y < 0) {
428         *h += *y;
429         *y = 0;
430     }
431     if (*w > canvas_w - *x)
432         *w = canvas_w - *x;
433     if (*h > canvas_h - *y)
434         *h = canvas_h - *y;
435
436     if (*w < 0)
437         *w = 0;
438     if (*h < 0)
439         *h = 0;
440 }
441
442 static void js_blitter_save(void *handle, blitter *bl, int x, int y)
443 {
444     int w = bl->w, h = bl->h;
445     trim_rect(&x, &y, &w, &h);
446     if (w > 0 && h > 0)
447         js_canvas_copy_to_blitter(bl->id, x, y, w, h);
448 }
449
450 static void js_blitter_load(void *handle, blitter *bl, int x, int y)
451 {
452     int w = bl->w, h = bl->h;
453     trim_rect(&x, &y, &w, &h);
454     if (w > 0 && h > 0)
455         js_canvas_copy_from_blitter(bl->id, x, y, w, h);
456 }
457
458 static void js_draw_update(void *handle, int x, int y, int w, int h)
459 {
460     trim_rect(&x, &y, &w, &h);
461     js_canvas_draw_update(x, y, w, h);
462 }
463
464 static void js_end_draw(void *handle)
465 {
466     js_canvas_end_draw();
467 }
468
469 static void js_status_bar(void *handle, char *text)
470 {
471     js_canvas_set_statusbar(text);
472 }
473
474 static char *js_text_fallback(void *handle, const char *const *strings,
475                               int nstrings)
476 {
477     return dupstr(strings[0]); /* Emscripten has no trouble with UTF-8 */
478 }
479
480 const struct drawing_api js_drawing = {
481     js_draw_text,
482     js_draw_rect,
483     js_draw_line,
484     js_draw_poly,
485     js_draw_circle,
486     js_draw_update,
487     js_clip,
488     js_unclip,
489     js_start_draw,
490     js_end_draw,
491     js_status_bar,
492     js_blitter_new,
493     js_blitter_free,
494     js_blitter_save,
495     js_blitter_load,
496     NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
497     NULL, NULL,                        /* line_width, line_dotted */
498     js_text_fallback,
499     js_draw_thick_line,
500 };
501
502 /* ----------------------------------------------------------------------
503  * Presets and game-configuration dialog support.
504  */
505 static game_params **presets;
506 static int custom_preset;
507 int have_presets_dropdown;
508
509 void select_appropriate_preset(void)
510 {
511     if (have_presets_dropdown) {
512         int preset = midend_which_preset(me);
513         js_select_preset(preset < 0 ? custom_preset : preset);
514     }
515 }
516
517 static config_item *cfg = NULL;
518 static int cfg_which;
519
520 /*
521  * Set up a dialog box. This is pretty easy on the C side; most of the
522  * work is done in JS.
523  */
524 static void cfg_start(int which)
525 {
526     char *title;
527     int i;
528
529     cfg = midend_get_config(me, which, &title);
530     cfg_which = which;
531
532     js_dialog_init(title);
533     sfree(title);
534
535     for (i = 0; cfg[i].type != C_END; i++) {
536         switch (cfg[i].type) {
537           case C_STRING:
538             js_dialog_string(i, cfg[i].name, cfg[i].sval);
539             break;
540           case C_BOOLEAN:
541             js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
542             break;
543           case C_CHOICES:
544             js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
545             break;
546         }
547     }
548
549     js_dialog_launch();
550 }
551
552 /*
553  * Callbacks from JS when the OK button is clicked, to return the
554  * final state of each control.
555  */
556 void dlg_return_sval(int index, const char *val)
557 {
558     sfree(cfg[index].sval);
559     cfg[index].sval = dupstr(val);
560 }
561 void dlg_return_ival(int index, int val)
562 {
563     cfg[index].ival = val;
564 }
565
566 /*
567  * Called when the user clicks OK or Cancel. use_results will be TRUE
568  * or FALSE respectively, in those cases. We terminate the dialog box,
569  * unless the user selected an invalid combination of parameters.
570  */
571 static void cfg_end(int use_results)
572 {
573     if (use_results) {
574         /*
575          * User hit OK.
576          */
577         char *err = midend_set_config(me, cfg_which, cfg);
578
579         if (err) {
580             /*
581              * The settings were unacceptable, so leave the config box
582              * open for the user to adjust them and try again.
583              */
584             js_error_box(err);
585         } else {
586             /*
587              * New settings are fine; start a new game and close the
588              * dialog.
589              */
590             select_appropriate_preset();
591             midend_new_game(me);
592             resize();
593             midend_redraw(me);
594             update_permalinks();
595             free_cfg(cfg);
596             js_dialog_cleanup();
597         }
598     } else {
599         /*
600          * User hit Cancel. Close the dialog, but also we must still
601          * reselect the right element of the dropdown list.
602          *
603          * (Because: imagine you have a preset selected, and then you
604          * select Custom from the list, but change your mind and hit
605          * Esc. The Custom option will now still be selected in the
606          * list, whereas obviously it should show the preset you still
607          * _actually_ have selected. Worse still, it'll be the visible
608          * rather than invisible Custom option - see the comment in
609          * js_add_preset in emcclib.js - so you won't even be able to
610          * select Custom without a faffy workaround.)
611          */
612         select_appropriate_preset();
613
614         free_cfg(cfg);
615         js_dialog_cleanup();
616     }
617 }
618
619 /* ----------------------------------------------------------------------
620  * Called from JS when a command is given to the puzzle by clicking a
621  * button or control of some sort.
622  */
623 void command(int n)
624 {
625     switch (n) {
626       case 0:                          /* specific game ID */
627         cfg_start(CFG_DESC);
628         break;
629       case 1:                          /* random game seed */
630         cfg_start(CFG_SEED);
631         break;
632       case 2:                          /* game parameter dropdown changed */
633         {
634             int i = js_get_selected_preset();
635             if (i == custom_preset) {
636                 /*
637                  * The user selected 'Custom', so launch the config
638                  * box.
639                  */
640                 if (thegame.can_configure) /* (double-check just in case) */
641                     cfg_start(CFG_SETTINGS);
642             } else {
643                 /*
644                  * The user selected a preset, so just switch straight
645                  * to that.
646                  */
647                 midend_set_params(me, presets[i]);
648                 midend_new_game(me);
649                 update_permalinks();
650                 resize();
651                 midend_redraw(me);
652                 update_undo_redo();
653                 js_focus_canvas();
654             }
655         }
656         break;
657       case 3:                          /* OK clicked in a config box */
658         cfg_end(TRUE);
659         update_undo_redo();
660         break;
661       case 4:                          /* Cancel clicked in a config box */
662         cfg_end(FALSE);
663         update_undo_redo();
664         break;
665       case 5:                          /* New Game */
666         midend_process_key(me, 0, 0, 'n');
667         update_undo_redo();
668         js_focus_canvas();
669         break;
670       case 6:                          /* Restart */
671         midend_restart_game(me);
672         update_undo_redo();
673         js_focus_canvas();
674         break;
675       case 7:                          /* Undo */
676         midend_process_key(me, 0, 0, 'u');
677         update_undo_redo();
678         js_focus_canvas();
679         break;
680       case 8:                          /* Redo */
681         midend_process_key(me, 0, 0, 'r');
682         update_undo_redo();
683         js_focus_canvas();
684         break;
685       case 9:                          /* Solve */
686         if (thegame.can_solve) {
687             char *msg = midend_solve(me);
688             if (msg)
689                 js_error_box(msg);
690         }
691         update_undo_redo();
692         js_focus_canvas();
693         break;
694     }
695 }
696
697 /* ----------------------------------------------------------------------
698  * Setup function called at page load time. It's called main() because
699  * that's the most convenient thing in Emscripten, but it's not main()
700  * in the usual sense of bounding the program's entire execution.
701  * Instead, this function returns once the initial puzzle is set up
702  * and working, and everything thereafter happens by means of JS event
703  * handlers sending us callbacks.
704  */
705 int main(int argc, char **argv)
706 {
707     char *param_err;
708     float *colours;
709     int i;
710
711     /*
712      * Instantiate a midend.
713      */
714     me = midend_new(NULL, &thegame, &js_drawing, NULL);
715
716     /*
717      * Chuck in the HTML fragment ID if we have one (trimming the
718      * leading # off the front first). If that's invalid, we retain
719      * the error message and will display it at the end, after setting
720      * up a random puzzle as usual.
721      */
722     if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
723         param_err = midend_game_id(me, argv[1] + 1);
724     else
725         param_err = NULL;
726
727     /*
728      * Create either a random game or the specified one, and set the
729      * canvas size appropriately.
730      */
731     midend_new_game(me);
732     resize();
733
734     /*
735      * Create a status bar, if needed.
736      */
737     if (midend_wants_statusbar(me))
738         js_canvas_make_statusbar();
739
740     /*
741      * Set up the game-type dropdown with presets and/or the Custom
742      * option. We remember the index of the Custom option (as
743      * custom_preset) so that we can easily treat it specially when
744      * it's selected.
745      */
746     custom_preset = midend_num_presets(me);
747     if (custom_preset == 0) {
748         /*
749          * This puzzle doesn't have selectable game types at all.
750          * Completely remove the drop-down list from the page.
751          */
752         js_remove_type_dropdown();
753         have_presets_dropdown = FALSE;
754     } else {
755         int preset;
756
757         presets = snewn(custom_preset, game_params *);
758         for (i = 0; i < custom_preset; i++) {
759             char *name;
760             midend_fetch_preset(me, i, &name, &presets[i]);
761             js_add_preset(name);
762         }
763         if (thegame.can_configure)
764             js_add_preset(NULL);   /* the 'Custom' entry in the dropdown */
765
766         have_presets_dropdown = TRUE;
767
768         /*
769          * Now ensure the appropriate element of the presets menu
770          * starts off selected, in case it isn't the first one in the
771          * list (e.g. Slant).
772          */
773         select_appropriate_preset();
774     }
775
776     /*
777      * Remove the Solve button if the game doesn't support it.
778      */
779     if (!thegame.can_solve)
780         js_remove_solve_button();
781
782     /*
783      * Retrieve the game's colours, and convert them into #abcdef type
784      * hex ID strings.
785      */
786     colours = midend_colours(me, &ncolours);
787     colour_strings = snewn(ncolours, char *);
788     for (i = 0; i < ncolours; i++) {
789         char col[40];
790         sprintf(col, "#%02x%02x%02x",
791                 (unsigned)(0.5 + 255 * colours[i*3+0]),
792                 (unsigned)(0.5 + 255 * colours[i*3+1]),
793                 (unsigned)(0.5 + 255 * colours[i*3+2]));
794         colour_strings[i] = dupstr(col);
795     }
796
797     /*
798      * Request notification if a puzzle (hopefully only ever Mines)
799      * supersedes its game description, so that we can proactively
800      * update the permalink.
801      */
802     midend_request_desc_changes(me, desc_changed, NULL);
803
804     /*
805      * Draw the puzzle's initial state, and set up the permalinks and
806      * undo/redo greying out.
807      */
808     midend_redraw(me);
809     update_permalinks();
810     update_undo_redo();
811
812     /*
813      * If we were given an erroneous game ID in argv[1], now's the
814      * time to put up the error box about it, after we've fully set up
815      * a random puzzle. Then when the user clicks 'ok', we have a
816      * puzzle for them.
817      */
818     if (param_err)
819         js_error_box(param_err);
820
821     /*
822      * Done. Return to JS, and await callbacks!
823      */
824     return 0;
825 }