ref: 92ac45fe240b2063455b0b01dedc6ef6996f18af
dir: /unruly.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)) return move_cursor(button, &ui->cx, &ui->cy, w2, h2, false, &ui->cursor); /* 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