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