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