mirror of
git://git.tartarus.org/simon/puzzles.git
synced 2025-04-21 08:01:30 -07:00
Files

definitions, so let's move it so that it's just next to the functions it relates to. This also opens the way for me to add more booleans next to other functions without getting confused as to which is which. [originally from svn r5723]
1629 lines
45 KiB
C
1629 lines
45 KiB
C
/*
|
|
* rect.c: Puzzle from nikoli.co.jp. You have a square grid with
|
|
* numbers in some squares; you must divide the square grid up into
|
|
* variously sized rectangles, such that every rectangle contains
|
|
* exactly one numbered square and the area of each rectangle is
|
|
* equal to the number contained in it.
|
|
*/
|
|
|
|
/*
|
|
* TODO:
|
|
*
|
|
* - Improve on singleton removal by making an aesthetic choice
|
|
* about which of the options to take.
|
|
*
|
|
* - When doing the 3x3 trick in singleton removal, limit the size
|
|
* of the generated rectangles in accordance with the max
|
|
* rectangle size.
|
|
*
|
|
* - It might be interesting to deliberately try to place
|
|
* numbers so as to reduce alternative solution patterns. I
|
|
* doubt we can do a perfect job of this, but we can make a
|
|
* start by, for example, noticing pairs of 2-rects
|
|
* alongside one another and _not_ putting their numbers at
|
|
* opposite ends.
|
|
*
|
|
* - If we start by sorting the rectlist in descending order
|
|
* of area, we might be able to bias our random number
|
|
* selection to produce a few large rectangles more often
|
|
* than oodles of small ones? Unsure, but might be worth a
|
|
* try.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
|
|
#include "puzzles.h"
|
|
|
|
enum {
|
|
COL_BACKGROUND,
|
|
COL_CORRECT,
|
|
COL_LINE,
|
|
COL_TEXT,
|
|
COL_GRID,
|
|
COL_DRAG,
|
|
NCOLOURS
|
|
};
|
|
|
|
struct game_params {
|
|
int w, h;
|
|
float expandfactor;
|
|
};
|
|
|
|
#define INDEX(state, x, y) (((y) * (state)->w) + (x))
|
|
#define index(state, a, x, y) ((a) [ INDEX(state,x,y) ])
|
|
#define grid(state,x,y) index(state, (state)->grid, x, y)
|
|
#define vedge(state,x,y) index(state, (state)->vedge, x, y)
|
|
#define hedge(state,x,y) index(state, (state)->hedge, x, y)
|
|
|
|
#define CRANGE(state,x,y,dx,dy) ( (x) >= dx && (x) < (state)->w && \
|
|
(y) >= dy && (y) < (state)->h )
|
|
#define RANGE(state,x,y) CRANGE(state,x,y,0,0)
|
|
#define HRANGE(state,x,y) CRANGE(state,x,y,0,1)
|
|
#define VRANGE(state,x,y) CRANGE(state,x,y,1,0)
|
|
|
|
#define TILE_SIZE 24
|
|
#define BORDER 18
|
|
|
|
#define CORNER_TOLERANCE 0.15F
|
|
#define CENTRE_TOLERANCE 0.15F
|
|
|
|
#define FLASH_TIME 0.13F
|
|
|
|
#define COORD(x) ( (x) * TILE_SIZE + BORDER )
|
|
#define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE )
|
|
|
|
struct game_state {
|
|
int w, h;
|
|
int *grid; /* contains the numbers */
|
|
unsigned char *vedge; /* (w+1) x h */
|
|
unsigned char *hedge; /* w x (h+1) */
|
|
int completed;
|
|
};
|
|
|
|
static game_params *default_params(void)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->w = ret->h = 7;
|
|
ret->expandfactor = 0.0F;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int game_fetch_preset(int i, char **name, game_params **params)
|
|
{
|
|
game_params *ret;
|
|
int w, h;
|
|
char buf[80];
|
|
|
|
switch (i) {
|
|
case 0: w = 7, h = 7; break;
|
|
case 1: w = 11, h = 11; break;
|
|
case 2: w = 15, h = 15; break;
|
|
case 3: w = 19, h = 19; break;
|
|
default: return FALSE;
|
|
}
|
|
|
|
sprintf(buf, "%dx%d", w, h);
|
|
*name = dupstr(buf);
|
|
*params = ret = snew(game_params);
|
|
ret->w = w;
|
|
ret->h = h;
|
|
ret->expandfactor = 0.0F;
|
|
return TRUE;
|
|
}
|
|
|
|
static void free_params(game_params *params)
|
|
{
|
|
sfree(params);
|
|
}
|
|
|
|
static game_params *dup_params(game_params *params)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
*ret = *params; /* structure copy */
|
|
return ret;
|
|
}
|
|
|
|
static game_params *decode_params(char const *string)
|
|
{
|
|
game_params *ret = default_params();
|
|
|
|
ret->w = ret->h = atoi(string);
|
|
ret->expandfactor = 0.0F;
|
|
while (*string && isdigit((unsigned char)*string)) string++;
|
|
if (*string == 'x') {
|
|
string++;
|
|
ret->h = atoi(string);
|
|
while (*string && isdigit((unsigned char)*string)) string++;
|
|
}
|
|
if (*string == 'e') {
|
|
string++;
|
|
ret->expandfactor = atof(string);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *encode_params(game_params *params)
|
|
{
|
|
char data[256];
|
|
|
|
sprintf(data, "%dx%d", params->w, params->h);
|
|
|
|
return dupstr(data);
|
|
}
|
|
|
|
static config_item *game_configure(game_params *params)
|
|
{
|
|
config_item *ret;
|
|
char buf[80];
|
|
|
|
ret = snewn(5, config_item);
|
|
|
|
ret[0].name = "Width";
|
|
ret[0].type = C_STRING;
|
|
sprintf(buf, "%d", params->w);
|
|
ret[0].sval = dupstr(buf);
|
|
ret[0].ival = 0;
|
|
|
|
ret[1].name = "Height";
|
|
ret[1].type = C_STRING;
|
|
sprintf(buf, "%d", params->h);
|
|
ret[1].sval = dupstr(buf);
|
|
ret[1].ival = 0;
|
|
|
|
ret[2].name = "Expansion factor";
|
|
ret[2].type = C_STRING;
|
|
sprintf(buf, "%g", params->expandfactor);
|
|
ret[2].sval = dupstr(buf);
|
|
ret[2].ival = 0;
|
|
|
|
ret[3].name = NULL;
|
|
ret[3].type = C_END;
|
|
ret[3].sval = NULL;
|
|
ret[3].ival = 0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static game_params *custom_params(config_item *cfg)
|
|
{
|
|
game_params *ret = snew(game_params);
|
|
|
|
ret->w = atoi(cfg[0].sval);
|
|
ret->h = atoi(cfg[1].sval);
|
|
ret->expandfactor = atof(cfg[2].sval);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static char *validate_params(game_params *params)
|
|
{
|
|
if (params->w <= 0 && params->h <= 0)
|
|
return "Width and height must both be greater than zero";
|
|
if (params->w < 2 && params->h < 2)
|
|
return "Grid area must be greater than one";
|
|
if (params->expandfactor < 0.0F)
|
|
return "Expansion factor may not be negative";
|
|
return NULL;
|
|
}
|
|
|
|
struct rect {
|
|
int x, y;
|
|
int w, h;
|
|
};
|
|
|
|
struct rectlist {
|
|
struct rect *rects;
|
|
int n;
|
|
};
|
|
|
|
static struct rectlist *get_rectlist(game_params *params, int *grid)
|
|
{
|
|
int rw, rh;
|
|
int x, y;
|
|
int maxarea;
|
|
struct rect *rects = NULL;
|
|
int nrects = 0, rectsize = 0;
|
|
|
|
/*
|
|
* Maximum rectangle area is 1/6 of total grid size, unless
|
|
* this means we can't place any rectangles at all in which
|
|
* case we set it to 2 at minimum.
|
|
*/
|
|
maxarea = params->w * params->h / 6;
|
|
if (maxarea < 2)
|
|
maxarea = 2;
|
|
|
|
for (rw = 1; rw <= params->w; rw++)
|
|
for (rh = 1; rh <= params->h; rh++) {
|
|
if (rw * rh > maxarea)
|
|
continue;
|
|
if (rw * rh == 1)
|
|
continue;
|
|
for (x = 0; x <= params->w - rw; x++)
|
|
for (y = 0; y <= params->h - rh; y++) {
|
|
if (nrects >= rectsize) {
|
|
rectsize = nrects + 256;
|
|
rects = sresize(rects, rectsize, struct rect);
|
|
}
|
|
|
|
rects[nrects].x = x;
|
|
rects[nrects].y = y;
|
|
rects[nrects].w = rw;
|
|
rects[nrects].h = rh;
|
|
nrects++;
|
|
}
|
|
}
|
|
|
|
if (nrects > 0) {
|
|
struct rectlist *ret;
|
|
ret = snew(struct rectlist);
|
|
ret->rects = rects;
|
|
ret->n = nrects;
|
|
return ret;
|
|
} else {
|
|
assert(rects == NULL); /* hence no need to free */
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static void free_rectlist(struct rectlist *list)
|
|
{
|
|
sfree(list->rects);
|
|
sfree(list);
|
|
}
|
|
|
|
static void place_rect(game_params *params, int *grid, struct rect r)
|
|
{
|
|
int idx = INDEX(params, r.x, r.y);
|
|
int x, y;
|
|
|
|
for (x = r.x; x < r.x+r.w; x++)
|
|
for (y = r.y; y < r.y+r.h; y++) {
|
|
index(params, grid, x, y) = idx;
|
|
}
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf(" placing rectangle at (%d,%d) size %d x %d\n",
|
|
r.x, r.y, r.w, r.h);
|
|
#endif
|
|
}
|
|
|
|
static struct rect find_rect(game_params *params, int *grid, int x, int y)
|
|
{
|
|
int idx, w, h;
|
|
struct rect r;
|
|
|
|
/*
|
|
* Find the top left of the rectangle.
|
|
*/
|
|
idx = index(params, grid, x, y);
|
|
|
|
if (idx < 0) {
|
|
r.x = x;
|
|
r.y = y;
|
|
r.w = r.h = 1;
|
|
return r; /* 1x1 singleton here */
|
|
}
|
|
|
|
y = idx / params->w;
|
|
x = idx % params->w;
|
|
|
|
/*
|
|
* Find the width and height of the rectangle.
|
|
*/
|
|
for (w = 1;
|
|
(x+w < params->w && index(params,grid,x+w,y)==idx);
|
|
w++);
|
|
for (h = 1;
|
|
(y+h < params->h && index(params,grid,x,y+h)==idx);
|
|
h++);
|
|
|
|
r.x = x;
|
|
r.y = y;
|
|
r.w = w;
|
|
r.h = h;
|
|
|
|
return r;
|
|
}
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
static void display_grid(game_params *params, int *grid, int *numbers, int all)
|
|
{
|
|
unsigned char *egrid = snewn((params->w*2+3) * (params->h*2+3),
|
|
unsigned char);
|
|
int x, y;
|
|
int r = (params->w*2+3);
|
|
|
|
memset(egrid, 0, (params->w*2+3) * (params->h*2+3));
|
|
|
|
for (x = 0; x < params->w; x++)
|
|
for (y = 0; y < params->h; y++) {
|
|
int i = index(params, grid, x, y);
|
|
if (x == 0 || index(params, grid, x-1, y) != i)
|
|
egrid[(2*y+2) * r + (2*x+1)] = 1;
|
|
if (x == params->w-1 || index(params, grid, x+1, y) != i)
|
|
egrid[(2*y+2) * r + (2*x+3)] = 1;
|
|
if (y == 0 || index(params, grid, x, y-1) != i)
|
|
egrid[(2*y+1) * r + (2*x+2)] = 1;
|
|
if (y == params->h-1 || index(params, grid, x, y+1) != i)
|
|
egrid[(2*y+3) * r + (2*x+2)] = 1;
|
|
}
|
|
|
|
for (y = 1; y < 2*params->h+2; y++) {
|
|
for (x = 1; x < 2*params->w+2; x++) {
|
|
if (!((y|x)&1)) {
|
|
int k = numbers ? index(params, numbers, x/2-1, y/2-1) : 0;
|
|
if (k || (all && numbers)) printf("%2d", k); else printf(" ");
|
|
} else if (!((y&x)&1)) {
|
|
int v = egrid[y*r+x];
|
|
if ((y&1) && v) v = '-';
|
|
if ((x&1) && v) v = '|';
|
|
if (!v) v = ' ';
|
|
putchar(v);
|
|
if (!(x&1)) putchar(v);
|
|
} else {
|
|
int c, d = 0;
|
|
if (egrid[y*r+(x+1)]) d |= 1;
|
|
if (egrid[(y-1)*r+x]) d |= 2;
|
|
if (egrid[y*r+(x-1)]) d |= 4;
|
|
if (egrid[(y+1)*r+x]) d |= 8;
|
|
c = " ??+?-++?+|+++++"[d];
|
|
putchar(c);
|
|
if (!(x&1)) putchar(c);
|
|
}
|
|
}
|
|
putchar('\n');
|
|
}
|
|
|
|
sfree(egrid);
|
|
}
|
|
#endif
|
|
|
|
static char *new_game_seed(game_params *params, random_state *rs)
|
|
{
|
|
int *grid, *numbers;
|
|
struct rectlist *list;
|
|
int x, y, y2, y2last, yx, run, i;
|
|
char *seed, *p;
|
|
game_params params2real, *params2 = ¶ms2real;
|
|
|
|
/*
|
|
* Set up the smaller width and height which we will use to
|
|
* generate the base grid.
|
|
*/
|
|
params2->w = params->w / (1.0F + params->expandfactor);
|
|
if (params2->w < 2 && params->w >= 2) params2->w = 2;
|
|
params2->h = params->h / (1.0F + params->expandfactor);
|
|
if (params2->h < 2 && params->h >= 2) params2->h = 2;
|
|
|
|
grid = snewn(params2->w * params2->h, int);
|
|
|
|
for (y = 0; y < params2->h; y++)
|
|
for (x = 0; x < params2->w; x++) {
|
|
index(params2, grid, x, y) = -1;
|
|
}
|
|
|
|
list = get_rectlist(params2, grid);
|
|
assert(list != NULL);
|
|
|
|
/*
|
|
* Place rectangles until we can't any more.
|
|
*/
|
|
while (list->n > 0) {
|
|
int i, m;
|
|
struct rect r;
|
|
|
|
/*
|
|
* Pick a random rectangle.
|
|
*/
|
|
i = random_upto(rs, list->n);
|
|
r = list->rects[i];
|
|
|
|
/*
|
|
* Place it.
|
|
*/
|
|
place_rect(params2, grid, r);
|
|
|
|
/*
|
|
* Winnow the list by removing any rectangles which
|
|
* overlap this one.
|
|
*/
|
|
m = 0;
|
|
for (i = 0; i < list->n; i++) {
|
|
struct rect s = list->rects[i];
|
|
if (s.x+s.w <= r.x || r.x+r.w <= s.x ||
|
|
s.y+s.h <= r.y || r.y+r.h <= s.y)
|
|
list->rects[m++] = s;
|
|
}
|
|
list->n = m;
|
|
}
|
|
|
|
free_rectlist(list);
|
|
|
|
/*
|
|
* Deal with singleton spaces remaining in the grid, one by
|
|
* one.
|
|
*
|
|
* We do this by making a local change to the layout. There are
|
|
* several possibilities:
|
|
*
|
|
* +-----+-----+ Here, we can remove the singleton by
|
|
* | | | extending the 1x2 rectangle below it
|
|
* +--+--+-----+ into a 1x3.
|
|
* | | | |
|
|
* | +--+ |
|
|
* | | | |
|
|
* | | | |
|
|
* | | | |
|
|
* +--+--+-----+
|
|
*
|
|
* +--+--+--+ Here, that trick doesn't work: there's no
|
|
* | | | 1 x n rectangle with the singleton at one
|
|
* | | | end. Instead, we extend a 1 x n rectangle
|
|
* | | | _out_ from the singleton, shaving a layer
|
|
* +--+--+ | off the end of another rectangle. So if we
|
|
* | | | | extended up, we'd make our singleton part
|
|
* | +--+--+ of a 1x3 and generate a 1x2 where the 2x2
|
|
* | | | used to be; or we could extend right into
|
|
* +--+-----+ a 2x1, turning the 1x3 into a 1x2.
|
|
*
|
|
* +-----+--+ Here, we can't even do _that_, since any
|
|
* | | | direction we choose to extend the singleton
|
|
* +--+--+ | will produce a new singleton as a result of
|
|
* | | | | truncating one of the size-2 rectangles.
|
|
* | +--+--+ Fortunately, this case can _only_ occur when
|
|
* | | | a singleton is surrounded by four size-2s
|
|
* +--+-----+ in this fashion; so instead we can simply
|
|
* replace the whole section with a single 3x3.
|
|
*/
|
|
for (x = 0; x < params2->w; x++) {
|
|
for (y = 0; y < params2->h; y++) {
|
|
if (index(params2, grid, x, y) < 0) {
|
|
int dirs[4], ndirs;
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
display_grid(params2, grid, NULL, FALSE);
|
|
printf("singleton at %d,%d\n", x, y);
|
|
#endif
|
|
|
|
/*
|
|
* Check in which directions we can feasibly extend
|
|
* the singleton. We can extend in a particular
|
|
* direction iff either:
|
|
*
|
|
* - the rectangle on that side of the singleton
|
|
* is not 2x1, and we are at one end of the edge
|
|
* of it we are touching
|
|
*
|
|
* - it is 2x1 but we are on its short side.
|
|
*
|
|
* FIXME: we could plausibly choose between these
|
|
* based on the sizes of the rectangles they would
|
|
* create?
|
|
*/
|
|
ndirs = 0;
|
|
if (x < params2->w-1) {
|
|
struct rect r = find_rect(params2, grid, x+1, y);
|
|
if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
|
|
dirs[ndirs++] = 1; /* right */
|
|
}
|
|
if (y > 0) {
|
|
struct rect r = find_rect(params2, grid, x, y-1);
|
|
if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
|
|
dirs[ndirs++] = 2; /* up */
|
|
}
|
|
if (x > 0) {
|
|
struct rect r = find_rect(params2, grid, x-1, y);
|
|
if ((r.w * r.h > 2 && (r.y==y || r.y+r.h-1==y)) || r.h==1)
|
|
dirs[ndirs++] = 4; /* left */
|
|
}
|
|
if (y < params2->h-1) {
|
|
struct rect r = find_rect(params2, grid, x, y+1);
|
|
if ((r.w * r.h > 2 && (r.x==x || r.x+r.w-1==x)) || r.w==1)
|
|
dirs[ndirs++] = 8; /* down */
|
|
}
|
|
|
|
if (ndirs > 0) {
|
|
int which, dir;
|
|
struct rect r1, r2;
|
|
|
|
which = random_upto(rs, ndirs);
|
|
dir = dirs[which];
|
|
|
|
switch (dir) {
|
|
case 1: /* right */
|
|
assert(x < params2->w+1);
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("extending right\n");
|
|
#endif
|
|
r1 = find_rect(params2, grid, x+1, y);
|
|
r2.x = x;
|
|
r2.y = y;
|
|
r2.w = 1 + r1.w;
|
|
r2.h = 1;
|
|
if (r1.y == y)
|
|
r1.y++;
|
|
r1.h--;
|
|
break;
|
|
case 2: /* up */
|
|
assert(y > 0);
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("extending up\n");
|
|
#endif
|
|
r1 = find_rect(params2, grid, x, y-1);
|
|
r2.x = x;
|
|
r2.y = r1.y;
|
|
r2.w = 1;
|
|
r2.h = 1 + r1.h;
|
|
if (r1.x == x)
|
|
r1.x++;
|
|
r1.w--;
|
|
break;
|
|
case 4: /* left */
|
|
assert(x > 0);
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("extending left\n");
|
|
#endif
|
|
r1 = find_rect(params2, grid, x-1, y);
|
|
r2.x = r1.x;
|
|
r2.y = y;
|
|
r2.w = 1 + r1.w;
|
|
r2.h = 1;
|
|
if (r1.y == y)
|
|
r1.y++;
|
|
r1.h--;
|
|
break;
|
|
case 8: /* down */
|
|
assert(y < params2->h+1);
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("extending down\n");
|
|
#endif
|
|
r1 = find_rect(params2, grid, x, y+1);
|
|
r2.x = x;
|
|
r2.y = y;
|
|
r2.w = 1;
|
|
r2.h = 1 + r1.h;
|
|
if (r1.x == x)
|
|
r1.x++;
|
|
r1.w--;
|
|
break;
|
|
}
|
|
if (r1.h > 0 && r1.w > 0)
|
|
place_rect(params2, grid, r1);
|
|
place_rect(params2, grid, r2);
|
|
} else {
|
|
#ifndef NDEBUG
|
|
/*
|
|
* Sanity-check that there really is a 3x3
|
|
* rectangle surrounding this singleton and it
|
|
* contains absolutely everything we could
|
|
* possibly need.
|
|
*/
|
|
{
|
|
int xx, yy;
|
|
assert(x > 0 && x < params2->w-1);
|
|
assert(y > 0 && y < params2->h-1);
|
|
|
|
for (xx = x-1; xx <= x+1; xx++)
|
|
for (yy = y-1; yy <= y+1; yy++) {
|
|
struct rect r = find_rect(params2,grid,xx,yy);
|
|
assert(r.x >= x-1);
|
|
assert(r.y >= y-1);
|
|
assert(r.x+r.w-1 <= x+1);
|
|
assert(r.y+r.h-1 <= y+1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("need the 3x3 trick\n");
|
|
#endif
|
|
|
|
/*
|
|
* FIXME: If the maximum rectangle area for
|
|
* this grid is less than 9, we ought to
|
|
* subdivide the 3x3 in some fashion. There are
|
|
* five other possibilities:
|
|
*
|
|
* - a 6 and a 3
|
|
* - a 4, a 3 and a 2
|
|
* - three 3s
|
|
* - a 3 and three 2s (two different arrangements).
|
|
*/
|
|
|
|
{
|
|
struct rect r;
|
|
r.x = x-1;
|
|
r.y = y-1;
|
|
r.w = r.h = 3;
|
|
place_rect(params2, grid, r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We have now constructed a grid of the size specified in
|
|
* params2. Now we extend it into a grid of the size specified
|
|
* in params. We do this in two passes: we extend it vertically
|
|
* until it's the right height, then we transpose it, then
|
|
* extend it vertically again (getting it effectively the right
|
|
* width), then finally transpose again.
|
|
*/
|
|
for (i = 0; i < 2; i++) {
|
|
int *grid2, *expand, *where;
|
|
game_params params3real, *params3 = ¶ms3real;
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("before expansion:\n");
|
|
display_grid(params2, grid, NULL, TRUE);
|
|
#endif
|
|
|
|
/*
|
|
* Set up the new grid.
|
|
*/
|
|
grid2 = snewn(params2->w * params->h, int);
|
|
expand = snewn(params2->h-1, int);
|
|
where = snewn(params2->w, int);
|
|
params3->w = params2->w;
|
|
params3->h = params->h;
|
|
|
|
/*
|
|
* Decide which horizontal edges are going to get expanded,
|
|
* and by how much.
|
|
*/
|
|
for (y = 0; y < params2->h-1; y++)
|
|
expand[y] = 0;
|
|
for (y = params2->h; y < params->h; y++) {
|
|
x = random_upto(rs, params2->h-1);
|
|
expand[x]++;
|
|
}
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("expand[] = {");
|
|
for (y = 0; y < params2->h-1; y++)
|
|
printf(" %d", expand[y]);
|
|
printf(" }\n");
|
|
#endif
|
|
|
|
/*
|
|
* Perform the expansion. The way this works is that we
|
|
* alternately:
|
|
*
|
|
* - copy a row from grid into grid2
|
|
*
|
|
* - invent some number of additional rows in grid2 where
|
|
* there was previously only a horizontal line between
|
|
* rows in grid, and make random decisions about where
|
|
* among these to place each rectangle edge that ran
|
|
* along this line.
|
|
*/
|
|
for (y = y2 = y2last = 0; y < params2->h; y++) {
|
|
/*
|
|
* Copy a single line from row y of grid into row y2 of
|
|
* grid2.
|
|
*/
|
|
for (x = 0; x < params2->w; x++) {
|
|
int val = index(params2, grid, x, y);
|
|
if (val / params2->w == y && /* rect starts on this line */
|
|
(y2 == 0 || /* we're at the very top, or... */
|
|
index(params3, grid2, x, y2-1) / params3->w < y2last
|
|
/* this rect isn't already started */))
|
|
index(params3, grid2, x, y2) =
|
|
INDEX(params3, val % params2->w, y2);
|
|
else
|
|
index(params3, grid2, x, y2) =
|
|
index(params3, grid2, x, y2-1);
|
|
}
|
|
|
|
/*
|
|
* If that was the last line, terminate the loop early.
|
|
*/
|
|
if (++y2 == params3->h)
|
|
break;
|
|
|
|
y2last = y2;
|
|
|
|
/*
|
|
* Invent some number of additional lines. First walk
|
|
* along this line working out where to put all the
|
|
* edges that coincide with it.
|
|
*/
|
|
yx = -1;
|
|
for (x = 0; x < params2->w; x++) {
|
|
if (index(params2, grid, x, y) !=
|
|
index(params2, grid, x, y+1)) {
|
|
/*
|
|
* This is a horizontal edge, so it needs
|
|
* placing.
|
|
*/
|
|
if (x == 0 ||
|
|
(index(params2, grid, x-1, y) !=
|
|
index(params2, grid, x, y) &&
|
|
index(params2, grid, x-1, y+1) !=
|
|
index(params2, grid, x, y+1))) {
|
|
/*
|
|
* Here we have the chance to make a new
|
|
* decision.
|
|
*/
|
|
yx = random_upto(rs, expand[y]+1);
|
|
} else {
|
|
/*
|
|
* Here we just reuse the previous value of
|
|
* yx.
|
|
*/
|
|
}
|
|
} else
|
|
yx = -1;
|
|
where[x] = yx;
|
|
}
|
|
|
|
for (yx = 0; yx < expand[y]; yx++) {
|
|
/*
|
|
* Invent a single row. For each square in the row,
|
|
* we copy the grid entry from the square above it,
|
|
* unless we're starting the new rectangle here.
|
|
*/
|
|
for (x = 0; x < params2->w; x++) {
|
|
if (yx == where[x]) {
|
|
int val = index(params2, grid, x, y+1);
|
|
val %= params2->w;
|
|
val = INDEX(params3, val, y2);
|
|
index(params3, grid2, x, y2) = val;
|
|
} else
|
|
index(params3, grid2, x, y2) =
|
|
index(params3, grid2, x, y2-1);
|
|
}
|
|
|
|
y2++;
|
|
}
|
|
}
|
|
|
|
sfree(expand);
|
|
sfree(where);
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("after expansion:\n");
|
|
display_grid(params3, grid2, NULL, TRUE);
|
|
#endif
|
|
/*
|
|
* Transpose.
|
|
*/
|
|
params2->w = params3->h;
|
|
params2->h = params3->w;
|
|
sfree(grid);
|
|
grid = snewn(params2->w * params2->h, int);
|
|
for (x = 0; x < params2->w; x++)
|
|
for (y = 0; y < params2->h; y++) {
|
|
int idx1 = INDEX(params2, x, y);
|
|
int idx2 = INDEX(params3, y, x);
|
|
int tmp;
|
|
|
|
tmp = grid2[idx2];
|
|
tmp = (tmp % params3->w) * params2->w + (tmp / params3->w);
|
|
grid[idx1] = tmp;
|
|
}
|
|
|
|
sfree(grid2);
|
|
|
|
{
|
|
int tmp;
|
|
tmp = params->w;
|
|
params->w = params->h;
|
|
params->h = tmp;
|
|
}
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
printf("after transposition:\n");
|
|
display_grid(params2, grid, NULL, TRUE);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Place numbers.
|
|
*/
|
|
numbers = snewn(params->w * params->h, int);
|
|
|
|
for (y = 0; y < params->h; y++)
|
|
for (x = 0; x < params->w; x++) {
|
|
index(params, numbers, x, y) = 0;
|
|
}
|
|
|
|
for (x = 0; x < params->w; x++) {
|
|
for (y = 0; y < params->h; y++) {
|
|
int idx = INDEX(params, x, y);
|
|
if (index(params, grid, x, y) == idx) {
|
|
struct rect r = find_rect(params, grid, x, y);
|
|
int n, xx, yy;
|
|
|
|
/*
|
|
* Decide where to put the number.
|
|
*/
|
|
n = random_upto(rs, r.w*r.h);
|
|
yy = n / r.w;
|
|
xx = n % r.w;
|
|
index(params,numbers,x+xx,y+yy) = r.w*r.h;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef GENERATION_DIAGNOSTICS
|
|
display_grid(params, grid, numbers, FALSE);
|
|
#endif
|
|
|
|
seed = snewn(11 * params->w * params->h, char);
|
|
p = seed;
|
|
run = 0;
|
|
for (i = 0; i <= params->w * params->h; i++) {
|
|
int n = (i < params->w * params->h ? numbers[i] : -1);
|
|
|
|
if (!n)
|
|
run++;
|
|
else {
|
|
if (run) {
|
|
while (run > 0) {
|
|
int c = 'a' - 1 + run;
|
|
if (run > 26)
|
|
c = 'z';
|
|
*p++ = c;
|
|
run -= c - ('a' - 1);
|
|
}
|
|
} else {
|
|
/*
|
|
* If there's a number in the very top left or
|
|
* bottom right, there's no point putting an
|
|
* unnecessary _ before or after it.
|
|
*/
|
|
if (p > seed && n > 0)
|
|
*p++ = '_';
|
|
}
|
|
if (n > 0)
|
|
p += sprintf(p, "%d", n);
|
|
run = 0;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
|
|
sfree(grid);
|
|
sfree(numbers);
|
|
|
|
return seed;
|
|
}
|
|
|
|
static char *validate_seed(game_params *params, char *seed)
|
|
{
|
|
int area = params->w * params->h;
|
|
int squares = 0;
|
|
|
|
while (*seed) {
|
|
int n = *seed++;
|
|
if (n >= 'a' && n <= 'z') {
|
|
squares += n - 'a' + 1;
|
|
} else if (n == '_') {
|
|
/* do nothing */;
|
|
} else if (n > '0' && n <= '9') {
|
|
squares++;
|
|
while (*seed >= '0' && *seed <= '9')
|
|
seed++;
|
|
} else
|
|
return "Invalid character in game specification";
|
|
}
|
|
|
|
if (squares < area)
|
|
return "Not enough data to fill grid";
|
|
|
|
if (squares > area)
|
|
return "Too much data to fit in grid";
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static game_state *new_game(game_params *params, char *seed)
|
|
{
|
|
game_state *state = snew(game_state);
|
|
int x, y, i, area;
|
|
|
|
state->w = params->w;
|
|
state->h = params->h;
|
|
|
|
area = state->w * state->h;
|
|
|
|
state->grid = snewn(area, int);
|
|
state->vedge = snewn(area, unsigned char);
|
|
state->hedge = snewn(area, unsigned char);
|
|
state->completed = FALSE;
|
|
|
|
i = 0;
|
|
while (*seed) {
|
|
int n = *seed++;
|
|
if (n >= 'a' && n <= 'z') {
|
|
int run = n - 'a' + 1;
|
|
assert(i + run <= area);
|
|
while (run-- > 0)
|
|
state->grid[i++] = 0;
|
|
} else if (n == '_') {
|
|
/* do nothing */;
|
|
} else if (n > '0' && n <= '9') {
|
|
assert(i < area);
|
|
state->grid[i++] = atoi(seed-1);
|
|
while (*seed >= '0' && *seed <= '9')
|
|
seed++;
|
|
} else {
|
|
assert(!"We can't get here");
|
|
}
|
|
}
|
|
assert(i == area);
|
|
|
|
for (y = 0; y < state->h; y++)
|
|
for (x = 0; x < state->w; x++)
|
|
vedge(state,x,y) = hedge(state,x,y) = 0;
|
|
|
|
return state;
|
|
}
|
|
|
|
static game_state *dup_game(game_state *state)
|
|
{
|
|
game_state *ret = snew(game_state);
|
|
|
|
ret->w = state->w;
|
|
ret->h = state->h;
|
|
|
|
ret->vedge = snewn(state->w * state->h, unsigned char);
|
|
ret->hedge = snewn(state->w * state->h, unsigned char);
|
|
ret->grid = snewn(state->w * state->h, int);
|
|
|
|
ret->completed = state->completed;
|
|
|
|
memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
|
|
memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
|
|
memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void free_game(game_state *state)
|
|
{
|
|
sfree(state->grid);
|
|
sfree(state->vedge);
|
|
sfree(state->hedge);
|
|
sfree(state);
|
|
}
|
|
|
|
static unsigned char *get_correct(game_state *state)
|
|
{
|
|
unsigned char *ret;
|
|
int x, y;
|
|
|
|
ret = snewn(state->w * state->h, unsigned char);
|
|
memset(ret, 0xFF, state->w * state->h);
|
|
|
|
for (x = 0; x < state->w; x++)
|
|
for (y = 0; y < state->h; y++)
|
|
if (index(state,ret,x,y) == 0xFF) {
|
|
int rw, rh;
|
|
int xx, yy;
|
|
int num, area, valid;
|
|
|
|
/*
|
|
* Find a rectangle starting at this point.
|
|
*/
|
|
rw = 1;
|
|
while (x+rw < state->w && !vedge(state,x+rw,y))
|
|
rw++;
|
|
rh = 1;
|
|
while (y+rh < state->h && !hedge(state,x,y+rh))
|
|
rh++;
|
|
|
|
/*
|
|
* We know what the dimensions of the rectangle
|
|
* should be if it's there at all. Find out if we
|
|
* really have a valid rectangle.
|
|
*/
|
|
valid = TRUE;
|
|
/* Check the horizontal edges. */
|
|
for (xx = x; xx < x+rw; xx++) {
|
|
for (yy = y; yy <= y+rh; yy++) {
|
|
int e = !HRANGE(state,xx,yy) || hedge(state,xx,yy);
|
|
int ec = (yy == y || yy == y+rh);
|
|
if (e != ec)
|
|
valid = FALSE;
|
|
}
|
|
}
|
|
/* Check the vertical edges. */
|
|
for (yy = y; yy < y+rh; yy++) {
|
|
for (xx = x; xx <= x+rw; xx++) {
|
|
int e = !VRANGE(state,xx,yy) || vedge(state,xx,yy);
|
|
int ec = (xx == x || xx == x+rw);
|
|
if (e != ec)
|
|
valid = FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If this is not a valid rectangle with no other
|
|
* edges inside it, we just mark this square as not
|
|
* complete and proceed to the next square.
|
|
*/
|
|
if (!valid) {
|
|
index(state, ret, x, y) = 0;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We have a rectangle. Now see what its area is,
|
|
* and how many numbers are in it.
|
|
*/
|
|
num = 0;
|
|
area = 0;
|
|
for (xx = x; xx < x+rw; xx++) {
|
|
for (yy = y; yy < y+rh; yy++) {
|
|
area++;
|
|
if (grid(state,xx,yy)) {
|
|
if (num > 0)
|
|
valid = FALSE; /* two numbers */
|
|
num = grid(state,xx,yy);
|
|
}
|
|
}
|
|
}
|
|
if (num != area)
|
|
valid = FALSE;
|
|
|
|
/*
|
|
* Now fill in the whole rectangle based on the
|
|
* value of `valid'.
|
|
*/
|
|
for (xx = x; xx < x+rw; xx++) {
|
|
for (yy = y; yy < y+rh; yy++) {
|
|
index(state, ret, xx, yy) = valid;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct game_ui {
|
|
/*
|
|
* These coordinates are 2 times the obvious grid coordinates.
|
|
* Hence, the top left of the grid is (0,0), the grid point to
|
|
* the right of that is (2,0), the one _below that_ is (2,2)
|
|
* and so on. This is so that we can specify a drag start point
|
|
* on an edge (one odd coordinate) or in the middle of a square
|
|
* (two odd coordinates) rather than always at a corner.
|
|
*
|
|
* -1,-1 means no drag is in progress.
|
|
*/
|
|
int drag_start_x;
|
|
int drag_start_y;
|
|
int drag_end_x;
|
|
int drag_end_y;
|
|
/*
|
|
* This flag is set as soon as a dragging action moves the
|
|
* mouse pointer away from its starting point, so that even if
|
|
* the pointer _returns_ to its starting point the action is
|
|
* treated as a small drag rather than a click.
|
|
*/
|
|
int dragged;
|
|
};
|
|
|
|
static game_ui *new_ui(game_state *state)
|
|
{
|
|
game_ui *ui = snew(game_ui);
|
|
ui->drag_start_x = -1;
|
|
ui->drag_start_y = -1;
|
|
ui->drag_end_x = -1;
|
|
ui->drag_end_y = -1;
|
|
ui->dragged = FALSE;
|
|
return ui;
|
|
}
|
|
|
|
static void free_ui(game_ui *ui)
|
|
{
|
|
sfree(ui);
|
|
}
|
|
|
|
static void coord_round(float x, float y, int *xr, int *yr)
|
|
{
|
|
float xs, ys, xv, yv, dx, dy, dist;
|
|
|
|
/*
|
|
* Find the nearest square-centre.
|
|
*/
|
|
xs = (float)floor(x) + 0.5F;
|
|
ys = (float)floor(y) + 0.5F;
|
|
|
|
/*
|
|
* And find the nearest grid vertex.
|
|
*/
|
|
xv = (float)floor(x + 0.5F);
|
|
yv = (float)floor(y + 0.5F);
|
|
|
|
/*
|
|
* We allocate clicks in parts of the grid square to either
|
|
* corners, edges or square centres, as follows:
|
|
*
|
|
* +--+--------+--+
|
|
* | | | |
|
|
* +--+ +--+
|
|
* | `. ,' |
|
|
* | +--+ |
|
|
* | | | |
|
|
* | +--+ |
|
|
* | ,' `. |
|
|
* +--+ +--+
|
|
* | | | |
|
|
* +--+--------+--+
|
|
*
|
|
* (Not to scale!)
|
|
*
|
|
* In other words: we measure the square distance (i.e.
|
|
* max(dx,dy)) from the click to the nearest corner, and if
|
|
* it's within CORNER_TOLERANCE then we return a corner click.
|
|
* We measure the square distance from the click to the nearest
|
|
* centre, and if that's within CENTRE_TOLERANCE we return a
|
|
* centre click. Failing that, we find which of the two edge
|
|
* centres is nearer to the click and return that edge.
|
|
*/
|
|
|
|
/*
|
|
* Check for corner click.
|
|
*/
|
|
dx = (float)fabs(x - xv);
|
|
dy = (float)fabs(y - yv);
|
|
dist = (dx > dy ? dx : dy);
|
|
if (dist < CORNER_TOLERANCE) {
|
|
*xr = 2 * (int)xv;
|
|
*yr = 2 * (int)yv;
|
|
} else {
|
|
/*
|
|
* Check for centre click.
|
|
*/
|
|
dx = (float)fabs(x - xs);
|
|
dy = (float)fabs(y - ys);
|
|
dist = (dx > dy ? dx : dy);
|
|
if (dist < CENTRE_TOLERANCE) {
|
|
*xr = 1 + 2 * (int)xs;
|
|
*yr = 1 + 2 * (int)ys;
|
|
} else {
|
|
/*
|
|
* Failing both of those, see which edge we're closer to.
|
|
* Conveniently, this is simply done by testing the relative
|
|
* magnitude of dx and dy (which are currently distances from
|
|
* the square centre).
|
|
*/
|
|
if (dx > dy) {
|
|
/* Vertical edge: x-coord of corner,
|
|
* y-coord of square centre. */
|
|
*xr = 2 * (int)xv;
|
|
*yr = 1 + 2 * (int)ys;
|
|
} else {
|
|
/* Horizontal edge: x-coord of square centre,
|
|
* y-coord of corner. */
|
|
*xr = 1 + 2 * (int)xs;
|
|
*yr = 2 * (int)yv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ui_draw_rect(game_state *state, game_ui *ui,
|
|
unsigned char *hedge, unsigned char *vedge, int c)
|
|
{
|
|
int x1, x2, y1, y2, x, y, t;
|
|
|
|
x1 = ui->drag_start_x;
|
|
x2 = ui->drag_end_x;
|
|
if (x2 < x1) { t = x1; x1 = x2; x2 = t; }
|
|
|
|
y1 = ui->drag_start_y;
|
|
y2 = ui->drag_end_y;
|
|
if (y2 < y1) { t = y1; y1 = y2; y2 = t; }
|
|
|
|
x1 = x1 / 2; /* rounds down */
|
|
x2 = (x2+1) / 2; /* rounds up */
|
|
y1 = y1 / 2; /* rounds down */
|
|
y2 = (y2+1) / 2; /* rounds up */
|
|
|
|
/*
|
|
* Draw horizontal edges of rectangles.
|
|
*/
|
|
for (x = x1; x < x2; x++)
|
|
for (y = y1; y <= y2; y++)
|
|
if (HRANGE(state,x,y)) {
|
|
int val = index(state,hedge,x,y);
|
|
if (y == y1 || y == y2)
|
|
val = c;
|
|
else if (c == 1)
|
|
val = 0;
|
|
index(state,hedge,x,y) = val;
|
|
}
|
|
|
|
/*
|
|
* Draw vertical edges of rectangles.
|
|
*/
|
|
for (y = y1; y < y2; y++)
|
|
for (x = x1; x <= x2; x++)
|
|
if (VRANGE(state,x,y)) {
|
|
int val = index(state,vedge,x,y);
|
|
if (x == x1 || x == x2)
|
|
val = c;
|
|
else if (c == 1)
|
|
val = 0;
|
|
index(state,vedge,x,y) = val;
|
|
}
|
|
}
|
|
|
|
static game_state *make_move(game_state *from, game_ui *ui,
|
|
int x, int y, int button)
|
|
{
|
|
int xc, yc;
|
|
int startdrag = FALSE, enddrag = FALSE, active = FALSE;
|
|
game_state *ret;
|
|
|
|
if (button == LEFT_BUTTON) {
|
|
startdrag = TRUE;
|
|
} else if (button == LEFT_RELEASE) {
|
|
enddrag = TRUE;
|
|
} else if (button != LEFT_DRAG) {
|
|
return NULL;
|
|
}
|
|
|
|
coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
|
|
|
|
if (startdrag) {
|
|
ui->drag_start_x = xc;
|
|
ui->drag_start_y = yc;
|
|
ui->drag_end_x = xc;
|
|
ui->drag_end_y = yc;
|
|
ui->dragged = FALSE;
|
|
active = TRUE;
|
|
}
|
|
|
|
if (xc != ui->drag_end_x || yc != ui->drag_end_y) {
|
|
ui->drag_end_x = xc;
|
|
ui->drag_end_y = yc;
|
|
ui->dragged = TRUE;
|
|
active = TRUE;
|
|
}
|
|
|
|
ret = NULL;
|
|
|
|
if (enddrag) {
|
|
if (xc >= 0 && xc <= 2*from->w &&
|
|
yc >= 0 && yc <= 2*from->h) {
|
|
ret = dup_game(from);
|
|
|
|
if (ui->dragged) {
|
|
ui_draw_rect(ret, ui, ret->hedge, ret->vedge, 1);
|
|
} else {
|
|
if ((xc & 1) && !(yc & 1) && HRANGE(from,xc/2,yc/2)) {
|
|
hedge(ret,xc/2,yc/2) = !hedge(ret,xc/2,yc/2);
|
|
}
|
|
if ((yc & 1) && !(xc & 1) && VRANGE(from,xc/2,yc/2)) {
|
|
vedge(ret,xc/2,yc/2) = !vedge(ret,xc/2,yc/2);
|
|
}
|
|
}
|
|
|
|
if (!memcmp(ret->hedge, from->hedge, from->w*from->h) &&
|
|
!memcmp(ret->vedge, from->vedge, from->w*from->h)) {
|
|
free_game(ret);
|
|
ret = NULL;
|
|
}
|
|
|
|
/*
|
|
* We've made a real change to the grid. Check to see
|
|
* if the game has been completed.
|
|
*/
|
|
if (ret && !ret->completed) {
|
|
int x, y, ok;
|
|
unsigned char *correct = get_correct(ret);
|
|
|
|
ok = TRUE;
|
|
for (x = 0; x < ret->w; x++)
|
|
for (y = 0; y < ret->h; y++)
|
|
if (!index(ret, correct, x, y))
|
|
ok = FALSE;
|
|
|
|
sfree(correct);
|
|
|
|
if (ok)
|
|
ret->completed = TRUE;
|
|
}
|
|
}
|
|
|
|
ui->drag_start_x = -1;
|
|
ui->drag_start_y = -1;
|
|
ui->drag_end_x = -1;
|
|
ui->drag_end_y = -1;
|
|
ui->dragged = FALSE;
|
|
active = TRUE;
|
|
}
|
|
|
|
if (ret)
|
|
return ret; /* a move has been made */
|
|
else if (active)
|
|
return from; /* UI activity has occurred */
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
* Drawing routines.
|
|
*/
|
|
|
|
#define CORRECT 65536
|
|
|
|
#define COLOUR(k) ( (k)==1 ? COL_LINE : COL_DRAG )
|
|
#define MAX(x,y) ( (x)>(y) ? (x) : (y) )
|
|
#define MAX4(x,y,z,w) ( MAX(MAX(x,y),MAX(z,w)) )
|
|
|
|
struct game_drawstate {
|
|
int started;
|
|
int w, h;
|
|
unsigned int *visible;
|
|
};
|
|
|
|
static void game_size(game_params *params, int *x, int *y)
|
|
{
|
|
*x = params->w * TILE_SIZE + 2*BORDER + 1;
|
|
*y = params->h * TILE_SIZE + 2*BORDER + 1;
|
|
}
|
|
|
|
static float *game_colours(frontend *fe, game_state *state, int *ncolours)
|
|
{
|
|
float *ret = snewn(3 * NCOLOURS, float);
|
|
|
|
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
|
|
|
|
ret[COL_GRID * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0];
|
|
ret[COL_GRID * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1];
|
|
ret[COL_GRID * 3 + 2] = 0.5F * ret[COL_BACKGROUND * 3 + 2];
|
|
|
|
ret[COL_DRAG * 3 + 0] = 1.0F;
|
|
ret[COL_DRAG * 3 + 1] = 0.0F;
|
|
ret[COL_DRAG * 3 + 2] = 0.0F;
|
|
|
|
ret[COL_CORRECT * 3 + 0] = 0.75F * ret[COL_BACKGROUND * 3 + 0];
|
|
ret[COL_CORRECT * 3 + 1] = 0.75F * ret[COL_BACKGROUND * 3 + 1];
|
|
ret[COL_CORRECT * 3 + 2] = 0.75F * ret[COL_BACKGROUND * 3 + 2];
|
|
|
|
ret[COL_LINE * 3 + 0] = 0.0F;
|
|
ret[COL_LINE * 3 + 1] = 0.0F;
|
|
ret[COL_LINE * 3 + 2] = 0.0F;
|
|
|
|
ret[COL_TEXT * 3 + 0] = 0.0F;
|
|
ret[COL_TEXT * 3 + 1] = 0.0F;
|
|
ret[COL_TEXT * 3 + 2] = 0.0F;
|
|
|
|
*ncolours = NCOLOURS;
|
|
return ret;
|
|
}
|
|
|
|
static game_drawstate *game_new_drawstate(game_state *state)
|
|
{
|
|
struct game_drawstate *ds = snew(struct game_drawstate);
|
|
int i;
|
|
|
|
ds->started = FALSE;
|
|
ds->w = state->w;
|
|
ds->h = state->h;
|
|
ds->visible = snewn(ds->w * ds->h, unsigned int);
|
|
for (i = 0; i < ds->w * ds->h; i++)
|
|
ds->visible[i] = 0xFFFF;
|
|
|
|
return ds;
|
|
}
|
|
|
|
static void game_free_drawstate(game_drawstate *ds)
|
|
{
|
|
sfree(ds->visible);
|
|
sfree(ds);
|
|
}
|
|
|
|
static void draw_tile(frontend *fe, game_state *state, int x, int y,
|
|
unsigned char *hedge, unsigned char *vedge,
|
|
unsigned char *corners, int correct)
|
|
{
|
|
int cx = COORD(x), cy = COORD(y);
|
|
char str[80];
|
|
|
|
draw_rect(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1, COL_GRID);
|
|
draw_rect(fe, cx+1, cy+1, TILE_SIZE-1, TILE_SIZE-1,
|
|
correct ? COL_CORRECT : COL_BACKGROUND);
|
|
|
|
if (grid(state,x,y)) {
|
|
sprintf(str, "%d", grid(state,x,y));
|
|
draw_text(fe, cx+TILE_SIZE/2, cy+TILE_SIZE/2, FONT_VARIABLE,
|
|
TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
|
|
}
|
|
|
|
/*
|
|
* Draw edges.
|
|
*/
|
|
if (!HRANGE(state,x,y) || index(state,hedge,x,y))
|
|
draw_rect(fe, cx, cy, TILE_SIZE+1, 2,
|
|
HRANGE(state,x,y) ? COLOUR(index(state,hedge,x,y)) :
|
|
COL_LINE);
|
|
if (!HRANGE(state,x,y+1) || index(state,hedge,x,y+1))
|
|
draw_rect(fe, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2,
|
|
HRANGE(state,x,y+1) ? COLOUR(index(state,hedge,x,y+1)) :
|
|
COL_LINE);
|
|
if (!VRANGE(state,x,y) || index(state,vedge,x,y))
|
|
draw_rect(fe, cx, cy, 2, TILE_SIZE+1,
|
|
VRANGE(state,x,y) ? COLOUR(index(state,vedge,x,y)) :
|
|
COL_LINE);
|
|
if (!VRANGE(state,x+1,y) || index(state,vedge,x+1,y))
|
|
draw_rect(fe, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1,
|
|
VRANGE(state,x+1,y) ? COLOUR(index(state,vedge,x+1,y)) :
|
|
COL_LINE);
|
|
|
|
/*
|
|
* Draw corners.
|
|
*/
|
|
if (index(state,corners,x,y))
|
|
draw_rect(fe, cx, cy, 2, 2,
|
|
COLOUR(index(state,corners,x,y)));
|
|
if (x+1 < state->w && index(state,corners,x+1,y))
|
|
draw_rect(fe, cx+TILE_SIZE-1, cy, 2, 2,
|
|
COLOUR(index(state,corners,x+1,y)));
|
|
if (y+1 < state->h && index(state,corners,x,y+1))
|
|
draw_rect(fe, cx, cy+TILE_SIZE-1, 2, 2,
|
|
COLOUR(index(state,corners,x,y+1)));
|
|
if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1))
|
|
draw_rect(fe, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
|
|
COLOUR(index(state,corners,x+1,y+1)));
|
|
|
|
draw_update(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
|
|
}
|
|
|
|
static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
|
|
game_state *state, int dir, game_ui *ui,
|
|
float animtime, float flashtime)
|
|
{
|
|
int x, y;
|
|
unsigned char *correct;
|
|
unsigned char *hedge, *vedge, *corners;
|
|
|
|
correct = get_correct(state);
|
|
|
|
if (ui->dragged) {
|
|
hedge = snewn(state->w*state->h, unsigned char);
|
|
vedge = snewn(state->w*state->h, unsigned char);
|
|
memcpy(hedge, state->hedge, state->w*state->h);
|
|
memcpy(vedge, state->vedge, state->w*state->h);
|
|
ui_draw_rect(state, ui, hedge, vedge, 2);
|
|
} else {
|
|
hedge = state->hedge;
|
|
vedge = state->vedge;
|
|
}
|
|
|
|
corners = snewn(state->w * state->h, unsigned char);
|
|
memset(corners, 0, state->w * state->h);
|
|
for (x = 0; x < state->w; x++)
|
|
for (y = 0; y < state->h; y++) {
|
|
if (x > 0) {
|
|
int e = index(state, vedge, x, y);
|
|
if (index(state,corners,x,y) < e)
|
|
index(state,corners,x,y) = e;
|
|
if (y+1 < state->h &&
|
|
index(state,corners,x,y+1) < e)
|
|
index(state,corners,x,y+1) = e;
|
|
}
|
|
if (y > 0) {
|
|
int e = index(state, hedge, x, y);
|
|
if (index(state,corners,x,y) < e)
|
|
index(state,corners,x,y) = e;
|
|
if (x+1 < state->w &&
|
|
index(state,corners,x+1,y) < e)
|
|
index(state,corners,x+1,y) = e;
|
|
}
|
|
}
|
|
|
|
if (!ds->started) {
|
|
draw_rect(fe, 0, 0,
|
|
state->w * TILE_SIZE + 2*BORDER + 1,
|
|
state->h * TILE_SIZE + 2*BORDER + 1, COL_BACKGROUND);
|
|
draw_rect(fe, COORD(0)-1, COORD(0)-1,
|
|
ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE);
|
|
ds->started = TRUE;
|
|
draw_update(fe, 0, 0,
|
|
state->w * TILE_SIZE + 2*BORDER + 1,
|
|
state->h * TILE_SIZE + 2*BORDER + 1);
|
|
}
|
|
|
|
for (x = 0; x < state->w; x++)
|
|
for (y = 0; y < state->h; y++) {
|
|
unsigned int c = 0;
|
|
|
|
if (HRANGE(state,x,y))
|
|
c |= index(state,hedge,x,y);
|
|
if (HRANGE(state,x,y+1))
|
|
c |= index(state,hedge,x,y+1) << 2;
|
|
if (VRANGE(state,x,y))
|
|
c |= index(state,vedge,x,y) << 4;
|
|
if (VRANGE(state,x+1,y))
|
|
c |= index(state,vedge,x+1,y) << 6;
|
|
c |= index(state,corners,x,y) << 8;
|
|
if (x+1 < state->w)
|
|
c |= index(state,corners,x+1,y) << 10;
|
|
if (y+1 < state->h)
|
|
c |= index(state,corners,x,y+1) << 12;
|
|
if (x+1 < state->w && y+1 < state->h)
|
|
c |= index(state,corners,x+1,y+1) << 14;
|
|
if (index(state, correct, x, y) && !flashtime)
|
|
c |= CORRECT;
|
|
|
|
if (index(ds,ds->visible,x,y) != c) {
|
|
draw_tile(fe, state, x, y, hedge, vedge, corners, c & CORRECT);
|
|
index(ds,ds->visible,x,y) = c;
|
|
}
|
|
}
|
|
|
|
if (hedge != state->hedge) {
|
|
sfree(hedge);
|
|
sfree(vedge);
|
|
}
|
|
|
|
sfree(corners);
|
|
sfree(correct);
|
|
}
|
|
|
|
static float game_anim_length(game_state *oldstate,
|
|
game_state *newstate, int dir)
|
|
{
|
|
return 0.0F;
|
|
}
|
|
|
|
static float game_flash_length(game_state *oldstate,
|
|
game_state *newstate, int dir)
|
|
{
|
|
if (!oldstate->completed && newstate->completed)
|
|
return FLASH_TIME;
|
|
return 0.0F;
|
|
}
|
|
|
|
static int game_wants_statusbar(void)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
#ifdef COMBINED
|
|
#define thegame rect
|
|
#endif
|
|
|
|
const struct game thegame = {
|
|
"Rectangles", "games.rectangles",
|
|
default_params,
|
|
game_fetch_preset,
|
|
decode_params,
|
|
encode_params,
|
|
free_params,
|
|
dup_params,
|
|
TRUE, game_configure, custom_params,
|
|
validate_params,
|
|
new_game_seed,
|
|
validate_seed,
|
|
new_game,
|
|
dup_game,
|
|
free_game,
|
|
new_ui,
|
|
free_ui,
|
|
make_move,
|
|
game_size,
|
|
game_colours,
|
|
game_new_drawstate,
|
|
game_free_drawstate,
|
|
game_redraw,
|
|
game_anim_length,
|
|
game_flash_length,
|
|
game_wants_statusbar,
|
|
};
|