#include <stdlib.h>
#include <string.h>
#include <assert.h>
+#include <ctype.h>
#include <math.h>
#include "puzzles.h"
COL_LINE,
COL_TEXT,
COL_GRID,
+ COL_DRAG,
NCOLOURS
};
#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 )
int *grid; /* contains the numbers */
unsigned char *vedge; /* (w+1) x h */
unsigned char *hedge; /* w x (h+1) */
+ int completed;
};
game_params *default_params(void)
return ret;
}
+game_params *decode_params(char const *string)
+{
+ game_params *ret = default_params();
+
+ ret->w = ret->h = atoi(string);
+ while (*string && isdigit(*string)) string++;
+ if (*string == 'x') {
+ string++;
+ ret->h = atoi(string);
+ }
+
+ return ret;
+}
+
+char *encode_params(game_params *params)
+{
+ char data[256];
+
+ sprintf(data, "%dx%d", params->w, params->h);
+
+ return dupstr(data);
+}
+
config_item *game_configure(game_params *params)
{
config_item *ret;
{
if (params->w <= 0 && params->h <= 0)
return "Width and height must both be greater than zero";
- if (params->w * params->h < 4)
- return "Total area must be at least 4";
+ if (params->w < 2 && params->h < 2)
+ return "Grid area must be greater than one";
return NULL;
}
int nrects = 0, rectsize = 0;
/*
- * Maximum rectangle area is 1/6 of total grid size.
+ * 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++) {
continue;
for (x = 0; x <= params->w - rw; x++)
for (y = 0; y <= params->h - rh; y++) {
- /*
- * We have a candidate rectangle placement. See
- * if it's unobstructed.
- */
- int xx, yy;
- int ok;
-
- ok = TRUE;
- for (xx = x; xx < x+rw; xx++)
- for (yy = y; yy < y+rh; yy++)
- if (index(params, grid, xx, yy) >= 0) {
- ok = FALSE;
- goto break1; /* break both loops at once */
- }
- break1:
-
- if (!ok)
- continue;
-
if (nrects >= rectsize) {
rectsize = nrects + 256;
rects = sresize(rects, rectsize, struct rect);
} else if (n == '_') {
/* do nothing */;
} else if (n > '0' && n <= '9') {
- squares += atoi(seed-1);
+ squares++;
while (*seed >= '0' && *seed <= '9')
seed++;
} else
state->grid = snewn(area, int);
state->vedge = snewn(area, unsigned char);
state->hedge = snewn(area, unsigned char);
+ state->completed = FALSE;
i = 0;
while (*seed) {
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;
}
+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;
+};
+
game_ui *new_ui(game_state *state)
{
- return NULL;
+ 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;
}
void free_ui(game_ui *ui)
{
+ sfree(ui);
+}
+
+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;
+ }
}
game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
{
- float xf, yf, dx, dy;
- int hxr, hyr, vxr, vyr;
+ int xc, yc;
+ int startdrag = FALSE, enddrag = FALSE, active = FALSE;
game_state *ret;
- if (button != LEFT_BUTTON)
- return NULL;
+ 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;
- xf = FROMCOORD(((float)x));
- yf = FROMCOORD(((float)y));
+ if (enddrag) {
+ if (xc >= 0 && xc <= 2*from->w &&
+ yc >= 0 && yc <= 2*from->h) {
+ ret = dup_game(from);
- hxr = (int)xf;
- hyr = (int)(yf + 0.5F);
+ 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;
+ }
- vxr = (int)(xf + 0.5F);
- vyr = (int)yf;
+ /*
+ * 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);
- dx = fabs(xf - vxr);
- dy = fabs(yf - hyr);
+ ok = TRUE;
+ for (x = 0; x < ret->w; x++)
+ for (y = 0; y < ret->h; y++)
+ if (!index(ret, correct, x, y))
+ ok = FALSE;
- if (dy < dx && HRANGE(from,hxr,hyr)) {
- ret = dup_game(from);
- hedge(ret,hxr,hyr) = !hedge(ret,hxr,hyr);
- return ret;
- } else if (dx < dy && VRANGE(from,vxr,vyr)) {
- ret = dup_game(from);
- vedge(ret,vxr,vyr) = !vedge(ret,vxr,vyr);
- return ret;
+ 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;
}
- return NULL;
+ if (ret)
+ return ret; /* a move has been made */
+ else if (active)
+ return from; /* UI activity has occurred */
+ else
+ return NULL;
}
/* ----------------------------------------------------------------------
* Drawing routines.
*/
-#define L 1
-#define R 2
-#define U 4
-#define D 8
-#define CORRECT 16
+#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 char *visible;
+ unsigned int *visible;
};
void game_size(game_params *params, int *x, int *y)
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];
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 char);
- memset(ds->visible, 0xFF, ds->w * ds->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;
}
sfree(ds);
}
-void draw_tile(frontend *fe, game_state *state, int x, int y, int correct)
+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];
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/3, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
+ TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_TEXT, str);
}
/*
* Draw edges.
*/
- if (!HRANGE(state,x,y) || hedge(state,x,y))
- draw_rect(fe, cx, cy, TILE_SIZE+1, 2, COL_LINE);
- if (!HRANGE(state,x,y+1) || hedge(state,x,y+1))
- draw_rect(fe, cx, cy+TILE_SIZE-1, TILE_SIZE+1, 2, COL_LINE);
- if (!VRANGE(state,x,y) || vedge(state,x,y))
- draw_rect(fe, cx, cy, 2, TILE_SIZE+1, COL_LINE);
- if (!VRANGE(state,x+1,y) || vedge(state,x+1,y))
- draw_rect(fe, cx+TILE_SIZE-1, cy, 2, TILE_SIZE+1, COL_LINE);
+ 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 ((HRANGE(state,x-1,y) && hedge(state,x-1,y)) ||
- (VRANGE(state,x,y-1) && vedge(state,x,y-1)))
- draw_rect(fe, cx, cy, 2, 2, COL_LINE);
- if ((HRANGE(state,x+1,y) && hedge(state,x+1,y)) ||
- (VRANGE(state,x+1,y-1) && vedge(state,x+1,y-1)))
- draw_rect(fe, cx+TILE_SIZE-1, cy, 2, 2, COL_LINE);
- if ((HRANGE(state,x-1,y+1) && hedge(state,x-1,y+1)) ||
- (VRANGE(state,x,y+1) && vedge(state,x,y+1)))
- draw_rect(fe, cx, cy+TILE_SIZE-1, 2, 2, COL_LINE);
- if ((HRANGE(state,x+1,y+1) && hedge(state,x+1,y+1)) ||
- (VRANGE(state,x+1,y+1) && vedge(state,x+1,y+1)))
- draw_rect(fe, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2, COL_LINE);
+ 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);
}
{
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 char c = 0;
-
- if (!HRANGE(state,x,y) || hedge(state,x,y))
- c |= L;
- if (!HRANGE(state,x+1,y) || hedge(state,x+1,y))
- c |= R;
- if (!VRANGE(state,x,y) || vedge(state,x,y))
- c |= U;
- if (!VRANGE(state,x,y+1) || vedge(state,x,y+1))
- c |= D;
- if (index(state, correct, x, 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, c & CORRECT);
- //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);
}
float game_flash_length(game_state *oldstate, game_state *newstate)
{
+ if (!oldstate->completed && newstate->completed)
+ return FLASH_TIME;
return 0.0F;
}