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