return whichdir(fromi%w, fromi/w, toi%w, toi/w);
}
-static int ispointing(game_state *state, int fromx, int fromy, int tox, int toy)
+static int ispointing(const game_state *state, int fromx, int fromy,
+ int tox, int toy)
{
int w = state->w, dir = state->dirs[fromy*w+fromx];
/* Taking the number 'num', work out the gap between it and the next
* available number up or down (depending on d). Return 1 if the region
* at (x,y) will fit in that gap, or 0 otherwise. */
-static int move_couldfit(game_state *state, int num, int d, int x, int y)
+static int move_couldfit(const game_state *state, int num, int d, int x, int y)
{
int n, gap, i = y*state->w+x, sz;
return (sz > gap) ? 0 : 1;
}
-static int isvalidmove(game_state *state, int clever,
+static int isvalidmove(const game_state *state, int clever,
int fromx, int fromy, int tox, int toy)
{
int w = state->w, from = fromy*w+fromx, to = toy*w+tox;
nfrom = state->nums[from]; nto = state->nums[to];
- /* can't move _from_ the final number, or _to_ the 1. */
- if (nfrom == state->n || nto == 1)
+ /* can't move _from_ the preset final number, or _to_ the preset 1. */
+ if (((nfrom == state->n) && (state->flags[from] & FLAG_IMMUTABLE)) ||
+ ((nto == 1) && (state->flags[to] & FLAG_IMMUTABLE)))
return 0;
/* can't create a new connection between cells in the same region
state->prev[to] = from;
}
-static int game_can_format_as_text_now(game_params *params)
+static int game_can_format_as_text_now(const game_params *params)
{
if (params->w * params->h >= 100) return 0;
return 1;
}
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
{
int len = state->h * 2 * (4*state->w + 1) + state->h + 2;
int x, y, i, num, n, set;
int i, ret = 1;
assert(copy->n == orig->n);
for (i = 0; i < copy->n; i++) {
- if (only_immutable && !copy->flags[i] & FLAG_IMMUTABLE) continue;
+ if (only_immutable && !(copy->flags[i] & FLAG_IMMUTABLE)) continue;
assert(copy->nums[i] >= 0);
assert(copy->nums[i] <= copy->n);
if (copy->nums[i] != orig->nums[i]) {
sfree(params);
}
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
}
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
{
char data[256];
return dupstr(data);
}
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[80];
return ret;
}
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
return ret;
}
-static char *validate_params(game_params *params, int full)
+static char *validate_params(const game_params *params, int full)
{
- if (params->w < 2 || params->h < 2)
- return "Width and height must both be at least two";
-
+ if (params->w < 1) return "Width must be at least one";
+ if (params->h < 1) return "Height must be at least one";
+ if (full && params->w == 1 && params->h == 1)
+ /* The UI doesn't let us move these from unsolved to solved,
+ * so we disallow generating (but not playing) them. */
+ return "Width and height cannot both be one";
return NULL;
}
return state;
}
-static void dup_game_to(game_state *to, game_state *from)
+static void dup_game_to(game_state *to, const game_state *from)
{
to->completed = from->completed;
to->used_solve = from->used_solve;
memcpy(to->numsi, from->numsi, (to->n+1)*sizeof(int));
}
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
{
game_state *ret = blank_game(state->w, state->h);
dup_game_to(ret, state);
sfree(state);
}
-static void unpick_desc(game_params *params, char *desc,
+static void unpick_desc(const game_params *params, const char *desc,
game_state **sout, char **mout)
{
game_state *state = blank_game(params->w, params->h);
}
c = *desc;
- if (isdigit(c)) {
+ if (isdigit((unsigned char)c)) {
num = (num*10) + (int)(c-'0');
if (num > state->n) {
msg = "Number too large";
state->dirs[taili] = 0;
nfilled = 2;
+ assert(state->n > 1);
while (nfilled < state->n) {
/* Try and expand _from_ headi; keep going if there's only one
an = cell_adj(state, headi, aidx, adir);
} while (an == 1);
+ if (nfilled == state->n) break;
+
/* Try and expand _to_ taili; keep going if there's only one
* place to go to. */
an = cell_adj(state, taili, aidx, adir);
copy->flags[j] |= FLAG_IMMUTABLE;
state->flags[j] |= FLAG_IMMUTABLE;
debug_state("Copy of state: ", copy);
+ strip_nums(copy);
if (solve_state(copy) > 0) goto solved;
assert(check_nums(state, copy, 1));
}
return ret;
}
-static char *new_game_desc(game_params *params, random_state *rs,
+static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive)
{
game_state *state = blank_game(params->w, params->h);
char *ret;
int headi, taili;
+ /* this shouldn't happen (validate_params), but let's play it safe */
+ if (params->w == 1 && params->h == 1) return dupstr("1a");
+
generate:
blank_game_into(state);
return ret;
}
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
{
char *ret = NULL;
/* --- Linked-list and numbers array --- */
/* Assuming numbers are always up-to-date, there are only four possibilities
- * for regions changing:
+ * for regions changing after a single valid move:
*
* 1) two differently-coloured regions being combined (the resulting colouring
* should be based on the larger of the two regions)
* There should never be any complications with regions containing 3 colours
* being combined, since two of those colours should have been merged on a
* previous move.
- */
-
-/* New algorithm for working out numbering:
*
- * At start, only remove numbers from cells with neither prev nor next.
- * Search for all cells with !prev && next (head of chain); for each one:
- * Search the group for a 'real' number: if we find one the num. for
- the head of the chain is trivial.
- * Otherwise, if we _don't_ have a number already:
- * If head->next has a number, that number is the one we should use
- * Otherwise pick the smallest unused colour set.
- * and if we _do_ have a number already:
- * Work out the size of this group (the dsf must already have been set up)
- * Start enumerating through the group counting squares that have the
- same colouring as us
- * If we reach a square with a different colour, work out which set is
- bigger (ncol1 vs ncol2 == sz-ncol1), and use that colour
- * If we reached a square with no colour (or the end of the group, which
- would be weird under the circumstances) just keep the existing colour.
+ * Most of the complications are in ensuring we don't accidentally set two
+ * regions with the same colour (e.g. if a region was split). If this happens
+ * we always try and give the largest original portion the original colour.
*/
#define COLOUR(a) ((a) / (state->n+1))
#define START(c) ((c) * (state->n+1))
-static int lowest_start(game_state *state, int *scratch)
-{
- int i, c;
-
- /* Fill in 'scratch' array with the currently-used colours... */
- memset(scratch, 0, state->n * sizeof(int));
- for (i = 0; i < state->n; i++) {
- if (state->nums[i] != 0)
- scratch[COLOUR(state->nums[i])] = 1;
- }
- /* ... and return the first one that was unused. */
- for (c = 1; c < state->n; c++) { /* NB start at 1 */
- if (scratch[c] == 0)
- return START(c);
- }
- assert(!"shouldn't get here");
- return -1; /* suyb */
-}
-
-static int used_colour(game_state *state, int i, int start)
-{
- int j;
- for (j = 0; j < i; j++) {
- if (state->nums[j] == start)
- return 1;
- }
- return 0;
-}
+struct head_meta {
+ int i; /* position */
+ int sz; /* size of region */
+ int start; /* region start number preferred, or 0 if !preference */
+ int preference; /* 0 if we have no preference (and should just pick one) */
+ const char *why;
+};
-static int head_number(game_state *state, int i, int *scratch)
+static void head_number(game_state *state, int i, struct head_meta *head)
{
- int off = 0, start = -1, ss, j = i, c, n, sz;
- const char *why = NULL;
+ int off = 0, ss, j = i, c, n, sz;
+ /* Insist we really were passed the head of a chain. */
assert(state->prev[i] == -1 && state->next[i] != -1);
+ head->i = i;
+ head->sz = dsf_size(state->dsf, i);
+ head->why = NULL;
+
/* Search through this chain looking for real numbers, checking that
* they match up (if there are more than one). */
+ head->preference = 0;
while (j != -1) {
if (state->flags[j] & FLAG_IMMUTABLE) {
ss = state->nums[j] - off;
- if (start == -1) {
- start = ss;
- why = "contains cell with immutable number";
- } else if (start != ss) {
- debug(("head_number: chain with non-sequential numbers."));
+ if (!head->preference) {
+ head->start = ss;
+ head->preference = 1;
+ head->why = "contains cell with immutable number";
+ } else if (head->start != ss) {
+ debug(("head_number: chain with non-sequential numbers!"));
state->impossible = 1;
}
}
j = state->next[j];
assert(j != i); /* we have created a loop, obviously wrong */
}
- if (start != -1) goto found;
-
- if (state->nums[i] == 0) {
- if (state->nums[state->next[i]] != 0) {
- /* make sure we start at a 0 offset. */
- start = START(COLOUR(state->nums[state->next[i]]));
- why = "adding blank cell to head of numbered region";
- } else {
- start = lowest_start(state, scratch);
- why = "lowest available colour group";
- }
+ if (head->preference) goto done;
+
+ if (state->nums[i] == 0 && state->nums[state->next[i]] > state->n) {
+ /* (probably) empty cell onto the head of a coloured region:
+ * make sure we start at a 0 offset. */
+ head->start = START(COLOUR(state->nums[state->next[i]]));
+ head->preference = 1;
+ head->why = "adding blank cell to head of numbered region";
+ } else if (state->nums[i] <= state->n) {
+ /* if we're 0 we're probably just blank -- but even if we're a
+ * (real) numbered region, we don't have an immutable number
+ * in it (any more) otherwise it'd have been caught above, so
+ * reassign the colour. */
+ head->start = 0;
+ head->preference = 0;
+ head->why = "lowest available colour group";
} else {
c = COLOUR(state->nums[i]);
n = 1;
j = i;
while (state->next[j] != -1) {
j = state->next[j];
- if (state->nums[j] == 0) {
- start = START(c);
- why = "adding blank cell to end of numbered region";
- break;
+ if (state->nums[j] == 0 && state->next[j] == -1) {
+ head->start = START(c);
+ head->preference = 1;
+ head->why = "adding blank cell to end of numbered region";
+ goto done;
}
if (COLOUR(state->nums[j]) == c)
n++;
else {
int start_alternate = START(COLOUR(state->nums[j]));
- if (n < (sz - n) && !used_colour(state, i, start_alternate)) {
- start = start_alternate;
- why = "joining two coloured regions, swapping to larger colour";
+ if (n < (sz - n)) {
+ head->start = start_alternate;
+ head->preference = 1;
+ head->why = "joining two coloured regions, swapping to larger colour";
} else {
- start = START(c);
- why = "joining two coloured regions, taking largest";
+ head->start = START(c);
+ head->preference = 1;
+ head->why = "joining two coloured regions, taking largest";
}
- break;
+ goto done;
}
}
/* If we got here then we may have split a region into
* two; make sure we don't assign a colour we've already used. */
- if (start == -1) {
- start = (c == 0) ? lowest_start(state, scratch) : START(c);
- why = "got to end of coloured region";
- }
- if (used_colour(state, i, start)) {
- start = lowest_start(state, scratch);
- why = "split region in two, lowest available colour group";
+ if (c == 0) {
+ /* not convinced this shouldn't be an assertion failure here. */
+ head->start = 0;
+ head->preference = 0;
+ } else {
+ head->start = START(c);
+ head->preference = 1;
}
+ head->why = "got to end of coloured region";
}
-found:
- assert(start != -1 && why != NULL);
- debug(("Chain at (%d,%d) numbered at %d: %s.",
- i%state->w, i/state->w, start, why));
- return start;
+done:
+ assert(head->why != NULL);
+ if (head->preference)
+ debug(("Chain at (%d,%d) numbered for preference at %d (colour %d): %s.",
+ head->i%state->w, head->i/state->w,
+ head->start, COLOUR(head->start), head->why));
+ else
+ debug(("Chain at (%d,%d) using next available colour: %s.",
+ head->i%state->w, head->i/state->w,
+ head->why));
}
#if 0
}
}
-static void update_numbers(game_state *state)
+static int compare_heads(const void *a, const void *b)
{
- int i, j, nnum;
- int *scratch = snewn(state->n, int);
+ struct head_meta *ha = (struct head_meta *)a;
+ struct head_meta *hb = (struct head_meta *)b;
- for (i = 0; i < state->n; i++) {
- assert(state->nums[i] >= 0);
- state->numsi[i] = -1;
+ /* Heads with preferred colours first... */
+ if (ha->preference && !hb->preference) return -1;
+ if (hb->preference && !ha->preference) return 1;
+
+ /* ...then heads with low colours first... */
+ if (ha->start < hb->start) return -1;
+ if (ha->start > hb->start) return 1;
+
+ /* ... then large regions first... */
+ if (ha->sz > hb->sz) return -1;
+ if (ha->sz < hb->sz) return 1;
+
+ /* ... then position. */
+ if (ha->i > hb->i) return -1;
+ if (ha->i < hb->i) return 1;
+
+ return 0;
+}
+
+static int lowest_start(game_state *state, struct head_meta *heads, int nheads)
+{
+ int n, c;
+
+ /* NB start at 1: colour 0 is real numbers */
+ for (c = 1; c < state->n; c++) {
+ for (n = 0; n < nheads; n++) {
+ if (COLOUR(heads[n].start) == c)
+ goto used;
+ }
+ return c;
+used:
+ ;
}
+ assert(!"No available colours!");
+ return 0;
+}
+
+static void update_numbers(game_state *state)
+{
+ int i, j, n, nnum, nheads;
+ struct head_meta *heads = snewn(state->n, struct head_meta);
+
+ for (n = 0; n < state->n; n++)
+ state->numsi[n] = -1;
for (i = 0; i < state->n; i++) {
if (state->flags[i] & FLAG_IMMUTABLE) {
- assert(state->nums[i] >= 0);
+ assert(state->nums[i] > 0);
assert(state->nums[i] <= state->n);
state->numsi[state->nums[i]] = i;
}
}
connect_numbers(state);
+ /* Construct an array of the heads of all current regions, together
+ * with their preferred colours. */
+ nheads = 0;
for (i = 0; i < state->n; i++) {
/* Look for a cell that is the start of a chain
* (has a next but no prev). */
if (state->prev[i] != -1 || state->next[i] == -1) continue;
- nnum = head_number(state, i, scratch);
- j = i;
+ head_number(state, i, &heads[nheads++]);
+ }
+
+ /* Sort that array:
+ * - heads with preferred colours first, then
+ * - heads with low colours first, then
+ * - large regions first
+ */
+ qsort(heads, nheads, sizeof(struct head_meta), compare_heads);
+
+ /* Remove duplicate-coloured regions. */
+ for (n = nheads-1; n >= 0; n--) { /* order is important! */
+ if ((n != 0) && (heads[n].start == heads[n-1].start)) {
+ /* We have a duplicate-coloured region: since we're
+ * sorted in size order and this is not the first
+ * of its colour it's not the largest: recolour it. */
+ heads[n].start = START(lowest_start(state, heads, nheads));
+ heads[n].preference = -1; /* '-1' means 'was duplicate' */
+ }
+ else if (!heads[n].preference) {
+ assert(heads[n].start == 0);
+ heads[n].start = START(lowest_start(state, heads, nheads));
+ }
+ }
+
+ debug(("Region colouring after duplicate removal:"));
+
+ for (n = 0; n < nheads; n++) {
+ debug((" Chain at (%d,%d) sz %d numbered at %d (colour %d): %s%s",
+ heads[n].i % state->w, heads[n].i / state->w, heads[n].sz,
+ heads[n].start, COLOUR(heads[n].start), heads[n].why,
+ heads[n].preference == 0 ? " (next available)" :
+ heads[n].preference < 0 ? " (duplicate, next available)" : ""));
+
+ nnum = heads[n].start;
+ j = heads[n].i;
while (j != -1) {
- if (nnum > 0 && nnum <= state->n)
- state->numsi[nnum] = j;
- state->nums[j] = nnum++;
+ if (!(state->flags[j] & FLAG_IMMUTABLE)) {
+ if (nnum > 0 && nnum <= state->n)
+ state->numsi[nnum] = j;
+ state->nums[j] = nnum;
+ }
+ nnum++;
j = state->next[j];
- assert(j != i); /* loop?! */
+ assert(j != heads[n].i); /* loop?! */
}
}
/*debug_numbers(state);*/
- sfree(scratch);
+ sfree(heads);
}
static int check_completion(game_state *state, int mark_errors)
}
}
+ /* Search and mark numbers less than 0, or 0 with links. */
+ for (n = 1; n < state->n; n++) {
+ if ((state->nums[n] < 0) ||
+ (state->nums[n] == 0 &&
+ (state->next[n] != -1 || state->prev[n] != -1))) {
+ error = 1;
+ if (mark_errors)
+ state->flags[n] |= FLAG_ERROR;
+ }
+ }
+
if (error) return 0;
return complete;
}
-static game_state *new_game(midend *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
{
game_state *state = NULL;
return ret;
}
-static char *solve_game(game_state *state, game_state *currstate,
- char *aux, char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, char **error)
{
game_state *tosolve;
char *ret = NULL;
int dx, dy; /* pixel coords of drag posn */
};
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
{
game_ui *ui = snew(game_ui);
sfree(ui);
}
-static char *encode_ui(game_ui *ui)
+static char *encode_ui(const game_ui *ui)
{
return NULL;
}
-static void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, const char *encoding)
{
}
-static void game_changed_state(game_ui *ui, game_state *oldstate,
- game_state *newstate)
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
{
if (!oldstate->completed && newstate->completed)
ui->cshow = ui->dragging = 0;
blitter *dragb;
};
-static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
- int mx, int my, int button)
+static char *interpret_move(const game_state *state, game_ui *ui,
+ const game_drawstate *ds,
+ int mx, int my, int button)
{
int x = FROMCOORD(mx), y = FROMCOORD(my), w = state->w;
char buf[80];
if (button == LEFT_BUTTON) {
/* disallow dragging from the final number. */
- if (state->nums[y*w+x] == state->n) return NULL;
+ if ((state->nums[y*w+x] == state->n) &&
+ (state->flags[y*w+x] & FLAG_IMMUTABLE))
+ return NULL;
} else if (button == RIGHT_BUTTON) {
/* disallow dragging to the first number. */
- if (state->nums[y*w+x] == 1) return NULL;
+ if ((state->nums[y*w+x] == 1) &&
+ (state->flags[y*w+x] & FLAG_IMMUTABLE))
+ return NULL;
}
ui->dragging = TRUE;
if (state->prev[si] == -1 && state->next[si] == -1)
return "";
sprintf(buf, "%c%d,%d",
- ui->drag_is_from ? 'C' : 'X', ui->sx, ui->sy);
+ (int)(ui->drag_is_from ? 'C' : 'X'), ui->sx, ui->sy);
return dupstr(buf);
}
if (state->prev[si] == -1 && state->next[si] == -1)
return "";
sprintf(buf, "%c%d,%d",
- (button == 'x') ? 'C' : 'X', ui->cx, ui->cy);
+ (int)((button == 'x') ? 'C' : 'X'), ui->cx, ui->cy);
return dupstr(buf);
}
}
}
-static game_state *execute_move(game_state *state, char *move)
+static game_state *execute_move(const game_state *state, const char *move)
{
game_state *ret = NULL;
int sx, sy, ex, ey, si, ei, w = state->w;
si = sy*w+sx; ei = ey*w+ex;
makelink(ret, si, ei);
} else if (sscanf(move, "%c%d,%d", &c, &sx, &sy) == 3) {
+ int sset;
+
if (c != 'C' && c != 'X') return NULL;
if (!INGRID(state, sx, sy)) return NULL;
si = sy*w+sx;
ret = dup_game(state);
- if (c == 'C') {
+ sset = state->nums[si] / (state->n+1);
+ if (c == 'C' || (c == 'X' && sset == 0)) {
/* Unlink the single cell we dragged from the board. */
unlink_cell(ret, si);
} else {
- int i, set, sset = state->nums[si] / (state->n+1);
+ int i, set;
for (i = 0; i < state->n; i++) {
/* Unlink all cells in the same set as the one we dragged
* from the board. */
* Drawing routines.
*/
-static void game_compute_size(game_params *params, int tilesize,
- int *x, int *y)
+static void game_compute_size(const game_params *params, int tilesize,
+ int *x, int *y)
{
/* Ick: fake up `ds->tilesize' for macro expansion purposes */
struct { int tilesize, order; } ads, *ds = &ads;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
- game_params *params, int tilesize)
+ const game_params *params, int tilesize)
{
ds->tilesize = tilesize;
assert(TILE_SIZE > 0);
return ret;
}
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
int i;
{
int set = num / (ds->n+1);
- if (num <= 0) return COL_BACKGROUND;
- return COL_B0 + (set % 16);
+ if (num <= 0 || set == 0) return COL_B0;
+ return COL_B0 + 1 + ((set-1) % 15);
}
#define ARROW_HALFSZ (7 * TILE_SIZE / 32)
int cb = TILE_SIZE / 16, textsz;
char buf[20];
int arrowcol, sarrowcol, setcol, textcol;
- int n = num % (ds->n+1), set = num / (ds->n+1);
- int acx, acy, asz;
+ int acx, acy, asz, empty = 0;
+
+ if (num == 0 && !(f & F_ARROW_POINT) && !(f & F_ARROW_INPOINT)) {
+ empty = 1;
+ /*
+ * We don't display text in empty cells: typically these are
+ * signified by num=0. However, in some cases a cell could
+ * have had the number 0 assigned to it if the user made an
+ * error (e.g. tried to connect a chain of length 5 to the
+ * immutable number 4) so we _do_ display the 0 if the cell
+ * has a link in or a link out.
+ */
+ }
/* Calculate colours. */
setcol = sarrowcol = -1; /* placate optimiser */
} else {
- setcol = num2col(ds, num);
+ setcol = empty ? COL_BACKGROUND : num2col(ds, num);
#define dim(fg,bg) ( \
(bg)==COL_BACKGROUND ? COL_ARROW_BG_DIM : \
else if (f & F_ARROW_POINT) arrowcol = mid(COL_ARROW, setcol);
else arrowcol = COL_ARROW;
- if (f & (F_ERROR)) textcol = COL_ERROR;
+ if ((f & F_ERROR) && !(f & F_IMMUTABLE)) textcol = COL_ERROR;
else {
if (f & F_IMMUTABLE) textcol = COL_NUMBER_SET;
else textcol = COL_NUMBER;
/* Draw text (number or set). */
- if (num != 0) {
- assert(num > 0);
- if (set == 0) {
- sprintf(buf, "%d", n);
+ if (!empty) {
+ int set = (num <= 0) ? 0 : num / (ds->n+1);
+
+ char *p = buf;
+ if (set == 0 || num <= 0) {
+ sprintf(buf, "%d", num);
} else {
- if (n == 0)
- sprintf(buf, "%c", (int)(set+'a'-1));
- else
- sprintf(buf, "%c+%d", (int)(set+'a'-1), n);
+ int n = num % (ds->n+1);
+ p += sizeof(buf) - 1;
+
+ if (n != 0) {
+ sprintf(buf, "+%d", n); /* Just to get the length... */
+ p -= strlen(buf);
+ sprintf(p, "+%d", n);
+ } else {
+ *p = '\0';
+ }
+ do {
+ set--;
+ p--;
+ *p = (char)((set % 26)+'a');
+ set /= 26;
+ } while (set);
}
- textsz = min(2*asz, (TILE_SIZE - 2 * cb) / (int)strlen(buf));
+ textsz = min(2*asz, (TILE_SIZE - 2 * cb) / (int)strlen(p));
draw_text(dr, tx + cb, ty + TILE_SIZE/4, FONT_VARIABLE, textsz,
- ALIGN_VCENTRE | ALIGN_HLEFT, textcol, buf);
+ ALIGN_VCENTRE | ALIGN_HLEFT, textcol, p);
}
if (print_ink < 0) {
}
static void draw_drag_indicator(drawing *dr, game_drawstate *ds,
- game_state *state, game_ui *ui, int validdrag)
+ const game_state *state, const game_ui *ui,
+ int validdrag)
{
int dir, w = ds->w, acol = COL_ARROW;
int fx = FROMCOORD(ui->dx), fy = FROMCOORD(ui->dy);
/* Draw an arrow pointing away from/towards the origin cell. */
int ox = COORD(ui->sx) + TILE_SIZE/2, oy = COORD(ui->sy) + TILE_SIZE/2;
double tana, offset;
- double xdiff = fabs(ox - ui->dx), ydiff = fabs(oy - ui->dy);
+ double xdiff = abs(ox - ui->dx), ydiff = abs(oy - ui->dy);
if (xdiff == 0) {
ang = (oy > ui->dy) ? 0.0F : PI;
draw_arrow(dr, ui->dx, ui->dy, ARROW_HALFSZ, ang, acol, acol);
}
-static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui,
- float animtime, float flashtime)
+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 x, y, i, w = ds->w, dirp, force = 0;
unsigned int f;
if (state->nums[i] != ds->nums[i] ||
f != ds->f[i] || dirp != ds->dirp[i] ||
force || !ds->started) {
+ int sign;
+ {
+ /*
+ * Trivial and foolish configurable option done on
+ * purest whim. With this option enabled, the
+ * victory flash is done by rotating each square
+ * in the opposite direction from its immediate
+ * neighbours, so that they behave like a field of
+ * interlocking gears. With it disabled, they all
+ * rotate in the same direction. Choose for
+ * yourself which is more brain-twisting :-)
+ */
+ static int gear_mode = -1;
+ if (gear_mode < 0) {
+ char *env = getenv("SIGNPOST_GEARS");
+ gear_mode = (env && (env[0] == 'y' || env[0] == 'Y'));
+ }
+ if (gear_mode)
+ sign = 1 - 2 * ((x ^ y) & 1);
+ else
+ sign = 1;
+ }
tile_redraw(dr, ds,
BORDER + x * TILE_SIZE,
BORDER + y * TILE_SIZE,
state->dirs[i], dirp, state->nums[i], f,
- angle_offset, -1);
+ sign * angle_offset, -1);
ds->nums[i] = state->nums[i];
ds->f[i] = f;
ds->dirp[i] = dirp;
if (!ds->started) ds->started = TRUE;
}
-static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+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(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
if (!oldstate->completed &&
newstate->completed && !newstate->used_solve)
return 0.0F;
}
-static int game_timing_state(game_state *state, game_ui *ui)
+static int game_status(const game_state *state)
+{
+ return state->completed ? +1 : 0;
+}
+
+static int game_timing_state(const game_state *state, game_ui *ui)
{
return TRUE;
}
-static void game_print_size(game_params *params, float *x, float *y)
+static void game_print_size(const game_params *params, float *x, float *y)
{
int pw, ph;
*y = ph / 100.0F;
}
-static void game_print(drawing *dr, game_state *state, int tilesize)
+static void game_print(drawing *dr, const game_state *state, int tilesize)
{
int ink = print_mono_colour(dr, 0);
int x, y;
const struct game thegame = {
"Signpost", "games.signpost", "signpost",
default_params,
- game_fetch_preset,
+ game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,
game_redraw,
game_anim_length,
game_flash_length,
+ game_status,
TRUE, FALSE, game_print_size, game_print,
FALSE, /* wants_statusbar */
FALSE, game_timing_state,
- REQUIRE_RBUTTON | REQUIRE_NUMPAD, /* flags */
+ REQUIRE_RBUTTON, /* flags */
};
#ifdef STANDALONE_SOLVER
}
}
- sprintf(newseed, "%lu", time(NULL));
+ sprintf(newseed, "%lu", (unsigned long) time(NULL));
seedstr = dupstr(newseed);
if (id || !stdin_desc) {