chiark / gitweb /
Introduce a mechanism by which calls to midend_supersede_game_desc()
[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
508 static config_item *cfg = NULL;
509 static int cfg_which;
510
511 /*
512  * Set up a dialog box. This is pretty easy on the C side; most of the
513  * work is done in JS.
514  */
515 static void cfg_start(int which)
516 {
517     char *title;
518     int i;
519
520     cfg = midend_get_config(me, which, &title);
521     cfg_which = which;
522
523     js_dialog_init(title);
524     sfree(title);
525
526     for (i = 0; cfg[i].type != C_END; i++) {
527         switch (cfg[i].type) {
528           case C_STRING:
529             js_dialog_string(i, cfg[i].name, cfg[i].sval);
530             break;
531           case C_BOOLEAN:
532             js_dialog_boolean(i, cfg[i].name, cfg[i].ival);
533             break;
534           case C_CHOICES:
535             js_dialog_choices(i, cfg[i].name, cfg[i].sval, cfg[i].ival);
536             break;
537         }
538     }
539
540     js_dialog_launch();
541 }
542
543 /*
544  * Callbacks from JS when the OK button is clicked, to return the
545  * final state of each control.
546  */
547 void dlg_return_sval(int index, const char *val)
548 {
549     sfree(cfg[index].sval);
550     cfg[index].sval = dupstr(val);
551 }
552 void dlg_return_ival(int index, int val)
553 {
554     cfg[index].ival = val;
555 }
556
557 /*
558  * Called when the user clicks OK or Cancel. use_results will be TRUE
559  * or FALSE respectively, in those cases. We terminate the dialog box,
560  * unless the user selected an invalid combination of parameters.
561  */
562 static void cfg_end(int use_results)
563 {
564     if (use_results) {
565         /*
566          * User hit OK.
567          */
568         char *err = midend_set_config(me, cfg_which, cfg);
569
570         if (err) {
571             /*
572              * The settings were unacceptable, so leave the config box
573              * open for the user to adjust them and try again.
574              */
575             js_error_box(err);
576         } else {
577             /*
578              * New settings are fine; start a new game and close the
579              * dialog.
580              */
581             int preset = midend_which_preset(me);
582             js_select_preset(preset < 0 ? custom_preset : preset);
583
584             midend_new_game(me);
585             resize();
586             midend_redraw(me);
587             update_permalinks();
588             free_cfg(cfg);
589             js_dialog_cleanup();
590         }
591     } else {
592         /*
593          * User hit Cancel. Close the dialog, but also we must still
594          * reselect the right element of the dropdown list.
595          *
596          * (Because: imagine you have a preset selected, and then you
597          * select Custom from the list, but change your mind and hit
598          * Esc. The Custom option will now still be selected in the
599          * list, whereas obviously it should show the preset you still
600          * _actually_ have selected. Worse still, it'll be the visible
601          * rather than invisible Custom option - see the comment in
602          * js_add_preset in emcclib.js - so you won't even be able to
603          * select Custom without a faffy workaround.)
604          */
605         int preset = midend_which_preset(me);
606         js_select_preset(preset < 0 ? custom_preset : preset);
607
608         free_cfg(cfg);
609         js_dialog_cleanup();
610     }
611 }
612
613 /* ----------------------------------------------------------------------
614  * Called from JS when a command is given to the puzzle by clicking a
615  * button or control of some sort.
616  */
617 void command(int n)
618 {
619     switch (n) {
620       case 0:                          /* specific game ID */
621         cfg_start(CFG_DESC);
622         break;
623       case 1:                          /* random game seed */
624         cfg_start(CFG_SEED);
625         break;
626       case 2:                          /* game parameter dropdown changed */
627         {
628             int i = js_get_selected_preset();
629             if (i == custom_preset) {
630                 /*
631                  * The user selected 'Custom', so launch the config
632                  * box.
633                  */
634                 if (thegame.can_configure) /* (double-check just in case) */
635                     cfg_start(CFG_SETTINGS);
636             } else {
637                 /*
638                  * The user selected a preset, so just switch straight
639                  * to that.
640                  */
641                 midend_set_params(me, presets[i]);
642                 midend_new_game(me);
643                 update_permalinks();
644                 resize();
645                 midend_redraw(me);
646                 update_undo_redo();
647                 js_focus_canvas();
648             }
649         }
650         break;
651       case 3:                          /* OK clicked in a config box */
652         cfg_end(TRUE);
653         update_undo_redo();
654         break;
655       case 4:                          /* Cancel clicked in a config box */
656         cfg_end(FALSE);
657         update_undo_redo();
658         break;
659       case 5:                          /* New Game */
660         midend_process_key(me, 0, 0, 'n');
661         update_undo_redo();
662         js_focus_canvas();
663         break;
664       case 6:                          /* Restart */
665         midend_restart_game(me);
666         update_undo_redo();
667         js_focus_canvas();
668         break;
669       case 7:                          /* Undo */
670         midend_process_key(me, 0, 0, 'u');
671         update_undo_redo();
672         js_focus_canvas();
673         break;
674       case 8:                          /* Redo */
675         midend_process_key(me, 0, 0, 'r');
676         update_undo_redo();
677         js_focus_canvas();
678         break;
679       case 9:                          /* Solve */
680         if (thegame.can_solve) {
681             char *msg = midend_solve(me);
682             if (msg)
683                 js_error_box(msg);
684         }
685         update_undo_redo();
686         js_focus_canvas();
687         break;
688     }
689 }
690
691 /* ----------------------------------------------------------------------
692  * Setup function called at page load time. It's called main() because
693  * that's the most convenient thing in Emscripten, but it's not main()
694  * in the usual sense of bounding the program's entire execution.
695  * Instead, this function returns once the initial puzzle is set up
696  * and working, and everything thereafter happens by means of JS event
697  * handlers sending us callbacks.
698  */
699 int main(int argc, char **argv)
700 {
701     char *param_err;
702     float *colours;
703     int i;
704
705     /*
706      * Instantiate a midend.
707      */
708     me = midend_new(NULL, &thegame, &js_drawing, NULL);
709
710     /*
711      * Chuck in the HTML fragment ID if we have one (trimming the
712      * leading # off the front first). If that's invalid, we retain
713      * the error message and will display it at the end, after setting
714      * up a random puzzle as usual.
715      */
716     if (argc > 1 && argv[1][0] == '#' && argv[1][1] != '\0')
717         param_err = midend_game_id(me, argv[1] + 1);
718     else
719         param_err = NULL;
720
721     /*
722      * Create either a random game or the specified one, and set the
723      * canvas size appropriately.
724      */
725     midend_new_game(me);
726     resize();
727
728     /*
729      * Create a status bar, if needed.
730      */
731     if (midend_wants_statusbar(me))
732         js_canvas_make_statusbar();
733
734     /*
735      * Set up the game-type dropdown with presets and/or the Custom
736      * option. We remember the index of the Custom option (as
737      * custom_preset) so that we can easily treat it specially when
738      * it's selected.
739      */
740     custom_preset = midend_num_presets(me);
741     presets = snewn(custom_preset, game_params *);
742     for (i = 0; i < custom_preset; i++) {
743         char *name;
744         midend_fetch_preset(me, i, &name, &presets[i]);
745         js_add_preset(name);
746     }
747     if (thegame.can_configure)
748         js_add_preset(NULL);           /* the 'Custom' entry in the dropdown */
749     else if (custom_preset == 0)
750         js_remove_type_dropdown();
751
752     /*
753      * Remove the Solve button if the game doesn't support it.
754      */
755     if (!thegame.can_solve)
756         js_remove_solve_button();
757
758     /*
759      * Retrieve the game's colours, and convert them into #abcdef type
760      * hex ID strings.
761      */
762     colours = midend_colours(me, &ncolours);
763     colour_strings = snewn(ncolours, char *);
764     for (i = 0; i < ncolours; i++) {
765         char col[40];
766         sprintf(col, "#%02x%02x%02x",
767                 (unsigned)(0.5 + 255 * colours[i*3+0]),
768                 (unsigned)(0.5 + 255 * colours[i*3+1]),
769                 (unsigned)(0.5 + 255 * colours[i*3+2]));
770         colour_strings[i] = dupstr(col);
771     }
772
773     /*
774      * Request notification if a puzzle (hopefully only ever Mines)
775      * supersedes its game description, so that we can proactively
776      * update the permalink.
777      */
778     midend_request_desc_changes(me, desc_changed, NULL);
779
780     /*
781      * Draw the puzzle's initial state, and set up the permalinks and
782      * undo/redo greying out.
783      */
784     midend_redraw(me);
785     update_permalinks();
786     update_undo_redo();
787
788     /*
789      * If we were given an erroneous game ID in argv[1], now's the
790      * time to put up the error box about it, after we've fully set up
791      * a random puzzle. Then when the user clicks 'ok', we have a
792      * puzzle for them.
793      */
794     if (param_err)
795         js_error_box(param_err);
796
797     /*
798      * Done. Return to JS, and await callbacks!
799      */
800     return 0;
801 }