X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=pattern.c;h=9a74e553186bb1b35c3f3f5ac476107423879c08;hb=a0a581c8b5422bf0c5ed3fde6aa25811e4eb89fc;hp=d7f71bede530c8ad8b45ce2bfbe2b2e5c5146a57;hpb=d1219cac3c2e0adf58a477e442a8656bcb55ed0f;p=sgt-puzzles.git diff --git a/pattern.c b/pattern.c index d7f71be..9a74e55 100644 --- a/pattern.c +++ b/pattern.c @@ -50,6 +50,7 @@ typedef struct game_state_common { int w, h; int rowsize; int *rowdata, *rowlen; + unsigned char *immutable; int refcount; } game_state_common; @@ -225,6 +226,7 @@ static char *validate_params(const game_params *params, int full) * it's useful to anyone.) */ +#ifndef STANDALONE_PICTURE_GENERATOR static int float_compare(const void *av, const void *bv) { const float *a = (const float *)av; @@ -318,6 +320,7 @@ static void generate(random_state *rs, int w, int h, unsigned char *retgrid) sfree(fgrid); } +#endif static int compute_rowdata(int *ret, unsigned char *start, int len, int step) { @@ -494,21 +497,27 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, int i, j, ok, max; int max_h, max_w; - assert((state!=NULL) ^ (grid!=NULL)); + assert((state!=NULL && state->common->rowdata!=NULL) ^ (grid!=NULL)); max = max(w, h); memset(matrix, 0, w*h); + if (state) { + for (i=0; icommon->immutable[i]) + matrix[i] = state->grid[i]; + } + } /* For each column, compute how many squares can be deduced - * from just the row-data. + * from just the row-data and initial clues. * Later, changed_* will hold how many squares were changed * in every row/column in the previous iteration * Changed_* is used to choose the next rows / cols to re-examine */ for (i=0; icommon->rowdata) { memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int)); rowlen = state->common->rowlen[w+i]; } else { @@ -524,13 +533,16 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, if (rowdata[j] > freespace) changed_h[i] += rowdata[j] - freespace; } + for (j = 0; j < w; j++) + if (matrix[i*w+j]) + changed_h[i]++; } for (i=0,max_h=0; i max_h) max_h = changed_h[i]; for (i=0; icommon->rowdata) { memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int)); rowlen = state->common->rowlen[i]; } else { @@ -546,6 +558,9 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, if (rowdata[j] > freespace) changed_w[i] += rowdata[j] - freespace; } + for (j = 0; j < h; j++) + if (matrix[j*w+i]) + changed_w[i]++; } for (i=0,max_w=0; i max_w) @@ -564,7 +579,7 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, for (; max_h && max_h >= max_w; max_h--) { for (i=0; i= max_h) { - if (state) { + if (state && state->common->rowdata) { memcpy(rowdata, state->common->rowdata + state->common->rowsize*(w+i), max*sizeof(int)); rowdata[state->common->rowlen[w+i]] = 0; } else { @@ -588,7 +603,7 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, for (; max_w && max_w >= max_h; max_w--) { for (i=0; i= max_w) { - if (state) { + if (state && state->common->rowdata) { memcpy(rowdata, state->common->rowdata + state->common->rowsize*i, max*sizeof(int)); rowdata[state->common->rowlen[i]] = 0; } else { @@ -622,6 +637,7 @@ static int solve_puzzle(const game_state *state, unsigned char *grid, return ok; } +#ifndef STANDALONE_PICTURE_GENERATOR static unsigned char *generate_soluble(random_state *rs, int w, int h) { int i, j, ok, ntries, max; @@ -685,6 +701,11 @@ static unsigned char *generate_soluble(random_state *rs, int w, int h) sfree(rowdata); return grid; } +#endif + +#ifdef STANDALONE_PICTURE_GENERATOR +unsigned char *picture; +#endif static char *new_game_desc(const game_params *params, random_state *rs, char **aux, int interactive) @@ -693,15 +714,63 @@ static char *new_game_desc(const game_params *params, random_state *rs, int i, j, max, rowlen, *rowdata; char intbuf[80], *desc; int desclen, descpos; +#ifdef STANDALONE_PICTURE_GENERATOR + game_state *state; + int *index; +#endif - grid = generate_soluble(rs, params->w, params->h); max = max(params->w, params->h); + +#ifdef STANDALONE_PICTURE_GENERATOR + /* + * Fixed input picture. + */ + grid = snewn(params->w * params->h, unsigned char); + memcpy(grid, picture, params->w * params->h); + + /* + * Now winnow the immutable square set as far as possible. + */ + state = snew(game_state); + state->grid = grid; + state->common = snew(game_state_common); + state->common->rowdata = NULL; + state->common->immutable = snewn(params->w * params->h, unsigned char); + memset(state->common->immutable, 1, params->w * params->h); + + index = snewn(params->w * params->h, int); + for (i = 0; i < params->w * params->h; i++) + index[i] = i; + shuffle(index, params->w * params->h, sizeof(*index), rs); + + { + unsigned char *matrix = snewn(params->w*params->h, unsigned char); + unsigned char *workspace = snewn(max*7, unsigned char); + unsigned int *changed_h = snewn(max+1, unsigned int); + unsigned int *changed_w = snewn(max+1, unsigned int); + int *rowdata = snewn(max+1, int); + for (i = 0; i < params->w * params->h; i++) { + state->common->immutable[index[i]] = 0; + if (!solve_puzzle(state, grid, params->w, params->h, + matrix, workspace, changed_h, changed_w, + rowdata, 0)) + state->common->immutable[index[i]] = 1; + } + sfree(workspace); + sfree(changed_h); + sfree(changed_w); + sfree(rowdata); + sfree(matrix); + } +#else + grid = generate_soluble(rs, params->w, params->h); +#endif rowdata = snewn(max, int); /* * Save the solved game in aux. */ - { + if (aux) { char *ai = snewn(params->w * params->h + 2, char); /* @@ -768,6 +837,40 @@ static char *new_game_desc(const game_params *params, random_state *rs, assert(descpos == desclen); assert(desc[desclen-1] == '/'); desc[desclen-1] = '\0'; +#ifdef STANDALONE_PICTURE_GENERATOR + for (i = 0; i < params->w * params->h; i++) + if (state->common->immutable[i]) + break; + if (i < params->w * params->h) { + /* + * At least one immutable square, so we need a suffix. + */ + int run; + + desc = sresize(desc, desclen + params->w * params->h + 3, char); + desc[descpos-1] = ','; + + run = 0; + for (i = 0; i < params->w * params->h; i++) { + if (!state->common->immutable[i]) { + run++; + if (run == 25) { + desc[descpos++] = 'z'; + run = 0; + } + } else { + desc[descpos++] = run + (grid[i] == GRID_FULL ? 'A' : 'a'); + run = 0; + } + } + if (run > 0) + desc[descpos++] = run + 'a'; + desc[descpos] = '\0'; + } + sfree(state->common->immutable); + sfree(state->common); + sfree(state); +#endif sfree(rowdata); sfree(grid); return desc; @@ -805,13 +908,41 @@ static char *validate_desc(const game_params *params, const char *desc) if (desc[-1] == '/') { if (i+1 == params->w + params->h) return "too many row/column specifications"; - } else if (desc[-1] == '\0') { + } else if (desc[-1] == '\0' || desc[-1] == ',') { if (i+1 < params->w + params->h) return "too few row/column specifications"; } else return "unrecognised character in game specification"; } + if (desc[-1] == ',') { + /* + * Optional extra piece of game description which fills in + * some grid squares as extra clues. + */ + i = 0; + while (i < params->w * params->h) { + int c = (unsigned char)*desc++; + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z')) { + int len = tolower(c) - 'a'; + i += len; + if (len < 25 && i < params->w*params->h) + i++; + if (i > params->w * params->h) { + return "too much data in clue-squares section"; + } + } else if (!c) { + return "too little data in clue-squares section"; + } else { + return "unrecognised character in clue-squares section"; + } + } + if (*desc) { + return "too much data in clue-squares section"; + } + } + return NULL; } @@ -831,6 +962,10 @@ static game_state *new_game(midend *me, const game_params *params, state->grid = snewn(state->common->w * state->common->h, unsigned char); memset(state->grid, GRID_UNKNOWN, state->common->w * state->common->h); + state->common->immutable = snewn(state->common->w * state->common->h, + unsigned char); + memset(state->common->immutable, 0, state->common->w * state->common->h); + state->common->rowsize = max(state->common->w, state->common->h); state->common->rowdata = snewn(state->common->rowsize * (state->common->w + state->common->h), int); state->common->rowlen = snewn(state->common->w + state->common->h, int); @@ -851,6 +986,24 @@ static game_state *new_game(midend *me, const game_params *params, } } + if (desc[-1] == ',') { + /* + * Optional extra piece of game description which fills in + * some grid squares as extra clues. + */ + i = 0; + while (i < params->w * params->h) { + int c = (unsigned char)*desc++; + int full = isupper(c), len = tolower(c) - 'a'; + i += len; + if (len < 25 && i < params->w*params->h) { + state->grid[i] = full ? GRID_FULL : GRID_EMPTY; + state->common->immutable[i] = TRUE; + i++; + } + } + } + return state; } @@ -875,6 +1028,7 @@ static void free_game(game_state *state) if (--state->common->refcount == 0) { sfree(state->common->rowdata); sfree(state->common->rowlen); + sfree(state->common->immutable); sfree(state->common); } sfree(state->grid); @@ -1161,7 +1315,8 @@ static char *interpret_move(const game_state *state, game_ui *ui, for (yy = y1; yy <= y2; yy++) for (xx = x1; xx <= x2; xx++) - if (state->grid[yy * state->common->w + xx] != ui->state) + if (!state->common->immutable[yy * state->common->w + xx] && + state->grid[yy * state->common->w + xx] != ui->state) move_needed = TRUE; ui->dragging = FALSE; @@ -1253,7 +1408,8 @@ static game_state *execute_move(const game_state *from, const char *move) ret = dup_game(from); for (yy = y1; yy < y2; yy++) for (xx = x1; xx < x2; xx++) - ret->grid[yy * ret->common->w + xx] = val; + if (!ret->common->immutable[yy * ret->common->w + xx]) + ret->grid[yy * ret->common->w + xx] = val; /* * An actual change, so check to see if we've completed the @@ -1674,7 +1830,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds, * 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) + if (ui->dragging && x1 <= j && j <= x2 && y1 <= i && i <= y2 && + !state->common->immutable[i * state->common->w + j]) val = ui->state; else val = state->grid[i * state->common->w + j]; @@ -1814,7 +1971,7 @@ static void game_print(drawing *dr, const game_state *state, int tilesize) const struct game thegame = { "Pattern", "games.pattern", "pattern", default_params, - game_fetch_preset, + game_fetch_preset, NULL, decode_params, encode_params, free_params, @@ -1943,4 +2100,156 @@ int main(int argc, char **argv) #endif +#ifdef STANDALONE_PICTURE_GENERATOR + +/* + * Main program for the standalone picture generator. To use it, + * simply provide it with an XBM-format bitmap file (note XBM, not + * XPM) on standard input, and it will output a game ID in return. + * For example: + * + * $ ./patternpicture < calligraphic-A.xbm + * 15x15:2/4/2/2/2/3/3/3.1/3.1/3.1/11/14/12/6/1/2/2/3/4/5/1.3/2.3/1.3/2.3/1.4/9/1.1.3/2.2.3/5.4/3.2 + * + * That looks easy, of course - all the program has done is to count + * up the clue numbers! But in fact, it's done more than that: it's + * also checked that the result is uniquely soluble from just the + * numbers. If it hadn't been, then it would have also left some + * filled squares in the playing area as extra clues. + * + * $ ./patternpicture < cube.xbm + * 15x15:10/2.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.10/1.1.1/1.1.1/1.1.1/2.1/10/10/1.2/1.1.1/1.1.1/1.1.1/10.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.1.1/1.2/10,TNINzzzzGNzw + * + * This enables a reasonably convenient design workflow for coming up + * with pictorial Pattern puzzles which _are_ uniquely soluble without + * those inelegant pre-filled squares. Fire up a bitmap editor (X11 + * bitmap(1) is good enough), save a trial .xbm, and then test it by + * running a command along the lines of + * + * $ ./pattern $(./patternpicture < test.xbm) + * + * If the resulting window pops up with some pre-filled squares, then + * that tells you which parts of the image are giving rise to + * ambiguities, so try making tweaks in those areas, try the test + * command again, and see if it helps. Once you have a design for + * which the Pattern starting grid comes out empty, there's your game + * ID. + */ + +#include + +int main(int argc, char **argv) +{ + game_params *par; + char *params, *desc; + random_state *rs; + time_t seed = time(NULL); + char buf[4096]; + int i; + int x, y; + + par = default_params(); + if (argc > 1) + decode_params(par, argv[1]); /* get difficulty */ + par->w = par->h = -1; + + /* + * Now read an XBM file from standard input. This is simple and + * hacky and will do very little error detection, so don't feed + * it bogus data. + */ + picture = NULL; + x = y = 0; + while (fgets(buf, sizeof(buf), stdin)) { + buf[strcspn(buf, "\r\n")] = '\0'; + if (!strncmp(buf, "#define", 7)) { + /* + * Lines starting `#define' give the width and height. + */ + char *num = buf + strlen(buf); + char *symend; + + while (num > buf && isdigit((unsigned char)num[-1])) + num--; + symend = num; + while (symend > buf && isspace((unsigned char)symend[-1])) + symend--; + + if (symend-5 >= buf && !strncmp(symend-5, "width", 5)) + par->w = atoi(num); + else if (symend-6 >= buf && !strncmp(symend-6, "height", 6)) + par->h = atoi(num); + } else { + /* + * Otherwise, break the string up into words and take + * any word of the form `0x' plus hex digits to be a + * byte. + */ + char *p, *wordstart; + + if (!picture) { + if (par->w < 0 || par->h < 0) { + printf("failed to read width and height\n"); + return 1; + } + picture = snewn(par->w * par->h, unsigned char); + for (i = 0; i < par->w * par->h; i++) + picture[i] = GRID_UNKNOWN; + } + + p = buf; + while (*p) { + while (*p && (*p == ',' || isspace((unsigned char)*p))) + p++; + wordstart = p; + while (*p && !(*p == ',' || *p == '}' || + isspace((unsigned char)*p))) + p++; + if (*p) + *p++ = '\0'; + + if (wordstart[0] == '0' && + (wordstart[1] == 'x' || wordstart[1] == 'X') && + !wordstart[2 + strspn(wordstart+2, + "0123456789abcdefABCDEF")]) { + unsigned long byte = strtoul(wordstart+2, NULL, 16); + for (i = 0; i < 8; i++) { + int bit = (byte >> i) & 1; + if (y < par->h && x < par->w) + picture[y * par->w + x] = + bit ? GRID_FULL : GRID_EMPTY; + x++; + } + + if (x >= par->w) { + x = 0; + y++; + } + } + } + } + } + + for (i = 0; i < par->w * par->h; i++) + if (picture[i] == GRID_UNKNOWN) { + fprintf(stderr, "failed to read enough bitmap data\n"); + return 1; + } + + rs = random_new((void*)&seed, sizeof(time_t)); + + desc = new_game_desc(par, rs, NULL, FALSE); + params = encode_params(par, FALSE); + printf("%s:%s\n", params, desc); + + sfree(desc); + sfree(params); + free_params(par); + random_free(rs); + + return 0; +} + +#endif + /* vim: set shiftwidth=4 tabstop=8: */