From b6b0369e58b13a0de27f1758af5e385a1d786e7a Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 7 Dec 2004 20:00:58 +0000 Subject: [PATCH] New puzzle: `pattern'. git-svn-id: svn://svn.tartarus.org/sgt/puzzles@4953 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 2 + pattern.c | 975 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ puzzles.but | 44 ++- 3 files changed, 1019 insertions(+), 2 deletions(-) create mode 100644 pattern.c diff --git a/Recipe b/Recipe index c6ecd8d..f68c738 100644 --- a/Recipe +++ b/Recipe @@ -23,6 +23,7 @@ cube : [X] gtk COMMON cube fifteen : [X] gtk COMMON fifteen sixteen : [X] gtk COMMON sixteen rect : [X] gtk COMMON rect +pattern : [X] gtk COMMON pattern # The Windows Net shouldn't be called `net.exe' since Windows # already has a reasonably important utility program by that name! @@ -32,6 +33,7 @@ cube : [G] WINDOWS COMMON cube fifteen : [G] WINDOWS COMMON fifteen sixteen : [G] WINDOWS COMMON sixteen rect : [G] WINDOWS COMMON rect +pattern : [G] WINDOWS COMMON pattern # The `nullgame' source file is a largely blank one, which contains # all the correct function definitions to compile and link, but diff --git a/pattern.c b/pattern.c new file mode 100644 index 0000000..d597127 --- /dev/null +++ b/pattern.c @@ -0,0 +1,975 @@ +/* + * pattern.c: the pattern-reconstruction game known as `nonograms'. + * + * TODO before checkin: + * + * - make some sort of stab at number-of-numbers judgment + */ + +#include +#include +#include +#include +#include +#include + +#include "puzzles.h" + +#define max(x,y) ( (x)>(y) ? (x):(y) ) +#define min(x,y) ( (x)<(y) ? (x):(y) ) + +const char *const game_name = "Pattern"; +const char *const game_winhelp_topic = "games.pattern"; +const int game_can_configure = TRUE; + +enum { + COL_BACKGROUND, + COL_EMPTY, + COL_FULL, + COL_UNKNOWN, + COL_GRID, + NCOLOURS +}; + +#define BORDER 18 +#define TLBORDER(d) ( (d) / 5 + 2 ) +#define GUTTER 12 +#define TILE_SIZE 24 + +#define FROMCOORD(d, x) \ + ( ((x) - (BORDER + GUTTER + TILE_SIZE * TLBORDER(d))) / TILE_SIZE ) + +#define SIZE(d) (2*BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (d))) + +#define TOCOORD(d, x) (BORDER + GUTTER + TILE_SIZE * (TLBORDER(d) + (x))) + +struct game_params { + int w, h; +}; + +#define GRID_UNKNOWN 2 +#define GRID_FULL 1 +#define GRID_EMPTY 0 + +struct game_state { + int w, h; + unsigned char *grid; + int rowsize; + int *rowdata, *rowlen; + int completed; +}; + +#define FLASH_TIME 0.13F + +game_params *default_params(void) +{ + game_params *ret = snew(game_params); + + ret->w = ret->h = 15; + + return ret; +} + +int game_fetch_preset(int i, char **name, game_params **params) +{ + game_params *ret; + char str[80]; + static const struct { int x, y; } values[] = { + {10, 10}, + {15, 15}, + {20, 20}, + {25, 25}, + {30, 30}, + }; + + if (i < 0 || i >= lenof(values)) + return FALSE; + + ret = snew(game_params); + ret->w = values[i].x; + ret->h = values[i].y; + + sprintf(str, "%dx%d", ret->w, ret->h); + + *name = dupstr(str); + *params = ret; + return TRUE; +} + +void free_params(game_params *params) +{ + sfree(params); +} + +game_params *dup_params(game_params *params) +{ + game_params *ret = snew(game_params); + *ret = *params; /* structure copy */ + return ret; +} + +game_params *decode_params(char const *string) +{ + game_params *ret = default_params(); + char const *p = string; + + ret->w = atoi(p); + while (*p && isdigit(*p)) p++; + if (*p == 'x') { + p++; + ret->h = atoi(p); + while (*p && isdigit(*p)) p++; + } else { + ret->h = ret->w; + } + + return ret; +} + +char *encode_params(game_params *params) +{ + char ret[400]; + int len; + + len = sprintf(ret, "%dx%d", params->w, params->h); + assert(len < lenof(ret)); + ret[len] = '\0'; + + return dupstr(ret); +} + +config_item *game_configure(game_params *params) +{ + config_item *ret; + char buf[80]; + + ret = snewn(3, 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 = NULL; + ret[2].type = C_END; + ret[2].sval = NULL; + ret[2].ival = 0; + + return ret; +} + +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); + + return ret; +} + +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 <= 0) + return "Width must be greater than zero"; + if (params->h <= 0) + return "Height must be greater than zero"; + return NULL; +} + +/* ---------------------------------------------------------------------- + * Puzzle generation code. + * + * For this particular puzzle, it seemed important to me to ensure + * a unique solution. I do this the brute-force way, by having a + * solver algorithm alongside the generator, and repeatedly + * generating a random grid until I find one whose solution is + * unique. It turns out that this isn't too onerous on a modern PC + * provided you keep grid size below around 30. Any offers of + * better algorithms, however, will be very gratefully received. + * + * Another annoyance of this approach is that it limits the + * available puzzles to those solvable by the algorithm I've used. + * My algorithm only ever considers a single row or column at any + * one time, which means it's incapable of solving the following + * difficult example (found by Bella Image around 1995/6, when she + * and I were both doing maths degrees): + * + * 2 1 2 1 + * + * +--+--+--+--+ + * 1 1 | | | | | + * +--+--+--+--+ + * 2 | | | | | + * +--+--+--+--+ + * 1 | | | | | + * +--+--+--+--+ + * 1 | | | | | + * +--+--+--+--+ + * + * Obviously this cannot be solved by a one-row-or-column-at-a-time + * algorithm (it would require at least one row or column reading + * `2 1', `1 2', `3' or `4' to get started). However, it can be + * proved to have a unique solution: if the top left square were + * empty, then the only option for the top row would be to fill the + * two squares in the 1 columns, which would imply the squares + * below those were empty, leaving no place for the 2 in the second + * row. Contradiction. Hence the top left square is full, and the + * unique solution follows easily from that starting point. + * + * (The game ID for this puzzle is 4x4:2/1/2/1/1.1/2/1/1 , in case + * it's useful to anyone.) + */ + +static int float_compare(const void *av, const void *bv) +{ + const float *a = (const float *)av; + const float *b = (const float *)bv; + if (*a < *b) + return -1; + else if (*a > *b) + return +1; + else + return 0; +} + +static void generate(random_state *rs, int w, int h, unsigned char *retgrid) +{ + float *fgrid; + float *fgrid2; + int step, i, j; + float threshold; + + fgrid = snewn(w*h, float); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + fgrid[i*w+j] = random_upto(rs, 100000000UL) / 100000000.F; + } + } + + /* + * The above gives a completely random splattering of black and + * white cells. We want to gently bias this in favour of _some_ + * reasonably thick areas of white and black, while retaining + * some randomness and fine detail. + * + * So we evolve the starting grid using a cellular automaton. + * Currently, I'm doing something very simple indeed, which is + * to set each square to the average of the surrounding nine + * cells (or the average of fewer, if we're on a corner). + */ + for (step = 0; step < 1; step++) { + fgrid2 = snewn(w*h, float); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + float sx, xbar; + int n, p, q; + + /* + * Compute the average of the surrounding cells. + */ + n = 0; + sx = 0.F; + for (p = -1; p <= +1; p++) { + for (q = -1; q <= +1; q++) { + if (i+p < 0 || i+p >= h || j+q < 0 || j+q >= w) + continue; + n++; + sx += fgrid[(i+p)*w+(j+q)]; + } + } + xbar = sx / n; + + fgrid2[i*w+j] = xbar; + } + } + + sfree(fgrid); + fgrid = fgrid2; + } + + fgrid2 = snewn(w*h, float); + memcpy(fgrid2, fgrid, w*h*sizeof(float)); + qsort(fgrid2, w*h, sizeof(float), float_compare); + threshold = fgrid2[w*h/2]; + sfree(fgrid2); + + for (i = 0; i < h; i++) { + for (j = 0; j < w; j++) { + retgrid[i*w+j] = (fgrid[i*w+j] > threshold ? GRID_FULL : + GRID_EMPTY); + } + } + + sfree(fgrid); +} + +int compute_rowdata(int *ret, unsigned char *start, int len, int step) +{ + int i, n; + + n = 0; + + for (i = 0; i < len; i++) { + if (start[i*step] == GRID_UNKNOWN) + return -1; + + if (start[i*step] == GRID_FULL) { + int runlen = 1; + while (i+runlen < len && start[(i+runlen)*step]) + runlen++; + ret[n++] = runlen; + i += runlen; + } + } + + return n; +} + +#define UNKNOWN 0 +#define BLOCK 1 +#define DOT 2 +#define STILL_UNKNOWN 3 + +static void do_recurse(unsigned char *known, unsigned char *deduced, + unsigned char *row, int *data, int len, + int freespace, int ndone, int lowest) +{ + int i, j, k; + + if (data[ndone]) { + for (i=0; i<=freespace; i++) { + j = lowest; + for (k=0; kw, params->h); + max = max(params->w, params->h); + rowdata = snewn(max, int); + + /* + * Seed is a slash-separated list of row contents; each row + * contents section is a dot-separated list of integers. Row + * contents are listed in the order (columns left to right, + * then rows top to bottom). + * + * Simplest way to handle memory allocation is to make two + * passes, first computing the seed size and then writing it + * out. + */ + seedlen = 0; + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w); + else + rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w, + params->w, 1); + if (rowlen > 0) { + for (j = 0; j < rowlen; j++) { + seedlen += 1 + sprintf(intbuf, "%d", rowdata[j]); + } + } else { + seedlen++; + } + } + seed = snewn(seedlen, char); + seedpos = 0; + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowlen = compute_rowdata(rowdata, grid+i, params->h, params->w); + else + rowlen = compute_rowdata(rowdata, grid+(i-params->w)*params->w, + params->w, 1); + if (rowlen > 0) { + for (j = 0; j < rowlen; j++) { + int len = sprintf(seed+seedpos, "%d", rowdata[j]); + if (j+1 < rowlen) + seed[seedpos + len] = '.'; + else + seed[seedpos + len] = '/'; + seedpos += len+1; + } + } else { + seed[seedpos++] = '/'; + } + } + assert(seedpos == seedlen); + assert(seed[seedlen-1] == '/'); + seed[seedlen-1] = '\0'; + sfree(rowdata); + return seed; +} + +char *validate_seed(game_params *params, char *seed) +{ + int i, n, rowspace; + char *p; + + for (i = 0; i < params->w + params->h; i++) { + if (i < params->w) + rowspace = params->h + 1; + else + rowspace = params->w + 1; + + if (*seed && isdigit((unsigned char)*seed)) { + do { + p = seed; + while (seed && isdigit((unsigned char)*seed)) seed++; + n = atoi(p); + rowspace -= n+1; + + if (rowspace < 0) { + if (i < params->w) + return "at least one column contains more numbers than will fit"; + else + return "at least one row contains more numbers than will fit"; + } + } while (*seed++ == '.'); + } else { + seed++; /* expect a slash immediately */ + } + + if (seed[-1] == '/') { + if (i+1 == params->w + params->h) + return "too many row/column specifications"; + } else if (seed[-1] == '\0') { + if (i+1 < params->w + params->h) + return "too few row/column specifications"; + } else + return "unrecognised character in game specification"; + } + + return NULL; +} + +game_state *new_game(game_params *params, char *seed) +{ + int i; + char *p; + game_state *state = snew(game_state); + + state->w = params->w; + state->h = params->h; + + state->grid = snewn(state->w * state->h, unsigned char); + memset(state->grid, GRID_UNKNOWN, state->w * state->h); + + state->rowsize = max(state->w, state->h); + state->rowdata = snewn(state->rowsize * (state->w + state->h), int); + state->rowlen = snewn(state->w + state->h, int); + + state->completed = FALSE; + + for (i = 0; i < params->w + params->h; i++) { + state->rowlen[i] = 0; + if (*seed && isdigit((unsigned char)*seed)) { + do { + p = seed; + while (seed && isdigit((unsigned char)*seed)) seed++; + state->rowdata[state->rowsize * i + state->rowlen[i]++] = + atoi(p); + } while (*seed++ == '.'); + } else { + seed++; /* expect a slash immediately */ + } + } + + return state; +} + +game_state *dup_game(game_state *state) +{ + game_state *ret = snew(game_state); + + ret->w = state->w; + ret->h = state->h; + + ret->grid = snewn(ret->w * ret->h, unsigned char); + memcpy(ret->grid, state->grid, ret->w * ret->h); + + ret->rowsize = state->rowsize; + ret->rowdata = snewn(ret->rowsize * (ret->w + ret->h), int); + ret->rowlen = snewn(ret->w + ret->h, int); + memcpy(ret->rowdata, state->rowdata, + ret->rowsize * (ret->w + ret->h) * sizeof(int)); + memcpy(ret->rowlen, state->rowlen, + (ret->w + ret->h) * sizeof(int)); + + ret->completed = state->completed; + + return ret; +} + +void free_game(game_state *state) +{ + sfree(state->rowdata); + sfree(state->rowlen); + sfree(state->grid); + sfree(state); +} + +struct game_ui { + int dragging; + int drag_start_x; + int drag_start_y; + int drag_end_x; + int drag_end_y; + int drag, release, state; +}; + +game_ui *new_ui(game_state *state) +{ + game_ui *ret; + + ret = snew(game_ui); + ret->dragging = FALSE; + + return ret; +} + +void free_ui(game_ui *ui) +{ + sfree(ui); +} + +game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button) +{ + game_state *ret; + + x = FROMCOORD(from->w, x); + y = FROMCOORD(from->h, y); + + if (x >= 0 && x < from->w && y >= 0 && y < from->h && + (button == LEFT_BUTTON || button == RIGHT_BUTTON || + button == MIDDLE_BUTTON)) { + + ui->dragging = TRUE; + + if (button == LEFT_BUTTON) { + ui->drag = LEFT_DRAG; + ui->release = LEFT_RELEASE; + ui->state = GRID_FULL; + } else if (button == RIGHT_BUTTON) { + ui->drag = RIGHT_DRAG; + ui->release = RIGHT_RELEASE; + ui->state = GRID_EMPTY; + } else /* if (button == MIDDLE_BUTTON) */ { + ui->drag = MIDDLE_DRAG; + ui->release = MIDDLE_RELEASE; + ui->state = GRID_UNKNOWN; + } + + ui->drag_start_x = ui->drag_end_x = x; + ui->drag_start_y = ui->drag_end_y = y; + + return from; /* UI activity occurred */ + } + + if (ui->dragging && button == ui->drag) { + /* + * There doesn't seem much point in allowing a rectangle + * drag; people will generally only want to drag a single + * horizontal or vertical line, so we make that easy by + * snapping to it. + * + * Exception: if we're _middle_-button dragging to tag + * things as UNKNOWN, we may well want to trash an entire + * area and start over! + */ + if (ui->state != GRID_UNKNOWN) { + if (abs(x - ui->drag_start_x) > abs(y - ui->drag_start_y)) + y = ui->drag_start_y; + else + x = ui->drag_start_x; + } + + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x >= from->w) x = from->w - 1; + if (y >= from->h) y = from->h - 1; + + ui->drag_end_x = x; + ui->drag_end_y = y; + + return from; /* UI activity occurred */ + } + + if (ui->dragging && button == ui->release) { + int x1, x2, y1, y2, xx, yy; + int move_needed = FALSE; + + x1 = min(ui->drag_start_x, ui->drag_end_x); + x2 = max(ui->drag_start_x, ui->drag_end_x); + y1 = min(ui->drag_start_y, ui->drag_end_y); + y2 = max(ui->drag_start_y, ui->drag_end_y); + + for (yy = y1; yy <= y2; yy++) + for (xx = x1; xx <= x2; xx++) + if (from->grid[yy * from->w + xx] != ui->state) + move_needed = TRUE; + + ui->dragging = FALSE; + + if (move_needed) { + ret = dup_game(from); + for (yy = y1; yy <= y2; yy++) + for (xx = x1; xx <= x2; xx++) + ret->grid[yy * ret->w + xx] = ui->state; + + /* + * An actual change, so check to see if we've completed + * the game. + */ + if (!ret->completed) { + int *rowdata = snewn(ret->rowsize, int); + int i, len; + + ret->completed = TRUE; + + for (i=0; iw; i++) { + len = compute_rowdata(rowdata, + ret->grid+i, ret->h, ret->w); + if (len != ret->rowlen[i] || + memcmp(ret->rowdata+i*ret->rowsize, rowdata, + len * sizeof(int))) { + ret->completed = FALSE; + break; + } + } + for (i=0; ih; i++) { + len = compute_rowdata(rowdata, + ret->grid+i*ret->w, ret->w, 1); + if (len != ret->rowlen[i+ret->w] || + memcmp(ret->rowdata+(i+ret->w)*ret->rowsize, rowdata, + len * sizeof(int))) { + ret->completed = FALSE; + break; + } + } + + sfree(rowdata); + } + + return ret; + } else + return from; /* UI activity occurred */ + } + + return NULL; +} + +/* ---------------------------------------------------------------------- + * Drawing routines. + */ + +struct game_drawstate { + int started; + int w, h; + unsigned char *visible; +}; + +void game_size(game_params *params, int *x, int *y) +{ + *x = SIZE(params->w); + *y = SIZE(params->h); +} + +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.3F; + ret[COL_GRID * 3 + 1] = 0.3F; + ret[COL_GRID * 3 + 2] = 0.3F; + + ret[COL_UNKNOWN * 3 + 0] = 0.5F; + ret[COL_UNKNOWN * 3 + 1] = 0.5F; + ret[COL_UNKNOWN * 3 + 2] = 0.5F; + + ret[COL_FULL * 3 + 0] = 0.0F; + ret[COL_FULL * 3 + 1] = 0.0F; + ret[COL_FULL * 3 + 2] = 0.0F; + + ret[COL_EMPTY * 3 + 0] = 1.0F; + ret[COL_EMPTY * 3 + 1] = 1.0F; + ret[COL_EMPTY * 3 + 2] = 1.0F; + + *ncolours = NCOLOURS; + return ret; +} + +game_drawstate *game_new_drawstate(game_state *state) +{ + struct game_drawstate *ds = snew(struct game_drawstate); + + ds->started = FALSE; + ds->w = state->w; + ds->h = state->h; + ds->visible = snewn(ds->w * ds->h, unsigned char); + memset(ds->visible, 255, ds->w * ds->h); + + return ds; +} + +void game_free_drawstate(game_drawstate *ds) +{ + sfree(ds->visible); + sfree(ds); +} + +static void grid_square(frontend *fe, game_drawstate *ds, + int y, int x, int state) +{ + int xl, xr, yt, yb; + + draw_rect(fe, TOCOORD(ds->w, x), TOCOORD(ds->h, y), + TILE_SIZE, TILE_SIZE, COL_GRID); + + xl = (x % 5 == 0 ? 1 : 0); + yt = (y % 5 == 0 ? 1 : 0); + xr = (x % 5 == 4 || x == ds->w-1 ? 1 : 0); + yb = (y % 5 == 4 || y == ds->h-1 ? 1 : 0); + + draw_rect(fe, TOCOORD(ds->w, x) + 1 + xl, TOCOORD(ds->h, y) + 1 + yt, + TILE_SIZE - xl - xr - 1, TILE_SIZE - yt - yb - 1, + (state == GRID_FULL ? COL_FULL : + state == GRID_EMPTY ? COL_EMPTY : COL_UNKNOWN)); + + draw_update(fe, TOCOORD(ds->w, x), TOCOORD(ds->h, y), + TILE_SIZE, TILE_SIZE); +} + +void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, + game_state *state, int dir, game_ui *ui, + float animtime, float flashtime) +{ + int i, j; + int x1, x2, y1, y2; + + if (!ds->started) { + /* + * The initial contents of the window are not guaranteed + * and can vary with front ends. To be on the safe side, + * all games should start by drawing a big background- + * colour rectangle covering the whole window. + */ + draw_rect(fe, 0, 0, SIZE(ds->w), SIZE(ds->h), COL_BACKGROUND); + + /* + * Draw the numbers. + */ + for (i = 0; i < ds->w + ds->h; i++) { + int rowlen = state->rowlen[i]; + int *rowdata = state->rowdata + state->rowsize * i; + int nfit; + + /* + * Normally I space the numbers out by the same + * distance as the tile size. However, if there are + * more numbers than available spaces, I have to squash + * them up a bit. + */ + nfit = max(rowlen, TLBORDER(ds->h))-1; + assert(nfit > 0); + + for (j = 0; j < rowlen; j++) { + int x, y; + char str[80]; + + if (i < ds->w) { + x = TOCOORD(ds->w, i); + y = BORDER + TILE_SIZE * (TLBORDER(ds->h)-1); + y -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(ds->h)-1) / nfit; + } else { + y = TOCOORD(ds->h, i - ds->w); + x = BORDER + TILE_SIZE * (TLBORDER(ds->w)-1); + x -= ((rowlen-j-1)*TILE_SIZE) * (TLBORDER(ds->h)-1) / nfit; + } + + sprintf(str, "%d", rowdata[j]); + draw_text(fe, x+TILE_SIZE/2, y+TILE_SIZE/2, FONT_VARIABLE, + TILE_SIZE/2, ALIGN_HCENTRE | ALIGN_VCENTRE, + COL_FULL, str); /* FIXME: COL_TEXT */ + } + } + + /* + * Draw the grid outline. + */ + draw_rect(fe, TOCOORD(ds->w, 0) - 1, TOCOORD(ds->h, 0) - 1, + ds->w * TILE_SIZE + 2, ds->h * TILE_SIZE + 2, + COL_GRID); + + ds->started = TRUE; + + draw_update(fe, 0, 0, SIZE(ds->w), SIZE(ds->h)); + } + + if (ui->dragging) { + x1 = min(ui->drag_start_x, ui->drag_end_x); + x2 = max(ui->drag_start_x, ui->drag_end_x); + y1 = min(ui->drag_start_y, ui->drag_end_y); + y2 = max(ui->drag_start_y, ui->drag_end_y); + } else { + x1 = x2 = y1 = y2 = -1; /* placate gcc warnings */ + } + + /* + * Now draw any grid squares which have changed since last + * redraw. + */ + for (i = 0; i < ds->h; i++) { + for (j = 0; j < ds->w; j++) { + int val; + + /* + * Work out what state this square should be drawn in, + * taking any current drag operation into account. + */ + if (ui->dragging && x1 <= j && j <= x2 && y1 <= i && i <= y2) + val = ui->state; + else + val = state->grid[i * state->w + j]; + + /* + * Briefly invert everything twice during a completion + * flash. + */ + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3) && + val != GRID_UNKNOWN) + val = (GRID_FULL ^ GRID_EMPTY) ^ val; + + if (ds->visible[i * ds->w + j] != val) { + grid_square(fe, ds, i, j, val); + ds->visible[i * ds->w + j] = val; + } + } + } +} + +float game_anim_length(game_state *oldstate, game_state *newstate, int dir) +{ + return 0.0F; +} + +float game_flash_length(game_state *oldstate, game_state *newstate, int dir) +{ + if (!oldstate->completed && newstate->completed) + return FLASH_TIME; + return 0.0F; +} + +int game_wants_statusbar(void) +{ + return FALSE; +} diff --git a/puzzles.but b/puzzles.but index 354a35d..15f73c5 100644 --- a/puzzles.but +++ b/puzzles.but @@ -22,7 +22,7 @@ This is a collection of small one-player puzzle games. reserved. You may distribute this documentation under the MIT licence. See \k{licence} for the licence text in full. -\versionid $Id: puzzles.but,v 1.3 2004/08/16 13:17:40 simon Exp $ +\versionid $Id$ \C{intro} Introduction @@ -467,13 +467,53 @@ generation of Net (see \k{net}) with the movement of Sixteen (see into place you have to slide them into place by moving a whole row at a time. - As in Sixteen, \I{controls, for Netslide}control is with the mouse. See \k{sixteen-controls}. \I{parameters, for Netslide}Game parameters are the same as for Net (see \k{net-params}). +\C{pattern} \i{Pattern} + +\cfg{winhelp-topic}{games.pattern} + +You have a grid of squares, which must all be filled in either black +or white. Beside each row of the grid are listed the lengths of the +runs of black squares on that row; above each column are listed the +lengths of the runs of black squares in that column. Your aim is to +fill in the entire grid black or white. + +I first saw this puzzle form around 1995, under the name +\q{nonograms}. I've seen it in various places since then, under +different names. + +Normally, puzzles of this type turn out to be a meaningful picture +of something once you've solved them. However, since this version +generates the puzzles automatically, they will just look like random +groupings of squares. (One user has suggested that this is actually +a \e{good} thing, since it prevents you from guessing the colour of +squares based on the picture, and forces you to use logic instead.) +The advantage, though, is that you never run out of them. + +\H{pattern-controls} \i{Pattern controls} + +This game is played with the mouse. + +Left-click in a square to colour it black. Right-click to colour it +white. If you make a mistake, you can middle-click, or hold down +Shift while clicking with any button, to colour the square in the +default grey (meaning \q{undecided}) again. + +You can click and drag with the left or right mouse button to colour +a vertical or horizontal line of squares black or white at a time +(respectively). If you click and drag with the middle button, or +with Shift held down, you can colour a whole rectangle of squares +grey. + +\H{pattern-parameters} \I{parameters, for Pattern}Pattern parameters + +The only options available from the \q{Custom...} option on the \q{Type} +menu are \e{Width} and \e{Height}, which are self-explanatory. \A{licence} \I{MIT licence}\ii{Licence} -- 2.11.0