X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=signpost.c;h=aa2e13af9abfa65ce51c0b663aead1a3cb32825d;hb=3ce69e84cad15844282d691fa03e711c5353c05e;hp=fec2084439a68162afda0fc4e0cfad840572ab3e;hpb=d54bbdadeef015f9360dddfd6284b72f9937ca94;p=sgt-puzzles.git diff --git a/signpost.c b/signpost.c index fec2084..aa2e13a 100644 --- a/signpost.c +++ b/signpost.c @@ -92,7 +92,8 @@ static int whichdiri(game_state *state, int fromi, int toi) 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]; @@ -119,7 +120,7 @@ static int ispointingi(game_state *state, int fromi, int toi) /* 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; @@ -144,7 +145,7 @@ static int move_couldfit(game_state *state, int num, int d, int x, int y) 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; @@ -159,8 +160,9 @@ static int isvalidmove(game_state *state, int clever, 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 @@ -195,13 +197,13 @@ static void makelink(game_state *state, int from, int to) 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; @@ -338,7 +340,7 @@ static void free_params(game_params *params) 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 */ @@ -362,7 +364,7 @@ static void decode_params(game_params *ret, char const *string) } -static char *encode_params(game_params *params, int full) +static char *encode_params(const game_params *params, int full) { char data[256]; @@ -375,7 +377,7 @@ static char *encode_params(game_params *params, int full) 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]; @@ -407,7 +409,7 @@ static config_item *game_configure(game_params *params) 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); @@ -418,11 +420,14 @@ static game_params *custom_params(config_item *cfg) 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; } @@ -460,7 +465,7 @@ static game_state *blank_game(int w, int h) 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; @@ -477,7 +482,7 @@ static void dup_game_to(game_state *to, game_state *from) 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); @@ -496,7 +501,7 @@ static void free_game(game_state *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); @@ -510,7 +515,7 @@ static void unpick_desc(game_params *params, char *desc, } c = *desc; - if (isdigit(c)) { + if (isdigit((unsigned char)c)) { num = (num*10) + (int)(c-'0'); if (num > state->n) { msg = "Number too large"; @@ -619,6 +624,7 @@ static int new_game_fill(game_state *state, random_state *rs, 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 @@ -634,6 +640,8 @@ static int new_game_fill(game_state *state, random_state *rs, 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); @@ -749,6 +757,7 @@ static int new_game_strip(game_state *state, random_state *rs) 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)); } @@ -789,13 +798,16 @@ done: 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); @@ -836,7 +848,7 @@ generate: 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; @@ -847,7 +859,7 @@ static char *validate_desc(game_params *params, char *desc) /* --- 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) @@ -861,76 +873,45 @@ static char *validate_desc(game_params *params, char *desc) * 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, found = 0, start = 0, 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 (!found) { - start = ss; - found = 1; - why = "contains cell with immutable number"; - } else if (start != ss) { + 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; } @@ -939,18 +920,22 @@ static int head_number(game_state *state, int i, int *scratch) j = state->next[j]; assert(j != i); /* we have created a loop, obviously wrong */ } - if (found) goto done; - - 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"; - } - found = 1; + 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; @@ -958,45 +943,51 @@ static int head_number(game_state *state, int i, int *scratch) j = i; while (state->next[j] != -1) { j = state->next[j]; - if (state->nums[j] == 0) { - start = START(c); - found = 1; - 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"; } - found = 1; - 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 (!found) { - start = (c == 0) ? lowest_start(state, scratch) : START(c); - why = "got to end of coloured region"; - found = 1; - } - 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"; } done: - assert(found && why != NULL); - debug(("Chain at (%d,%d) numbered at %d: %s.", - i%state->w, i/state->w, start, why)); - return start; + 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 @@ -1035,17 +1026,59 @@ static void connect_numbers(game_state *state) } } +static int compare_heads(const void *a, const void *b) +{ + struct head_meta *ha = (struct head_meta *)a; + struct head_meta *hb = (struct head_meta *)b; + + /* 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, nnum; - int *scratch = snewn(state->n, int); + int i, j, n, nnum, nheads; + struct head_meta *heads = snewn(state->n, struct head_meta); - for (i = 0; i < state->n; i++) - state->numsi[i] = -1; + 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; } @@ -1054,23 +1087,63 @@ static void update_numbers(game_state *state) } 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) @@ -1137,7 +1210,8 @@ static int check_completion(game_state *state, int mark_errors) 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; @@ -1267,8 +1341,8 @@ static int solve_state(game_state *state) 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; @@ -1306,7 +1380,7 @@ struct game_ui { 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); @@ -1326,17 +1400,17 @@ static void free_ui(game_ui *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; @@ -1353,8 +1427,9 @@ struct game_drawstate { 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]; @@ -1400,10 +1475,14 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, 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; @@ -1427,7 +1506,7 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, 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); } @@ -1446,7 +1525,7 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, 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); } @@ -1470,7 +1549,7 @@ static void unlink_cell(game_state *state, int si) } } -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; @@ -1506,6 +1585,8 @@ static game_state *execute_move(game_state *state, char *move) 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; @@ -1514,11 +1595,12 @@ static game_state *execute_move(game_state *state, char *move) 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. */ @@ -1546,8 +1628,8 @@ static game_state *execute_move(game_state *state, char *move) * 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; @@ -1558,7 +1640,7 @@ static void game_compute_size(game_params *params, int tilesize, } 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); @@ -1637,7 +1719,7 @@ static float *game_colours(frontend *fe, int *ncolours) 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; @@ -1743,8 +1825,8 @@ static int num2col(game_drawstate *ds, int num) { int set = num / (ds->n+1); - if (num <= 0) return COL_B0; - 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) @@ -1810,7 +1892,7 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, int tx, int ty, 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; @@ -1861,19 +1943,30 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, int tx, int ty, if (!empty) { int set = (num <= 0) ? 0 : num / (ds->n+1); + char *p = buf; if (set == 0 || num <= 0) { sprintf(buf, "%d", num); } else { int n = num % (ds->n+1); + p += sizeof(buf) - 1; - if (n == 0) - sprintf(buf, "%c", (int)(set+'a'-1)); - else - sprintf(buf, "%c+%d", (int)(set+'a'-1), n); + 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) { @@ -1883,7 +1976,8 @@ static void tile_redraw(drawing *dr, game_drawstate *ds, int tx, int ty, } 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); @@ -1898,7 +1992,7 @@ static void draw_drag_indicator(drawing *dr, game_drawstate *ds, /* 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; @@ -1927,9 +2021,10 @@ static void draw_drag_indicator(drawing *dr, game_drawstate *ds, 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; @@ -2014,11 +2109,33 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, 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; @@ -2037,14 +2154,14 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, 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) @@ -2053,12 +2170,17 @@ static float game_flash_length(game_state *oldstate, game_state *newstate, 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; @@ -2067,7 +2189,7 @@ static void game_print_size(game_params *params, float *x, float *y) *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; @@ -2134,10 +2256,11 @@ const struct game thegame = { 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 @@ -2280,7 +2403,7 @@ int main(int argc, const char *argv[]) } } - sprintf(newseed, "%lu", time(NULL)); + sprintf(newseed, "%lu", (unsigned long) time(NULL)); seedstr = dupstr(newseed); if (id || !stdin_desc) {