Files
puzzles/unruly.c
Ben Harris 5ec86c03a8 move_cursor(): handle visible flag; return useful value
This adds an extra parameter to move_cursor() that's an optional pointer
to a bool indicating whether the cursor is visible.  This allows for
centralising the common idiom of having the keyboard cursor become
visible when a cursor key is pressed.  Consistently with the vast
majority of existing puzzles, the cursor moves even if it was invisible
before, and becomes visible even if it can't move.

The function now also returns one of the special constants that can be
returned by interpret_move(), so that the caller can correctly return
MOVE_UI_UPDATE or MOVE_NO_EFFECT without needing to carefully check for
changes itself.

Callers are updated only to the extent that they all pass NULL as the
new argument.  Most of them could now be substantially simplified.
2023-08-09 11:44:25 +01:00

2183 lines
61 KiB
C

/*
* unruly.c: Implementation for Binary Puzzles.
* (C) 2012 Lennard Sprong
* Created for Simon Tatham's Portable Puzzle Collection
* See LICENCE for licence details
*
* Objective of the game: Fill the grid with zeros and ones, with the
* following rules:
* - There can't be a run of three or more equal numbers.
* - Each row and column contains an equal amount of zeros and ones.
*
* This puzzle type is known under several names, including
* Tohu-Wa-Vohu, One and Two and Binairo.
*
* Some variants include an extra constraint, stating that no two rows or two
* columns may contain the same exact sequence of zeros and ones.
* This rule is rarely used, so it is not enabled in the default presets
* (but it can be selected via the Custom configurer).
*
* More information:
* http://www.janko.at/Raetsel/Tohu-Wa-Vohu/index.htm
*/
/*
* Possible future improvements:
*
* More solver cleverness
*
* - a counting-based deduction in which you find groups of squares
* which must each contain at least one of a given colour, plus
* other squares which are already known to be that colour, and see
* if you have any squares left over when you've worked out where
* they all have to be. This is a generalisation of the current
* check_near_complete: where that only covers rows with three
* unfilled squares, this would handle more, such as
* 0 . . 1 0 1 . . 0 .
* in which each of the two-square gaps must contain a 0, and there
* are three 0s placed, and that means the rightmost square can't
* be a 0.
*
* - an 'Unreasonable' difficulty level, supporting recursion and
* backtracking.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#ifdef NO_TGMATH_H
# include <math.h>
#else
# include <tgmath.h>
#endif
#include "puzzles.h"
#ifdef STANDALONE_SOLVER
static bool solver_verbose = false;
#endif
enum {
COL_BACKGROUND,
COL_GRID,
COL_EMPTY,
/*
* When editing this enum, maintain the invariants
* COL_n_HIGHLIGHT = COL_n + 1
* COL_n_LOWLIGHT = COL_n + 2
*/
COL_0,
COL_0_HIGHLIGHT,
COL_0_LOWLIGHT,
COL_1,
COL_1_HIGHLIGHT,
COL_1_LOWLIGHT,
COL_CURSOR,
COL_ERROR,
NCOLOURS
};
struct game_params {
int w2, h2; /* full grid width and height respectively */
bool unique; /* should row and column patterns be unique? */
int diff;
};
#define DIFFLIST(A) \
A(TRIVIAL,Trivial, t) \
A(EASY,Easy, e) \
A(NORMAL,Normal, n) \
#define ENUM(upper,title,lower) DIFF_ ## upper,
#define TITLE(upper,title,lower) #title,
#define ENCODE(upper,title,lower) #lower
#define CONFIG(upper,title,lower) ":" #title
enum { DIFFLIST(ENUM) DIFFCOUNT };
static char const *const unruly_diffnames[] = { DIFFLIST(TITLE) };
static char const unruly_diffchars[] = DIFFLIST(ENCODE);
#define DIFFCONFIG DIFFLIST(CONFIG)
static const struct game_params unruly_presets[] = {
{ 8, 8, false, DIFF_TRIVIAL},
{ 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
enum {
EMPTY,
N_ONE,
N_ZERO,
BOGUS
};
#define FE_HOR_ROW_LEFT 0x0001
#define FE_HOR_ROW_MID 0x0003
#define FE_HOR_ROW_RIGHT 0x0002
#define FE_VER_ROW_TOP 0x0004
#define FE_VER_ROW_MID 0x000C
#define FE_VER_ROW_BOTTOM 0x0008
#define FE_COUNT 0x0010
#define FE_ROW_MATCH 0x0020
#define FE_COL_MATCH 0x0040
#define FF_ONE 0x0080
#define FF_ZERO 0x0100
#define FF_CURSOR 0x0200
#define FF_FLASH1 0x0400
#define FF_FLASH2 0x0800
#define FF_IMMUTABLE 0x1000
typedef struct unruly_common {
int refcount;
bool *immutable;
} unruly_common;
struct game_state {
int w2, h2;
bool unique;
char *grid;
unruly_common *common;
bool completed, cheated;
};
static game_params *default_params(void)
{
game_params *ret = snew(game_params);
*ret = unruly_presets[DEFAULT_PRESET]; /* structure copy */
return ret;
}
static bool game_fetch_preset(int i, char **name, game_params **params)
{
game_params *ret;
char buf[80];
if (i < 0 || i >= lenof(unruly_presets))
return false;
ret = snew(game_params);
*ret = unruly_presets[i]; /* structure copy */
sprintf(buf, "%dx%d %s", ret->w2, ret->h2, unruly_diffnames[ret->diff]);
*name = dupstr(buf);
*params = ret;
return true;
}
static void free_params(game_params *params)
{
sfree(params);
}
static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
return ret;
}
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') {
p++;
params->h2 = atoi(p);
while (*p && isdigit((unsigned char)*p)) p++;
} else {
params->h2 = params->w2;
}
if (*p == 'u') {
p++;
params->unique = true;
}
if (*p == 'd') {
int i;
p++;
params->diff = DIFFCOUNT + 1; /* ...which is invalid */
if (*p) {
for (i = 0; i < DIFFCOUNT; i++) {
if (*p == unruly_diffchars[i])
params->diff = i;
}
p++;
}
}
}
static char *encode_params(const game_params *params, bool 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]);
return dupstr(buf);
}
static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[80];
ret = snewn(5, config_item);
ret[0].name = "Width";
ret[0].type = C_STRING;
sprintf(buf, "%d", params->w2);
ret[0].u.string.sval = dupstr(buf);
ret[1].name = "Height";
ret[1].type = C_STRING;
sprintf(buf, "%d", params->h2);
ret[1].u.string.sval = dupstr(buf);
ret[2].name = "Unique rows and columns";
ret[2].type = C_BOOLEAN;
ret[2].u.boolean.bval = params->unique;
ret[3].name = "Difficulty";
ret[3].type = C_CHOICES;
ret[3].u.choices.choicenames = DIFFCONFIG;
ret[3].u.choices.selected = params->diff;
ret[4].name = NULL;
ret[4].type = C_END;
return ret;
}
static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
ret->w2 = atoi(cfg[0].u.string.sval);
ret->h2 = atoi(cfg[1].u.string.sval);
ret->unique = cfg[2].u.boolean.bval;
ret->diff = cfg[3].u.choices.selected;
return ret;
}
static const char *validate_params(const game_params *params, bool full)
{
if ((params->w2 & 1) || (params->h2 & 1))
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->w2 > INT_MAX / params->h2)
return "Width times height must not be unreasonably large";
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";
return NULL;
}
static const char *validate_desc(const game_params *params, const char *desc)
{
int w2 = params->w2, h2 = params->h2;
int s = w2 * h2;
const char *p = desc;
int pos = 0;
while (*p) {
if (*p >= 'a' && *p < 'z')
pos += 1 + (*p - 'a');
else if (*p >= 'A' && *p < 'Z')
pos += 1 + (*p - 'A');
else if (*p == 'Z' || *p == 'z')
pos += 25;
else
return "Description contains invalid characters";
++p;
}
if (pos < s+1)
return "Description too short";
if (pos > s+1)
return "Description too long";
return NULL;
}
static game_state *blank_state(int w2, int h2, bool unique, bool new_common)
{
game_state *state = snew(game_state);
int s = w2 * h2;
state->w2 = w2;
state->h2 = h2;
state->unique = unique;
state->grid = snewn(s, char);
memset(state->grid, EMPTY, s);
if (new_common) {
state->common = snew(unruly_common);
state->common->refcount = 1;
state->common->immutable = snewn(s, bool);
memset(state->common->immutable, 0, s*sizeof(bool));
}
state->completed = state->cheated = false;
return state;
}
static game_state *new_game(midend *me, const game_params *params,
const char *desc)
{
int w2 = params->w2, h2 = params->h2;
int s = w2 * h2;
game_state *state = blank_state(w2, h2, params->unique, true);
const char *p = desc;
int pos = 0;
while (*p) {
if (*p >= 'a' && *p < 'z') {
pos += (*p - 'a');
if (pos < s) {
state->grid[pos] = N_ZERO;
state->common->immutable[pos] = true;
}
pos++;
} else if (*p >= 'A' && *p < 'Z') {
pos += (*p - 'A');
if (pos < s) {
state->grid[pos] = N_ONE;
state->common->immutable[pos] = true;
}
pos++;
} else if (*p == 'Z' || *p == 'z') {
pos += 25;
} else
assert(!"Description contains invalid characters");
++p;
}
assert(pos == s+1);
return state;
}
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, state->unique, false);
memcpy(ret->grid, state->grid, s);
ret->common = state->common;
ret->common->refcount++;
ret->completed = state->completed;
ret->cheated = state->cheated;
return ret;
}
static void free_game(game_state *state)
{
sfree(state->grid);
if (--state->common->refcount == 0) {
sfree(state->common->immutable);
sfree(state->common);
}
sfree(state);
}
static bool game_can_format_as_text_now(const game_params *params)
{
return true;
}
static char *game_text_format(const game_state *state)
{
int w2 = state->w2, h2 = state->h2;
int lr = w2*2 + 1;
char *ret = snewn(lr * h2 + 1, char);
char *p = ret;
int x, y;
for (y = 0; y < h2; y++) {
for (x = 0; x < w2; x++) {
/* Place number */
char c = state->grid[y * w2 + x];
*p++ = (c == N_ONE ? '1' : c == N_ZERO ? '0' : '.');
*p++ = ' ';
}
/* End line */
*p++ = '\n';
}
/* End with NUL */
*p++ = '\0';
return ret;
}
/* ****** *
* Solver *
* ****** */
struct unruly_scratch {
int *ones_rows;
int *ones_cols;
int *zeros_rows;
int *zeros_cols;
};
static void unruly_solver_update_remaining(const game_state *state,
struct unruly_scratch *scratch)
{
int w2 = state->w2, h2 = state->h2;
int x, y;
/* Reset all scratch data */
memset(scratch->ones_rows, 0, h2 * sizeof(int));
memset(scratch->ones_cols, 0, w2 * sizeof(int));
memset(scratch->zeros_rows, 0, h2 * sizeof(int));
memset(scratch->zeros_cols, 0, w2 * sizeof(int));
for (x = 0; x < w2; x++)
for (y = 0; y < h2; y++) {
if (state->grid[y * w2 + x] == N_ONE) {
scratch->ones_rows[y]++;
scratch->ones_cols[x]++;
} else if (state->grid[y * w2 + x] == N_ZERO) {
scratch->zeros_rows[y]++;
scratch->zeros_cols[x]++;
}
}
}
static struct unruly_scratch *unruly_new_scratch(const game_state *state)
{
int w2 = state->w2, h2 = state->h2;
struct unruly_scratch *ret = snew(struct unruly_scratch);
ret->ones_rows = snewn(h2, int);
ret->ones_cols = snewn(w2, int);
ret->zeros_rows = snewn(h2, int);
ret->zeros_cols = snewn(w2, int);
unruly_solver_update_remaining(state, ret);
return ret;
}
static void unruly_free_scratch(struct unruly_scratch *scratch)
{
sfree(scratch->ones_rows);
sfree(scratch->ones_cols);
sfree(scratch->zeros_rows);
sfree(scratch->zeros_cols);
sfree(scratch);
}
static int unruly_solver_check_threes(game_state *state, int *rowcount,
int *colcount, bool horizontal,
char check, char block)
{
int w2 = state->w2, h2 = state->h2;
int dx = horizontal ? 1 : 0, dy = 1 - dx;
int sx = dx, sy = dy;
int ex = w2 - dx, ey = h2 - dy;
int x, y;
int ret = 0;
/* Check for any three squares which almost form three in a row */
for (y = sy; y < ey; y++) {
for (x = sx; x < ex; x++) {
int i1 = (y-dy) * w2 + (x-dx);
int i2 = y * w2 + x;
int i3 = (y+dy) * w2 + (x+dx);
if (state->grid[i1] == check && state->grid[i2] == check
&& state->grid[i3] == EMPTY) {
ret++;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
i1 % w2, i1 / w2, i2 % w2, i2 / w2,
(block == N_ONE ? '1' : '0'), i3 % w2,
i3 / w2);
}
#endif
state->grid[i3] = block;
rowcount[i3 / w2]++;
colcount[i3 % w2]++;
}
if (state->grid[i1] == check && state->grid[i2] == EMPTY
&& state->grid[i3] == check) {
ret++;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
i1 % w2, i1 / w2, i3 % w2, i3 / w2,
(block == N_ONE ? '1' : '0'), i2 % w2,
i2 / w2);
}
#endif
state->grid[i2] = block;
rowcount[i2 / w2]++;
colcount[i2 % w2]++;
}
if (state->grid[i1] == EMPTY && state->grid[i2] == check
&& state->grid[i3] == check) {
ret++;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: %i,%i and %i,%i confirm %c at %i,%i\n",
i2 % w2, i2 / w2, i3 % w2, i3 / w2,
(block == N_ONE ? '1' : '0'), i1 % w2,
i1 / w2);
}
#endif
state->grid[i1] = block;
rowcount[i1 / w2]++;
colcount[i1 % w2]++;
}
}
}
return ret;
}
static int unruly_solver_check_all_threes(game_state *state,
struct unruly_scratch *scratch)
{
int ret = 0;
ret +=
unruly_solver_check_threes(state, scratch->zeros_rows,
scratch->zeros_cols, true, N_ONE, N_ZERO);
ret +=
unruly_solver_check_threes(state, scratch->ones_rows,
scratch->ones_cols, true, N_ZERO, N_ONE);
ret +=
unruly_solver_check_threes(state, scratch->zeros_rows,
scratch->zeros_cols, false, N_ONE,
N_ZERO);
ret +=
unruly_solver_check_threes(state, scratch->ones_rows,
scratch->ones_cols, false, N_ZERO, N_ONE);
return ret;
}
static int unruly_solver_check_uniques(game_state *state, int *rowcount,
bool 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, bool horizontal,
int *rowcount, int *colcount, char fill)
{
int ret = 0;
int w2 = state->w2, h2 = state->h2;
int j;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Filling %s %i with %c:",
(horizontal ? "Row" : "Col"), i,
(fill == N_ZERO ? '0' : '1'));
}
#endif
/* Place a number in every empty square in a row/column */
for (j = 0; j < (horizontal ? w2 : h2); j++) {
int p = (horizontal ? i * w2 + j : j * w2 + i);
if (state->grid[p] == EMPTY) {
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf(" (%i,%i)", (horizontal ? j : i),
(horizontal ? i : j));
}
#endif
ret++;
state->grid[p] = fill;
rowcount[(horizontal ? i : j)]++;
colcount[(horizontal ? j : i)]++;
}
}
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("\n");
}
#endif
return ret;
}
static int unruly_solver_check_single_gap(game_state *state,
int *complete, bool horizontal,
int *rowcount, int *colcount,
char fill)
{
int w2 = state->w2, h2 = state->h2;
int count = (horizontal ? h2 : w2); /* number of rows to check */
int target = (horizontal ? w2 : h2) / 2; /* target number of 0s/1s */
int *other = (horizontal ? rowcount : colcount);
int ret = 0;
int i;
/* Check for completed rows/cols for one number, then fill in the rest */
for (i = 0; i < count; i++) {
if (complete[i] == target && other[i] == target - 1) {
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i has only one square left which must be "
"%c\n", i, (fill == N_ZERO ? '0' : '1'));
}
#endif
ret += unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
}
}
return ret;
}
static int unruly_solver_check_all_single_gap(game_state *state,
struct unruly_scratch *scratch)
{
int ret = 0;
ret +=
unruly_solver_check_single_gap(state, scratch->ones_rows, true,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_single_gap(state, scratch->ones_cols, false,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_single_gap(state, scratch->zeros_rows, true,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
ret +=
unruly_solver_check_single_gap(state, scratch->zeros_cols, false,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
return ret;
}
static int unruly_solver_check_complete_nums(game_state *state,
int *complete, bool horizontal,
int *rowcount, int *colcount,
char fill)
{
int w2 = state->w2, h2 = state->h2;
int count = (horizontal ? h2 : w2); /* number of rows to check */
int target = (horizontal ? w2 : h2) / 2; /* target number of 0s/1s */
int *other = (horizontal ? rowcount : colcount);
int ret = 0;
int i;
/* Check for completed rows/cols for one number, then fill in the rest */
for (i = 0; i < count; i++) {
if (complete[i] == target && other[i] < target) {
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i satisfied for %c\n", i,
(fill != N_ZERO ? '0' : '1'));
}
#endif
ret += unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
}
}
return ret;
}
static int unruly_solver_check_all_complete_nums(game_state *state,
struct unruly_scratch *scratch)
{
int ret = 0;
ret +=
unruly_solver_check_complete_nums(state, scratch->ones_rows, true,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_complete_nums(state, scratch->ones_cols, false,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_complete_nums(state, scratch->zeros_rows, true,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
ret +=
unruly_solver_check_complete_nums(state, scratch->zeros_cols, false,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
return ret;
}
static int unruly_solver_check_near_complete(game_state *state,
int *complete, bool horizontal,
int *rowcount, int *colcount,
char fill)
{
int w2 = state->w2, h2 = state->h2;
int w = w2/2, h = h2/2;
int dx = horizontal ? 1 : 0, dy = 1 - dx;
int sx = dx, sy = dy;
int ex = w2 - dx, ey = h2 - dy;
int x, y;
int ret = 0;
/*
* This function checks for a row with one Y remaining, then looks
* for positions that could cause the remaining squares in the row
* to make 3 X's in a row. Example:
*
* Consider the following row:
* 1 1 0 . . .
* If the last 1 was placed in the last square, the remaining
* squares would be 0:
* 1 1 0 0 0 1
* This violates the 3 in a row rule. We now know that the last 1
* shouldn't be in the last cell.
* 1 1 0 . . 0
*/
/* Check for any two blank and one filled square */
for (y = sy; y < ey; y++) {
/* One type must have 1 remaining, the other at least 2 */
if (horizontal && (complete[y] < w - 1 || rowcount[y] > w - 2))
continue;
for (x = sx; x < ex; x++) {
int i, i1, i2, i3;
if (!horizontal
&& (complete[x] < h - 1 || colcount[x] > h - 2))
continue;
i = (horizontal ? y : x);
i1 = (y-dy) * w2 + (x-dx);
i2 = y * w2 + x;
i3 = (y+dy) * w2 + (x+dx);
if (state->grid[i1] == fill && state->grid[i2] == EMPTY
&& state->grid[i3] == EMPTY) {
/*
* Temporarily fill the empty spaces with something else.
* This avoids raising the counts for the row and column
*/
state->grid[i2] = BOGUS;
state->grid[i3] = BOGUS;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i nearly satisfied for %c\n", i,
(fill != N_ZERO ? '0' : '1'));
}
#endif
ret +=
unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
state->grid[i2] = EMPTY;
state->grid[i3] = EMPTY;
}
else if (state->grid[i1] == EMPTY && state->grid[i2] == fill
&& state->grid[i3] == EMPTY) {
state->grid[i1] = BOGUS;
state->grid[i3] = BOGUS;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i nearly satisfied for %c\n", i,
(fill != N_ZERO ? '0' : '1'));
}
#endif
ret +=
unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
state->grid[i1] = EMPTY;
state->grid[i3] = EMPTY;
}
else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY
&& state->grid[i3] == fill) {
state->grid[i1] = BOGUS;
state->grid[i2] = BOGUS;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i nearly satisfied for %c\n", i,
(fill != N_ZERO ? '0' : '1'));
}
#endif
ret +=
unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
state->grid[i1] = EMPTY;
state->grid[i2] = EMPTY;
}
else if (state->grid[i1] == EMPTY && state->grid[i2] == EMPTY
&& state->grid[i3] == EMPTY) {
state->grid[i1] = BOGUS;
state->grid[i2] = BOGUS;
state->grid[i3] = BOGUS;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Solver: Row %i nearly satisfied for %c\n", i,
(fill != N_ZERO ? '0' : '1'));
}
#endif
ret +=
unruly_solver_fill_row(state, i, horizontal, rowcount,
colcount, fill);
state->grid[i1] = EMPTY;
state->grid[i2] = EMPTY;
state->grid[i3] = EMPTY;
}
}
}
return ret;
}
static int unruly_solver_check_all_near_complete(game_state *state,
struct unruly_scratch *scratch)
{
int ret = 0;
ret +=
unruly_solver_check_near_complete(state, scratch->ones_rows, true,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_near_complete(state, scratch->ones_cols, false,
scratch->zeros_rows,
scratch->zeros_cols, N_ZERO);
ret +=
unruly_solver_check_near_complete(state, scratch->zeros_rows, true,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
ret +=
unruly_solver_check_near_complete(state, scratch->zeros_cols, false,
scratch->ones_rows,
scratch->ones_cols, N_ONE);
return ret;
}
static int unruly_validate_rows(const game_state *state, bool horizontal,
char check, int *errors)
{
int w2 = state->w2, h2 = state->h2;
int dx = horizontal ? 1 : 0, dy = 1 - dx;
int sx = dx, sy = dy;
int ex = w2 - dx, ey = h2 - dy;
int x, y;
int ret = 0;
int err1 = (horizontal ? FE_HOR_ROW_LEFT : FE_VER_ROW_TOP);
int err2 = (horizontal ? FE_HOR_ROW_MID : FE_VER_ROW_MID);
int err3 = (horizontal ? FE_HOR_ROW_RIGHT : FE_VER_ROW_BOTTOM);
/* Check for any three in a row, and mark errors accordingly (if
* required) */
for (y = sy; y < ey; y++) {
for (x = sx; x < ex; x++) {
int i1 = (y-dy) * w2 + (x-dx);
int i2 = y * w2 + x;
int i3 = (y+dy) * w2 + (x+dx);
if (state->grid[i1] == check && state->grid[i2] == check
&& state->grid[i3] == check) {
ret++;
if (errors) {
errors[i1] |= err1;
errors[i2] |= err2;
errors[i3] |= err3;
}
}
}
}
return ret;
}
static int unruly_validate_unique(const game_state *state, bool 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 != nc)
continue;
for (r2 = r+1; r2 < nr; r2++) {
bool 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;
errcount += unruly_validate_rows(state, true, N_ONE, errors);
errcount += unruly_validate_rows(state, false, N_ONE, 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;
}
static int unruly_validate_counts(const game_state *state,
struct unruly_scratch *scratch, bool *errors)
{
int w2 = state->w2, h2 = state->h2;
int w = w2/2, h = h2/2;
bool below = false;
bool above = false;
int i;
/* See if all rows/columns are satisfied. If one is exceeded,
* mark it as an error (if required)
*/
bool hasscratch = true;
if (!scratch) {
scratch = unruly_new_scratch(state);
hasscratch = false;
}
for (i = 0; i < w2; i++) {
if (scratch->ones_cols[i] < h)
below = true;
if (scratch->zeros_cols[i] < h)
below = true;
if (scratch->ones_cols[i] > h) {
above = true;
if (errors)
errors[2*h2 + i] = true;
} else if (errors)
errors[2*h2 + i] = false;
if (scratch->zeros_cols[i] > h) {
above = true;
if (errors)
errors[2*h2 + w2 + i] = true;
} else if (errors)
errors[2*h2 + w2 + i] = false;
}
for (i = 0; i < h2; i++) {
if (scratch->ones_rows[i] < w)
below = true;
if (scratch->zeros_rows[i] < w)
below = true;
if (scratch->ones_rows[i] > w) {
above = true;
if (errors)
errors[i] = true;
} else if (errors)
errors[i] = false;
if (scratch->zeros_rows[i] > w) {
above = true;
if (errors)
errors[h2 + i] = true;
} else if (errors)
errors[h2 + i] = false;
}
if (!hasscratch)
unruly_free_scratch(scratch);
return (above ? -1 : below ? 1 : 0);
}
static int unruly_solve_game(game_state *state,
struct unruly_scratch *scratch, int diff)
{
int done, maxdiff = -1;
while (true) {
done = 0;
/* Check for impending 3's */
done += unruly_solver_check_all_threes(state, scratch);
/* Keep using the simpler techniques while they produce results */
if (done) {
if (maxdiff < DIFF_TRIVIAL)
maxdiff = DIFF_TRIVIAL;
continue;
}
/* Check for rows with only one unfilled square */
done += unruly_solver_check_all_single_gap(state, scratch);
if (done) {
if (maxdiff < DIFF_TRIVIAL)
maxdiff = DIFF_TRIVIAL;
continue;
}
/* Easy techniques */
if (diff < DIFF_EASY)
break;
/* Check for completed rows */
done += unruly_solver_check_all_complete_nums(state, scratch);
if (done) {
if (maxdiff < DIFF_EASY)
maxdiff = DIFF_EASY;
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;
/* Check for nearly completed rows */
done += unruly_solver_check_all_near_complete(state, scratch);
if (done) {
if (maxdiff < DIFF_NORMAL)
maxdiff = DIFF_NORMAL;
continue;
}
break;
}
return maxdiff;
}
static char *solve_game(const game_state *state, const game_state *currstate,
const char *aux, const char **error)
{
game_state *solved = dup_game(state);
struct unruly_scratch *scratch = unruly_new_scratch(solved);
char *ret = NULL;
int result;
unruly_solve_game(solved, scratch, DIFFCOUNT);
result = unruly_validate_counts(solved, scratch, NULL);
if (unruly_validate_all_rows(solved, NULL) == -1)
result = -1;
if (result == 0) {
int w2 = solved->w2, h2 = solved->h2;
int s = w2 * h2;
char *p;
int i;
ret = snewn(s + 2, char);
p = ret;
*p++ = 'S';
for (i = 0; i < s; i++)
*p++ = (solved->grid[i] == N_ONE ? '1' : '0');
*p++ = '\0';
} else if (result == 1)
*error = "No solution found.";
else if (result == -1)
*error = "Puzzle is invalid.";
free_game(solved);
unruly_free_scratch(scratch);
return ret;
}
/* ********* *
* Generator *
* ********* */
static bool unruly_fill_game(game_state *state, struct unruly_scratch *scratch,
random_state *rs)
{
int w2 = state->w2, h2 = state->h2;
int s = w2 * h2;
int i, j;
int *spaces;
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Generator: Attempt to fill grid\n");
}
#endif
/* Generate random array of spaces */
spaces = snewn(s, int);
for (i = 0; i < s; i++)
spaces[i] = i;
shuffle(spaces, s, sizeof(*spaces), rs);
/*
* Construct a valid filled grid by repeatedly picking an unfilled
* space and fill it, then calling the solver to fill in any
* spaces forced by the change.
*/
for (j = 0; j < s; j++) {
i = spaces[j];
if (state->grid[i] != EMPTY)
continue;
if (random_upto(rs, 2)) {
state->grid[i] = N_ONE;
scratch->ones_rows[i / w2]++;
scratch->ones_cols[i % w2]++;
} else {
state->grid[i] = N_ZERO;
scratch->zeros_rows[i / w2]++;
scratch->zeros_cols[i % w2]++;
}
unruly_solve_game(state, scratch, DIFFCOUNT);
}
sfree(spaces);
if (unruly_validate_all_rows(state, NULL) != 0
|| unruly_validate_counts(state, scratch, NULL) != 0)
return false;
return true;
}
static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, bool interactive)
{
#ifdef STANDALONE_SOLVER
char *debug;
bool temp_verbose = false;
#endif
int w2 = params->w2, h2 = params->h2;
int s = w2 * h2;
int *spaces;
int i, j, run;
char *ret, *p;
game_state *state;
struct unruly_scratch *scratch;
int attempts = 0;
while (1) {
while (true) {
attempts++;
state = blank_state(w2, h2, params->unique, true);
scratch = unruly_new_scratch(state);
if (unruly_fill_game(state, scratch, rs))
break;
free_game(state);
unruly_free_scratch(scratch);
}
#ifdef STANDALONE_SOLVER
if (solver_verbose) {
printf("Puzzle generated in %i attempts\n", attempts);
debug = game_text_format(state);
fputs(debug, stdout);
sfree(debug);
temp_verbose = solver_verbose;
solver_verbose = false;
}
#else
(void)attempts;
#endif
unruly_free_scratch(scratch);
/* Generate random array of spaces */
spaces = snewn(s, int);
for (i = 0; i < s; i++)
spaces[i] = i;
shuffle(spaces, s, sizeof(*spaces), rs);
/*
* Winnow the clues by starting from our filled grid, repeatedly
* picking a filled space and emptying it, as long as the solver
* reports that the puzzle can still be solved after doing so.
*/
for (j = 0; j < s; j++) {
char c;
game_state *solver;
i = spaces[j];
c = state->grid[i];
state->grid[i] = EMPTY;
solver = dup_game(state);
scratch = unruly_new_scratch(state);
unruly_solve_game(solver, scratch, params->diff);
if (unruly_validate_counts(solver, scratch, NULL) != 0)
state->grid[i] = c;
free_game(solver);
unruly_free_scratch(scratch);
}
sfree(spaces);
#ifdef STANDALONE_SOLVER
if (temp_verbose) {
solver_verbose = true;
printf("Final puzzle:\n");
debug = game_text_format(state);
fputs(debug, stdout);
sfree(debug);
}
#endif
/*
* See if the game has accidentally come out too easy.
*/
if (params->diff > 0) {
bool ok;
game_state *solver;
solver = dup_game(state);
scratch = unruly_new_scratch(state);
unruly_solve_game(solver, scratch, params->diff - 1);
ok = unruly_validate_counts(solver, scratch, NULL) > 0;
free_game(solver);
unruly_free_scratch(scratch);
if (ok)
break;
} else {
/*
* Puzzles of the easiest difficulty can't be too easy.
*/
break;
}
}
/* Encode description */
ret = snewn(s + 1, char);
p = ret;
run = 0;
for (i = 0; i < s+1; i++) {
if (i == s || state->grid[i] == N_ZERO) {
while (run > 24) {
*p++ = 'z';
run -= 25;
}
*p++ = 'a' + run;
run = 0;
} else if (state->grid[i] == N_ONE) {
while (run > 24) {
*p++ = 'Z';
run -= 25;
}
*p++ = 'A' + run;
run = 0;
} else {
run++;
}
}
*p = '\0';
free_game(state);
return ret;
}
/* ************** *
* User Interface *
* ************** */
struct game_ui {
int cx, cy;
bool cursor;
};
static game_ui *new_ui(const game_state *state)
{
game_ui *ret = snew(game_ui);
ret->cx = ret->cy = 0;
ret->cursor = getenv_bool("PUZZLES_SHOW_CURSOR", false);
return ret;
}
static void free_ui(game_ui *ui)
{
sfree(ui);
}
static void game_changed_state(game_ui *ui, const game_state *oldstate,
const game_state *newstate)
{
}
static const char *current_key_label(const game_ui *ui,
const game_state *state, int button)
{
int hx = ui->cx, hy = ui->cy;
int w2 = state->w2;
char i = state->grid[hy * w2 + hx];
if (ui->cursor && IS_CURSOR_SELECT(button)) {
if (state->common->immutable[hy * w2 + hx]) return "";
switch (i) {
case EMPTY:
return button == CURSOR_SELECT ? "Black" : "White";
case N_ONE:
return button == CURSOR_SELECT ? "White" : "Empty";
case N_ZERO:
return button == CURSOR_SELECT ? "Empty" : "Black";
}
}
return "";
}
struct game_drawstate {
int tilesize;
int w2, h2;
bool started;
int *gridfs;
bool *rowfs;
int *grid;
};
static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
int w2 = state->w2, h2 = state->h2;
int s = w2 * h2;
int i;
ds->tilesize = 0;
ds->w2 = w2;
ds->h2 = h2;
ds->started = false;
ds->gridfs = snewn(s, int);
ds->rowfs = snewn(2 * (w2 + h2), bool);
ds->grid = snewn(s, int);
for (i = 0; i < s; i++)
ds->grid[i] = -1;
return ds;
}
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
sfree(ds->gridfs);
sfree(ds->rowfs);
sfree(ds->grid);
sfree(ds);
}
#define COORD(x) ( (x) * ds->tilesize + ds->tilesize/2 )
#define FROMCOORD(x) ( ((x)-(ds->tilesize/2)) / ds->tilesize )
static char *interpret_move(const game_state *state, game_ui *ui,
const game_drawstate *ds,
int ox, int oy, int button)
{
int hx = ui->cx;
int hy = ui->cy;
int gx = FROMCOORD(ox);
int gy = FROMCOORD(oy);
int w2 = state->w2, h2 = state->h2;
button &= ~MOD_MASK;
/* Mouse click */
if (button == LEFT_BUTTON || button == RIGHT_BUTTON ||
button == MIDDLE_BUTTON) {
if (ox >= (ds->tilesize / 2) && gx < w2
&& oy >= (ds->tilesize / 2) && gy < h2) {
hx = gx;
hy = gy;
ui->cursor = false;
} else
return NULL;
}
/* Keyboard move */
if (IS_CURSOR_MOVE(button)) {
move_cursor(button, &ui->cx, &ui->cy, w2, h2, false, NULL);
ui->cursor = true;
return MOVE_UI_UPDATE;
}
/* Place one */
if ((ui->cursor && (button == CURSOR_SELECT || button == CURSOR_SELECT2
|| button == '\b' || button == '0' || button == '1'
|| button == '2')) ||
button == LEFT_BUTTON || button == RIGHT_BUTTON ||
button == MIDDLE_BUTTON) {
char buf[80];
char c, i;
if (state->common->immutable[hy * w2 + hx])
return NULL;
c = '-';
i = state->grid[hy * w2 + hx];
if (button == '0' || button == '2')
c = '0';
else if (button == '1')
c = '1';
else if (button == MIDDLE_BUTTON)
c = '-';
/* Cycle through options */
else if (button == CURSOR_SELECT2 || button == RIGHT_BUTTON)
c = (i == EMPTY ? '0' : i == N_ZERO ? '1' : '-');
else if (button == CURSOR_SELECT || button == LEFT_BUTTON)
c = (i == EMPTY ? '1' : i == N_ONE ? '0' : '-');
if (state->grid[hy * w2 + hx] ==
(c == '0' ? N_ZERO : c == '1' ? N_ONE : EMPTY))
return NULL; /* don't put no-ops on the undo chain */
sprintf(buf, "P%c,%d,%d", c, hx, hy);
return dupstr(buf);
}
return NULL;
}
static game_state *execute_move(const game_state *state, const char *move)
{
int w2 = state->w2, h2 = state->h2;
int s = w2 * h2;
int x, y, i;
char c;
game_state *ret;
if (move[0] == 'S') {
const char *p;
ret = dup_game(state);
p = move + 1;
for (i = 0; i < s; i++) {
if (!*p || !(*p == '1' || *p == '0')) {
free_game(ret);
return NULL;
}
ret->grid[i] = (*p == '1' ? N_ONE : N_ZERO);
p++;
}
ret->completed = ret->cheated = true;
return ret;
} else if (move[0] == 'P'
&& sscanf(move + 1, "%c,%d,%d", &c, &x, &y) == 3 && x >= 0
&& x < w2 && y >= 0 && y < h2 && (c == '-' || c == '0'
|| c == '1')) {
ret = dup_game(state);
i = y * w2 + x;
if (state->common->immutable[i]) {
free_game(ret);
return NULL;
}
ret->grid[i] = (c == '1' ? N_ONE : c == '0' ? N_ZERO : EMPTY);
if (!ret->completed && unruly_validate_counts(ret, NULL, NULL) == 0
&& (unruly_validate_all_rows(ret, NULL) == 0))
ret->completed = true;
return ret;
}
return NULL;
}
/* ----------------------------------------------------------------------
* Drawing routines.
*/
static void game_compute_size(const game_params *params, int tilesize,
const game_ui *ui, int *x, int *y)
{
*x = tilesize * (params->w2 + 1);
*y = tilesize * (params->h2 + 1);
}
static void game_set_size(drawing *dr, game_drawstate *ds,
const game_params *params, int tilesize)
{
ds->tilesize = tilesize;
}
static float *game_colours(frontend *fe, int *ncolours)
{
float *ret = snewn(3 * NCOLOURS, float);
int i;
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
for (i = 0; i < 3; i++) {
ret[COL_1 * 3 + i] = 0.2F;
ret[COL_1_HIGHLIGHT * 3 + i] = 0.4F;
ret[COL_1_LOWLIGHT * 3 + i] = 0.0F;
ret[COL_0 * 3 + i] = 0.95F;
ret[COL_0_HIGHLIGHT * 3 + i] = 1.0F;
ret[COL_0_LOWLIGHT * 3 + i] = 0.9F;
ret[COL_EMPTY * 3 + i] = 0.5F;
ret[COL_GRID * 3 + i] = 0.3F;
}
game_mkhighlight_specific(fe, ret, COL_0, COL_0_HIGHLIGHT, COL_0_LOWLIGHT);
game_mkhighlight_specific(fe, ret, COL_1, COL_1_HIGHLIGHT, COL_1_LOWLIGHT);
ret[COL_ERROR * 3 + 0] = 1.0F;
ret[COL_ERROR * 3 + 1] = 0.0F;
ret[COL_ERROR * 3 + 2] = 0.0F;
ret[COL_CURSOR * 3 + 0] = 0.0F;
ret[COL_CURSOR * 3 + 1] = 0.7F;
ret[COL_CURSOR * 3 + 2] = 0.0F;
*ncolours = NCOLOURS;
return ret;
}
static void unruly_draw_err_rectangle(drawing *dr, int x, int y, int w, int h,
int tilesize)
{
double thick = tilesize / 10;
double margin = tilesize / 20;
draw_rect(dr, x+margin, y+margin, w-2*margin, thick, COL_ERROR);
draw_rect(dr, x+margin, y+margin, thick, h-2*margin, COL_ERROR);
draw_rect(dr, x+margin, y+h-margin-thick, w-2*margin, thick, COL_ERROR);
draw_rect(dr, x+w-margin-thick, y+margin, thick, h-2*margin, COL_ERROR);
}
static void unruly_draw_tile(drawing *dr, int x, int y, int tilesize, int tile)
{
clip(dr, x, y, tilesize, tilesize);
/* Draw the grid edge first, so the tile can overwrite it */
draw_rect(dr, x, y, tilesize, tilesize, COL_GRID);
/* Background of the tile */
{
int val = (tile & FF_ZERO ? 0 : tile & FF_ONE ? 2 : 1);
val = (val == 0 ? COL_0 : val == 2 ? COL_1 : COL_EMPTY);
if ((tile & (FF_FLASH1 | FF_FLASH2)) &&
(val == COL_0 || val == COL_1)) {
val += (tile & FF_FLASH1 ? 1 : 2);
}
draw_rect(dr, x, y, tilesize-1, tilesize-1, val);
if ((val == COL_0 || val == COL_1) && (tile & FF_IMMUTABLE)) {
draw_rect(dr, x + tilesize/6, y + tilesize/6,
tilesize - 2*(tilesize/6) - 2, 1, val + 2);
draw_rect(dr, x + tilesize/6, y + tilesize/6,
1, tilesize - 2*(tilesize/6) - 2, val + 2);
draw_rect(dr, x + tilesize/6 + 1, y + tilesize - tilesize/6 - 2,
tilesize - 2*(tilesize/6) - 2, 1, val + 1);
draw_rect(dr, x + tilesize - tilesize/6 - 2, y + tilesize/6 + 1,
1, tilesize - 2*(tilesize/6) - 2, val + 1);
}
}
/* 3-in-a-row errors */
if (tile & (FE_HOR_ROW_LEFT | FE_HOR_ROW_RIGHT)) {
int left = x, right = x + tilesize - 1;
if ((tile & FE_HOR_ROW_LEFT))
right += tilesize/2;
if ((tile & FE_HOR_ROW_RIGHT))
left -= tilesize/2;
unruly_draw_err_rectangle(dr, left, y, right-left, tilesize-1, tilesize);
}
if (tile & (FE_VER_ROW_TOP | FE_VER_ROW_BOTTOM)) {
int top = y, bottom = y + tilesize - 1;
if ((tile & FE_VER_ROW_TOP))
bottom += tilesize/2;
if ((tile & FE_VER_ROW_BOTTOM))
top -= tilesize/2;
unruly_draw_err_rectangle(dr, x, top, tilesize-1, bottom-top, tilesize);
}
/* Count errors */
if (tile & FE_COUNT) {
draw_text(dr, x + tilesize/2, y + tilesize/2, FONT_VARIABLE,
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);
draw_rect(dr, x, y, tilesize-1, tilesize/12, COL_CURSOR);
draw_rect(dr, x+tilesize-1-tilesize/12, y, tilesize/12, tilesize-1,
COL_CURSOR);
draw_rect(dr, x, y+tilesize-1-tilesize/12, tilesize-1, tilesize/12,
COL_CURSOR);
}
unclip(dr);
draw_update(dr, x, y, tilesize, tilesize);
}
#define TILE_SIZE (ds->tilesize)
#define DEFAULT_TILE_SIZE 32
#define FLASH_FRAME 0.12F
#define FLASH_TIME (FLASH_FRAME * 3)
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 w2 = state->w2, h2 = state->h2;
int s = w2 * h2;
int flash;
int x, y, i;
if (!ds->started) {
/* Outer edge of grid */
draw_rect(dr, COORD(0)-TILE_SIZE/10, COORD(0)-TILE_SIZE/10,
TILE_SIZE*w2 + 2*(TILE_SIZE/10) - 1,
TILE_SIZE*h2 + 2*(TILE_SIZE/10) - 1, COL_GRID);
draw_update(dr, 0, 0, TILE_SIZE * (w2+1), TILE_SIZE * (h2+1));
ds->started = true;
}
flash = 0;
if (flashtime > 0)
flash = (int)(flashtime / FLASH_FRAME) == 1 ? FF_FLASH2 : FF_FLASH1;
for (i = 0; i < s; i++)
ds->gridfs[i] = 0;
unruly_validate_all_rows(state, ds->gridfs);
for (i = 0; i < 2 * (h2 + w2); i++)
ds->rowfs[i] = false;
unruly_validate_counts(state, NULL, ds->rowfs);
for (y = 0; y < h2; y++) {
for (x = 0; x < w2; x++) {
int tile;
i = y * w2 + x;
tile = ds->gridfs[i];
if (state->grid[i] == N_ONE) {
tile |= FF_ONE;
if (ds->rowfs[y] || ds->rowfs[2*h2 + x])
tile |= FE_COUNT;
} else if (state->grid[i] == N_ZERO) {
tile |= FF_ZERO;
if (ds->rowfs[h2 + y] || ds->rowfs[2*h2 + w2 + x])
tile |= FE_COUNT;
}
tile |= flash;
if (state->common->immutable[i])
tile |= FF_IMMUTABLE;
if (ui->cursor && ui->cx == x && ui->cy == y)
tile |= FF_CURSOR;
if (ds->grid[i] != tile) {
ds->grid[i] = tile;
unruly_draw_tile(dr, COORD(x), COORD(y), TILE_SIZE, tile);
}
}
}
}
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(const game_state *oldstate,
const game_state *newstate, int dir, game_ui *ui)
{
if (!oldstate->completed && newstate->completed &&
!oldstate->cheated && !newstate->cheated)
return FLASH_TIME;
return 0.0F;
}
static void game_get_cursor_location(const game_ui *ui,
const game_drawstate *ds,
const game_state *state,
const game_params *params,
int *x, int *y, int *w, int *h)
{
if(ui->cursor) {
*x = COORD(ui->cx);
*y = COORD(ui->cy);
*w = *h = TILE_SIZE;
}
}
static int game_status(const game_state *state)
{
return state->completed ? +1 : 0;
}
static void game_print_size(const game_params *params, const game_ui *ui,
float *x, float *y)
{
int pw, ph;
/* Using 7mm squares */
game_compute_size(params, 700, ui, &pw, &ph);
*x = pw / 100.0F;
*y = ph / 100.0F;
}
static void game_print(drawing *dr, const game_state *state, const game_ui *ui,
int tilesize)
{
int w2 = state->w2, h2 = state->h2;
int x, y;
int ink = print_mono_colour(dr, 0);
for (y = 0; y < h2; y++)
for (x = 0; x < w2; x++) {
int tx = x * tilesize + (tilesize / 2);
int ty = y * tilesize + (tilesize / 2);
/* Draw the border */
int coords[8];
coords[0] = tx;
coords[1] = ty - 1;
coords[2] = tx + tilesize;
coords[3] = ty - 1;
coords[4] = tx + tilesize;
coords[5] = ty + tilesize - 1;
coords[6] = tx;
coords[7] = ty + tilesize - 1;
draw_polygon(dr, coords, 4, -1, ink);
if (state->grid[y * w2 + x] == N_ONE)
draw_rect(dr, tx, ty, tilesize, tilesize, ink);
else if (state->grid[y * w2 + x] == N_ZERO)
draw_circle(dr, tx + tilesize/2, ty + tilesize/2,
tilesize/12, ink, ink);
}
}
#ifdef COMBINED
#define thegame unruly
#endif
const struct game thegame = {
"Unruly", "games.unruly", "unruly",
default_params,
game_fetch_preset, NULL,
decode_params,
encode_params,
free_params,
dup_params,
true, game_configure, custom_params,
validate_params,
new_game_desc,
validate_desc,
new_game,
dup_game,
free_game,
true, solve_game,
true, game_can_format_as_text_now, game_text_format,
NULL, NULL, /* get_prefs, set_prefs */
new_ui,
free_ui,
NULL, /* encode_ui */
NULL, /* decode_ui */
NULL, /* game_request_keys */
game_changed_state,
current_key_label,
interpret_move,
execute_move,
DEFAULT_TILE_SIZE, game_compute_size, game_set_size,
game_colours,
game_new_drawstate,
game_free_drawstate,
game_redraw,
game_anim_length,
game_flash_length,
game_get_cursor_location,
game_status,
true, false, game_print_size, game_print,
false, /* wants_statusbar */
false, NULL, /* timing_state */
0, /* flags */
};
/* ***************** *
* Standalone solver *
* ***************** */
#ifdef STANDALONE_SOLVER
#include <time.h>
#include <stdarg.h>
/* Most of the standalone solver code was copied from unequal.c and singles.c */
static const char *quis;
static void usage_exit(const char *msg)
{
if (msg)
fprintf(stderr, "%s: %s\n", quis, msg);
fprintf(stderr,
"Usage: %s [-v] [--seed SEED] <params> | [game_id [game_id ...]]\n",
quis);
exit(1);
}
int main(int argc, char *argv[])
{
random_state *rs;
time_t seed = time(NULL);
game_params *params = NULL;
char *id = NULL, *desc = NULL;
const char *err;
quis = argv[0];
while (--argc > 0) {
char *p = *++argv;
if (!strcmp(p, "--seed")) {
if (argc == 0)
usage_exit("--seed needs an argument");
seed = (time_t) atoi(*++argv);
argc--;
} else if (!strcmp(p, "-v"))
solver_verbose = true;
else if (*p == '-')
usage_exit("unrecognised option");
else
id = p;
}
if (id) {
desc = strchr(id, ':');
if (desc)
*desc++ = '\0';
params = default_params();
decode_params(params, id);
err = validate_params(params, true);
if (err) {
fprintf(stderr, "Parameters are invalid\n");
fprintf(stderr, "%s: %s", argv[0], err);
exit(1);
}
}
if (!desc) {
char *desc_gen, *aux;
rs = random_new((void *) &seed, sizeof(time_t));
if (!params)
params = default_params();
printf("Generating puzzle with parameters %s\n",
encode_params(params, true));
desc_gen = new_game_desc(params, rs, &aux, false);
if (!solver_verbose) {
char *fmt = game_text_format(new_game(NULL, params, desc_gen));
fputs(fmt, stdout);
sfree(fmt);
}
printf("Game ID: %s\n", desc_gen);
} else {
game_state *input;
struct unruly_scratch *scratch;
int maxdiff, errcode;
err = validate_desc(params, desc);
if (err) {
fprintf(stderr, "Description is invalid\n");
fprintf(stderr, "%s", err);
exit(1);
}
input = new_game(NULL, params, desc);
scratch = unruly_new_scratch(input);
maxdiff = unruly_solve_game(input, scratch, DIFFCOUNT);
errcode = unruly_validate_counts(input, scratch, NULL);
if (unruly_validate_all_rows(input, NULL) == -1)
errcode = -1;
if (errcode != -1) {
char *fmt = game_text_format(input);
fputs(fmt, stdout);
sfree(fmt);
if (maxdiff < 0)
printf("Difficulty: already solved!\n");
else
printf("Difficulty: %s\n", unruly_diffnames[maxdiff]);
}
if (errcode == 1)
printf("No solution found.\n");
else if (errcode == -1)
printf("Puzzle is invalid.\n");
free_game(input);
unruly_free_scratch(scratch);
}
return 0;
}
#endif