Forcing chains in Map give rise to a new `Hard' difficulty level.

Also implemented the Map analogue of Solo's pencil marks, to make
this mode more playable.

[originally from svn r6240]
This commit is contained in:
Simon Tatham
2005-08-30 19:42:45 +00:00
parent e7a02ae333
commit 121f664b62
2 changed files with 245 additions and 18 deletions

244
map.c
View File

@ -6,9 +6,9 @@
* TODO:
*
* - clue marking
* - more solver brains?
* - better four-colouring algorithm?
* - pencil marks?
* - can we make the pencil marks look nicer?
* - ability to drag a set of pencil marks?
*/
#include <stdio.h>
@ -43,6 +43,7 @@ static float flash_length;
#define DIFFLIST(A) \
A(EASY,Easy,e) \
A(NORMAL,Normal,n) \
A(HARD,Hard,h) \
A(RECURSE,Unreasonable,u)
#define ENUM(upper,title,lower) DIFF_ ## upper,
#define TITLE(upper,title,lower) #title,
@ -80,7 +81,7 @@ struct map {
struct game_state {
game_params p;
struct map *map;
int *colouring;
int *colouring, *pencil;
int completed, cheated;
};
@ -99,7 +100,10 @@ static game_params *default_params(void)
static const struct game_params map_presets[] = {
{20, 15, 30, DIFF_EASY},
{20, 15, 30, DIFF_NORMAL},
{20, 15, 30, DIFF_HARD},
{20, 15, 30, DIFF_RECURSE},
{30, 25, 75, DIFF_NORMAL},
{30, 25, 75, DIFF_HARD},
};
static int game_fetch_preset(int i, char **name, game_params **params)
@ -788,6 +792,8 @@ static void fourcolour(int *graph, int n, int ngraph, int *colouring,
struct solver_scratch {
unsigned char *possible; /* bitmap of colours for each region */
int *graph;
int *bfsqueue;
int *bfscolour;
int n;
int ngraph;
int depth;
@ -803,6 +809,8 @@ static struct solver_scratch *new_scratch(int *graph, int n, int ngraph)
sc->ngraph = ngraph;
sc->possible = snewn(n, unsigned char);
sc->depth = 0;
sc->bfsqueue = snewn(n, int);
sc->bfscolour = snewn(n, int);
return sc;
}
@ -810,9 +818,22 @@ static struct solver_scratch *new_scratch(int *graph, int n, int ngraph)
static void free_scratch(struct solver_scratch *sc)
{
sfree(sc->possible);
sfree(sc->bfsqueue);
sfree(sc->bfscolour);
sfree(sc);
}
/*
* Count the bits in a word. Only needs to cope with FOUR bits.
*/
static int bitcount(int word)
{
assert(FOUR <= 4); /* or this needs changing */
word = ((word & 0xA) >> 1) + (word & 0x5);
word = ((word & 0xC) >> 2) + (word & 0x3);
return word;
}
static int place_colour(struct solver_scratch *sc,
int *colouring, int index, int colour)
{
@ -955,6 +976,126 @@ static int map_solver(struct solver_scratch *sc,
}
}
if (done_something)
continue;
if (difficulty < DIFF_HARD)
break; /* can't do anything harder */
/*
* Right; now we get creative. Now we're going to look for
* `forcing chains'. A forcing chain is a path through the
* graph with the following properties:
*
* (a) Each vertex on the path has precisely two possible
* colours.
*
* (b) Each pair of vertices which are adjacent on the
* path share at least one possible colour in common.
*
* (c) Each vertex in the middle of the path shares _both_
* of its colours with at least one of its neighbours
* (not the same one with both neighbours).
*
* These together imply that at least one of the possible
* colour choices at one end of the path forces _all_ the
* rest of the colours along the path. In order to make
* real use of this, we need further properties:
*
* (c) Ruling out some colour C from the vertex at one end
* of the path forces the vertex at the other end to
* take colour C.
*
* (d) The two end vertices are mutually adjacent to some
* third vertex.
*
* (e) That third vertex currently has C as a possibility.
*
* If we can find all of that lot, we can deduce that at
* least one of the two ends of the forcing chain has
* colour C, and that therefore the mutually adjacent third
* vertex does not.
*
* To find forcing chains, we're going to start a bfs at
* each suitable vertex of the graph, once for each of its
* two possible colours.
*/
for (i = 0; i < n; i++) {
int c;
if (colouring[i] >= 0 || bitcount(sc->possible[i]) != 2)
continue;
for (c = 0; c < FOUR; c++)
if (sc->possible[i] & (1 << c)) {
int j, k, gi, origc, currc, head, tail;
/*
* Try a bfs from this vertex, ruling out
* colour c.
*
* Within this loop, we work in colour bitmaps
* rather than actual colours, because
* converting back and forth is a needless
* computational expense.
*/
origc = 1 << c;
for (j = 0; j < n; j++)
sc->bfscolour[j] = -1;
head = tail = 0;
sc->bfsqueue[tail++] = i;
sc->bfscolour[i] = sc->possible[i] &~ origc;
while (head < tail) {
j = sc->bfsqueue[head++];
currc = sc->bfscolour[j];
/*
* Try neighbours of j.
*/
for (gi = graph_vertex_start(graph, n, ngraph, j);
gi < ngraph && graph[gi] < n*(j+1); gi++) {
k = graph[gi] - j*n;
/*
* To continue with the bfs in vertex
* k, we need k to be
* (a) not already visited
* (b) have two possible colours
* (c) those colours include currc.
*/
if (sc->bfscolour[k] < 0 &&
colouring[k] < 0 &&
bitcount(sc->possible[k]) == 2 &&
(sc->possible[k] & currc)) {
sc->bfsqueue[tail++] = k;
sc->bfscolour[k] =
sc->possible[k] &~ currc;
}
/*
* One other possibility is that k
* might be the region in which we can
* make a real deduction: if it's
* adjacent to i, contains currc as a
* possibility, and currc is equal to
* the original colour we ruled out.
*/
if (currc == origc &&
graph_adjacent(graph, n, ngraph, k, i) &&
(sc->possible[k] & currc)) {
sc->possible[k] &= ~origc;
done_something = TRUE;
}
}
}
assert(tail <= n);
}
}
if (!done_something)
break;
}
@ -1497,6 +1638,9 @@ static game_state *new_game(midend *me, game_params *params, char *desc)
state->colouring = snewn(n, int);
for (i = 0; i < n; i++)
state->colouring[i] = -1;
state->pencil = snewn(n, int);
for (i = 0; i < n; i++)
state->pencil[i] = 0;
state->completed = state->cheated = FALSE;
@ -1801,6 +1945,8 @@ static game_state *dup_game(game_state *state)
ret->p = state->p;
ret->colouring = snewn(state->p.n, int);
memcpy(ret->colouring, state->colouring, state->p.n * sizeof(int));
ret->pencil = snewn(state->p.n, int);
memcpy(ret->pencil, state->pencil, state->p.n * sizeof(int));
ret->map = state->map;
ret->map->refcount++;
ret->completed = state->completed;
@ -1922,15 +2068,20 @@ static void game_changed_state(game_ui *ui, game_state *oldstate,
struct game_drawstate {
int tilesize;
unsigned short *drawn, *todraw;
unsigned long *drawn, *todraw;
int started;
int dragx, dragy, drag_visible;
blitter *bl;
};
/* Flags in `drawn'. */
#define ERR_BASE 0x0080
#define ERR_MASK 0xFF80
#define ERR_BASE 0x00800000L
#define ERR_MASK 0xFF800000L
#define PENCIL_T_BASE 0x00080000L
#define PENCIL_T_MASK 0x00780000L
#define PENCIL_B_BASE 0x00008000L
#define PENCIL_B_MASK 0x00078000L
#define PENCIL_MASK 0x007F8000L
#define TILESIZE (ds->tilesize)
#define BORDER (TILESIZE)
@ -2000,7 +2151,11 @@ static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
if (state->colouring[r] == c)
return ""; /* don't _need_ to change this region */
sprintf(buf, "%c:%d", (int)(c < 0 ? 'C' : '0' + c), r);
if (button == RIGHT_RELEASE && state->colouring[r] >= 0)
return ""; /* can't pencil on a coloured region */
sprintf(buf, "%s%c:%d", (button == RIGHT_RELEASE ? "p" : ""),
(int)(c < 0 ? 'C' : '0' + c), r);
return dupstr(buf);
}
@ -2014,12 +2169,30 @@ static game_state *execute_move(game_state *state, char *move)
int c, k, adv, i;
while (*move) {
int pencil = FALSE;
c = *move;
if (c == 'p') {
pencil = TRUE;
c = *++move;
}
if ((c == 'C' || (c >= '0' && c < '0'+FOUR)) &&
sscanf(move+1, ":%d%n", &k, &adv) == 1 &&
k >= 0 && k < state->p.n) {
move += 1 + adv;
ret->colouring[k] = (c == 'C' ? -1 : c - '0');
if (pencil) {
if (ret->colouring[k] >= 0) {
free_game(ret);
return NULL;
}
if (c == 'C')
ret->pencil[k] = 0;
else
ret->pencil[k] ^= 1 << (c - '0');
} else {
ret->colouring[k] = (c == 'C' ? -1 : c - '0');
ret->pencil[k] = 0;
}
} else if (*move == 'S') {
move++;
ret->cheated = TRUE;
@ -2134,10 +2307,10 @@ static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
int i;
ds->tilesize = 0;
ds->drawn = snewn(state->p.w * state->p.h, unsigned short);
ds->drawn = snewn(state->p.w * state->p.h, unsigned long);
for (i = 0; i < state->p.w * state->p.h; i++)
ds->drawn[i] = 0xFFFF;
ds->todraw = snewn(state->p.w * state->p.h, unsigned short);
ds->drawn[i] = 0xFFFFL;
ds->todraw = snewn(state->p.w * state->p.h, unsigned long);
ds->started = FALSE;
ds->bl = NULL;
ds->drag_visible = FALSE;
@ -2191,10 +2364,12 @@ static void draw_square(drawing *dr, game_drawstate *ds,
int x, int y, int v)
{
int w = params->w, h = params->h, wh = w*h;
int tv, bv, xo, yo, errs;
int tv, bv, xo, yo, errs, pencil;
errs = v & ERR_MASK;
v &= ~ERR_MASK;
pencil = v & PENCIL_MASK;
v &= ~PENCIL_MASK;
tv = v / FIVE;
bv = v % FIVE;
@ -2224,6 +2399,39 @@ static void draw_square(drawing *dr, game_drawstate *ds,
(bv == FOUR ? COL_BACKGROUND : COL_0 + bv), COL_GRID);
}
/*
* Draw `pencil marks'. Currently we arrange these in a square
* formation, which means we may be in trouble if the value of
* FOUR changes later...
*/
assert(FOUR == 4);
for (yo = 0; yo < 4; yo++)
for (xo = 0; xo < 4; xo++) {
int te = map->map[TE * wh + y*w+x];
int e, ee, c;
e = (yo < xo && yo < 3-xo ? TE :
yo > xo && yo > 3-xo ? BE :
xo < 2 ? LE : RE);
ee = map->map[e * wh + y*w+x];
c = (yo & 1) * 2 + (xo & 1);
if (!(pencil & ((ee == te ? PENCIL_T_BASE : PENCIL_B_BASE) << c)))
continue;
if (yo == xo &&
(map->map[TE * wh + y*w+x] != map->map[LE * wh + y*w+x]))
continue; /* avoid TL-BR diagonal line */
if (yo == 3-xo &&
(map->map[TE * wh + y*w+x] != map->map[RE * wh + y*w+x]))
continue; /* avoid BL-TR diagonal line */
draw_rect(dr, COORD(x) + (5*xo+1)*TILESIZE/20,
COORD(y) + (5*yo+1)*TILESIZE/20,
4*TILESIZE/20, 4*TILESIZE/20, COL_0 + c);
}
/*
* Draw the grid lines, if required.
*/
@ -2324,6 +2532,18 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
v = tv * FIVE + bv;
/*
* Add pencil marks.
*/
for (i = 0; i < FOUR; i++) {
if (state->colouring[state->map->map[TE * wh + y*w+x]] < 0 &&
(state->pencil[state->map->map[TE * wh + y*w+x]] & (1<<i)))
v |= PENCIL_T_BASE << i;
if (state->colouring[state->map->map[BE * wh + y*w+x]] < 0 &&
(state->pencil[state->map->map[BE * wh + y*w+x]] & (1<<i)))
v |= PENCIL_B_BASE << i;
}
ds->todraw[y*w+x] = v;
}

View File

@ -1624,8 +1624,9 @@ for many detailed suggestions.
\IM{Map controls} controls, for Map
To colour a region, click on an existing region of the desired
colour and drag that colour into the new region.
To colour a region, click the left mouse button on an existing
region of the desired colour and drag that colour into the new
region.
(The program will always ensure the starting puzzle has at least one
region of each colour, so that this is always possible!)
@ -1633,6 +1634,12 @@ region of each colour, so that this is always possible!)
If you need to clear a region, you can drag from an empty region, or
from the puzzle boundary if there are no empty regions left.
Dragging a colour using the \e{right} mouse button will stipple the
region in that colour, which you can use as a note to yourself that
you think the region \e{might} be that colour. A region can contain
stipples in multiple colours at once. (This is often useful at the
harder difficulty levels.)
(All the actions described in \k{common-actions} are also available.)
\H{map-parameters} \I{parameters, for Map}Map parameters
@ -1651,10 +1658,10 @@ These parameters are available from the \q{Custom...} option on the
\dt \e{Difficulty}
\dd In \q{Easy} mode, there should always be at least one region
whose colour can be determined trivially. In \q{Normal} mode, you
will have to use more complex logic to deduce the colour of some
regions. However, it will always be possible without having to
guess or backtrack.
whose colour can be determined trivially. In \q{Normal} and \q{Hard}
modes, you will have to use increasingly complex logic to deduce the
colour of some regions. However, it will always be possible without
having to guess or backtrack.
\lcont{