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