Files
puzzles/towers.c
Franklin Wei 5de69c22b0 Refactor button & ~MOD_MASK' as STRIP_BUTTON_MODIFIERS(button)'.
This refactors all instances of bitwise-ANDs with `~MOD_MASK'. There is
a handful of more complex instances I left unchanged (in cube.c, midend.c,
and twiddle.c), since those AND with `~MOD_MASK | MOD_NUM_KEYPAD' or
similar. I don't think it's worth writing a macro for those cases.

Also document this new macro's usage in devel.but.
2024-07-31 23:29:00 +01:00

2258 lines
56 KiB
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 = STRIP_BUTTON_MODIFIERS(button);
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: */