~ubuntu-branches/ubuntu/dapper/sgt-puzzles/dapper

« back to all changes in this revision

Viewing changes to twiddle.c

  • Committer: Bazaar Package Importer
  • Author(s): Ben Hutchings
  • Date: 2005-11-13 16:23:36 UTC
  • Revision ID: james.westby@ubuntu.com-20051113162336-yxo6hm2m7md4pi6h
Tags: upstream-6452
ImportĀ upstreamĀ versionĀ 6452

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * twiddle.c: Puzzle involving rearranging a grid of squares by
 
3
 * rotating subsquares. Adapted and generalised from a
 
4
 * door-unlocking puzzle in Metroid Prime 2 (the one in the Main
 
5
 * Gyro Chamber).
 
6
 */
 
7
 
 
8
#include <stdio.h>
 
9
#include <stdlib.h>
 
10
#include <string.h>
 
11
#include <assert.h>
 
12
#include <ctype.h>
 
13
#include <math.h>
 
14
 
 
15
#include "puzzles.h"
 
16
 
 
17
#define PREFERRED_TILE_SIZE 48
 
18
#define TILE_SIZE (ds->tilesize)
 
19
#define BORDER    (TILE_SIZE / 2)
 
20
#define HIGHLIGHT_WIDTH (TILE_SIZE / 20)
 
21
#define COORD(x)  ( (x) * TILE_SIZE + BORDER )
 
22
#define FROMCOORD(x)  ( ((x) - BORDER + TILE_SIZE) / TILE_SIZE - 1 )
 
23
 
 
24
#define ANIM_PER_RADIUS_UNIT 0.13F
 
25
#define FLASH_FRAME 0.13F
 
26
 
 
27
enum {
 
28
    COL_BACKGROUND,
 
29
    COL_TEXT,
 
30
    COL_HIGHLIGHT,
 
31
    COL_HIGHLIGHT_GENTLE,
 
32
    COL_LOWLIGHT,
 
33
    COL_LOWLIGHT_GENTLE,
 
34
    NCOLOURS
 
35
};
 
36
 
 
37
struct game_params {
 
38
    int w, h, n;
 
39
    int rowsonly;
 
40
    int orientable;
 
41
    int movetarget;
 
42
};
 
43
 
 
44
struct game_state {
 
45
    int w, h, n;
 
46
    int orientable;
 
47
    int *grid;
 
48
    int completed;
 
49
    int used_solve;                    /* used to suppress completion flash */
 
50
    int movecount, movetarget;
 
51
    int lastx, lasty, lastr;           /* coordinates of last rotation */
 
52
};
 
53
 
 
54
static game_params *default_params(void)
 
55
{
 
56
    game_params *ret = snew(game_params);
 
57
 
 
58
    ret->w = ret->h = 3;
 
59
    ret->n = 2;
 
60
    ret->rowsonly = ret->orientable = FALSE;
 
61
    ret->movetarget = 0;
 
62
 
 
63
    return ret;
 
64
}
 
65
 
 
66
 
 
67
static void free_params(game_params *params)
 
68
{
 
69
    sfree(params);
 
70
}
 
71
 
 
72
static game_params *dup_params(game_params *params)
 
73
{
 
74
    game_params *ret = snew(game_params);
 
75
    *ret = *params;                    /* structure copy */
 
76
    return ret;
 
77
}
 
78
 
 
79
static int game_fetch_preset(int i, char **name, game_params **params)
 
80
{
 
81
    static struct {
 
82
        char *title;
 
83
        game_params params;
 
84
    } presets[] = {
 
85
        { "3x3 rows only", { 3, 3, 2, TRUE, FALSE } },
 
86
        { "3x3 normal", { 3, 3, 2, FALSE, FALSE } },
 
87
        { "3x3 orientable", { 3, 3, 2, FALSE, TRUE } },
 
88
        { "4x4 normal", { 4, 4, 2, FALSE } },
 
89
        { "4x4 orientable", { 4, 4, 2, FALSE, TRUE } },
 
90
        { "4x4 radius 3", { 4, 4, 3, FALSE } },
 
91
        { "5x5 radius 3", { 5, 5, 3, FALSE } },
 
92
        { "6x6 radius 4", { 6, 6, 4, FALSE } },
 
93
    };
 
94
 
 
95
    if (i < 0 || i >= lenof(presets))
 
96
        return FALSE;
 
97
 
 
98
    *name = dupstr(presets[i].title);
 
99
    *params = dup_params(&presets[i].params);
 
100
 
 
101
    return TRUE;
 
102
}
 
103
 
 
104
static void decode_params(game_params *ret, char const *string)
 
105
{
 
106
    ret->w = ret->h = atoi(string);
 
107
    ret->n = 2;
 
108
    ret->rowsonly = ret->orientable = FALSE;
 
109
    ret->movetarget = 0;
 
110
    while (*string && isdigit((unsigned char)*string)) string++;
 
111
    if (*string == 'x') {
 
112
        string++;
 
113
        ret->h = atoi(string);
 
114
        while (*string && isdigit((unsigned char)*string)) string++;
 
115
    }
 
116
    if (*string == 'n') {
 
117
        string++;
 
118
        ret->n = atoi(string);
 
119
        while (*string && isdigit((unsigned char)*string)) string++;
 
120
    }
 
121
    while (*string) {
 
122
        if (*string == 'r') {
 
123
            ret->rowsonly = TRUE;
 
124
        } else if (*string == 'o') {
 
125
            ret->orientable = TRUE;
 
126
        } else if (*string == 'm') {
 
127
            string++;
 
128
            ret->movetarget = atoi(string);
 
129
            while (string[1] && isdigit((unsigned char)string[1])) string++;
 
130
        }
 
131
        string++;
 
132
    }
 
133
}
 
134
 
 
135
static char *encode_params(game_params *params, int full)
 
136
{
 
137
    char buf[256];
 
138
    sprintf(buf, "%dx%dn%d%s%s", params->w, params->h, params->n,
 
139
            params->rowsonly ? "r" : "",
 
140
            params->orientable ? "o" : "");
 
141
    /* Shuffle limit is part of the limited parameters, because we have to
 
142
     * supply the target move count. */
 
143
    if (params->movetarget)
 
144
        sprintf(buf + strlen(buf), "m%d", params->movetarget);
 
145
    return dupstr(buf);
 
146
}
 
147
 
 
148
static config_item *game_configure(game_params *params)
 
149
{
 
150
    config_item *ret;
 
151
    char buf[80];
 
152
 
 
153
    ret = snewn(7, config_item);
 
154
 
 
155
    ret[0].name = "Width";
 
156
    ret[0].type = C_STRING;
 
157
    sprintf(buf, "%d", params->w);
 
158
    ret[0].sval = dupstr(buf);
 
159
    ret[0].ival = 0;
 
160
 
 
161
    ret[1].name = "Height";
 
162
    ret[1].type = C_STRING;
 
163
    sprintf(buf, "%d", params->h);
 
164
    ret[1].sval = dupstr(buf);
 
165
    ret[1].ival = 0;
 
166
 
 
167
    ret[2].name = "Rotation radius";
 
168
    ret[2].type = C_STRING;
 
169
    sprintf(buf, "%d", params->n);
 
170
    ret[2].sval = dupstr(buf);
 
171
    ret[2].ival = 0;
 
172
 
 
173
    ret[3].name = "One number per row";
 
174
    ret[3].type = C_BOOLEAN;
 
175
    ret[3].sval = NULL;
 
176
    ret[3].ival = params->rowsonly;
 
177
 
 
178
    ret[4].name = "Orientation matters";
 
179
    ret[4].type = C_BOOLEAN;
 
180
    ret[4].sval = NULL;
 
181
    ret[4].ival = params->orientable;
 
182
 
 
183
    ret[5].name = "Number of shuffling moves";
 
184
    ret[5].type = C_STRING;
 
185
    sprintf(buf, "%d", params->movetarget);
 
186
    ret[5].sval = dupstr(buf);
 
187
    ret[5].ival = 0;
 
188
 
 
189
    ret[6].name = NULL;
 
190
    ret[6].type = C_END;
 
191
    ret[6].sval = NULL;
 
192
    ret[6].ival = 0;
 
193
 
 
194
    return ret;
 
195
}
 
196
 
 
197
static game_params *custom_params(config_item *cfg)
 
198
{
 
199
    game_params *ret = snew(game_params);
 
200
 
 
201
    ret->w = atoi(cfg[0].sval);
 
202
    ret->h = atoi(cfg[1].sval);
 
203
    ret->n = atoi(cfg[2].sval);
 
204
    ret->rowsonly = cfg[3].ival;
 
205
    ret->orientable = cfg[4].ival;
 
206
    ret->movetarget = atoi(cfg[5].sval);
 
207
 
 
208
    return ret;
 
209
}
 
210
 
 
211
static char *validate_params(game_params *params, int full)
 
212
{
 
213
    if (params->n < 2)
 
214
        return "Rotation radius must be at least two";
 
215
    if (params->w < params->n)
 
216
        return "Width must be at least the rotation radius";
 
217
    if (params->h < params->n)
 
218
        return "Height must be at least the rotation radius";
 
219
    return NULL;
 
220
}
 
221
 
 
222
/*
 
223
 * This function actually performs a rotation on a grid. The `x'
 
224
 * and `y' coordinates passed in are the coordinates of the _top
 
225
 * left corner_ of the rotated region. (Using the centre would have
 
226
 * involved half-integers and been annoyingly fiddly. Clicking in
 
227
 * the centre is good for a user interface, but too inconvenient to
 
228
 * use internally.)
 
229
 */
 
230
static void do_rotate(int *grid, int w, int h, int n, int orientable,
 
231
                      int x, int y, int dir)
 
232
{
 
233
    int i, j;
 
234
 
 
235
    assert(x >= 0 && x+n <= w);
 
236
    assert(y >= 0 && y+n <= h);
 
237
    dir &= 3;
 
238
    if (dir == 0)
 
239
        return;                        /* nothing to do */
 
240
 
 
241
    grid += y*w+x;                     /* translate region to top corner */
 
242
 
 
243
    /*
 
244
     * If we were leaving the result of the rotation in a separate
 
245
     * grid, the simple thing to do would be to loop over each
 
246
     * square within the rotated region and assign it from its
 
247
     * source square. However, to do it in place without taking
 
248
     * O(n^2) memory, we need to be marginally more clever. What
 
249
     * I'm going to do is loop over about one _quarter_ of the
 
250
     * rotated region and permute each element within that quarter
 
251
     * with its rotational coset.
 
252
     * 
 
253
     * The size of the region I need to loop over is (n+1)/2 by
 
254
     * n/2, which is an obvious exact quarter for even n and is a
 
255
     * rectangle for odd n. (For odd n, this technique leaves out
 
256
     * one element of the square, which is of course the central
 
257
     * one that never moves anyway.)
 
258
     */
 
259
    for (i = 0; i < (n+1)/2; i++) {
 
260
        for (j = 0; j < n/2; j++) {
 
261
            int k;
 
262
            int g[4];
 
263
            int p[4];
 
264
            
 
265
            p[0] = j*w+i;
 
266
            p[1] = i*w+(n-j-1);
 
267
            p[2] = (n-j-1)*w+(n-i-1);
 
268
            p[3] = (n-i-1)*w+j;
 
269
 
 
270
            for (k = 0; k < 4; k++)
 
271
                g[k] = grid[p[k]];
 
272
 
 
273
            for (k = 0; k < 4; k++) {
 
274
                int v = g[(k+dir) & 3];
 
275
                if (orientable)
 
276
                    v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
 
277
                grid[p[k]] = v;
 
278
            }
 
279
        }
 
280
    }
 
281
 
 
282
    /*
 
283
     * Don't forget the orientation on the centre square, if n is
 
284
     * odd.
 
285
     */
 
286
    if (orientable && (n & 1)) {
 
287
        int v = grid[n/2*(w+1)];
 
288
        v ^= ((v+dir) ^ v) & 3;  /* alter orientation */
 
289
        grid[n/2*(w+1)] = v;
 
290
    }
 
291
}
 
292
 
 
293
static int grid_complete(int *grid, int wh, int orientable)
 
294
{
 
295
    int ok = TRUE;
 
296
    int i;
 
297
    for (i = 1; i < wh; i++)
 
298
        if (grid[i] < grid[i-1])
 
299
            ok = FALSE;
 
300
    if (orientable) {
 
301
        for (i = 0; i < wh; i++)
 
302
            if (grid[i] & 3)
 
303
                ok = FALSE;
 
304
    }
 
305
    return ok;
 
306
}
 
307
 
 
308
static char *new_game_desc(game_params *params, random_state *rs,
 
309
                           char **aux, int interactive)
 
310
{
 
311
    int *grid;
 
312
    int w = params->w, h = params->h, n = params->n, wh = w*h;
 
313
    int i;
 
314
    char *ret;
 
315
    int retlen;
 
316
    int total_moves;
 
317
 
 
318
    /*
 
319
     * Set up a solved grid.
 
320
     */
 
321
    grid = snewn(wh, int);
 
322
    for (i = 0; i < wh; i++)
 
323
        grid[i] = ((params->rowsonly ? i/w : i) + 1) * 4;
 
324
 
 
325
    /*
 
326
     * Shuffle it. This game is complex enough that I don't feel up
 
327
     * to analysing its full symmetry properties (particularly at
 
328
     * n=4 and above!), so I'm going to do it the pedestrian way
 
329
     * and simply shuffle the grid by making a long sequence of
 
330
     * randomly chosen moves.
 
331
     */
 
332
    total_moves = params->movetarget;
 
333
    if (!total_moves)
 
334
        /* Add a random move to avoid parity issues. */
 
335
        total_moves = w*h*n*n*2 + random_upto(rs, 2);
 
336
 
 
337
    do {
 
338
        int *prevmoves;
 
339
        int rw, rh;                    /* w/h of rotation centre space */
 
340
 
 
341
        rw = w - n + 1;
 
342
        rh = h - n + 1;
 
343
        prevmoves = snewn(rw * rh, int);
 
344
        for (i = 0; i < rw * rh; i++)
 
345
            prevmoves[i] = 0;
 
346
 
 
347
        for (i = 0; i < total_moves; i++) {
 
348
            int x, y, r, oldtotal, newtotal, dx, dy;
 
349
 
 
350
            do {
 
351
                x = random_upto(rs, w - n + 1);
 
352
                y = random_upto(rs, h - n + 1);
 
353
                r = 2 * random_upto(rs, 2) - 1;
 
354
 
 
355
                /*
 
356
                 * See if any previous rotations has happened at
 
357
                 * this point which nothing has overlapped since.
 
358
                 * If so, ensure we haven't either undone a
 
359
                 * previous move or repeated one so many times that
 
360
                 * it turns into fewer moves in the inverse
 
361
                 * direction (i.e. three identical rotations).
 
362
                 */
 
363
                oldtotal = prevmoves[y*rw+x];
 
364
                newtotal = oldtotal + r;
 
365
                
 
366
                /*
 
367
                 * Special case here for w==h==n, in which case
 
368
                 * there is actually no way to _avoid_ all moves
 
369
                 * repeating or undoing previous ones.
 
370
                 */
 
371
            } while ((w != n || h != n) &&
 
372
                     (abs(newtotal) < abs(oldtotal) || abs(newtotal) > 2));
 
373
 
 
374
            do_rotate(grid, w, h, n, params->orientable, x, y, r);
 
375
 
 
376
            /*
 
377
             * Log the rotation we've just performed at this point,
 
378
             * for inversion detection in the next move.
 
379
             * 
 
380
             * Also zero a section of the prevmoves array, because
 
381
             * any rotation area which _overlaps_ this one is now
 
382
             * entirely safe to perform further moves in.
 
383
             * 
 
384
             * Two rotation areas overlap if their top left
 
385
             * coordinates differ by strictly less than n in both
 
386
             * directions
 
387
             */
 
388
            prevmoves[y*rw+x] += r;
 
389
            for (dy = -n+1; dy <= n-1; dy++) {
 
390
                if (y + dy < 0 || y + dy >= rh)
 
391
                    continue;
 
392
                for (dx = -n+1; dx <= n-1; dx++) {
 
393
                    if (x + dx < 0 || x + dx >= rw)
 
394
                        continue;
 
395
                    if (dx == 0 && dy == 0)
 
396
                        continue;
 
397
                    prevmoves[(y+dy)*rw+(x+dx)] = 0;
 
398
                }
 
399
            }
 
400
        }
 
401
 
 
402
        sfree(prevmoves);
 
403
 
 
404
    } while (grid_complete(grid, wh, params->orientable));
 
405
 
 
406
    /*
 
407
     * Now construct the game description, by describing the grid
 
408
     * as a simple sequence of integers. They're comma-separated,
 
409
     * unless the puzzle is orientable in which case they're
 
410
     * separated by orientation letters `u', `d', `l' and `r'.
 
411
     */
 
412
    ret = NULL;
 
413
    retlen = 0;
 
414
    for (i = 0; i < wh; i++) {
 
415
        char buf[80];
 
416
        int k;
 
417
 
 
418
        k = sprintf(buf, "%d%c", grid[i] / 4,
 
419
                    (char)(params->orientable ? "uldr"[grid[i] & 3] : ','));
 
420
 
 
421
        ret = sresize(ret, retlen + k + 1, char);
 
422
        strcpy(ret + retlen, buf);
 
423
        retlen += k;
 
424
    }
 
425
    if (!params->orientable)
 
426
        ret[retlen-1] = '\0';          /* delete last comma */
 
427
 
 
428
    sfree(grid);
 
429
    return ret;
 
430
}
 
431
 
 
432
static char *validate_desc(game_params *params, char *desc)
 
433
{
 
434
    char *p, *err;
 
435
    int w = params->w, h = params->h, wh = w*h;
 
436
    int i;
 
437
 
 
438
    p = desc;
 
439
    err = NULL;
 
440
 
 
441
    for (i = 0; i < wh; i++) {
 
442
        if (*p < '0' || *p > '9')
 
443
            return "Not enough numbers in string";
 
444
        while (*p >= '0' && *p <= '9')
 
445
            p++;
 
446
        if (!params->orientable && i < wh-1) {
 
447
            if (*p != ',')
 
448
                return "Expected comma after number";
 
449
        } else if (params->orientable && i < wh) {
 
450
            if (*p != 'l' && *p != 'r' && *p != 'u' && *p != 'd')
 
451
                return "Expected orientation letter after number";
 
452
        } else if (i == wh-1 && *p) {
 
453
            return "Excess junk at end of string";
 
454
        }
 
455
 
 
456
        if (*p) p++;                   /* eat comma */
 
457
    }
 
458
 
 
459
    return NULL;
 
460
}
 
461
 
 
462
static game_state *new_game(midend *me, game_params *params, char *desc)
 
463
{
 
464
    game_state *state = snew(game_state);
 
465
    int w = params->w, h = params->h, n = params->n, wh = w*h;
 
466
    int i;
 
467
    char *p;
 
468
 
 
469
    state->w = w;
 
470
    state->h = h;
 
471
    state->n = n;
 
472
    state->orientable = params->orientable;
 
473
    state->completed = 0;
 
474
    state->used_solve = FALSE;
 
475
    state->movecount = 0;
 
476
    state->movetarget = params->movetarget;
 
477
    state->lastx = state->lasty = state->lastr = -1;
 
478
 
 
479
    state->grid = snewn(wh, int);
 
480
 
 
481
    p = desc;
 
482
 
 
483
    for (i = 0; i < wh; i++) {
 
484
        state->grid[i] = 4 * atoi(p);
 
485
        while (*p >= '0' && *p <= '9')
 
486
            p++;
 
487
        if (*p) {
 
488
            if (params->orientable) {
 
489
                switch (*p) {
 
490
                  case 'l': state->grid[i] |= 1; break;
 
491
                  case 'd': state->grid[i] |= 2; break;
 
492
                  case 'r': state->grid[i] |= 3; break;
 
493
                }
 
494
            }
 
495
            p++;
 
496
        }
 
497
    }
 
498
 
 
499
    return state;
 
500
}
 
501
 
 
502
static game_state *dup_game(game_state *state)
 
503
{
 
504
    game_state *ret = snew(game_state);
 
505
 
 
506
    ret->w = state->w;
 
507
    ret->h = state->h;
 
508
    ret->n = state->n;
 
509
    ret->orientable = state->orientable;
 
510
    ret->completed = state->completed;
 
511
    ret->movecount = state->movecount;
 
512
    ret->movetarget = state->movetarget;
 
513
    ret->lastx = state->lastx;
 
514
    ret->lasty = state->lasty;
 
515
    ret->lastr = state->lastr;
 
516
    ret->used_solve = state->used_solve;
 
517
 
 
518
    ret->grid = snewn(ret->w * ret->h, int);
 
519
    memcpy(ret->grid, state->grid, ret->w * ret->h * sizeof(int));
 
520
 
 
521
    return ret;
 
522
}
 
523
 
 
524
static void free_game(game_state *state)
 
525
{
 
526
    sfree(state->grid);
 
527
    sfree(state);
 
528
}
 
529
 
 
530
static int compare_int(const void *av, const void *bv)
 
531
{
 
532
    const int *a = (const int *)av;
 
533
    const int *b = (const int *)bv;
 
534
    if (*a < *b)
 
535
        return -1;
 
536
    else if (*a > *b)
 
537
        return +1;
 
538
    else
 
539
        return 0;
 
540
}
 
541
 
 
542
static char *solve_game(game_state *state, game_state *currstate,
 
543
                        char *aux, char **error)
 
544
{
 
545
    return dupstr("S");
 
546
}
 
547
 
 
548
static char *game_text_format(game_state *state)
 
549
{
 
550
    char *ret, *p, buf[80];
 
551
    int i, x, y, col, o, maxlen;
 
552
 
 
553
    /*
 
554
     * First work out how many characters we need to display each
 
555
     * number. We're pretty flexible on grid contents here, so we
 
556
     * have to scan the entire grid.
 
557
     */
 
558
    col = 0;
 
559
    for (i = 0; i < state->w * state->h; i++) {
 
560
        x = sprintf(buf, "%d", state->grid[i] / 4);
 
561
        if (col < x) col = x;
 
562
    }
 
563
    o = (state->orientable ? 1 : 0);
 
564
 
 
565
    /*
 
566
     * Now we know the exact total size of the grid we're going to
 
567
     * produce: it's got h rows, each containing w lots of col+o,
 
568
     * w-1 spaces and a trailing newline.
 
569
     */
 
570
    maxlen = state->h * state->w * (col+o+1);
 
571
 
 
572
    ret = snewn(maxlen+1, char);
 
573
    p = ret;
 
574
 
 
575
    for (y = 0; y < state->h; y++) {
 
576
        for (x = 0; x < state->w; x++) {
 
577
            int v = state->grid[state->w*y+x];
 
578
            sprintf(buf, "%*d", col, v/4);
 
579
            memcpy(p, buf, col);
 
580
            p += col;
 
581
            if (o)
 
582
                *p++ = "^<v>"[v & 3];
 
583
            if (x+1 == state->w)
 
584
                *p++ = '\n';
 
585
            else
 
586
                *p++ = ' ';
 
587
        }
 
588
    }
 
589
 
 
590
    assert(p - ret == maxlen);
 
591
    *p = '\0';
 
592
    return ret;
 
593
}
 
594
 
 
595
static game_ui *new_ui(game_state *state)
 
596
{
 
597
    return NULL;
 
598
}
 
599
 
 
600
static void free_ui(game_ui *ui)
 
601
{
 
602
}
 
603
 
 
604
static char *encode_ui(game_ui *ui)
 
605
{
 
606
    return NULL;
 
607
}
 
608
 
 
609
static void decode_ui(game_ui *ui, char *encoding)
 
610
{
 
611
}
 
612
 
 
613
static void game_changed_state(game_ui *ui, game_state *oldstate,
 
614
                               game_state *newstate)
 
615
{
 
616
}
 
617
 
 
618
struct game_drawstate {
 
619
    int started;
 
620
    int w, h, bgcolour;
 
621
    int *grid;
 
622
    int tilesize;
 
623
};
 
624
 
 
625
static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
 
626
                            int x, int y, int button)
 
627
{
 
628
    int w = state->w, h = state->h, n = state->n /* , wh = w*h */;
 
629
    char buf[80];
 
630
    int dir;
 
631
 
 
632
    button = button & (~MOD_MASK | MOD_NUM_KEYPAD);
 
633
 
 
634
    if (button == LEFT_BUTTON || button == RIGHT_BUTTON) {
 
635
        /*
 
636
         * Determine the coordinates of the click. We offset by n-1
 
637
         * half-blocks so that the user must click at the centre of
 
638
         * a rotation region rather than at the corner.
 
639
         */
 
640
        x -= (n-1) * TILE_SIZE / 2;
 
641
        y -= (n-1) * TILE_SIZE / 2;
 
642
        x = FROMCOORD(x);
 
643
        y = FROMCOORD(y);
 
644
        dir = (button == LEFT_BUTTON ? 1 : -1);
 
645
        if (x < 0 || x > w-n || y < 0 || y > h-n)
 
646
            return NULL;
 
647
    } else if (button == 'a' || button == 'A' || button==MOD_NUM_KEYPAD+'7') {
 
648
        x = y = 0;
 
649
        dir = (button == 'A' ? -1 : +1);
 
650
    } else if (button == 'b' || button == 'B' || button==MOD_NUM_KEYPAD+'9') {
 
651
        x = w-n;
 
652
        y = 0;
 
653
        dir = (button == 'B' ? -1 : +1);
 
654
    } else if (button == 'c' || button == 'C' || button==MOD_NUM_KEYPAD+'1') {
 
655
        x = 0;
 
656
        y = h-n;
 
657
        dir = (button == 'C' ? -1 : +1);
 
658
    } else if (button == 'd' || button == 'D' || button==MOD_NUM_KEYPAD+'3') {
 
659
        x = w-n;
 
660
        y = h-n;
 
661
        dir = (button == 'D' ? -1 : +1);
 
662
    } else if (button==MOD_NUM_KEYPAD+'8' && (w-n) % 2 == 0) {
 
663
        x = (w-n) / 2;
 
664
        y = 0;
 
665
        dir = +1;
 
666
    } else if (button==MOD_NUM_KEYPAD+'2' && (w-n) % 2 == 0) {
 
667
        x = (w-n) / 2;
 
668
        y = h-n;
 
669
        dir = +1;
 
670
    } else if (button==MOD_NUM_KEYPAD+'4' && (h-n) % 2 == 0) {
 
671
        x = 0;
 
672
        y = (h-n) / 2;
 
673
        dir = +1;
 
674
    } else if (button==MOD_NUM_KEYPAD+'6' && (h-n) % 2 == 0) {
 
675
        x = w-n;
 
676
        y = (h-n) / 2;
 
677
        dir = +1;
 
678
    } else if (button==MOD_NUM_KEYPAD+'5' && (w-n) % 2 == 0 && (h-n) % 2 == 0){
 
679
        x = (w-n) / 2;
 
680
        y = (h-n) / 2;
 
681
        dir = +1;
 
682
    } else {
 
683
        return NULL;                   /* no move to be made */
 
684
    }
 
685
 
 
686
    /*
 
687
     * If we reach here, we have a valid move.
 
688
     */
 
689
    sprintf(buf, "M%d,%d,%d", x, y, dir);
 
690
    return dupstr(buf);
 
691
}
 
692
 
 
693
static game_state *execute_move(game_state *from, char *move)
 
694
{
 
695
    game_state *ret;
 
696
    int w = from->w, h = from->h, n = from->n, wh = w*h;
 
697
    int x, y, dir;
 
698
 
 
699
    if (!strcmp(move, "S")) {
 
700
        int i;
 
701
        ret = dup_game(from);
 
702
 
 
703
        /*
 
704
         * Simply replace the grid with a solved one. For this game,
 
705
         * this isn't a useful operation for actually telling the user
 
706
         * what they should have done, but it is useful for
 
707
         * conveniently being able to get hold of a clean state from
 
708
         * which to practise manoeuvres.
 
709
         */
 
710
        qsort(ret->grid, ret->w*ret->h, sizeof(int), compare_int);
 
711
        for (i = 0; i < ret->w*ret->h; i++)
 
712
            ret->grid[i] &= ~3;
 
713
        ret->used_solve = TRUE;
 
714
        ret->completed = ret->movecount = 1;
 
715
 
 
716
        return ret;
 
717
    }
 
718
 
 
719
    if (move[0] != 'M' ||
 
720
        sscanf(move+1, "%d,%d,%d", &x, &y, &dir) != 3 ||
 
721
        x < 0 || y < 0 || x > from->w - n || y > from->h - n)
 
722
        return NULL;                   /* can't parse this move string */
 
723
 
 
724
    ret = dup_game(from);
 
725
    ret->movecount++;
 
726
    do_rotate(ret->grid, w, h, n, ret->orientable, x, y, dir);
 
727
    ret->lastx = x;
 
728
    ret->lasty = y;
 
729
    ret->lastr = dir;
 
730
 
 
731
    /*
 
732
     * See if the game has been completed. To do this we simply
 
733
     * test that the grid contents are in increasing order.
 
734
     */
 
735
    if (!ret->completed && grid_complete(ret->grid, wh, ret->orientable))
 
736
        ret->completed = ret->movecount;
 
737
    return ret;
 
738
}
 
739
 
 
740
/* ----------------------------------------------------------------------
 
741
 * Drawing routines.
 
742
 */
 
743
 
 
744
static void game_compute_size(game_params *params, int tilesize,
 
745
                              int *x, int *y)
 
746
{
 
747
    /* Ick: fake up `ds->tilesize' for macro expansion purposes */
 
748
    struct { int tilesize; } ads, *ds = &ads;
 
749
    ads.tilesize = tilesize;
 
750
 
 
751
    *x = TILE_SIZE * params->w + 2 * BORDER;
 
752
    *y = TILE_SIZE * params->h + 2 * BORDER;
 
753
}
 
754
 
 
755
static void game_set_size(drawing *dr, game_drawstate *ds,
 
756
                          game_params *params, int tilesize)
 
757
{
 
758
    ds->tilesize = tilesize;
 
759
}
 
760
 
 
761
static float *game_colours(frontend *fe, int *ncolours)
 
762
{
 
763
    float *ret = snewn(3 * NCOLOURS, float);
 
764
    int i;
 
765
 
 
766
    game_mkhighlight(fe, ret, COL_BACKGROUND, COL_HIGHLIGHT, COL_LOWLIGHT);
 
767
 
 
768
    for (i = 0; i < 3; i++) {
 
769
        ret[COL_HIGHLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 1.1F;
 
770
        ret[COL_LOWLIGHT_GENTLE * 3 + i] = ret[COL_BACKGROUND * 3 + i] * 0.9F;
 
771
        ret[COL_TEXT * 3 + i] = 0.0;
 
772
    }
 
773
 
 
774
    *ncolours = NCOLOURS;
 
775
    return ret;
 
776
}
 
777
 
 
778
static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
 
779
{
 
780
    struct game_drawstate *ds = snew(struct game_drawstate);
 
781
    int i;
 
782
 
 
783
    ds->started = FALSE;
 
784
    ds->w = state->w;
 
785
    ds->h = state->h;
 
786
    ds->bgcolour = COL_BACKGROUND;
 
787
    ds->grid = snewn(ds->w*ds->h, int);
 
788
    ds->tilesize = 0;                  /* haven't decided yet */
 
789
    for (i = 0; i < ds->w*ds->h; i++)
 
790
        ds->grid[i] = -1;
 
791
 
 
792
    return ds;
 
793
}
 
794
 
 
795
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
 
796
{
 
797
    sfree(ds->grid);
 
798
    sfree(ds);
 
799
}
 
800
 
 
801
struct rotation {
 
802
    int cx, cy, cw, ch;                /* clip region */
 
803
    int ox, oy;                        /* rotation origin */
 
804
    float c, s;                        /* cos and sin of rotation angle */
 
805
    int lc, rc, tc, bc;                /* colours of tile edges */
 
806
};
 
807
 
 
808
static void rotate(int *xy, struct rotation *rot)
 
809
{
 
810
    if (rot) {
 
811
        float xf = xy[0] - rot->ox, yf = xy[1] - rot->oy;
 
812
        float xf2, yf2;
 
813
 
 
814
        xf2 = rot->c * xf + rot->s * yf;
 
815
        yf2 = - rot->s * xf + rot->c * yf;
 
816
 
 
817
        xy[0] = xf2 + rot->ox + 0.5;   /* round to nearest */
 
818
        xy[1] = yf2 + rot->oy + 0.5;   /* round to nearest */
 
819
    }
 
820
}
 
821
 
 
822
static void draw_tile(drawing *dr, game_drawstate *ds, game_state *state,
 
823
                      int x, int y, int tile, int flash_colour,
 
824
                      struct rotation *rot)
 
825
{
 
826
    int coords[8];
 
827
    char str[40];
 
828
 
 
829
    /*
 
830
     * If we've been passed a rotation region but we're drawing a
 
831
     * tile which is outside it, we must draw it normally. This can
 
832
     * occur if we're cleaning up after a completion flash while a
 
833
     * new move is also being made.
 
834
     */
 
835
    if (rot && (x < rot->cx || y < rot->cy ||
 
836
                x >= rot->cx+rot->cw || y >= rot->cy+rot->ch))
 
837
        rot = NULL;
 
838
 
 
839
    if (rot)
 
840
        clip(dr, rot->cx, rot->cy, rot->cw, rot->ch);
 
841
 
 
842
    /*
 
843
     * We must draw each side of the tile's highlight separately,
 
844
     * because in some cases (during rotation) they will all need
 
845
     * to be different colours.
 
846
     */
 
847
 
 
848
    /* The centre point is common to all sides. */
 
849
    coords[4] = x + TILE_SIZE / 2;
 
850
    coords[5] = y + TILE_SIZE / 2;
 
851
    rotate(coords+4, rot);
 
852
 
 
853
    /* Right side. */
 
854
    coords[0] = x + TILE_SIZE - 1;
 
855
    coords[1] = y + TILE_SIZE - 1;
 
856
    rotate(coords+0, rot);
 
857
    coords[2] = x + TILE_SIZE - 1;
 
858
    coords[3] = y;
 
859
    rotate(coords+2, rot);
 
860
    draw_polygon(dr, coords, 3, rot ? rot->rc : COL_LOWLIGHT,
 
861
                 rot ? rot->rc : COL_LOWLIGHT);
 
862
 
 
863
    /* Bottom side. */
 
864
    coords[2] = x;
 
865
    coords[3] = y + TILE_SIZE - 1;
 
866
    rotate(coords+2, rot);
 
867
    draw_polygon(dr, coords, 3, rot ? rot->bc : COL_LOWLIGHT,
 
868
                 rot ? rot->bc : COL_LOWLIGHT);
 
869
 
 
870
    /* Left side. */
 
871
    coords[0] = x;
 
872
    coords[1] = y;
 
873
    rotate(coords+0, rot);
 
874
    draw_polygon(dr, coords, 3, rot ? rot->lc : COL_HIGHLIGHT,
 
875
                 rot ? rot->lc : COL_HIGHLIGHT);
 
876
 
 
877
    /* Top side. */
 
878
    coords[2] = x + TILE_SIZE - 1;
 
879
    coords[3] = y;
 
880
    rotate(coords+2, rot);
 
881
    draw_polygon(dr, coords, 3, rot ? rot->tc : COL_HIGHLIGHT,
 
882
                 rot ? rot->tc : COL_HIGHLIGHT);
 
883
 
 
884
    /*
 
885
     * Now the main blank area in the centre of the tile.
 
886
     */
 
887
    if (rot) {
 
888
        coords[0] = x + HIGHLIGHT_WIDTH;
 
889
        coords[1] = y + HIGHLIGHT_WIDTH;
 
890
        rotate(coords+0, rot);
 
891
        coords[2] = x + HIGHLIGHT_WIDTH;
 
892
        coords[3] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
 
893
        rotate(coords+2, rot);
 
894
        coords[4] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
 
895
        coords[5] = y + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
 
896
        rotate(coords+4, rot);
 
897
        coords[6] = x + TILE_SIZE - 1 - HIGHLIGHT_WIDTH;
 
898
        coords[7] = y + HIGHLIGHT_WIDTH;
 
899
        rotate(coords+6, rot);
 
900
        draw_polygon(dr, coords, 4, flash_colour, flash_colour);
 
901
    } else {
 
902
        draw_rect(dr, x + HIGHLIGHT_WIDTH, y + HIGHLIGHT_WIDTH,
 
903
                  TILE_SIZE - 2*HIGHLIGHT_WIDTH, TILE_SIZE - 2*HIGHLIGHT_WIDTH,
 
904
                  flash_colour);
 
905
    }
 
906
 
 
907
    /*
 
908
     * Next, the triangles for orientation.
 
909
     */
 
910
    if (state->orientable) {
 
911
        int xdx, xdy, ydx, ydy;
 
912
        int cx, cy, displ, displ2;
 
913
        switch (tile & 3) {
 
914
          case 0:
 
915
            xdx = 1, xdy = 0;
 
916
            ydx = 0, ydy = 1;
 
917
            break;
 
918
          case 1:
 
919
            xdx = 0, xdy = -1;
 
920
            ydx = 1, ydy = 0;
 
921
            break;
 
922
          case 2:
 
923
            xdx = -1, xdy = 0;
 
924
            ydx = 0, ydy = -1;
 
925
            break;
 
926
          default /* case 3 */:
 
927
            xdx = 0, xdy = 1;
 
928
            ydx = -1, ydy = 0;
 
929
            break;
 
930
        }
 
931
 
 
932
        cx = x + TILE_SIZE / 2;
 
933
        cy = y + TILE_SIZE / 2;
 
934
        displ = TILE_SIZE / 2 - HIGHLIGHT_WIDTH - 2;
 
935
        displ2 = TILE_SIZE / 3 - HIGHLIGHT_WIDTH;
 
936
 
 
937
        coords[0] = cx - displ * xdx + displ2 * ydx;
 
938
        coords[1] = cy - displ * xdy + displ2 * ydy;
 
939
        rotate(coords+0, rot);
 
940
        coords[2] = cx + displ * xdx + displ2 * ydx;
 
941
        coords[3] = cy + displ * xdy + displ2 * ydy;
 
942
        rotate(coords+2, rot);
 
943
        coords[4] = cx - displ * ydx;
 
944
        coords[5] = cy - displ * ydy;
 
945
        rotate(coords+4, rot);
 
946
        draw_polygon(dr, coords, 3, COL_LOWLIGHT_GENTLE, COL_LOWLIGHT_GENTLE);
 
947
    }
 
948
 
 
949
    coords[0] = x + TILE_SIZE/2;
 
950
    coords[1] = y + TILE_SIZE/2;
 
951
    rotate(coords+0, rot);
 
952
    sprintf(str, "%d", tile / 4);
 
953
    draw_text(dr, coords[0], coords[1],
 
954
              FONT_VARIABLE, TILE_SIZE/3, ALIGN_VCENTRE | ALIGN_HCENTRE,
 
955
              COL_TEXT, str);
 
956
 
 
957
    if (rot)
 
958
        unclip(dr);
 
959
 
 
960
    draw_update(dr, x, y, TILE_SIZE, TILE_SIZE);
 
961
}
 
962
 
 
963
static int highlight_colour(float angle)
 
964
{
 
965
    int colours[32] = {
 
966
        COL_LOWLIGHT,
 
967
        COL_LOWLIGHT_GENTLE,
 
968
        COL_LOWLIGHT_GENTLE,
 
969
        COL_LOWLIGHT_GENTLE,
 
970
        COL_HIGHLIGHT_GENTLE,
 
971
        COL_HIGHLIGHT_GENTLE,
 
972
        COL_HIGHLIGHT_GENTLE,
 
973
        COL_HIGHLIGHT,
 
974
        COL_HIGHLIGHT,
 
975
        COL_HIGHLIGHT,
 
976
        COL_HIGHLIGHT,
 
977
        COL_HIGHLIGHT,
 
978
        COL_HIGHLIGHT,
 
979
        COL_HIGHLIGHT,
 
980
        COL_HIGHLIGHT,
 
981
        COL_HIGHLIGHT,
 
982
        COL_HIGHLIGHT,
 
983
        COL_HIGHLIGHT_GENTLE,
 
984
        COL_HIGHLIGHT_GENTLE,
 
985
        COL_HIGHLIGHT_GENTLE,
 
986
        COL_LOWLIGHT_GENTLE,
 
987
        COL_LOWLIGHT_GENTLE,
 
988
        COL_LOWLIGHT_GENTLE,
 
989
        COL_LOWLIGHT,
 
990
        COL_LOWLIGHT,
 
991
        COL_LOWLIGHT,
 
992
        COL_LOWLIGHT,
 
993
        COL_LOWLIGHT,
 
994
        COL_LOWLIGHT,
 
995
        COL_LOWLIGHT,
 
996
        COL_LOWLIGHT,
 
997
        COL_LOWLIGHT,
 
998
    };
 
999
 
 
1000
    return colours[(int)((angle + 2*PI) / (PI/16)) & 31];
 
1001
}
 
1002
 
 
1003
static float game_anim_length(game_state *oldstate, game_state *newstate,
 
1004
                              int dir, game_ui *ui)
 
1005
{
 
1006
    return ANIM_PER_RADIUS_UNIT * sqrt(newstate->n-1);
 
1007
}
 
1008
 
 
1009
static float game_flash_length(game_state *oldstate, game_state *newstate,
 
1010
                               int dir, game_ui *ui)
 
1011
{
 
1012
    if (!oldstate->completed && newstate->completed &&
 
1013
        !oldstate->used_solve && !newstate->used_solve)
 
1014
        return 2 * FLASH_FRAME;
 
1015
    else
 
1016
        return 0.0F;
 
1017
}
 
1018
 
 
1019
static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
 
1020
                        game_state *state, int dir, game_ui *ui,
 
1021
                        float animtime, float flashtime)
 
1022
{
 
1023
    int i, bgcolour;
 
1024
    struct rotation srot, *rot;
 
1025
    int lastx = -1, lasty = -1, lastr = -1;
 
1026
 
 
1027
    if (flashtime > 0) {
 
1028
        int frame = (int)(flashtime / FLASH_FRAME);
 
1029
        bgcolour = (frame % 2 ? COL_LOWLIGHT : COL_HIGHLIGHT);
 
1030
    } else
 
1031
        bgcolour = COL_BACKGROUND;
 
1032
 
 
1033
    if (!ds->started) {
 
1034
        int coords[10];
 
1035
 
 
1036
        draw_rect(dr, 0, 0,
 
1037
                  TILE_SIZE * state->w + 2 * BORDER,
 
1038
                  TILE_SIZE * state->h + 2 * BORDER, COL_BACKGROUND);
 
1039
        draw_update(dr, 0, 0,
 
1040
                    TILE_SIZE * state->w + 2 * BORDER,
 
1041
                    TILE_SIZE * state->h + 2 * BORDER);
 
1042
 
 
1043
        /*
 
1044
         * Recessed area containing the whole puzzle.
 
1045
         */
 
1046
        coords[0] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
 
1047
        coords[1] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
 
1048
        coords[2] = COORD(state->w) + HIGHLIGHT_WIDTH - 1;
 
1049
        coords[3] = COORD(0) - HIGHLIGHT_WIDTH;
 
1050
        coords[4] = coords[2] - TILE_SIZE;
 
1051
        coords[5] = coords[3] + TILE_SIZE;
 
1052
        coords[8] = COORD(0) - HIGHLIGHT_WIDTH;
 
1053
        coords[9] = COORD(state->h) + HIGHLIGHT_WIDTH - 1;
 
1054
        coords[6] = coords[8] + TILE_SIZE;
 
1055
        coords[7] = coords[9] - TILE_SIZE;
 
1056
        draw_polygon(dr, coords, 5, COL_HIGHLIGHT, COL_HIGHLIGHT);
 
1057
 
 
1058
        coords[1] = COORD(0) - HIGHLIGHT_WIDTH;
 
1059
        coords[0] = COORD(0) - HIGHLIGHT_WIDTH;
 
1060
        draw_polygon(dr, coords, 5, COL_LOWLIGHT, COL_LOWLIGHT);
 
1061
 
 
1062
        ds->started = TRUE;
 
1063
    }
 
1064
 
 
1065
    /*
 
1066
     * If we're drawing any rotated tiles, sort out the rotation
 
1067
     * parameters, and also zap the rotation region to the
 
1068
     * background colour before doing anything else.
 
1069
     */
 
1070
    if (oldstate) {
 
1071
        float angle;
 
1072
        float anim_max = game_anim_length(oldstate, state, dir, ui);
 
1073
 
 
1074
        if (dir > 0) {
 
1075
            lastx = state->lastx;
 
1076
            lasty = state->lasty;
 
1077
            lastr = state->lastr;
 
1078
        } else {
 
1079
            lastx = oldstate->lastx;
 
1080
            lasty = oldstate->lasty;
 
1081
            lastr = -oldstate->lastr;
 
1082
        }
 
1083
 
 
1084
        rot = &srot;
 
1085
        rot->cx = COORD(lastx);
 
1086
        rot->cy = COORD(lasty);
 
1087
        rot->cw = rot->ch = TILE_SIZE * state->n;
 
1088
        rot->ox = rot->cx + rot->cw/2;
 
1089
        rot->oy = rot->cy + rot->ch/2;
 
1090
        angle = (-PI/2 * lastr) * (1.0 - animtime / anim_max);
 
1091
        rot->c = cos(angle);
 
1092
        rot->s = sin(angle);
 
1093
 
 
1094
        /*
 
1095
         * Sort out the colours of the various sides of the tile.
 
1096
         */
 
1097
        rot->lc = highlight_colour(PI + angle);
 
1098
        rot->rc = highlight_colour(angle);
 
1099
        rot->tc = highlight_colour(PI/2 + angle);
 
1100
        rot->bc = highlight_colour(-PI/2 + angle);
 
1101
 
 
1102
        draw_rect(dr, rot->cx, rot->cy, rot->cw, rot->ch, bgcolour);
 
1103
    } else
 
1104
        rot = NULL;
 
1105
 
 
1106
    /*
 
1107
     * Now draw each tile.
 
1108
     */
 
1109
    for (i = 0; i < state->w * state->h; i++) {
 
1110
        int t;
 
1111
        int tx = i % state->w, ty = i / state->w;
 
1112
 
 
1113
        /*
 
1114
         * Figure out what should be displayed at this location.
 
1115
         * Usually it will be state->grid[i], unless we're in the
 
1116
         * middle of animating an actual rotation and this cell is
 
1117
         * within the rotation region, in which case we set -1
 
1118
         * (always display).
 
1119
         */
 
1120
        if (oldstate && lastx >= 0 && lasty >= 0 &&
 
1121
            tx >= lastx && tx < lastx + state->n &&
 
1122
            ty >= lasty && ty < lasty + state->n)
 
1123
            t = -1;
 
1124
        else
 
1125
            t = state->grid[i];
 
1126
 
 
1127
        if (ds->bgcolour != bgcolour ||   /* always redraw when flashing */
 
1128
            ds->grid[i] != t || ds->grid[i] == -1 || t == -1) {
 
1129
            int x = COORD(tx), y = COORD(ty);
 
1130
 
 
1131
            draw_tile(dr, ds, state, x, y, state->grid[i], bgcolour, rot);
 
1132
            ds->grid[i] = t;
 
1133
        }
 
1134
    }
 
1135
    ds->bgcolour = bgcolour;
 
1136
 
 
1137
    /*
 
1138
     * Update the status bar.
 
1139
     */
 
1140
    {
 
1141
        char statusbuf[256];
 
1142
 
 
1143
        /*
 
1144
         * Don't show the new status until we're also showing the
 
1145
         * new _state_ - after the game animation is complete.
 
1146
         */
 
1147
        if (oldstate)
 
1148
            state = oldstate;
 
1149
 
 
1150
        if (state->used_solve)
 
1151
            sprintf(statusbuf, "Moves since auto-solve: %d",
 
1152
                    state->movecount - state->completed);
 
1153
        else {
 
1154
            sprintf(statusbuf, "%sMoves: %d",
 
1155
                    (state->completed ? "COMPLETED! " : ""),
 
1156
                    (state->completed ? state->completed : state->movecount));
 
1157
            if (state->movetarget)
 
1158
                sprintf(statusbuf+strlen(statusbuf), " (target %d)",
 
1159
                        state->movetarget);
 
1160
        }
 
1161
 
 
1162
        status_bar(dr, statusbuf);
 
1163
    }
 
1164
}
 
1165
 
 
1166
static int game_timing_state(game_state *state, game_ui *ui)
 
1167
{
 
1168
    return TRUE;
 
1169
}
 
1170
 
 
1171
static void game_print_size(game_params *params, float *x, float *y)
 
1172
{
 
1173
}
 
1174
 
 
1175
static void game_print(drawing *dr, game_state *state, int tilesize)
 
1176
{
 
1177
}
 
1178
 
 
1179
#ifdef COMBINED
 
1180
#define thegame twiddle
 
1181
#endif
 
1182
 
 
1183
const struct game thegame = {
 
1184
    "Twiddle", "games.twiddle",
 
1185
    default_params,
 
1186
    game_fetch_preset,
 
1187
    decode_params,
 
1188
    encode_params,
 
1189
    free_params,
 
1190
    dup_params,
 
1191
    TRUE, game_configure, custom_params,
 
1192
    validate_params,
 
1193
    new_game_desc,
 
1194
    validate_desc,
 
1195
    new_game,
 
1196
    dup_game,
 
1197
    free_game,
 
1198
    TRUE, solve_game,
 
1199
    TRUE, game_text_format,
 
1200
    new_ui,
 
1201
    free_ui,
 
1202
    encode_ui,
 
1203
    decode_ui,
 
1204
    game_changed_state,
 
1205
    interpret_move,
 
1206
    execute_move,
 
1207
    PREFERRED_TILE_SIZE, game_compute_size, game_set_size,
 
1208
    game_colours,
 
1209
    game_new_drawstate,
 
1210
    game_free_drawstate,
 
1211
    game_redraw,
 
1212
    game_anim_length,
 
1213
    game_flash_length,
 
1214
    FALSE, FALSE, game_print_size, game_print,
 
1215
    TRUE,                              /* wants_statusbar */
 
1216
    FALSE, game_timing_state,
 
1217
    0,                                 /* flags */
 
1218
};