~ubuntu-branches/ubuntu/saucy/sgt-puzzles/saucy-proposed

« back to all changes in this revision

Viewing changes to emcc.c

  • Committer: Package Import Robot
  • Author(s): Ben Hutchings
  • Date: 2013-06-30 03:20:16 UTC
  • mfrom: (1.2.13)
  • Revision ID: package-import@ubuntu.com-20130630032016-v8xqt6vtg6tgs420
Tags: 9872-1
* New upstream version
  - Add an explicit -lm to the link lines in Makefile.gtk (Closes: #713476)
  - Add Undead by Steffen Bauer, an implementation of 'Haunted Mirror Maze'
  - Add Unruly by Lennard Sprong, an implementation of a puzzle usually
    called 'Tohu wa Vohu'
* Add DEP-3 headers to patches
* pearl: Require width or height to be at least 6 for Tricky
  (Closes: #667963)
* debian/watch: Update ViewVC URL regex
* Add 'sgt-' prefix to all command names and remove 'game' suffix, but
  retain symlinks under the old names (see #684193)
* Use upstream short descriptions in English manual pages and package
  description
* Update German translation, thanks to Helge Kreutzmann

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
}