ref: 26a3b98f4f30de1f3faf0bb97eeeb8403864b5d3
dir: /towers.c/
/* * towers.c: the puzzle also known as 'Skyscrapers'. * * Possible future work: * * - Relax the upper bound on grid size at 9? * + I'd need TOCHAR and FROMCHAR macros a bit like group's, to * be used wherever this code has +'0' or -'0' * + the pencil marks in the drawstate would need a separate * word to live in * + the clues outside the grid would have to cope with being * multi-digit, meaning in particular that the text formatting * would become more unpleasant * + most importantly, though, the solver just isn't fast * enough. Even at size 9 it can't really do the solver_hard * factorial-time enumeration at a sensible rate. Easy puzzles * higher than that would be possible, but more latin-squarey * than skyscrapery, as it were. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ctype.h> #ifdef NO_TGMATH_H # include <math.h> #else # include <tgmath.h> #endif #include "puzzles.h" #include "latin.h" /* * Difficulty levels. I do some macro ickery here to ensure that my * enum and the various forms of my name list always match up. */ #define DIFFLIST(A) \ A(EASY,Easy,solver_easy,e) \ A(HARD,Hard,solver_hard,h) \ A(EXTREME,Extreme,NULL,x) \ A(UNREASONABLE,Unreasonable,NULL,u) #define ENUM(upper,title,func,lower) DIFF_ ## upper, #define TITLE(upper,title,func,lower) #title, #define ENCODE(upper,title,func,lower) #lower #define CONFIG(upper,title,func,lower) ":" #title enum { DIFFLIST(ENUM) DIFFCOUNT }; static char const *const towers_diffnames[] = { DIFFLIST(TITLE) }; static char const towers_diffchars[] = DIFFLIST(ENCODE); #define DIFFCONFIG DIFFLIST(CONFIG) enum { COL_BACKGROUND, COL_GRID, COL_USER, COL_HIGHLIGHT, COL_ERROR, COL_PENCIL, COL_DONE, NCOLOURS }; struct game_params { int w, diff; }; struct clues { int refcount; int w; /* * An array of 4w integers, of which: * - the first w run across the top * - the next w across the bottom * - the third w down the left * - the last w down the right. */ int *clues; /* * An array of w*w digits. */ digit *immutable; }; /* * Macros to compute clue indices and coordinates. */ #define STARTSTEP(start, step, index, w) do { \ if (index < w) \ start = index, step = w; \ else if (index < 2*w) \ start = (w-1)*w+(index-w), step = -w; \ else if (index < 3*w) \ start = w*(index-2*w), step = 1; \ else \ start = w*(index-3*w)+(w-1), step = -1; \ } while (0) #define CSTARTSTEP(start, step, index, w) \ STARTSTEP(start, step, (((index)+2*w)%(4*w)), w) #define CLUEPOS(x, y, index, w) do { \ if (index < w) \ x = index, y = -1; \ else if (index < 2*w) \ x = index-w, y = w; \ else if (index < 3*w) \ x = -1, y = index-2*w; \ else \ x = w, y = index-3*w; \ } while (0) #ifdef STANDALONE_SOLVER static const char *const cluepos[] = { "above column", "below column", "left of row", "right of row" }; #endif struct game_state { game_params par; struct clues *clues; bool *clues_done; digit *grid; int *pencil; /* bitmaps using bits 1<<1..1<<n */ bool completed, cheated; }; static game_params *default_params(void) { game_params *ret = snew(game_params); ret->w = 5; ret->diff = DIFF_EASY; return ret; } static const struct game_params towers_presets[] = { { 4, DIFF_EASY }, { 5, DIFF_EASY }, { 5, DIFF_HARD }, { 6, DIFF_EASY }, { 6, DIFF_HARD }, { 6, DIFF_EXTREME }, { 6, DIFF_UNREASONABLE }, }; static bool game_fetch_preset(int i, char **name, game_params **params) { game_params *ret; char buf[80]; if (i < 0 || i >= lenof(towers_presets)) return false; ret = snew(game_params); *ret = towers_presets[i]; /* structure copy */ sprintf(buf, "%dx%d %s", ret->w, ret->w, towers_diffnames[ret->diff]); *name = dupstr(buf); *params = ret; return true; } static void free_params(game_params *params) { sfree(params); } static game_params *dup_params(const game_params *params) { game_params *ret = snew(game_params); *ret = *params; /* structure copy */ return ret; } static void decode_params(game_params *params, char const *string) { char const *p = string; params->w = atoi(p); while (*p && isdigit((unsigned char)*p)) p++; if (*p == 'd') { int i; p++; params->diff = DIFFCOUNT+1; /* ...which is invalid */ if (*p) { for (i = 0; i < DIFFCOUNT; i++) { if (*p == towers_diffchars[i]) params->diff = i; } p++; } } } static char *encode_params(const game_params *params, bool full) { char ret[80]; sprintf(ret, "%d", params->w); if (full) sprintf(ret + strlen(ret), "d%c", towers_diffchars[params->diff]); return dupstr(ret); } static config_item *game_configure(const game_params *params) { config_item *ret; char buf[80]; ret = snewn(3, config_item); ret[0].name = "Grid size"; ret[0].type = C_STRING; sprintf(buf, "%d", params->w); ret[0].u.string.sval = dupstr(buf); ret[1].name = "Difficulty"; ret[1].type = C_CHOICES; ret[1].u.choices.choicenames = DIFFCONFIG; ret[1].u.choices.selected = params->diff; ret[2].name = NULL; ret[2].type = C_END; return ret; } static game_params *custom_params(const config_item *cfg) { game_params *ret = snew(game_params); ret->w = atoi(cfg[0].u.string.sval); ret->diff = cfg[1].u.choices.selected; return ret; } static const char *validate_params(const game_params *params, bool full) { if (params->w < 3 || params->w > 9) return "Grid size must be between 3 and 9"; if (params->diff >= DIFFCOUNT) return "Unknown difficulty rating"; return NULL; } /* ---------------------------------------------------------------------- * Solver. */ struct solver_ctx { int w, diff; bool started; int *clues; long *iscratch; int *dscratch; }; static int solver_easy(struct latin_solver *solver, void *vctx) { struct solver_ctx *ctx = (struct solver_ctx *)vctx; int w = ctx->w; int c, i, j, n, m, furthest; int start, step, cstart, cstep, clue, pos, cpos; int ret = 0; #ifdef STANDALONE_SOLVER char prefix[256]; #endif if (!ctx->started) { ctx->started = true; /* * One-off loop to help get started: when a pair of facing * clues sum to w+1, it must mean that the row consists of * two increasing sequences back to back, so we can * immediately place the highest digit by knowing the * lengths of those two sequences. */ for (c = 0; c < 3*w; c = (c == w-1 ? 2*w : c+1)) { int c2 = c + w; if (ctx->clues[c] && ctx->clues[c2] && ctx->clues[c] + ctx->clues[c2] == w+1) { STARTSTEP(start, step, c, w); CSTARTSTEP(cstart, cstep, c, w); pos = start + (ctx->clues[c]-1)*step; cpos = cstart + (ctx->clues[c]-1)*cstep; if (solver->cube[cpos*w+w-1]) { #ifdef STANDALONE_SOLVER if (solver_show_working) { printf("%*sfacing clues on %s %d are maximal:\n", solver_recurse_depth*4, "", c>=2*w ? "row" : "column", c % w + 1); printf("%*s placing %d at (%d,%d)\n", solver_recurse_depth*4, "", w, pos%w+1, pos/w+1); } #endif latin_solver_place(solver, pos%w, pos/w, w); ret = 1; } else { ret = -1; } } } if (ret) return ret; } /* * Go over every clue doing reasonably simple heuristic * deductions. */ for (c = 0; c < 4*w; c++) { clue = ctx->clues[c]; if (!clue) continue; STARTSTEP(start, step, c, w); CSTARTSTEP(cstart, cstep, c, w); /* Find the location of each number in the row. */ for (i = 0; i < w; i++) ctx->dscratch[i] = w; for (i = 0; i < w; i++) if (solver->grid[start+i*step]) ctx->dscratch[solver->grid[start+i*step]-1] = i; n = m = 0; furthest = w; for (i = w; i >= 1; i--) { if (ctx->dscratch[i-1] == w) { break; } else if (ctx->dscratch[i-1] < furthest) { furthest = ctx->dscratch[i-1]; m = i; n++; } } if (clue == n+1 && furthest > 1) { #ifdef STANDALONE_SOLVER if (solver_show_working) sprintf(prefix, "%*sclue %s %d is nearly filled:\n", solver_recurse_depth*4, "", cluepos[c/w], c%w+1); else prefix[0] = '\0'; /* placate optimiser */ #endif /* * We can already see an increasing sequence of the very * highest numbers, of length one less than that * specified in the clue. All of those numbers _must_ be * part of the clue sequence, so the number right next * to the clue must be the final one - i.e. it must be * bigger than any of the numbers between it and m. This * allows us to rule out small numbers in that square. * * (This is a generalisation of the obvious deduction * that when you see a clue saying 1, it must be right * next to the largest possible number; and similarly, * when you see a clue saying 2 opposite that, it must * be right next to the second-largest.) */ j = furthest-1; /* number of small numbers we can rule out */ for (i = 1; i <= w && j > 0; i++) { if (ctx->dscratch[i-1] < w && ctx->dscratch[i-1] >= furthest) continue; /* skip this number, it's elsewhere */ j--; if (solver->cube[cstart*w+i-1]) { #ifdef STANDALONE_SOLVER if (solver_show_working) { printf("%s%*s ruling out %d at (%d,%d)\n", prefix, solver_recurse_depth*4, "", i, start%w+1, start/w+1); prefix[0] = '\0'; } #endif solver->cube[cstart*w+i-1] = 0; ret = 1; } } } if (ret) return ret; #ifdef STANDALONE_SOLVER if (solver_show_working) sprintf(prefix, "%*slower bounds for clue %s %d:\n", solver_recurse_depth*4, "", cluepos[c/w], c%w+1); else prefix[0] = '\0'; /* placate optimiser */ #endif i = 0; for (n = w; n > 0; n--) { /* * The largest number cannot occur in the first (clue-1) * squares of the row, or else there wouldn't be space * for a sufficiently long increasing sequence which it * terminated. The second-largest number (not counting * any that are known to be on the far side of a larger * number and hence excluded from this sequence) cannot * occur in the first (clue-2) squares, similarly, and * so on. */ if (ctx->dscratch[n-1] < w) { for (m = n+1; m < w; m++) if (ctx->dscratch[m] < ctx->dscratch[n-1]) break; if (m < w) continue; /* this number doesn't count */ } for (j = 0; j < clue - i - 1; j++) if (solver->cube[(cstart + j*cstep)*w+n-1]) { #ifdef STANDALONE_SOLVER if (solver_show_working) { int pos = start+j*step; printf("%s%*s ruling out %d at (%d,%d)\n", prefix, solver_recurse_depth*4, "", n, pos%w+1, pos/w+1); prefix[0] = '\0'; } #endif solver->cube[(cstart + j*cstep)*w+n-1] = 0; ret = 1; } i++; } } if (ret) return ret; return 0; } static int solver_hard(struct latin_solver *solver, void *vctx) { struct solver_ctx *ctx = (struct solver_ctx *)vctx; int w = ctx->w; int c, i, j, n, best, clue, start, step, ret; long bitmap; #ifdef STANDALONE_SOLVER char prefix[256]; #endif /* * Go over every clue analysing all possibilities. */ for (c = 0; c < 4*w; c++) { clue = ctx->clues[c]; if (!clue) continue; CSTARTSTEP(start, step, c, w); for (i = 0; i < w; i++) ctx->iscratch[i] = 0; /* * Instead of a tedious physical recursion, I iterate in the * scratch array through all possibilities. At any given * moment, i indexes the element of the box that will next * be incremented. */ i = 0; ctx->dscratch[i] = 0; best = n = 0; bitmap = 0; while (1) { if (i < w) { /* * Find the next valid value for cell i. */ int limit = (n == clue ? best : w); int pos = start + step * i; for (j = ctx->dscratch[i] + 1; j <= limit; j++) { if (bitmap & (1L << j)) continue; /* used this one already */ if (!solver->cube[pos*w+j-1]) continue; /* ruled out already */ /* Found one. */ break; } if (j > limit) { /* No valid values left; drop back. */ i--; if (i < 0) break; /* overall iteration is finished */ bitmap &= ~(1L << ctx->dscratch[i]); if (ctx->dscratch[i] == best) { n--; best = 0; for (j = 0; j < i; j++) if (best < ctx->dscratch[j]) best = ctx->dscratch[j]; } } else { /* Got a valid value; store it and move on. */ bitmap |= 1L << j; ctx->dscratch[i++] = j; if (j > best) { best = j; n++; } ctx->dscratch[i] = 0; } } else { if (n == clue) { for (j = 0; j < w; j++) ctx->iscratch[j] |= 1L << ctx->dscratch[j]; } i--; bitmap &= ~(1L << ctx->dscratch[i]); if (ctx->dscratch[i] == best) { n--; best = 0; for (j = 0; j < i; j++) if (best < ctx->dscratch[j]) best = ctx->dscratch[j]; } } } #ifdef STANDALONE_SOLVER if (solver_show_working) sprintf(prefix, "%*sexhaustive analysis of clue %s %d:\n", solver_recurse_depth*4, "", cluepos[c/w], c%w+1); else prefix[0] = '\0'; /* placate optimiser */ #endif ret = 0; for (i = 0; i < w; i++) { int pos = start + step * i; for (j = 1; j <= w; j++) { if (solver->cube[pos*w+j-1] && !(ctx->iscratch[i] & (1L << j))) { #ifdef STANDALONE_SOLVER if (solver_show_working) { printf("%s%*s ruling out %d at (%d,%d)\n", prefix, solver_recurse_depth*4, "", j, pos/w+1, pos%w+1); prefix[0] = '\0'; } #endif solver->cube[pos*w+j-1] = 0; ret = 1; } } /* * Once we find one clue we can do something with in * this way, revert to trying easier deductions, so as * not to generate solver diagnostics that make the * problem look harder than it is. */ if (ret) return ret; } } return 0; } #define SOLVER(upper,title,func,lower) func, static usersolver_t const towers_solvers[] = { DIFFLIST(SOLVER) }; static bool towers_valid(struct latin_solver *solver, void *vctx) { struct solver_ctx *ctx = (struct solver_ctx *)vctx; int w = ctx->w; int c, i, n, best, clue, start, step; for (c = 0; c < 4*w; c++) { clue = ctx->clues[c]; if (!clue) continue; STARTSTEP(start, step, c, w); n = best = 0; for (i = 0; i < w; i++) { if (solver->grid[start+i*step] > best) { best = solver->grid[start+i*step]; n++; } } if (n != clue) { #ifdef STANDALONE_SOLVER if (solver_show_working) printf("%*sclue %s %d is violated\n", solver_recurse_depth*4, "", cluepos[c/w], c%w+1); #endif return false; } } return true; } static int solver(int w, int *clues, digit *soln, int maxdiff) { int ret; struct solver_ctx ctx; ctx.w = w; ctx.diff = maxdiff; ctx.clues = clues; ctx.started = false; ctx.iscratch = snewn(w, long); ctx.dscratch = snewn(w+1, int); ret = latin_solver(soln, w, maxdiff, DIFF_EASY, DIFF_HARD, DIFF_EXTREME, DIFF_EXTREME, DIFF_UNREASONABLE, towers_solvers, towers_valid, &ctx, NULL, NULL); sfree(ctx.iscratch); sfree(ctx.dscratch); return ret; } /* ---------------------------------------------------------------------- * Grid generation. */ static char *new_game_desc(const game_params *params, random_state *rs, char **aux, bool interactive) { int w = params->w, a = w*w; digit *grid, *soln, *soln2; int *clues, *order; int i, ret; int diff = params->diff; char *desc, *p; /* * Difficulty exceptions: some combinations of size and * difficulty cannot be satisfied, because all puzzles of at * most that difficulty are actually even easier. * * Remember to re-test this whenever a change is made to the * solver logic! * * I tested it using the following shell command: for d in e h x u; do for i in {3..9}; do echo -n "./towers --generate 1 ${i}d${d}: " perl -e 'alarm 30; exec @ARGV' ./towers --generate 1 ${i}d${d} >/dev/null \ && echo ok done done * Of course, it's better to do that after taking the exceptions * _out_, so as to detect exceptions that should be removed as * well as those which should be added. */ if (diff > DIFF_HARD && w <= 3) diff = DIFF_HARD; grid = NULL; clues = snewn(4*w, int); soln = snewn(a, digit); soln2 = snewn(a, digit); order = snewn(max(4*w,a), int); while (1) { /* * Construct a latin square to be the solution. */ sfree(grid); grid = latin_generate(w, rs); /* * Fill in the clues. */ for (i = 0; i < 4*w; i++) { int start, step, j, k, best; STARTSTEP(start, step, i, w); k = best = 0; for (j = 0; j < w; j++) { if (grid[start+j*step] > best) { best = grid[start+j*step]; k++; } } clues[i] = k; } /* * Remove the grid numbers and then the clues, one by one, * for as long as the game remains soluble at the given * difficulty. */ memcpy(soln, grid, a); if (diff == DIFF_EASY && w <= 5) { /* * Special case: for Easy-mode grids that are small * enough, it's nice to be able to find completely empty * grids. */ memset(soln2, 0, a); ret = solver(w, clues, soln2, diff); if (ret > diff) continue; } for (i = 0; i < a; i++) order[i] = i; shuffle(order, a, sizeof(*order), rs); for (i = 0; i < a; i++) { int j = order[i]; memcpy(soln2, grid, a); soln2[j] = 0; ret = solver(w, clues, soln2, diff); if (ret <= diff) grid[j] = 0; } if (diff > DIFF_EASY) { /* leave all clues on Easy mode */ for (i = 0; i < 4*w; i++) order[i] = i; shuffle(order, 4*w, sizeof(*order), rs); for (i = 0; i < 4*w; i++) { int j = order[i]; int clue = clues[j]; memcpy(soln2, grid, a); clues[j] = 0; ret = solver(w, clues, soln2, diff); if (ret > diff) clues[j] = clue; } } /* * See if the game can be solved at the specified difficulty * level, but not at the one below. */ memcpy(soln2, grid, a); ret = solver(w, clues, soln2, diff); if (ret != diff) continue; /* go round again */ /* * We've got a usable puzzle! */ break; } /* * Encode the puzzle description. */ desc = snewn(40*a, char); p = desc; for (i = 0; i < 4*w; i++) { if (i) *p++ = '/'; if (clues[i]) p += sprintf(p, "%d", clues[i]); } for (i = 0; i < a; i++) if (grid[i]) break; if (i < a) { int run = 0; *p++ = ','; for (i = 0; i <= a; i++) { int n = (i < a ? grid[i] : -1); if (!n) run++; else { if (run) { while (run > 0) { int thisrun = min(run, 26); *p++ = thisrun - 1 + 'a'; run -= thisrun; } } 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 (i > 0 && n > 0) *p++ = '_'; } if (n > 0) p += sprintf(p, "%d", n); run = 0; } } } *p++ = '\0'; desc = sresize(desc, p - desc, char); /* * Encode the solution. */ *aux = snewn(a+2, char); (*aux)[0] = 'S'; for (i = 0; i < a; i++) (*aux)[i+1] = '0' + soln[i]; (*aux)[a+1] = '\0'; sfree(grid); sfree(clues); sfree(soln); sfree(soln2); sfree(order); return desc; } /* ---------------------------------------------------------------------- * Gameplay. */ static const char *validate_desc(const game_params *params, const char *desc) { int w = params->w, a = w*w; const char *p = desc; int i, clue; /* * Verify that the right number of clues are given, and that * they're in range. */ for (i = 0; i < 4*w; i++) { if (!*p) return "Too few clues for grid size"; if (i > 0) { if (*p != '/') return "Expected commas between clues"; p++; } if (isdigit((unsigned char)*p)) { clue = atoi(p); while (*p && isdigit((unsigned char)*p)) p++; if (clue <= 0 || clue > w) return "Clue number out of range"; } } if (*p == '/') return "Too many clues for grid size"; if (*p == ',') { /* * Verify that the right amount of grid data is given, and * that any grid elements provided are in range. */ int squares = 0; p++; while (*p) { int c = *p++; if (c >= 'a' && c <= 'z') { squares += c - 'a' + 1; } else if (c == '_') { /* do nothing */; } else if (c > '0' && c <= '9') { int val = atoi(p-1); if (val < 1 || val > w) return "Out-of-range number in grid description"; squares++; while (*p && isdigit((unsigned char)*p)) p++; } else return "Invalid character in game description"; } if (squares < a) return "Not enough data to fill grid"; if (squares > a) return "Too much data to fit in grid"; } if (*p) return "Rubbish at end of game description"; return NULL; } static key_label *game_request_keys(const game_params *params, int *nkeys) { int i; int w = params->w; key_label *keys = snewn(w+1, key_label); *nkeys = w + 1; for (i = 0; i < w; i++) { if (i<9) keys[i].button = '1' + i; else keys[i].button = 'a' + i - 9; keys[i].label = NULL; } keys[w].button = '\b'; keys[w].label = NULL; return keys; } static game_state *new_game(midend *me, const game_params *params, const char *desc) { int w = params->w, a = w*w; game_state *state = snew(game_state); const char *p = desc; int i; state->par = *params; /* structure copy */ state->clues = snew(struct clues); state->clues->refcount = 1; state->clues->w = w; state->clues->clues = snewn(4*w, int); state->clues->immutable = snewn(a, digit); state->grid = snewn(a, digit); state->clues_done = snewn(4*w, bool); state->pencil = snewn(a, int); for (i = 0; i < a; i++) { state->grid[i] = 0; state->pencil[i] = 0; } memset(state->clues->immutable, 0, a); memset(state->clues_done, 0, 4*w*sizeof(bool)); for (i = 0; i < 4*w; i++) { if (i > 0) { assert(*p == '/'); p++; } if (*p && isdigit((unsigned char)*p)) { state->clues->clues[i] = atoi(p); while (*p && isdigit((unsigned char)*p)) p++; } else state->clues->clues[i] = 0; } if (*p == ',') { int pos = 0; p++; while (*p) { int c = *p++; if (c >= 'a' && c <= 'z') { pos += c - 'a' + 1; } else if (c == '_') { /* do nothing */; } else if (c > '0' && c <= '9') { int val = atoi(p-1); assert(val >= 1 && val <= w); assert(pos < a); state->grid[pos] = state->clues->immutable[pos] = val; pos++; while (*p && isdigit((unsigned char)*p)) p++; } else assert(!"Corrupt game description"); } assert(pos == a); } assert(!*p); state->completed = false; state->cheated = false; return state; } static game_state *dup_game(const game_state *state) { int w = state->par.w, a = w*w; game_state *ret = snew(game_state); ret->par = state->par; /* structure copy */ ret->clues = state->clues; ret->clues->refcount++; ret->grid = snewn(a, digit); ret->pencil = snewn(a, int); ret->clues_done = snewn(4*w, bool); memcpy(ret->grid, state->grid, a*sizeof(digit)); memcpy(ret->pencil, state->pencil, a*sizeof(int)); memcpy(ret->clues_done, state->clues_done, 4*w*sizeof(bool)); ret->completed = state->completed; ret->cheated = state->cheated; return ret; } static void free_game(game_state *state) { sfree(state->grid); sfree(state->pencil); sfree(state->clues_done); if (--state->clues->refcount <= 0) { sfree(state->clues->immutable); sfree(state->clues->clues); sfree(state->clues); } sfree(state); } static char *solve_game(const game_state *state, const game_state *currstate, const char *aux, const char **error) { int w = state->par.w, a = w*w; int i, ret; digit *soln; char *out; if (aux) return dupstr(aux); soln = snewn(a, digit); memcpy(soln, state->clues->immutable, a); ret = solver(w, state->clues->clues, soln, DIFFCOUNT-1); if (ret == diff_impossible) { *error = "No solution exists for this puzzle"; out = NULL; } else if (ret == diff_ambiguous) { *error = "Multiple solutions exist for this puzzle"; out = NULL; } else { out = snewn(a+2, char); out[0] = 'S'; for (i = 0; i < a; i++) out[i+1] = '0' + soln[i]; out[a+1] = '\0'; } sfree(soln); return out; } static bool game_can_format_as_text_now(const game_params *params) { return true; } static char *game_text_format(const game_state *state) { int w = state->par.w /* , a = w*w */; char *ret; char *p; int x, y; int total; /* * We have: * - a top clue row, consisting of three spaces, then w clue * digits with spaces between (total 2*w+3 chars including * newline) * - a blank line (one newline) * - w main rows, consisting of a left clue digit, two spaces, * w grid digits with spaces between, two spaces and a right * clue digit (total 2*w+6 chars each including newline) * - a blank line (one newline) * - a bottom clue row (same as top clue row) * - terminating NUL. * * Total size is therefore 2*(2*w+3) + 2 + w*(2*w+6) + 1 * = 2w^2+10w+9. */ total = 2*w*w + 10*w + 9; ret = snewn(total, char); p = ret; /* Top clue row. */ *p++ = ' '; *p++ = ' '; for (x = 0; x < w; x++) { *p++ = ' '; *p++ = (state->clues->clues[x] ? '0' + state->clues->clues[x] : ' '); } *p++ = '\n'; /* Blank line. */ *p++ = '\n'; /* Main grid. */ for (y = 0; y < w; y++) { *p++ = (state->clues->clues[y+2*w] ? '0' + state->clues->clues[y+2*w] : ' '); *p++ = ' '; for (x = 0; x < w; x++) { *p++ = ' '; *p++ = (state->grid[y*w+x] ? '0' + state->grid[y*w+x] : ' '); } *p++ = ' '; *p++ = ' '; *p++ = (state->clues->clues[y+3*w] ? '0' + state->clues->clues[y+3*w] : ' '); *p++ = '\n'; } /* Blank line. */ *p++ = '\n'; /* Bottom clue row. */ *p++ = ' '; *p++ = ' '; for (x = 0; x < w; x++) { *p++ = ' '; *p++ = (state->clues->clues[x+w] ? '0' + state->clues->clues[x+w] : ' '); } *p++ = '\n'; *p++ = '\0'; assert(p == ret + total); return ret; } struct game_ui { /* * These are the coordinates of the currently highlighted * square on the grid, if hshow = 1. */ int hx, hy; /* * This indicates whether the current highlight is a * pencil-mark one or a real one. */ bool hpencil; /* * This indicates whether or not we're showing the highlight * (used to be hx = hy = -1); important so that when we're * using the cursor keys it doesn't keep coming back at a * fixed position. When hshow = 1, pressing a valid number * or letter key or Space will enter that number or letter in the grid. */ bool hshow; /* * This indicates whether we're using the highlight as a cursor; * it means that it doesn't vanish on a keypress, and that it is * allowed on immutable squares. */ bool hcursor; /* * User preference option which can be set to FALSE to disable the * 3D graphical style, and instead just display the puzzle as if * it was a Sudoku variant, i.e. each square just has a digit in * it. * * I was initially a bit uncertain about whether the 3D style * would be the right thing, on the basis that it uses up space in * the cells and makes it hard to use many pencil marks. Actually * nobody seems to have complained, but having put in the option * while I was still being uncertain, it seems silly not to leave * it in just in case. */ int three_d; /* * User preference option: if the user right-clicks in a square * and presses a number key to add/remove a pencil mark, do we * hide the mouse highlight again afterwards? * * Historically our answer was yes. The Android port prefers no. * There are advantages both ways, depending how much you dislike * the highlight cluttering your view. So it's a preference. */ bool pencil_keep_highlight; }; static void legacy_prefs_override(struct game_ui *ui_out) { static bool initialised = false; static int three_d = -1; if (!initialised) { initialised = true; three_d = getenv_bool("TOWERS_2D", -1); } if (three_d != -1) ui_out->three_d = three_d; } static game_ui *new_ui(const game_state *state) { game_ui *ui = snew(game_ui); ui->hx = ui->hy = 0; ui->hpencil = false; ui->hshow = ui->hcursor = getenv_bool("PUZZLES_SHOW_CURSOR", false); ui->three_d = true; ui->pencil_keep_highlight = false; legacy_prefs_override(ui); return ui; } static void free_ui(game_ui *ui) { sfree(ui); } static config_item *get_prefs(game_ui *ui) { config_item *ret; ret = snewn(3, config_item); ret[0].name = "Keep mouse highlight after changing a pencil mark"; ret[0].kw = "pencil-keep-highlight"; ret[0].type = C_BOOLEAN; ret[0].u.boolean.bval = ui->pencil_keep_highlight; ret[1].name = "Puzzle appearance"; ret[1].kw = "appearance"; ret[1].type = C_CHOICES; ret[1].u.choices.choicenames = ":2D:3D"; ret[1].u.choices.choicekws = ":2d:3d"; ret[1].u.choices.selected = ui->three_d; ret[2].name = NULL; ret[2].type = C_END; return ret; } static void set_prefs(game_ui *ui, const config_item *cfg) { ui->pencil_keep_highlight = cfg[0].u.boolean.bval; ui->three_d = cfg[1].u.choices.selected; } static void game_changed_state(game_ui *ui, const game_state *oldstate, const game_state *newstate) { int w = newstate->par.w; /* * We prevent pencil-mode highlighting of a filled square, unless * we're using the cursor keys. So if the user has just filled in * a square which we had a pencil-mode highlight in (by Undo, or * by Redo, or by Solve), then we cancel the highlight. */ if (ui->hshow && ui->hpencil && !ui->hcursor && newstate->grid[ui->hy * w + ui->hx] != 0) { ui->hshow = false; } } static const char *current_key_label(const game_ui *ui, const game_state *state, int button) { if (ui->hshow && (button == CURSOR_SELECT)) return ui->hpencil ? "Ink" : "Pencil"; return ""; } #define PREFERRED_TILESIZE 48 #define TILESIZE (ds->tilesize) #define BORDER (TILESIZE * 9 / 8) #define COORD(x) ((x)*TILESIZE + BORDER) #define FROMCOORD(x) (((x)+(TILESIZE-BORDER)) / TILESIZE - 1) /* These always return positive values, though y offsets are actually -ve */ #define X_3D_DISP(height, w) ((height) * TILESIZE / (8 * (w))) #define Y_3D_DISP(height, w) ((height) * TILESIZE / (4 * (w))) #define FLASH_TIME 0.4F #define DF_PENCIL_SHIFT 16 #define DF_CLUE_DONE 0x10000 #define DF_ERROR 0x8000 #define DF_HIGHLIGHT 0x4000 #define DF_HIGHLIGHT_PENCIL 0x2000 #define DF_IMMUTABLE 0x1000 #define DF_PLAYAREA 0x0800 #define DF_DIGIT_MASK 0x00FF struct game_drawstate { int tilesize; long *tiles; /* (w+2)*(w+2) temp space */ long *drawn; /* (w+2)*(w+2)*4: current drawn data */ bool *errtmp; }; static bool check_errors(const game_state *state, bool *errors) { int w = state->par.w /*, a = w*w */; int W = w+2, A = W*W; /* the errors array is (w+2) square */ int *clues = state->clues->clues; digit *grid = state->grid; int i, x, y; bool errs = false; int tmp[32]; assert(w < lenof(tmp)); if (errors) for (i = 0; i < A; i++) errors[i] = false; for (y = 0; y < w; y++) { unsigned long mask = 0, errmask = 0; for (x = 0; x < w; x++) { unsigned long bit = 1UL << grid[y*w+x]; errmask |= (mask & bit); mask |= bit; } if (mask != (1L << (w+1)) - (1L << 1)) { errs = true; errmask &= ~1UL; if (errors) { for (x = 0; x < w; x++) if (errmask & (1UL << grid[y*w+x])) errors[(y+1)*W+(x+1)] = true; } } } for (x = 0; x < w; x++) { unsigned long mask = 0, errmask = 0; for (y = 0; y < w; y++) { unsigned long bit = 1UL << grid[y*w+x]; errmask |= (mask & bit); mask |= bit; } if (mask != (1 << (w+1)) - (1 << 1)) { errs = true; errmask &= ~1UL; if (errors) { for (y = 0; y < w; y++) if (errmask & (1UL << grid[y*w+x])) errors[(y+1)*W+(x+1)] = true; } } } for (i = 0; i < 4*w; i++) { int start, step, j, n, best; STARTSTEP(start, step, i, w); if (!clues[i]) continue; best = n = 0; for (j = 0; j < w; j++) { int number = grid[start+j*step]; if (!number) break; /* can't tell what happens next */ if (number > best) { best = number; n++; } } if (n > clues[i] || (best == w && n < clues[i]) || (best < w && n == clues[i])) { if (errors) { int x, y; CLUEPOS(x, y, i, w); errors[(y+1)*W+(x+1)] = true; } errs = true; } } return errs; } static int clue_index(const game_state *state, int x, int y) { int w = state->par.w; if (x == -1 || x == w) return w * (x == -1 ? 2 : 3) + y; else if (y == -1 || y == w) return (y == -1 ? 0 : w) + x; return -1; } static bool is_clue(const game_state *state, int x, int y) { int w = state->par.w; if (((x == -1 || x == w) && y >= 0 && y < w) || ((y == -1 || y == w) && x >= 0 && x < w)) { if (state->clues->clues[clue_index(state, x, y)] & DF_DIGIT_MASK) return true; } return false; } static char *interpret_move(const game_state *state, game_ui *ui, const game_drawstate *ds, int x, int y, int button) { int w = state->par.w; bool shift_or_control = button & (MOD_SHFT | MOD_CTRL); int tx, ty; char buf[80]; button &= ~MOD_MASK; tx = FROMCOORD(x); ty = FROMCOORD(y); if (ui->three_d) { /* * In 3D mode, just locating the mouse click in the natural * square grid may not be sufficient to tell which tower the * user clicked on. Investigate the _tops_ of the nearby * towers to see if a click on one grid square was actually * a click on a tower protruding into that region from * another. */ int dx, dy; for (dy = 0; dy <= 1; dy++) for (dx = 0; dx >= -1; dx--) { int cx = tx + dx, cy = ty + dy; if (cx >= 0 && cx < w && cy >= 0 && cy < w) { int height = state->grid[cy*w+cx]; int bx = COORD(cx), by = COORD(cy); int ox = bx + X_3D_DISP(height, w); int oy = by - Y_3D_DISP(height, w); if (/* on top face? */ (x - ox >= 0 && x - ox < TILESIZE && y - oy >= 0 && y - oy < TILESIZE) || /* in triangle between top-left corners? */ (ox > bx && x >= bx && x <= ox && y <= by && (by-y) * (ox-bx) <= (by-oy) * (x-bx)) || /* in triangle between bottom-right corners? */ (ox > bx && x >= bx+TILESIZE && x <= ox+TILESIZE && y >= oy+TILESIZE && (by-y+TILESIZE)*(ox-bx) >= (by-oy)*(x-bx-TILESIZE))) { tx = cx; ty = cy; } } } } if (tx >= 0 && tx < w && ty >= 0 && ty < w) { if (button == LEFT_BUTTON) { if (tx == ui->hx && ty == ui->hy && ui->hshow && !ui->hpencil) { ui->hshow = false; } else { ui->hx = tx; ui->hy = ty; ui->hshow = !state->clues->immutable[ty*w+tx]; ui->hpencil = false; } ui->hcursor = false; return MOVE_UI_UPDATE; } if (button == RIGHT_BUTTON) { /* * Pencil-mode highlighting for non filled squares. */ if (state->grid[ty*w+tx] == 0) { if (tx == ui->hx && ty == ui->hy && ui->hshow && ui->hpencil) { ui->hshow = false; } else { ui->hpencil = true; ui->hx = tx; ui->hy = ty; ui->hshow = true; } } else { ui->hshow = false; } ui->hcursor = false; return MOVE_UI_UPDATE; } } else if (button == LEFT_BUTTON) { if (is_clue(state, tx, ty)) { sprintf(buf, "%c%d,%d", 'D', tx, ty); return dupstr(buf); } } if (IS_CURSOR_MOVE(button)) { if (shift_or_control) { int x = ui->hx, y = ui->hy; switch (button) { case CURSOR_LEFT: x = -1; break; case CURSOR_RIGHT: x = w; break; case CURSOR_UP: y = -1; break; case CURSOR_DOWN: y = w; break; } if (is_clue(state, x, y)) { sprintf(buf, "%c%d,%d", 'D', x, y); return dupstr(buf); } return NULL; } ui->hcursor = true; return move_cursor(button, &ui->hx, &ui->hy, w, w, false, &ui->hshow); } if (ui->hshow && (button == CURSOR_SELECT)) { ui->hpencil = !ui->hpencil; ui->hcursor = true; return MOVE_UI_UPDATE; } if (ui->hshow && ((button >= '0' && button <= '9' && button - '0' <= w) || button == CURSOR_SELECT2 || button == '\b')) { int n = button - '0'; if (button == CURSOR_SELECT2 || button == '\b') n = 0; /* * Can't make pencil marks in a filled square. This can only * become highlighted if we're using cursor keys. */ if (ui->hpencil && state->grid[ui->hy*w+ui->hx]) return NULL; /* * Can't do anything to an immutable square. */ if (state->clues->immutable[ui->hy*w+ui->hx]) return NULL; /* * If you ask to fill a square with what it already contains, * or blank it when it's already empty, that has no effect... */ if ((!ui->hpencil || n == 0) && state->grid[ui->hy*w+ui->hx] == n && state->pencil[ui->hy*w+ui->hx] == 0) { /* ... expect to remove the cursor in mouse mode. */ if (!ui->hcursor) { ui->hshow = false; return MOVE_UI_UPDATE; } return NULL; } sprintf(buf, "%c%d,%d,%d", (char)(ui->hpencil && n > 0 ? 'P' : 'R'), ui->hx, ui->hy, n); /* * Hide the highlight after a keypress, if it was mouse- * generated. Also, don't hide it if this move has changed * pencil marks and the user preference says not to hide the * highlight in that situation. */ if (!ui->hcursor && !(ui->hpencil && ui->pencil_keep_highlight)) ui->hshow = false; return dupstr(buf); } if (button == 'M' || button == 'm') return dupstr("M"); return NULL; } static game_state *execute_move(const game_state *from, const char *move) { int w = from->par.w, a = w*w; game_state *ret = dup_game(from); int x, y, i, n; if (move[0] == 'S') { ret->completed = ret->cheated = true; for (i = 0; i < a; i++) { if (move[i+1] < '1' || move[i+1] > '0'+w) goto badmove; ret->grid[i] = move[i+1] - '0'; ret->pencil[i] = 0; } if (move[a+1] != '\0') goto badmove; return ret; } else if ((move[0] == 'P' || move[0] == 'R') && sscanf(move+1, "%d,%d,%d", &x, &y, &n) == 3 && x >= 0 && x < w && y >= 0 && y < w && n >= 0 && n <= w) { if (from->clues->immutable[y*w+x]) goto badmove; if (move[0] == 'P' && n > 0) { ret->pencil[y*w+x] ^= 1L << n; } else { ret->grid[y*w+x] = n; ret->pencil[y*w+x] = 0; if (!ret->completed && !check_errors(ret, NULL)) ret->completed = true; } return ret; } else if (move[0] == 'M') { /* * Fill in absolutely all pencil marks everywhere. (I * wouldn't use this for actual play, but it's a handy * starting point when following through a set of * diagnostics output by the standalone solver.) */ for (i = 0; i < a; i++) { if (!ret->grid[i]) ret->pencil[i] = (1L << (w+1)) - (1L << 1); } return ret; } else if (move[0] == 'D' && sscanf(move+1, "%d,%d", &x, &y) == 2 && is_clue(from, x, y)) { int index = clue_index(from, x, y); ret->clues_done[index] = !ret->clues_done[index]; return ret; } badmove: /* couldn't parse move string */ free_game(ret); return NULL; } /* ---------------------------------------------------------------------- * Drawing routines. */ #define SIZE(w) ((w) * TILESIZE + 2*BORDER) static void game_compute_size(const game_params *params, int tilesize, const game_ui *ui, int *x, int *y) { /* Ick: fake up `ds->tilesize' for macro expansion purposes */ struct { int tilesize; } ads, *ds = &ads; ads.tilesize = tilesize; *x = *y = SIZE(params->w); } static void game_set_size(drawing *dr, game_drawstate *ds, const game_params *params, int tilesize) { ds->tilesize = tilesize; } static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]); ret[COL_GRID * 3 + 0] = 0.0F; ret[COL_GRID * 3 + 1] = 0.0F; ret[COL_GRID * 3 + 2] = 0.0F; ret[COL_USER * 3 + 0] = 0.0F; ret[COL_USER * 3 + 1] = 0.6F * ret[COL_BACKGROUND * 3 + 1]; ret[COL_USER * 3 + 2] = 0.0F; ret[COL_HIGHLIGHT * 3 + 0] = 0.78F * ret[COL_BACKGROUND * 3 + 0]; ret[COL_HIGHLIGHT * 3 + 1] = 0.78F * ret[COL_BACKGROUND * 3 + 1]; ret[COL_HIGHLIGHT * 3 + 2] = 0.78F * ret[COL_BACKGROUND * 3 + 2]; ret[COL_ERROR * 3 + 0] = 1.0F; ret[COL_ERROR * 3 + 1] = 0.0F; ret[COL_ERROR * 3 + 2] = 0.0F; ret[COL_PENCIL * 3 + 0] = 0.5F * ret[COL_BACKGROUND * 3 + 0]; ret[COL_PENCIL * 3 + 1] = 0.5F * ret[COL_BACKGROUND * 3 + 1]; ret[COL_PENCIL * 3 + 2] = ret[COL_BACKGROUND * 3 + 2]; ret[COL_DONE * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] / 1.5F; ret[COL_DONE * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] / 1.5F; ret[COL_DONE * 3 + 2] = ret[COL_BACKGROUND * 3 + 2] / 1.5F; *ncolours = NCOLOURS; return ret; } static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state) { int w = state->par.w /*, a = w*w */; struct game_drawstate *ds = snew(struct game_drawstate); int i; ds->tilesize = 0; ds->tiles = snewn((w+2)*(w+2), long); ds->drawn = snewn((w+2)*(w+2)*4, long); for (i = 0; i < (w+2)*(w+2)*4; i++) ds->drawn[i] = -1; ds->errtmp = snewn((w+2)*(w+2), bool); return ds; } static void game_free_drawstate(drawing *dr, game_drawstate *ds) { sfree(ds->errtmp); sfree(ds->tiles); sfree(ds->drawn); sfree(ds); } static void draw_tile(drawing *dr, game_drawstate *ds, const game_ui *ui, struct clues *clues, int x, int y, long tile) { int w = clues->w /* , a = w*w */; int tx, ty, bg; char str[64]; tx = COORD(x); ty = COORD(y); bg = (tile & DF_HIGHLIGHT) ? COL_HIGHLIGHT : COL_BACKGROUND; /* draw tower */ if (ui->three_d && (tile & DF_PLAYAREA) && (tile & DF_DIGIT_MASK)) { int coords[8]; int xoff = X_3D_DISP(tile & DF_DIGIT_MASK, w); int yoff = Y_3D_DISP(tile & DF_DIGIT_MASK, w); /* left face of tower */ coords[0] = tx; coords[1] = ty - 1; coords[2] = tx; coords[3] = ty + TILESIZE - 1; coords[4] = coords[2] + xoff; coords[5] = coords[3] - yoff; coords[6] = coords[0] + xoff; coords[7] = coords[1] - yoff; draw_polygon(dr, coords, 4, bg, COL_GRID); /* bottom face of tower */ coords[0] = tx + TILESIZE; coords[1] = ty + TILESIZE - 1; coords[2] = tx; coords[3] = ty + TILESIZE - 1; coords[4] = coords[2] + xoff; coords[5] = coords[3] - yoff; coords[6] = coords[0] + xoff; coords[7] = coords[1] - yoff; draw_polygon(dr, coords, 4, bg, COL_GRID); /* now offset all subsequent drawing to the top of the tower */ tx += xoff; ty -= yoff; } /* erase background */ draw_rect(dr, tx, ty, TILESIZE, TILESIZE, bg); /* pencil-mode highlight */ if (tile & DF_HIGHLIGHT_PENCIL) { int coords[6]; coords[0] = tx; coords[1] = ty; coords[2] = tx+TILESIZE/2; coords[3] = ty; coords[4] = tx; coords[5] = ty+TILESIZE/2; draw_polygon(dr, coords, 3, COL_HIGHLIGHT, COL_HIGHLIGHT); } /* draw box outline */ if (tile & DF_PLAYAREA) { int coords[8]; coords[0] = tx; coords[1] = ty - 1; coords[2] = tx + TILESIZE; coords[3] = ty - 1; coords[4] = tx + TILESIZE; coords[5] = ty + TILESIZE - 1; coords[6] = tx; coords[7] = ty + TILESIZE - 1; draw_polygon(dr, coords, 4, -1, COL_GRID); } /* new number needs drawing? */ if (tile & DF_DIGIT_MASK) { int color; str[1] = '\0'; str[0] = (tile & DF_DIGIT_MASK) + '0'; if (tile & DF_ERROR) color = COL_ERROR; else if (tile & DF_CLUE_DONE) color = COL_DONE; else if (x < 0 || y < 0 || x >= w || y >= w) color = COL_GRID; else if (tile & DF_IMMUTABLE) color = COL_GRID; else color = COL_USER; draw_text(dr, tx + TILESIZE/2, ty + TILESIZE/2, FONT_VARIABLE, (tile & DF_PLAYAREA ? TILESIZE/2 : TILESIZE*2/5), ALIGN_VCENTRE | ALIGN_HCENTRE, color, str); } else { int i, j, npencil; int pl, pr, pt, pb; float bestsize; int pw, ph, minph, pbest, fontsize; /* Count the pencil marks required. */ for (i = 1, npencil = 0; i <= w; i++) if (tile & (1L << (i + DF_PENCIL_SHIFT))) npencil++; if (npencil) { minph = 2; /* * Determine the bounding rectangle within which we're going * to put the pencil marks. */ /* Start with the whole square, minus space for impinging towers */ pl = tx + (ui->three_d ? X_3D_DISP(w,w) : 0); pr = tx + TILESIZE; pt = ty; pb = ty + TILESIZE - (ui->three_d ? Y_3D_DISP(w,w) : 0); /* * We arrange our pencil marks in a grid layout, with * the number of rows and columns adjusted to allow the * maximum font size. * * So now we work out what the grid size ought to be. */ bestsize = 0.0; pbest = 0; /* Minimum */ for (pw = 3; pw < max(npencil,4); pw++) { float fw, fh, fs; ph = (npencil + pw - 1) / pw; ph = max(ph, minph); fw = (pr - pl) / (float)pw; fh = (pb - pt) / (float)ph; fs = min(fw, fh); if (fs > bestsize) { bestsize = fs; pbest = pw; } } assert(pbest > 0); pw = pbest; ph = (npencil + pw - 1) / pw; ph = max(ph, minph); /* * Now we've got our grid dimensions, work out the pixel * size of a grid element, and round it to the nearest * pixel. (We don't want rounding errors to make the * grid look uneven at low pixel sizes.) */ fontsize = min((pr - pl) / pw, (pb - pt) / ph); /* * Centre the resulting figure in the square. */ pl = pl + (pr - pl - fontsize * pw) / 2; pt = pt + (pb - pt - fontsize * ph) / 2; /* * Now actually draw the pencil marks. */ for (i = 1, j = 0; i <= w; i++) if (tile & (1L << (i + DF_PENCIL_SHIFT))) { int dx = j % pw, dy = j / pw; str[1] = '\0'; str[0] = i + '0'; draw_text(dr, pl + fontsize * (2*dx+1) / 2, pt + fontsize * (2*dy+1) / 2, FONT_VARIABLE, fontsize, ALIGN_VCENTRE | ALIGN_HCENTRE, COL_PENCIL, str); j++; } } } } static void game_redraw(drawing *dr, game_drawstate *ds, const game_state *oldstate, const game_state *state, int dir, const game_ui *ui, float animtime, float flashtime) { int w = state->par.w /*, a = w*w */; int i, x, y; check_errors(state, ds->errtmp); /* * Work out what data each tile should contain. */ for (i = 0; i < (w+2)*(w+2); i++) ds->tiles[i] = 0; /* completely blank square */ /* The clue squares... */ for (i = 0; i < 4*w; i++) { long tile = state->clues->clues[i]; CLUEPOS(x, y, i, w); if (ds->errtmp[(y+1)*(w+2)+(x+1)]) tile |= DF_ERROR; else if (state->clues_done[i]) tile |= DF_CLUE_DONE; ds->tiles[(y+1)*(w+2)+(x+1)] = tile; } /* ... and the main grid. */ for (y = 0; y < w; y++) { for (x = 0; x < w; x++) { long tile = DF_PLAYAREA; if (state->grid[y*w+x]) tile |= state->grid[y*w+x]; else tile |= (long)state->pencil[y*w+x] << DF_PENCIL_SHIFT; if (ui->hshow && ui->hx == x && ui->hy == y) tile |= (ui->hpencil ? DF_HIGHLIGHT_PENCIL : DF_HIGHLIGHT); if (state->clues->immutable[y*w+x]) tile |= DF_IMMUTABLE; if (flashtime > 0 && (flashtime <= FLASH_TIME/3 || flashtime >= FLASH_TIME*2/3)) tile |= DF_HIGHLIGHT; /* completion flash */ if (ds->errtmp[(y+1)*(w+2)+(x+1)]) tile |= DF_ERROR; ds->tiles[(y+1)*(w+2)+(x+1)] = tile; } } /* * Now actually draw anything that needs to be changed. */ for (y = 0; y < w+2; y++) { for (x = 0; x < w+2; x++) { long tl, tr, bl, br; int i = y*(w+2)+x; tr = ds->tiles[y*(w+2)+x]; tl = (x == 0 ? 0 : ds->tiles[y*(w+2)+(x-1)]); br = (y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+x]); bl = (x == 0 || y == w+1 ? 0 : ds->tiles[(y+1)*(w+2)+(x-1)]); if (ds->drawn[i*4] != tl || ds->drawn[i*4+1] != tr || ds->drawn[i*4+2] != bl || ds->drawn[i*4+3] != br) { clip(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); draw_tile(dr, ds, ui, state->clues, x-1, y-1, tr); if (x > 0) draw_tile(dr, ds, ui, state->clues, x-2, y-1, tl); if (y <= w) draw_tile(dr, ds, ui, state->clues, x-1, y, br); if (x > 0 && y <= w) draw_tile(dr, ds, ui, state->clues, x-2, y, bl); unclip(dr); draw_update(dr, COORD(x-1), COORD(y-1), TILESIZE, TILESIZE); ds->drawn[i*4] = tl; ds->drawn[i*4+1] = tr; ds->drawn[i*4+2] = bl; ds->drawn[i*4+3] = br; } } } } static float game_anim_length(const game_state *oldstate, const game_state *newstate, int dir, game_ui *ui) { return 0.0F; } static float game_flash_length(const game_state *oldstate, const game_state *newstate, int dir, game_ui *ui) { if (!oldstate->completed && newstate->completed && !oldstate->cheated && !newstate->cheated) return FLASH_TIME; return 0.0F; } static void game_get_cursor_location(const game_ui *ui, const game_drawstate *ds, const game_state *state, const game_params *params, int *x, int *y, int *w, int *h) { if(ui->hshow) { *x = COORD(ui->hx); *y = COORD(ui->hy); *w = *h = TILESIZE; } } static int game_status(const game_state *state) { return state->completed ? +1 : 0; } static void game_print_size(const game_params *params, const game_ui *ui, float *x, float *y) { int pw, ph; /* * We use 9mm squares by default, like Solo. */ game_compute_size(params, 900, ui, &pw, &ph); *x = pw / 100.0F; *y = ph / 100.0F; } static void game_print(drawing *dr, const game_state *state, const game_ui *ui, int tilesize) { int w = state->par.w; int ink = print_mono_colour(dr, 0); int i, x, y; /* Ick: fake up `ds->tilesize' for macro expansion purposes */ game_drawstate ads, *ds = &ads; game_set_size(dr, ds, NULL, tilesize); /* * Border. */ print_line_width(dr, 3 * TILESIZE / 40); draw_rect_outline(dr, BORDER, BORDER, w*TILESIZE, w*TILESIZE, ink); /* * Main grid. */ for (x = 1; x < w; x++) { print_line_width(dr, TILESIZE / 40); draw_line(dr, BORDER+x*TILESIZE, BORDER, BORDER+x*TILESIZE, BORDER+w*TILESIZE, ink); } for (y = 1; y < w; y++) { print_line_width(dr, TILESIZE / 40); draw_line(dr, BORDER, BORDER+y*TILESIZE, BORDER+w*TILESIZE, BORDER+y*TILESIZE, ink); } /* * Clues. */ for (i = 0; i < 4*w; i++) { char str[128]; if (!state->clues->clues[i]) continue; CLUEPOS(x, y, i, w); sprintf (str, "%d", state->clues->clues[i]); draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2, BORDER + y*TILESIZE + TILESIZE/2, FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); } /* * Numbers for the solution, if any. */ for (y = 0; y < w; y++) for (x = 0; x < w; x++) if (state->grid[y*w+x]) { char str[2]; str[1] = '\0'; str[0] = state->grid[y*w+x] + '0'; draw_text(dr, BORDER + x*TILESIZE + TILESIZE/2, BORDER + y*TILESIZE + TILESIZE/2, FONT_VARIABLE, TILESIZE/2, ALIGN_VCENTRE | ALIGN_HCENTRE, ink, str); } } #ifdef COMBINED #define thegame towers #endif const struct game thegame = { "Towers", "games.towers", "towers", default_params, game_fetch_preset, NULL, decode_params, encode_params, free_params, dup_params, true, game_configure, custom_params, validate_params, new_game_desc, validate_desc, new_game, dup_game, free_game, true, solve_game, true, game_can_format_as_text_now, game_text_format, get_prefs, set_prefs, new_ui, free_ui, NULL, /* encode_ui */ NULL, /* decode_ui */ game_request_keys, game_changed_state, current_key_label, interpret_move, execute_move, PREFERRED_TILESIZE, game_compute_size, game_set_size, game_colours, game_new_drawstate, game_free_drawstate, game_redraw, game_anim_length, game_flash_length, game_get_cursor_location, game_status, true, false, game_print_size, game_print, false, /* wants_statusbar */ false, NULL, /* timing_state */ REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */ }; #ifdef STANDALONE_SOLVER #include <stdarg.h> int main(int argc, char **argv) { game_params *p; game_state *s; char *id = NULL, *desc; const char *err; bool grade = false; int ret, diff; bool really_show_working = false; while (--argc > 0) { char *p = *++argv; if (!strcmp(p, "-v")) { really_show_working = true; } else if (!strcmp(p, "-g")) { grade = true; } else if (*p == '-') { fprintf(stderr, "%s: unrecognised option `%s'\n", argv[0], p); return 1; } else { id = p; } } if (!id) { fprintf(stderr, "usage: %s [-g | -v] <game_id>\n", argv[0]); return 1; } desc = strchr(id, ':'); if (!desc) { fprintf(stderr, "%s: game id expects a colon in it\n", argv[0]); return 1; } *desc++ = '\0'; p = default_params(); decode_params(p, id); err = validate_desc(p, desc); if (err) { fprintf(stderr, "%s: %s\n", argv[0], err); return 1; } s = new_game(NULL, p, desc); /* * When solving an Easy puzzle, we don't want to bother the * user with Hard-level deductions. For this reason, we grade * the puzzle internally before doing anything else. */ ret = -1; /* placate optimiser */ solver_show_working = 0; for (diff = 0; diff < DIFFCOUNT; diff++) { memcpy(s->grid, s->clues->immutable, p->w * p->w); ret = solver(p->w, s->clues->clues, s->grid, diff); if (ret <= diff) break; } if (really_show_working) { /* * Now run the solver again at the last difficulty level we * tried, but this time with diagnostics enabled. */ solver_show_working = really_show_working; memcpy(s->grid, s->clues->immutable, p->w * p->w); ret = solver(p->w, s->clues->clues, s->grid, diff < DIFFCOUNT ? diff : DIFFCOUNT-1); } if (diff == DIFFCOUNT) { if (grade) printf("Difficulty rating: ambiguous\n"); else printf("Unable to find a unique solution\n"); } else { if (grade) { if (ret == diff_impossible) printf("Difficulty rating: impossible (no solution exists)\n"); else printf("Difficulty rating: %s\n", towers_diffnames[ret]); } else { if (ret != diff) printf("Puzzle is inconsistent\n"); else fputs(game_text_format(s), stdout); } } return 0; } #endif /* vim: set shiftwidth=4 tabstop=8: */