New configuration option in Unruly, to enable a mode in which no two

rows are permitted to have exactly the same pattern, and likewise
columns. (A row and a column can match, though.) This rule is
sometimes used, but not always; http://www.binarypuzzle.com/ in
particular has it (as a user just pointed out).

Since the filled-grid-generation stage of Unruly is implemented by
repeatedly choosing a square and then calling the solver to fill in
all its consequences, simply adding a solver mode conditional on this
flag is sufficient to do the whole of the interesting part of the job.

[originally from svn r9837]
This commit is contained in:
Simon Tatham
2013-05-05 20:21:50 +00:00
parent 16de3a8f22
commit 2f7831bb16

269
unruly.c
View File

@ -76,6 +76,7 @@ enum {
struct game_params {
int w2, h2; /* full grid width and height respectively */
int unique; /* should row and column patterns be unique? */
int diff;
};
#define DIFFLIST(A) \
@ -93,12 +94,12 @@ static char const unruly_diffchars[] = DIFFLIST(ENCODE);
#define DIFFCONFIG DIFFLIST(CONFIG)
const static struct game_params unruly_presets[] = {
{ 8, 8, DIFF_EASY},
{ 8, 8, DIFF_NORMAL},
{10, 10, DIFF_EASY},
{10, 10, DIFF_NORMAL},
{14, 14, DIFF_EASY},
{14, 14, DIFF_NORMAL}
{ 8, 8, FALSE, DIFF_EASY},
{ 8, 8, FALSE, DIFF_NORMAL},
{10, 10, FALSE, DIFF_EASY},
{10, 10, FALSE, DIFF_NORMAL},
{14, 14, FALSE, DIFF_EASY},
{14, 14, FALSE, DIFF_NORMAL}
};
#define DEFAULT_PRESET 0
@ -110,26 +111,30 @@ enum {
BOGUS
};
#define FE_HOR_ROW_LEFT 0x001
#define FE_HOR_ROW_MID 0x003
#define FE_HOR_ROW_RIGHT 0x002
#define FE_HOR_ROW_LEFT 0x0001
#define FE_HOR_ROW_MID 0x0003
#define FE_HOR_ROW_RIGHT 0x0002
#define FE_VER_ROW_TOP 0x004
#define FE_VER_ROW_MID 0x00C
#define FE_VER_ROW_BOTTOM 0x008
#define FE_VER_ROW_TOP 0x0004
#define FE_VER_ROW_MID 0x000C
#define FE_VER_ROW_BOTTOM 0x0008
#define FE_COUNT 0x010
#define FE_COUNT 0x0010
#define FF_ONE 0x020
#define FF_ZERO 0x040
#define FF_CURSOR 0x080
#define FE_ROW_MATCH 0x0020
#define FE_COL_MATCH 0x0040
#define FF_FLASH1 0x100
#define FF_FLASH2 0x200
#define FF_IMMUTABLE 0x400
#define FF_ONE 0x0080
#define FF_ZERO 0x0100
#define FF_CURSOR 0x0200
#define FF_FLASH1 0x0400
#define FF_FLASH2 0x0800
#define FF_IMMUTABLE 0x1000
struct game_state {
int w2, h2;
int unique;
char *grid;
unsigned char *immutable;
@ -179,6 +184,8 @@ static void decode_params(game_params *params, char const *string)
{
char const *p = string;
params->unique = FALSE;
params->w2 = atoi(p);
while (*p && isdigit((unsigned char)*p)) p++;
if (*p == 'x') {
@ -189,6 +196,11 @@ static void decode_params(game_params *params, char const *string)
params->h2 = params->w2;
}
if (*p == 'u') {
p++;
params->unique = TRUE;
}
if (*p == 'd') {
int i;
p++;
@ -208,6 +220,8 @@ static char *encode_params(const game_params *params, int full)
char buf[80];
sprintf(buf, "%dx%d", params->w2, params->h2);
if (params->unique)
strcat(buf, "u");
if (full)
sprintf(buf + strlen(buf), "d%c", unruly_diffchars[params->diff]);
@ -219,7 +233,7 @@ static config_item *game_configure(const game_params *params)
config_item *ret;
char buf[80];
ret = snewn(4, config_item);
ret = snewn(5, config_item);
ret[0].name = "Width";
ret[0].type = C_STRING;
@ -233,15 +247,19 @@ static config_item *game_configure(const game_params *params)
ret[1].sval = dupstr(buf);
ret[1].ival = 0;
ret[2].name = "Difficulty";
ret[2].type = C_CHOICES;
ret[2].sval = DIFFCONFIG;
ret[2].ival = params->diff;
ret[2].name = "Unique rows and columns";
ret[2].type = C_BOOLEAN;
ret[2].ival = params->unique;
ret[3].name = NULL;
ret[3].type = C_END;
ret[3].sval = NULL;
ret[3].ival = 0;
ret[3].name = "Difficulty";
ret[3].type = C_CHOICES;
ret[3].sval = DIFFCONFIG;
ret[3].ival = params->diff;
ret[4].name = NULL;
ret[4].type = C_END;
ret[4].sval = NULL;
ret[4].ival = 0;
return ret;
}
@ -252,7 +270,8 @@ static game_params *custom_params(const config_item *cfg)
ret->w2 = atoi(cfg[0].sval);
ret->h2 = atoi(cfg[1].sval);
ret->diff = cfg[2].ival;
ret->unique = cfg[2].ival;
ret->diff = cfg[3].ival;
return ret;
}
@ -263,6 +282,36 @@ static char *validate_params(const game_params *params, int full)
return "Width and height must both be even";
if (params->w2 < 6 || params->h2 < 6)
return "Width and height must be at least 6";
if (params->unique) {
static const long A177790[] = {
/*
* The nth element of this array gives the number of
* distinct possible Unruly rows of length 2n (that is,
* containing exactly n 1s and n 0s and not containing
* three consecutive elements the same) for as long as
* those numbers fit in a 32-bit signed int.
*
* So in unique-rows mode, if the puzzle width is 2n, then
* the height must be at most (this array)[n], and vice
* versa.
*
* This is sequence A177790 in the Online Encyclopedia of
* Integer Sequences: http://oeis.org/A177790
*/
1L, 2L, 6L, 14L, 34L, 84L, 208L, 518L, 1296L, 3254L,
8196L, 20700L, 52404L, 132942L, 337878L, 860142L,
2192902L, 5598144L, 14308378L, 36610970L, 93770358L,
240390602L, 616787116L, 1583765724L
};
if (params->w2 < 2*lenof(A177790) &&
params->h2 > A177790[params->w2/2]) {
return "Puzzle is too tall for unique-rows mode";
}
if (params->h2 < 2*lenof(A177790) &&
params->w2 > A177790[params->h2/2]) {
return "Puzzle is too long for unique-rows mode";
}
}
if (params->diff >= DIFFCOUNT)
return "Unknown difficulty rating";
@ -298,13 +347,14 @@ static char *validate_desc(const game_params *params, const char *desc)
return NULL;
}
static game_state *blank_state(int w2, int h2)
static game_state *blank_state(int w2, int h2, int unique)
{
game_state *state = snew(game_state);
int s = w2 * h2;
state->w2 = w2;
state->h2 = h2;
state->unique = unique;
state->grid = snewn(s, char);
state->immutable = snewn(s, unsigned char);
@ -322,7 +372,7 @@ static game_state *new_game(midend *me, const game_params *params,
int w2 = params->w2, h2 = params->h2;
int s = w2 * h2;
game_state *state = blank_state(w2, h2);
game_state *state = blank_state(w2, h2, params->unique);
const char *p = desc;
int pos = 0;
@ -359,7 +409,7 @@ static game_state *dup_game(const game_state *state)
int w2 = state->w2, h2 = state->h2;
int s = w2 * h2;
game_state *ret = blank_state(w2, h2);
game_state *ret = blank_state(w2, h2, state->unique);
memcpy(ret->grid, state->grid, s);
memcpy(ret->immutable, state->immutable, s);
@ -562,6 +612,88 @@ static int unruly_solver_check_all_threes(game_state *state,
return ret;
}
static int unruly_solver_check_uniques(game_state *state, int *rowcount,
int horizontal, char check, char block,
struct unruly_scratch *scratch)
{
int w2 = state->w2, h2 = state->h2;
int rmult = (horizontal ? w2 : 1);
int cmult = (horizontal ? 1 : w2);
int nr = (horizontal ? h2 : w2);
int nc = (horizontal ? w2 : h2);
int max = nc / 2;
int r, r2, c;
int ret = 0;
/*
* Find each row that has max entries of type 'check', and see if
* all those entries match those in any row with max-1 entries. If
* so, set the last non-matching entry of the latter row to ensure
* that it's different.
*/
for (r = 0; r < nr; r++) {
if (rowcount[r] != max)
continue;
for (r2 = 0; r2 < nr; r2++) {
int nmatch = 0, nonmatch = -1;
if (rowcount[r2] != max-1)
continue;
for (c = 0; c < nc; c++) {
if (state->grid[r*rmult + c*cmult] == check) {
if (state->grid[r2*rmult + c*cmult] == check)
nmatch++;
else
nonmatch = c;
}
}
if (nmatch == max-1) {
int i1 = r2 * rmult + nonmatch * cmult;
assert(nonmatch != -1);
if (state->grid[i1] == block)
continue;
assert(state->grid[i1] == EMPTY);
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: matching %s %i, %i gives %c at %i,%i\n",
horizontal ? "rows" : "cols",
r, r2, (block == N_ONE ? '1' : '0'), i1 % w2,
i1 / w2);
}
#endif
state->grid[i1] = block;
if (block == N_ONE) {
scratch->ones_rows[i1 / w2]++;
scratch->ones_cols[i1 % w2]++;
} else {
scratch->zeros_rows[i1 / w2]++;
scratch->zeros_cols[i1 % w2]++;
}
ret++;
}
}
}
return ret;
}
static int unruly_solver_check_all_uniques(game_state *state,
struct unruly_scratch *scratch)
{
int ret = 0;
ret += unruly_solver_check_uniques(state, scratch->ones_rows,
TRUE, N_ONE, N_ZERO, scratch);
ret += unruly_solver_check_uniques(state, scratch->zeros_rows,
TRUE, N_ZERO, N_ONE, scratch);
ret += unruly_solver_check_uniques(state, scratch->ones_cols,
FALSE, N_ONE, N_ZERO, scratch);
ret += unruly_solver_check_uniques(state, scratch->zeros_cols,
FALSE, N_ZERO, N_ONE, scratch);
return ret;
}
static int unruly_solver_fill_row(game_state *state, int i, int horizontal,
int *rowcount, int *colcount, char fill)
{
@ -858,6 +990,50 @@ static int unruly_validate_rows(const game_state *state, int horizontal,
return ret;
}
static int unruly_validate_unique(const game_state *state, int horizontal,
int *errors)
{
int w2 = state->w2, h2 = state->h2;
int rmult = (horizontal ? w2 : 1);
int cmult = (horizontal ? 1 : w2);
int nr = (horizontal ? h2 : w2);
int nc = (horizontal ? w2 : h2);
int err = (horizontal ? FE_ROW_MATCH : FE_COL_MATCH);
int r, r2, c;
int ret = 0;
/* Check for any two full rows matching exactly, and mark errors
* accordingly (if required) */
for (r = 0; r < nr; r++) {
int nfull = 0;
for (c = 0; c < nc; c++)
if (state->grid[r*rmult + c*cmult] != EMPTY)
nfull++;
if (nfull != nr)
continue;
for (r2 = r+1; r2 < nr; r2++) {
int match = TRUE;
for (c = 0; c < nc; c++)
if (state->grid[r*rmult + c*cmult] !=
state->grid[r2*rmult + c*cmult])
match = FALSE;
if (match) {
if (errors) {
for (c = 0; c < nc; c++) {
errors[r*rmult + c*cmult] |= err;
errors[r2*rmult + c*cmult] |= err;
}
}
ret++;
}
}
}
return ret;
}
static int unruly_validate_all_rows(const game_state *state, int *errors)
{
int errcount = 0;
@ -867,6 +1043,11 @@ static int unruly_validate_all_rows(const game_state *state, int *errors)
errcount += unruly_validate_rows(state, TRUE, N_ZERO, errors);
errcount += unruly_validate_rows(state, FALSE, N_ZERO, errors);
if (state->unique) {
errcount += unruly_validate_unique(state, TRUE, errors);
errcount += unruly_validate_unique(state, FALSE, errors);
}
if (errcount)
return -1;
return 0;
@ -965,6 +1146,18 @@ static int unruly_solve_game(game_state *state,
continue;
}
/* Check for impending failures of row/column uniqueness, if
* it's enabled in this game mode */
if (state->unique) {
done += unruly_solver_check_all_uniques(state, scratch);
if (done) {
if (maxdiff < DIFF_EASY)
maxdiff = DIFF_EASY;
continue;
}
}
/* Normal techniques */
if (diff < DIFF_NORMAL)
break;
@ -1101,7 +1294,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
while (TRUE) {
attempts++;
state = blank_state(w2, h2);
state = blank_state(w2, h2, params->unique);
scratch = unruly_new_scratch(state);
if (unruly_fill_game(state, scratch, rs))
break;
@ -1547,6 +1740,16 @@ static void unruly_draw_tile(drawing *dr, int x, int y, int tilesize, int tile)
tilesize/2, ALIGN_HCENTRE | ALIGN_VCENTRE, COL_ERROR, "!");
}
/* Row-match errors */
if (tile & FE_ROW_MATCH) {
draw_rect(dr, x, y+tilesize/2-tilesize/12,
tilesize, 2*(tilesize/12), COL_ERROR);
}
if (tile & FE_COL_MATCH) {
draw_rect(dr, x+tilesize/2-tilesize/12, y,
2*(tilesize/12), tilesize, COL_ERROR);
}
/* Cursor rectangle */
if (tile & FF_CURSOR) {
draw_rect(dr, x, y, tilesize/12, tilesize-1, COL_CURSOR);