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