ref: 121f664b628c4c1d434cad6950526e5dd469f5ea
parent: e7a02ae3330d9d7f6d81d35985efee47e26f57be
author: Simon Tatham <anakin@pobox.com>
date: Tue Aug 30 15:42:45 EDT 2005
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]
--- a/map.c
+++ b/map.c
@@ -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 @@
#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 game_state {
game_params p;
struct map *map;
- int *colouring;
+ int *colouring, *pencil;
int completed, cheated;
};
@@ -99,7 +100,10 @@
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 @@
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 @@
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 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 @@
}
}
+ 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 @@
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 @@
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,7 +2068,7 @@
struct game_drawstate {
int tilesize;
- unsigned short *drawn, *todraw;
+ unsigned long *drawn, *todraw;
int started;
int dragx, dragy, drag_visible;
blitter *bl;
@@ -1929,8 +2075,13 @@
};
/* 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 @@
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 @@
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 @@
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 @@
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;
@@ -2225,6 +2400,39 @@
}
/*
+ * 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.
*/
if (x <= 0 || map->map[RE*wh+y*w+(x-1)] != map->map[LE*wh+y*w+x])
@@ -2323,6 +2531,18 @@
}
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;
}
--- a/puzzles.but
+++ b/puzzles.but
@@ -1624,8 +1624,9 @@
\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 @@
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 @@
\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{