#include "puzzles.h"
#include "tree234.h"
#include "grid.h"
+#include "loopgen.h"
/* Debugging options */
};
struct game_state {
- grid *game_grid;
+ grid *game_grid; /* ref-counted (internally) */
/* Put -1 in a face that doesn't get a clue */
signed char *clues;
char *lines;
unsigned char *line_errors;
+ int exactly_one_loop;
int solved;
int cheated;
SOLVERLIST(SOLVER_FN_DECL)
static int (*(solver_fns[]))(solver_state *) = { SOLVERLIST(SOLVER_FN) };
static int const solver_diffs[] = { SOLVERLIST(SOLVER_DIFF) };
-const int NUM_SOLVERS = sizeof(solver_diffs)/sizeof(*solver_diffs);
+static const int NUM_SOLVERS = sizeof(solver_diffs)/sizeof(*solver_diffs);
struct game_params {
int w, h;
int diff;
int type;
-
- /* Grid generation is expensive, so keep a (ref-counted) reference to the
- * grid for these parameters, and only generate when required. */
- grid *game_grid;
};
/* line_drawstate is the same as line_state, but with the extra ERROR
int started;
int tilesize;
int flashing;
+ int *textx, *texty;
char *lines;
char *clue_error;
char *clue_satisfied;
};
-static char *validate_desc(game_params *params, char *desc);
+static char *validate_desc(const game_params *params, const char *desc);
static int dot_order(const game_state* state, int i, char line_type);
static int face_order(const game_state* state, int i, char line_type);
static solver_state *solve_game_rec(const solver_state *sstate);
#define check_caches(s)
#endif
-/* ------- List of grid generators ------- */
-#define GRIDLIST(A) \
- A(Squares,grid_new_square,3,3) \
- A(Triangular,grid_new_triangular,3,3) \
- A(Honeycomb,grid_new_honeycomb,3,3) \
- A(Snub-Square,grid_new_snubsquare,3,3) \
- A(Cairo,grid_new_cairo,3,4) \
- A(Great-Hexagonal,grid_new_greathexagonal,3,3) \
- A(Octagonal,grid_new_octagonal,3,3) \
- A(Kites,grid_new_kites,3,3) \
- A(Floret,grid_new_floret,1,2) \
- A(Dodecagonal,grid_new_dodecagonal,2,2) \
- A(Great-Dodecagonal,grid_new_greatdodecagonal,2,2)
-
-#define GRID_NAME(title,fn,amin,omin) #title,
-#define GRID_CONFIG(title,fn,amin,omin) ":" #title
-#define GRID_FN(title,fn,amin,omin) &fn,
-#define GRID_SIZES(title,fn,amin,omin) \
+/*
+ * Grid type config options available in Loopy.
+ *
+ * Annoyingly, we have to use an enum here which doesn't match up
+ * exactly to the grid-type enum in grid.h. Values in params->types
+ * are given by names such as LOOPY_GRID_SQUARE, which shouldn't be
+ * confused with GRID_SQUARE which is the value you pass to grid_new()
+ * and friends. So beware!
+ *
+ * (This is partly for historical reasons - Loopy's version of the
+ * enum is encoded in game parameter strings, so we keep it for
+ * backwards compatibility. But also, we need to store additional data
+ * here alongside each enum value, such as names for the presets menu,
+ * which isn't stored in grid.h; so we have to have our own list macro
+ * here anyway, and C doesn't make it easy to enforce that that lines
+ * up exactly with grid.h.)
+ *
+ * Do not add values to this list _except_ at the end, or old game ids
+ * will stop working!
+ */
+#define GRIDLIST(A) \
+ A("Squares",SQUARE,3,3) \
+ A("Triangular",TRIANGULAR,3,3) \
+ A("Honeycomb",HONEYCOMB,3,3) \
+ A("Snub-Square",SNUBSQUARE,3,3) \
+ A("Cairo",CAIRO,3,4) \
+ A("Great-Hexagonal",GREATHEXAGONAL,3,3) \
+ A("Octagonal",OCTAGONAL,3,3) \
+ A("Kites",KITE,3,3) \
+ A("Floret",FLORET,1,2) \
+ A("Dodecagonal",DODECAGONAL,2,2) \
+ A("Great-Dodecagonal",GREATDODECAGONAL,2,2) \
+ A("Penrose (kite/dart)",PENROSE_P2,3,3) \
+ A("Penrose (rhombs)",PENROSE_P3,3,3) \
+ A("Great-Great-Dodecagonal",GREATGREATDODECAGONAL,2,2) \
+ /* end of list */
+
+#define GRID_NAME(title,type,amin,omin) title,
+#define GRID_CONFIG(title,type,amin,omin) ":" title
+#define GRID_LOOPYTYPE(title,type,amin,omin) LOOPY_GRID_ ## type,
+#define GRID_GRIDTYPE(title,type,amin,omin) GRID_ ## type,
+#define GRID_SIZES(title,type,amin,omin) \
{amin, omin, \
"Width and height for this grid type must both be at least " #amin, \
"At least one of width and height for this grid type must be at least " #omin,},
+enum { GRIDLIST(GRID_LOOPYTYPE) LOOPY_GRID_DUMMY_TERMINATOR };
static char const *const gridnames[] = { GRIDLIST(GRID_NAME) };
#define GRID_CONFIGS GRIDLIST(GRID_CONFIG)
-static grid * (*(grid_fns[]))(int w, int h) = { GRIDLIST(GRID_FN) };
-#define NUM_GRID_TYPES (sizeof(grid_fns) / sizeof(grid_fns[0]))
+static grid_type grid_types[] = { GRIDLIST(GRID_GRIDTYPE) };
+#define NUM_GRID_TYPES (sizeof(grid_types) / sizeof(grid_types[0]))
static const struct {
int amin, omin;
char *aerr, *oerr;
/* Generates a (dynamically allocated) new grid, according to the
* type and size requested in params. Does nothing if the grid is already
- * generated. The allocated grid is owned by the params object, and will be
- * freed in free_params(). */
-static void params_generate_grid(game_params *params)
+ * generated. */
+static grid *loopy_generate_grid(const game_params *params,
+ const char *grid_desc)
{
- if (!params->game_grid) {
- params->game_grid = grid_fns[params->type](params->w, params->h);
- }
+ return grid_new(grid_types[params->type], params->w, params->h, grid_desc);
}
/* ----------------------------------------------------------------------
* General struct manipulation and other straightforward code
*/
-static game_state *dup_game(game_state *state)
+static game_state *dup_game(const game_state *state)
{
game_state *ret = snew(game_state);
ret->line_errors = snewn(state->game_grid->num_edges, unsigned char);
memcpy(ret->line_errors, state->line_errors, state->game_grid->num_edges);
+ ret->exactly_one_loop = state->exactly_one_loop;
ret->grid_type = state->grid_type;
return ret;
}
}
-static solver_state *new_solver_state(game_state *state, int diff) {
+static solver_state *new_solver_state(const game_state *state, int diff) {
int i;
int num_dots = state->game_grid->num_dots;
int num_faces = state->game_grid->num_faces;
ret->diff = DIFF_EASY;
ret->type = 0;
- ret->game_grid = NULL;
-
return ret;
}
-static game_params *dup_params(game_params *params)
+static game_params *dup_params(const game_params *params)
{
game_params *ret = snew(game_params);
*ret = *params; /* structure copy */
- if (ret->game_grid) {
- ret->game_grid->refcount++;
- }
return ret;
}
-static const game_params presets[] = {
+static const game_params loopy_presets_top[] = {
+#ifdef SMALL_SCREEN
+ { 7, 7, DIFF_EASY, LOOPY_GRID_SQUARE },
+ { 7, 7, DIFF_NORMAL, LOOPY_GRID_SQUARE },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_SQUARE },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_TRIANGULAR },
+ { 5, 5, DIFF_HARD, LOOPY_GRID_SNUBSQUARE },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_CAIRO },
+ { 5, 5, DIFF_HARD, LOOPY_GRID_KITE },
+ { 6, 6, DIFF_HARD, LOOPY_GRID_PENROSE_P2 },
+ { 6, 6, DIFF_HARD, LOOPY_GRID_PENROSE_P3 },
+#else
+ { 7, 7, DIFF_EASY, LOOPY_GRID_SQUARE },
+ { 10, 10, DIFF_EASY, LOOPY_GRID_SQUARE },
+ { 7, 7, DIFF_NORMAL, LOOPY_GRID_SQUARE },
+ { 10, 10, DIFF_NORMAL, LOOPY_GRID_SQUARE },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_SQUARE },
+ { 10, 10, DIFF_HARD, LOOPY_GRID_SQUARE },
+ { 12, 10, DIFF_HARD, LOOPY_GRID_TRIANGULAR },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_SNUBSQUARE },
+ { 9, 9, DIFF_HARD, LOOPY_GRID_CAIRO },
+ { 5, 5, DIFF_HARD, LOOPY_GRID_KITE },
+ { 10, 10, DIFF_HARD, LOOPY_GRID_PENROSE_P2 },
+ { 10, 10, DIFF_HARD, LOOPY_GRID_PENROSE_P3 },
+#endif
+};
+
+static const game_params loopy_presets_more[] = {
#ifdef SMALL_SCREEN
- { 7, 7, DIFF_EASY, 0, NULL },
- { 7, 7, DIFF_NORMAL, 0, NULL },
- { 7, 7, DIFF_HARD, 0, NULL },
- { 7, 7, DIFF_HARD, 1, NULL },
- { 7, 7, DIFF_HARD, 2, NULL },
- { 5, 5, DIFF_HARD, 3, NULL },
- { 7, 7, DIFF_HARD, 4, NULL },
- { 5, 4, DIFF_HARD, 5, NULL },
- { 5, 5, DIFF_HARD, 6, NULL },
- { 5, 5, DIFF_HARD, 7, NULL },
- { 3, 3, DIFF_HARD, 8, NULL },
- { 3, 3, DIFF_HARD, 9, NULL },
- { 3, 3, DIFF_HARD, 10, NULL },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_HONEYCOMB },
+ { 5, 4, DIFF_HARD, LOOPY_GRID_GREATHEXAGONAL },
+ { 5, 5, DIFF_HARD, LOOPY_GRID_OCTAGONAL },
+ { 3, 3, DIFF_HARD, LOOPY_GRID_FLORET },
+ { 3, 3, DIFF_HARD, LOOPY_GRID_DODECAGONAL },
+ { 3, 3, DIFF_HARD, LOOPY_GRID_GREATDODECAGONAL },
+ { 3, 2, DIFF_HARD, LOOPY_GRID_GREATGREATDODECAGONAL },
#else
- { 7, 7, DIFF_EASY, 0, NULL },
- { 10, 10, DIFF_EASY, 0, NULL },
- { 7, 7, DIFF_NORMAL, 0, NULL },
- { 10, 10, DIFF_NORMAL, 0, NULL },
- { 7, 7, DIFF_HARD, 0, NULL },
- { 10, 10, DIFF_HARD, 0, NULL },
- { 10, 10, DIFF_HARD, 1, NULL },
- { 12, 10, DIFF_HARD, 2, NULL },
- { 7, 7, DIFF_HARD, 3, NULL },
- { 9, 9, DIFF_HARD, 4, NULL },
- { 5, 4, DIFF_HARD, 5, NULL },
- { 7, 7, DIFF_HARD, 6, NULL },
- { 5, 5, DIFF_HARD, 7, NULL },
- { 5, 5, DIFF_HARD, 8, NULL },
- { 5, 4, DIFF_HARD, 9, NULL },
- { 5, 4, DIFF_HARD, 10, NULL },
+ { 10, 10, DIFF_HARD, LOOPY_GRID_HONEYCOMB },
+ { 5, 4, DIFF_HARD, LOOPY_GRID_GREATHEXAGONAL },
+ { 7, 7, DIFF_HARD, LOOPY_GRID_OCTAGONAL },
+ { 5, 5, DIFF_HARD, LOOPY_GRID_FLORET },
+ { 5, 4, DIFF_HARD, LOOPY_GRID_DODECAGONAL },
+ { 5, 4, DIFF_HARD, LOOPY_GRID_GREATDODECAGONAL },
+ { 5, 3, DIFF_HARD, LOOPY_GRID_GREATGREATDODECAGONAL },
#endif
};
-static int game_fetch_preset(int i, char **name, game_params **params)
+static void preset_menu_add_preset_with_title(struct preset_menu *menu,
+ const game_params *params)
{
- game_params *tmppar;
char buf[80];
+ game_params *dup_params;
- if (i < 0 || i >= lenof(presets))
- return FALSE;
+ sprintf(buf, "%dx%d %s - %s", params->h, params->w,
+ gridnames[params->type], diffnames[params->diff]);
- tmppar = snew(game_params);
- *tmppar = presets[i];
- *params = tmppar;
- sprintf(buf, "%dx%d %s - %s", tmppar->h, tmppar->w,
- gridnames[tmppar->type], diffnames[tmppar->diff]);
- *name = dupstr(buf);
+ dup_params = snew(game_params);
+ *dup_params = *params;
- return TRUE;
+ preset_menu_add_preset(menu, dupstr(buf), dup_params);
+}
+
+static struct preset_menu *game_preset_menu(void)
+{
+ struct preset_menu *top, *more;
+ int i;
+
+ top = preset_menu_new();
+ for (i = 0; i < lenof(loopy_presets_top); i++)
+ preset_menu_add_preset_with_title(top, &loopy_presets_top[i]);
+
+ more = preset_menu_add_submenu(top, dupstr("More..."));
+ for (i = 0; i < lenof(loopy_presets_more); i++)
+ preset_menu_add_preset_with_title(more, &loopy_presets_more[i]);
+
+ return top;
}
static void free_params(game_params *params)
{
- if (params->game_grid) {
- grid_free(params->game_grid);
- }
sfree(params);
}
static void decode_params(game_params *params, char const *string)
{
- if (params->game_grid) {
- grid_free(params->game_grid);
- params->game_grid = NULL;
- }
params->h = params->w = atoi(string);
params->diff = DIFF_EASY;
while (*string && isdigit((unsigned char)*string)) string++;
}
}
-static char *encode_params(game_params *params, int full)
+static char *encode_params(const game_params *params, int full)
{
char str[80];
sprintf(str, "%dx%dt%d", params->w, params->h, params->type);
return dupstr(str);
}
-static config_item *game_configure(game_params *params)
+static config_item *game_configure(const game_params *params)
{
config_item *ret;
char buf[80];
return ret;
}
-static game_params *custom_params(config_item *cfg)
+static game_params *custom_params(const config_item *cfg)
{
game_params *ret = snew(game_params);
ret->type = cfg[2].ival;
ret->diff = cfg[3].ival;
- ret->game_grid = NULL;
return ret;
}
-static char *validate_params(game_params *params, int full)
+static char *validate_params(const game_params *params, int full)
{
if (params->type < 0 || params->type >= NUM_GRID_TYPES)
return "Illegal grid type";
return retval;
}
+#define GRID_DESC_SEP '_'
+
+/* Splits up a (optional) grid_desc from the game desc. Returns the
+ * grid_desc (which needs freeing) and updates the desc pointer to
+ * start of real desc, or returns NULL if no desc. */
+static char *extract_grid_desc(const char **desc)
+{
+ char *sep = strchr(*desc, GRID_DESC_SEP), *gd;
+ int gd_len;
+
+ if (!sep) return NULL;
+
+ gd_len = sep - (*desc);
+ gd = snewn(gd_len+1, char);
+ memcpy(gd, *desc, gd_len);
+ gd[gd_len] = '\0';
+
+ *desc = sep+1;
+
+ return gd;
+}
+
/* We require that the params pass the test in validate_params and that the
* description fills the entire game area */
-static char *validate_desc(game_params *params, char *desc)
+static char *validate_desc(const game_params *params, const char *desc)
{
int count = 0;
grid *g;
- params_generate_grid(params);
- g = params->game_grid;
+ char *grid_desc, *ret;
+
+ /* It's pretty inefficient to do this just for validation. All we need to
+ * know is the precise number of faces. */
+ grid_desc = extract_grid_desc(&desc);
+ ret = grid_validate_desc(grid_types[params->type], params->w, params->h, grid_desc);
+ if (ret) return ret;
+
+ g = loopy_generate_grid(params, grid_desc);
+ if (grid_desc) sfree(grid_desc);
for (; *desc; ++desc) {
if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) {
if (count > g->num_faces)
return "Description too long for board size";
+ grid_free(g);
+
return NULL;
}
return ret;
}
-static game_ui *new_ui(game_state *state)
+static game_ui *new_ui(const game_state *state)
{
return NULL;
}
{
}
-static char *encode_ui(game_ui *ui)
+static char *encode_ui(const game_ui *ui)
{
return NULL;
}
-static void decode_ui(game_ui *ui, char *encoding)
+static void decode_ui(game_ui *ui, const char *encoding)
{
}
-static void game_changed_state(game_ui *ui, game_state *oldstate,
- game_state *newstate)
+static void game_changed_state(game_ui *ui, const game_state *oldstate,
+ const game_state *newstate)
{
}
-static void game_compute_size(game_params *params, int tilesize,
+static void game_compute_size(const game_params *params, int tilesize,
int *x, int *y)
{
- grid *g;
int grid_width, grid_height, rendered_width, rendered_height;
+ int g_tilesize;
+
+ grid_compute_size(grid_types[params->type], params->w, params->h,
+ &g_tilesize, &grid_width, &grid_height);
- params_generate_grid(params);
- g = params->game_grid;
- grid_width = g->highest_x - g->lowest_x;
- grid_height = g->highest_y - g->lowest_y;
/* multiply first to minimise rounding error on integer division */
- rendered_width = grid_width * tilesize / g->tilesize;
- rendered_height = grid_height * tilesize / g->tilesize;
+ rendered_width = grid_width * tilesize / g_tilesize;
+ rendered_height = grid_height * tilesize / g_tilesize;
*x = rendered_width + 2 * BORDER(tilesize) + 1;
*y = rendered_height + 2 * BORDER(tilesize) + 1;
}
static void game_set_size(drawing *dr, game_drawstate *ds,
- game_params *params, int tilesize)
+ const game_params *params, int tilesize)
{
ds->tilesize = tilesize;
}
static float *game_colours(frontend *fe, int *ncolours)
{
- float *ret = snewn(4 * NCOLOURS, float);
+ float *ret = snewn(3 * NCOLOURS, float);
frontend_default_colour(fe, &ret[COL_BACKGROUND * 3]);
ret[COL_FOREGROUND * 3 + 1] = 0.0F;
ret[COL_FOREGROUND * 3 + 2] = 0.0F;
- ret[COL_LINEUNKNOWN * 3 + 0] = 0.8F;
- ret[COL_LINEUNKNOWN * 3 + 1] = 0.8F;
+ /*
+ * We want COL_LINEUNKNOWN to be a yellow which is a bit darker
+ * than the background. (I previously set it to 0.8,0.8,0, but
+ * found that this went badly with the 0.8,0.8,0.8 favoured as a
+ * background by the Java frontend.)
+ */
+ ret[COL_LINEUNKNOWN * 3 + 0] = ret[COL_BACKGROUND * 3 + 0] * 0.9F;
+ ret[COL_LINEUNKNOWN * 3 + 1] = ret[COL_BACKGROUND * 3 + 1] * 0.9F;
ret[COL_LINEUNKNOWN * 3 + 2] = 0.0F;
ret[COL_HIGHLIGHT * 3 + 0] = 1.0F;
return ret;
}
-static game_drawstate *game_new_drawstate(drawing *dr, game_state *state)
+static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
{
struct game_drawstate *ds = snew(struct game_drawstate);
int num_faces = state->game_grid->num_faces;
int num_edges = state->game_grid->num_edges;
+ int i;
ds->tilesize = 0;
ds->started = 0;
ds->lines = snewn(num_edges, char);
ds->clue_error = snewn(num_faces, char);
ds->clue_satisfied = snewn(num_faces, char);
+ ds->textx = snewn(num_faces, int);
+ ds->texty = snewn(num_faces, int);
ds->flashing = 0;
memset(ds->lines, LINE_UNKNOWN, num_edges);
memset(ds->clue_error, 0, num_faces);
memset(ds->clue_satisfied, 0, num_faces);
+ for (i = 0; i < num_faces; i++)
+ ds->textx[i] = ds->texty[i] = -1;
return ds;
}
static void game_free_drawstate(drawing *dr, game_drawstate *ds)
{
+ sfree(ds->textx);
+ sfree(ds->texty);
sfree(ds->clue_error);
sfree(ds->clue_satisfied);
sfree(ds->lines);
sfree(ds);
}
-static int game_timing_state(game_state *state, game_ui *ui)
+static int game_timing_state(const game_state *state, game_ui *ui)
{
return TRUE;
}
-static float game_anim_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_anim_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
return 0.0F;
}
-static int game_can_format_as_text_now(game_params *params)
+static int game_can_format_as_text_now(const game_params *params)
{
if (params->type != 0)
return FALSE;
return TRUE;
}
-static char *game_text_format(game_state *state)
+static char *game_text_format(const game_state *state)
{
int w, h, W, H;
int x, y, i;
* Loop generation and clue removal
*/
-/* We're going to store lists of current candidate faces for colouring black
- * or white.
- * Each face gets a 'score', which tells us how adding that face right
- * now would affect the curliness of the solution loop. We're trying to
- * maximise that quantity so will bias our random selection of faces to
- * colour those with high scores */
-struct face_score {
- int white_score;
- int black_score;
- unsigned long random;
- /* No need to store a grid_face* here. The 'face_scores' array will
- * be a list of 'face_score' objects, one for each face of the grid, so
- * the position (index) within the 'face_scores' array will determine
- * which face corresponds to a particular face_score.
- * Having a single 'face_scores' array for all faces simplifies memory
- * management, and probably improves performance, because we don't have to
- * malloc/free each individual face_score, and we don't have to maintain
- * a mapping from grid_face* pointers to face_score* pointers.
- */
-};
-
-static int generic_sort_cmpfn(void *v1, void *v2, size_t offset)
-{
- struct face_score *f1 = v1;
- struct face_score *f2 = v2;
- int r;
-
- r = *(int *)((char *)f2 + offset) - *(int *)((char *)f1 + offset);
- if (r) {
- return r;
- }
-
- if (f1->random < f2->random)
- return -1;
- else if (f1->random > f2->random)
- return 1;
-
- /*
- * It's _just_ possible that two faces might have been given
- * the same random value. In that situation, fall back to
- * comparing based on the positions within the face_scores list.
- * This introduces a tiny directional bias, but not a significant one.
- */
- return f1 - f2;
-}
-
-static int white_sort_cmpfn(void *v1, void *v2)
-{
- return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,white_score));
-}
-
-static int black_sort_cmpfn(void *v1, void *v2)
-{
- return generic_sort_cmpfn(v1, v2, offsetof(struct face_score,black_score));
-}
-
-enum face_colour { FACE_WHITE, FACE_GREY, FACE_BLACK };
-
-/* face should be of type grid_face* here. */
-#define FACE_COLOUR(face) \
- ( (face) == NULL ? FACE_BLACK : \
- board[(face) - g->faces] )
-
-/* 'board' is an array of these enums, indicating which faces are
- * currently black/white/grey. 'colour' is FACE_WHITE or FACE_BLACK.
- * Returns whether it's legal to colour the given face with this colour. */
-static int can_colour_face(grid *g, char* board, int face_index,
- enum face_colour colour)
-{
- int i, j;
- grid_face *test_face = g->faces + face_index;
- grid_face *starting_face, *current_face;
- grid_dot *starting_dot;
- int transitions;
- int current_state, s; /* booleans: equal or not-equal to 'colour' */
- int found_same_coloured_neighbour = FALSE;
- assert(board[face_index] != colour);
-
- /* Can only consider a face for colouring if it's adjacent to a face
- * with the same colour. */
- for (i = 0; i < test_face->order; i++) {
- grid_edge *e = test_face->edges[i];
- grid_face *f = (e->face1 == test_face) ? e->face2 : e->face1;
- if (FACE_COLOUR(f) == colour) {
- found_same_coloured_neighbour = TRUE;
- break;
- }
- }
- if (!found_same_coloured_neighbour)
- return FALSE;
-
- /* Need to avoid creating a loop of faces of this colour around some
- * differently-coloured faces.
- * Also need to avoid meeting a same-coloured face at a corner, with
- * other-coloured faces in between. Here's a simple test that (I believe)
- * takes care of both these conditions:
- *
- * Take the circular path formed by this face's edges, and inflate it
- * slightly outwards. Imagine walking around this path and consider
- * the faces that you visit in sequence. This will include all faces
- * touching the given face, either along an edge or just at a corner.
- * Count the number of 'colour'/not-'colour' transitions you encounter, as
- * you walk along the complete loop. This will obviously turn out to be
- * an even number.
- * If 0, we're either in the middle of an "island" of this colour (should
- * be impossible as we're not supposed to create black or white loops),
- * or we're about to start a new island - also not allowed.
- * If 4 or greater, there are too many separate coloured regions touching
- * this face, and colouring it would create a loop or a corner-violation.
- * The only allowed case is when the count is exactly 2. */
-
- /* i points to a dot around the test face.
- * j points to a face around the i^th dot.
- * The current face will always be:
- * test_face->dots[i]->faces[j]
- * We assume dots go clockwise around the test face,
- * and faces go clockwise around dots. */
-
- /*
- * The end condition is slightly fiddly. In sufficiently strange
- * degenerate grids, our test face may be adjacent to the same
- * other face multiple times (typically if it's the exterior
- * face). Consider this, in particular:
- *
- * +--+
- * | |
- * +--+--+
- * | | |
- * +--+--+
- *
- * The bottom left face there is adjacent to the exterior face
- * twice, so we can't just terminate our iteration when we reach
- * the same _face_ we started at. Furthermore, we can't
- * condition on having the same (i,j) pair either, because
- * several (i,j) pairs identify the bottom left contiguity with
- * the exterior face! We canonicalise the (i,j) pair by taking
- * one step around before we set the termination tracking.
- */
-
- i = j = 0;
- current_face = test_face->dots[0]->faces[0];
- if (current_face == test_face) {
- j = 1;
- current_face = test_face->dots[0]->faces[1];
- }
- transitions = 0;
- current_state = (FACE_COLOUR(current_face) == colour);
- starting_dot = NULL;
- starting_face = NULL;
- while (TRUE) {
- /* Advance to next face.
- * Need to loop here because it might take several goes to
- * find it. */
- while (TRUE) {
- j++;
- if (j == test_face->dots[i]->order)
- j = 0;
-
- if (test_face->dots[i]->faces[j] == test_face) {
- /* Advance to next dot round test_face, then
- * find current_face around new dot
- * and advance to the next face clockwise */
- i++;
- if (i == test_face->order)
- i = 0;
- for (j = 0; j < test_face->dots[i]->order; j++) {
- if (test_face->dots[i]->faces[j] == current_face)
- break;
- }
- /* Must actually find current_face around new dot,
- * or else something's wrong with the grid. */
- assert(j != test_face->dots[i]->order);
- /* Found, so advance to next face and try again */
- } else {
- break;
- }
- }
- /* (i,j) are now advanced to next face */
- current_face = test_face->dots[i]->faces[j];
- s = (FACE_COLOUR(current_face) == colour);
- if (!starting_dot) {
- starting_dot = test_face->dots[i];
- starting_face = current_face;
- current_state = s;
- } else {
- if (s != current_state) {
- ++transitions;
- current_state = s;
- if (transitions > 2)
- break;
- }
- if (test_face->dots[i] == starting_dot &&
- current_face == starting_face)
- break;
- }
- }
-
- return (transitions == 2) ? TRUE : FALSE;
-}
-
-/* Count the number of neighbours of 'face', having colour 'colour' */
-static int face_num_neighbours(grid *g, char *board, grid_face *face,
- enum face_colour colour)
-{
- int colour_count = 0;
- int i;
- grid_face *f;
- grid_edge *e;
- for (i = 0; i < face->order; i++) {
- e = face->edges[i];
- f = (e->face1 == face) ? e->face2 : e->face1;
- if (FACE_COLOUR(f) == colour)
- ++colour_count;
- }
- return colour_count;
-}
-
-/* The 'score' of a face reflects its current desirability for selection
- * as the next face to colour white or black. We want to encourage moving
- * into grey areas and increasing loopiness, so we give scores according to
- * how many of the face's neighbours are currently coloured the same as the
- * proposed colour. */
-static int face_score(grid *g, char *board, grid_face *face,
- enum face_colour colour)
-{
- /* Simple formula: score = 0 - num. same-coloured neighbours,
- * so a higher score means fewer same-coloured neighbours. */
- return -face_num_neighbours(g, board, face, colour);
-}
-
-/* Generate a new complete set of clues for the given game_state.
- * The method is to generate a WHITE/BLACK colouring of all the faces,
- * such that the WHITE faces will define the inside of the path, and the
- * BLACK faces define the outside.
- * To do this, we initially colour all faces GREY. The infinite space outside
- * the grid is coloured BLACK, and we choose a random face to colour WHITE.
- * Then we gradually grow the BLACK and the WHITE regions, eliminating GREY
- * faces, until the grid is filled with BLACK/WHITE. As we grow the regions,
- * we avoid creating loops of a single colour, to preserve the topological
- * shape of the WHITE and BLACK regions.
- * We also try to make the boundary as loopy and twisty as possible, to avoid
- * generating paths that are uninteresting.
- * The algorithm works by choosing a BLACK/WHITE colour, then choosing a GREY
- * face that can be coloured with that colour (without violating the
- * topological shape of that region). It's not obvious, but I think this
- * algorithm is guaranteed to terminate without leaving any GREY faces behind.
- * Indeed, if there are any GREY faces at all, both the WHITE and BLACK
- * regions can be grown.
- * This is checked using assert()ions, and I haven't seen any failures yet.
- *
- * Hand-wavy proof: imagine what can go wrong...
- *
- * Could the white faces get completely cut off by the black faces, and still
- * leave some grey faces remaining?
- * No, because then the black faces would form a loop around both the white
- * faces and the grey faces, which is disallowed because we continually
- * maintain the correct topological shape of the black region.
- * Similarly, the black faces can never get cut off by the white faces. That
- * means both the WHITE and BLACK regions always have some room to grow into
- * the GREY regions.
- * Could it be that we can't colour some GREY face, because there are too many
- * WHITE/BLACK transitions as we walk round the face? (see the
- * can_colour_face() function for details)
- * No. Imagine otherwise, and we see WHITE/BLACK/WHITE/BLACK as we walk
- * around the face. The two WHITE faces would be connected by a WHITE path,
- * and the BLACK faces would be connected by a BLACK path. These paths would
- * have to cross, which is impossible.
- * Another thing that could go wrong: perhaps we can't find any GREY face to
- * colour WHITE, because it would create a loop-violation or a corner-violation
- * with the other WHITE faces?
- * This is a little bit tricky to prove impossible. Imagine you have such a
- * GREY face (that is, if you coloured it WHITE, you would create a WHITE loop
- * or corner violation).
- * That would cut all the non-white area into two blobs. One of those blobs
- * must be free of BLACK faces (because the BLACK stuff is a connected blob).
- * So we have a connected GREY area, completely surrounded by WHITE
- * (including the GREY face we've tentatively coloured WHITE).
- * A well-known result in graph theory says that you can always find a GREY
- * face whose removal leaves the remaining GREY area connected. And it says
- * there are at least two such faces, so we can always choose the one that
- * isn't the "tentative" GREY face. Colouring that face WHITE leaves
- * everything nice and connected, including that "tentative" GREY face which
- * acts as a gateway to the rest of the non-WHITE grid.
- */
static void add_full_clues(game_state *state, random_state *rs)
{
signed char *clues = state->clues;
- char *board;
grid *g = state->game_grid;
- int i, j;
- int num_faces = g->num_faces;
- struct face_score *face_scores; /* Array of face_score objects */
- struct face_score *fs; /* Points somewhere in the above list */
- struct grid_face *cur_face;
- tree234 *lightable_faces_sorted;
- tree234 *darkable_faces_sorted;
- int *face_list;
- int do_random_pass;
-
- board = snewn(num_faces, char);
-
- /* Make a board */
- memset(board, FACE_GREY, num_faces);
-
- /* Create and initialise the list of face_scores */
- face_scores = snewn(num_faces, struct face_score);
- for (i = 0; i < num_faces; i++) {
- face_scores[i].random = random_bits(rs, 31);
- face_scores[i].black_score = face_scores[i].white_score = 0;
- }
-
- /* Colour a random, finite face white. The infinite face is implicitly
- * coloured black. Together, they will seed the random growth process
- * for the black and white areas. */
- i = random_upto(rs, num_faces);
- board[i] = FACE_WHITE;
-
- /* We need a way of favouring faces that will increase our loopiness.
- * We do this by maintaining a list of all candidate faces sorted by
- * their score and choose randomly from that with appropriate skew.
- * In order to avoid consistently biasing towards particular faces, we
- * need the sort order _within_ each group of scores to be completely
- * random. But it would be abusing the hospitality of the tree234 data
- * structure if our comparison function were nondeterministic :-). So with
- * each face we associate a random number that does not change during a
- * particular run of the generator, and use that as a secondary sort key.
- * Yes, this means we will be biased towards particular random faces in
- * any one run but that doesn't actually matter. */
-
- lightable_faces_sorted = newtree234(white_sort_cmpfn);
- darkable_faces_sorted = newtree234(black_sort_cmpfn);
-
- /* Initialise the lists of lightable and darkable faces. This is
- * slightly different from the code inside the while-loop, because we need
- * to check every face of the board (the grid structure does not keep a
- * list of the infinite face's neighbours). */
- for (i = 0; i < num_faces; i++) {
- grid_face *f = g->faces + i;
- struct face_score *fs = face_scores + i;
- if (board[i] != FACE_GREY) continue;
- /* We need the full colourability check here, it's not enough simply
- * to check neighbourhood. On some grids, a neighbour of the infinite
- * face is not necessarily darkable. */
- if (can_colour_face(g, board, i, FACE_BLACK)) {
- fs->black_score = face_score(g, board, f, FACE_BLACK);
- add234(darkable_faces_sorted, fs);
- }
- if (can_colour_face(g, board, i, FACE_WHITE)) {
- fs->white_score = face_score(g, board, f, FACE_WHITE);
- add234(lightable_faces_sorted, fs);
- }
- }
-
- /* Colour faces one at a time until no more faces are colourable. */
- while (TRUE)
- {
- enum face_colour colour;
- struct face_score *fs_white, *fs_black;
- int c_lightable = count234(lightable_faces_sorted);
- int c_darkable = count234(darkable_faces_sorted);
- if (c_lightable == 0 && c_darkable == 0) {
- /* No more faces we can use at all. */
- break;
- }
- assert(c_lightable != 0 && c_darkable != 0);
-
- fs_white = (struct face_score *)index234(lightable_faces_sorted, 0);
- fs_black = (struct face_score *)index234(darkable_faces_sorted, 0);
-
- /* Choose a colour, and colour the best available face
- * with that colour. */
- colour = random_upto(rs, 2) ? FACE_WHITE : FACE_BLACK;
-
- if (colour == FACE_WHITE)
- fs = fs_white;
- else
- fs = fs_black;
- assert(fs);
- i = fs - face_scores;
- assert(board[i] == FACE_GREY);
- board[i] = colour;
-
- /* Remove this newly-coloured face from the lists. These lists should
- * only contain grey faces. */
- del234(lightable_faces_sorted, fs);
- del234(darkable_faces_sorted, fs);
-
- /* Remember which face we've just coloured */
- cur_face = g->faces + i;
-
- /* The face we've just coloured potentially affects the colourability
- * and the scores of any neighbouring faces (touching at a corner or
- * edge). So the search needs to be conducted around all faces
- * touching the one we've just lit. Iterate over its corners, then
- * over each corner's faces. For each such face, we remove it from
- * the lists, recalculate any scores, then add it back to the lists
- * (depending on whether it is lightable, darkable or both). */
- for (i = 0; i < cur_face->order; i++) {
- grid_dot *d = cur_face->dots[i];
- for (j = 0; j < d->order; j++) {
- grid_face *f = d->faces[j];
- int fi; /* face index of f */
-
- if (f == NULL)
- continue;
- if (f == cur_face)
- continue;
-
- /* If the face is already coloured, it won't be on our
- * lightable/darkable lists anyway, so we can skip it without
- * bothering with the removal step. */
- if (FACE_COLOUR(f) != FACE_GREY) continue;
-
- /* Find the face index and face_score* corresponding to f */
- fi = f - g->faces;
- fs = face_scores + fi;
-
- /* Remove from lightable list if it's in there. We do this,
- * even if it is still lightable, because the score might
- * be different, and we need to remove-then-add to maintain
- * correct sort order. */
- del234(lightable_faces_sorted, fs);
- if (can_colour_face(g, board, fi, FACE_WHITE)) {
- fs->white_score = face_score(g, board, f, FACE_WHITE);
- add234(lightable_faces_sorted, fs);
- }
- /* Do the same for darkable list. */
- del234(darkable_faces_sorted, fs);
- if (can_colour_face(g, board, fi, FACE_BLACK)) {
- fs->black_score = face_score(g, board, f, FACE_BLACK);
- add234(darkable_faces_sorted, fs);
- }
- }
- }
- }
-
- /* Clean up */
- freetree234(lightable_faces_sorted);
- freetree234(darkable_faces_sorted);
- sfree(face_scores);
-
- /* The next step requires a shuffled list of all faces */
- face_list = snewn(num_faces, int);
- for (i = 0; i < num_faces; ++i) {
- face_list[i] = i;
- }
- shuffle(face_list, num_faces, sizeof(int), rs);
-
- /* The above loop-generation algorithm can often leave large clumps
- * of faces of one colour. In extreme cases, the resulting path can be
- * degenerate and not very satisfying to solve.
- * This next step alleviates this problem:
- * Go through the shuffled list, and flip the colour of any face we can
- * legally flip, and which is adjacent to only one face of the opposite
- * colour - this tends to grow 'tendrils' into any clumps.
- * Repeat until we can find no more faces to flip. This will
- * eventually terminate, because each flip increases the loop's
- * perimeter, which cannot increase for ever.
- * The resulting path will have maximal loopiness (in the sense that it
- * cannot be improved "locally". Unfortunately, this allows a player to
- * make some illicit deductions. To combat this (and make the path more
- * interesting), we do one final pass making random flips. */
-
- /* Set to TRUE for final pass */
- do_random_pass = FALSE;
-
- while (TRUE) {
- /* Remember whether a flip occurred during this pass */
- int flipped = FALSE;
-
- for (i = 0; i < num_faces; ++i) {
- int j = face_list[i];
- enum face_colour opp =
- (board[j] == FACE_WHITE) ? FACE_BLACK : FACE_WHITE;
- if (can_colour_face(g, board, j, opp)) {
- grid_face *face = g->faces +j;
- if (do_random_pass) {
- /* final random pass */
- if (!random_upto(rs, 10))
- board[j] = opp;
- } else {
- /* normal pass - flip when neighbour count is 1 */
- if (face_num_neighbours(g, board, face, opp) == 1) {
- board[j] = opp;
- flipped = TRUE;
- }
- }
- }
- }
-
- if (do_random_pass) break;
- if (!flipped) do_random_pass = TRUE;
- }
+ char *board = snewn(g->num_faces, char);
+ int i;
- sfree(face_list);
+ generate_loop(g, board, rs, NULL, NULL);
/* Fill out all the clues by initialising to 0, then iterating over
* all edges and incrementing each clue as we find edges that border
* between BLACK/WHITE faces. While we're at it, we verify that the
* algorithm does work, and there aren't any GREY faces still there. */
- memset(clues, 0, num_faces);
+ memset(clues, 0, g->num_faces);
for (i = 0; i < g->num_edges; i++) {
grid_edge *e = g->edges + i;
grid_face *f1 = e->face1;
if (f2) clues[f2 - g->faces]++;
}
}
-
sfree(board);
}
}
-static char *new_game_desc(game_params *params, random_state *rs,
+static char *new_game_desc(const game_params *params, random_state *rs,
char **aux, int interactive)
{
/* solution and description both use run-length encoding in obvious ways */
- char *retval;
+ char *retval, *game_desc, *grid_desc;
grid *g;
game_state *state = snew(game_state);
game_state *state_new;
- int count = 0;
- params_generate_grid(params);
- state->game_grid = g = params->game_grid;
- g->refcount++;
+
+ grid_desc = grid_new_desc(grid_types[params->type], params->w, params->h, rs);
+ state->game_grid = g = loopy_generate_grid(params, grid_desc);
+
state->clues = snewn(g->num_faces, signed char);
state->lines = snewn(g->num_edges, char);
state->line_errors = snewn(g->num_edges, unsigned char);
+ state->exactly_one_loop = FALSE;
state->grid_type = params->type;
* preventing games smaller than 4x4 seems to stop this happening */
do {
add_full_clues(state, rs);
- if (++count%100 == 0) printf("tried %d times to make a unique board\n", count);
} while (!game_has_unique_soln(state, params->diff));
state_new = remove_clues(state, rs, params->diff);
goto newboard_please;
}
- retval = state_to_text(state);
+ game_desc = state_to_text(state);
free_game(state);
+ if (grid_desc) {
+ retval = snewn(strlen(grid_desc) + 1 + strlen(game_desc) + 1, char);
+ sprintf(retval, "%s%c%s", grid_desc, (int)GRID_DESC_SEP, game_desc);
+ sfree(grid_desc);
+ sfree(game_desc);
+ } else {
+ retval = game_desc;
+ }
+
assert(!validate_desc(params, retval));
return retval;
}
-static game_state *new_game(midend *me, game_params *params, char *desc)
+static game_state *new_game(midend *me, const game_params *params,
+ const char *desc)
{
int i;
game_state *state = snew(game_state);
int empties_to_make = 0;
int n,n2;
- const char *dp = desc;
+ const char *dp;
+ char *grid_desc;
grid *g;
int num_faces, num_edges;
- params_generate_grid(params);
- state->game_grid = g = params->game_grid;
- g->refcount++;
+ grid_desc = extract_grid_desc(&desc);
+ state->game_grid = g = loopy_generate_grid(params, grid_desc);
+ if (grid_desc) sfree(grid_desc);
+
+ dp = desc;
+
num_faces = g->num_faces;
num_edges = g->num_edges;
state->clues = snewn(num_faces, signed char);
state->lines = snewn(num_edges, char);
state->line_errors = snewn(num_edges, unsigned char);
+ state->exactly_one_loop = FALSE;
state->solved = state->cheated = FALSE;
static int check_completion(game_state *state)
{
grid *g = state->game_grid;
- int *dsf;
- int num_faces = g->num_faces;
- int i;
- int infinite_area, finite_area;
- int loops_found = 0;
- int found_edge_not_in_loop = FALSE;
+ int i, ret;
+ int *dsf, *component_state;
+ int nsilly, nloop, npath, largest_comp, largest_size, total_pathsize;
+ enum { COMP_NONE, COMP_LOOP, COMP_PATH, COMP_SILLY, COMP_EMPTY };
memset(state->line_errors, 0, g->num_edges);
- /* LL implementation of SGT's idea:
- * A loop will partition the grid into an inside and an outside.
- * If there is more than one loop, the grid will be partitioned into
- * even more distinct regions. We can therefore track equivalence of
- * faces, by saying that two faces are equivalent when there is a non-YES
- * edge between them.
- * We could keep track of the number of connected components, by counting
- * the number of dsf-merges that aren't no-ops.
- * But we're only interested in 3 separate cases:
- * no loops, one loop, more than one loop.
+ /*
+ * Find loops in the grid, and determine whether the puzzle is
+ * solved.
+ *
+ * Loopy is a bit more complicated than most puzzles that care
+ * about loop detection. In most of them, loops are simply
+ * _forbidden_; so the obviously right way to do
+ * error-highlighting during play is to light up a graph edge red
+ * iff it is part of a loop, which is exactly what the centralised
+ * findloop.c makes easy.
*
- * No loops: all faces are equivalent to the infinite face.
- * One loop: only two equivalence classes - finite and infinite.
- * >= 2 loops: there are 2 distinct finite regions.
+ * But Loopy is unusual in that you're _supposed_ to be making a
+ * loop - and yet _some_ loops are not the right loop. So we need
+ * to be more discriminating, by identifying loops one by one and
+ * then thinking about which ones to highlight, and so findloop.c
+ * isn't quite the right tool for the job in this case.
*
- * So we simply make two passes through all the edges.
- * In the first pass, we dsf-merge the two faces bordering each non-YES
- * edge.
- * In the second pass, we look for YES-edges bordering:
- * a) two non-equivalent faces.
- * b) two non-equivalent faces, and one of them is part of a different
- * finite area from the first finite area we've seen.
+ * Worse still, consider situations in which the grid contains a
+ * loop and also some non-loop edges: there are some cases like
+ * this in which the user's intuitive expectation would be to
+ * highlight the loop (if you're only about half way through the
+ * puzzle and have accidentally made a little loop in some corner
+ * of the grid), and others in which they'd be more likely to
+ * expect you to highlight the non-loop edges (if you've just
+ * closed off a whole loop that you thought was the entire
+ * solution, but forgot some disconnected edges in a corner
+ * somewhere). So while it's easy enough to check whether the
+ * solution is _right_, highlighting the wrong parts is a tricky
+ * problem for this puzzle!
*
- * An occurrence of a) means there is at least one loop.
- * An occurrence of b) means there is more than one loop.
- * Edges satisfying a) are marked as errors.
+ * I'd quite like, in some situations, to identify the largest
+ * loop among the player's YES edges, and then light up everything
+ * other than that. But finding the longest cycle in a graph is an
+ * NP-complete problem (because, in particular, it must return a
+ * Hamilton cycle if one exists).
*
- * While we're at it, we set a flag if we find a YES edge that is not
- * part of a loop.
- * This information will help decide, if there's a single loop, whether it
- * is a candidate for being a solution (that is, all YES edges are part of
- * this loop).
+ * However, I think we can make the problem tractable by
+ * exercising the Puzzles principle that it isn't absolutely
+ * necessary to highlight _all_ errors: the key point is that by
+ * the time the user has filled in the whole grid, they should
+ * either have seen a completion flash, or have _some_ error
+ * highlight showing them why the solution isn't right. So in
+ * principle it would be *just about* good enough to highlight
+ * just one error in the whole grid, if there was really no better
+ * way. But we'd like to highlight as many errors as possible.
*
- * If there is a candidate loop, we then go through all clues and check
- * they are all satisfied. If so, we have found a solution and we can
- * unmark all line_errors.
+ * In this case, I think the simple approach is to make use of the
+ * fact that no vertex may have degree > 2, and that's really
+ * simple to detect. So the plan goes like this:
+ *
+ * - Form the dsf of connected components of the graph vertices.
+ *
+ * - Highlight an error at any vertex with degree > 2. (It so
+ * happens that we do this by lighting up all the edges
+ * incident to that vertex, but that's an output detail.)
+ *
+ * - Any component that contains such a vertex is now excluded
+ * from further consideration, because it already has a
+ * highlight.
+ *
+ * - The remaining components have no vertex with degree > 2, and
+ * hence they all consist of either a simple loop, or a simple
+ * path with two endpoints.
+ *
+ * - For these purposes, group together all the paths and imagine
+ * them to be a single component (because in most normal
+ * situations the player will gradually build up the solution
+ * _not_ all in one connected segment, but as lots of separate
+ * little path pieces that gradually connect to each other).
+ *
+ * - After doing that, if there is exactly one (sensible)
+ * component - be it a collection of paths or a loop - then
+ * highlight no further edge errors. (The former case is normal
+ * during play, and the latter is a potentially solved puzzle.)
+ *
+ * - Otherwise, find the largest of the sensible components,
+ * leave that one unhighlighted, and light the rest up in red.
*/
-
- /* Infinite face is at the end - its index is num_faces.
- * This macro is just to make this obvious! */
- #define INF_FACE num_faces
- dsf = snewn(num_faces + 1, int);
- dsf_init(dsf, num_faces + 1);
-
- /* First pass */
- for (i = 0; i < g->num_edges; i++) {
- grid_edge *e = g->edges + i;
- int f1 = e->face1 ? e->face1 - g->faces : INF_FACE;
- int f2 = e->face2 ? e->face2 - g->faces : INF_FACE;
- if (state->lines[i] != LINE_YES)
- dsf_merge(dsf, f1, f2);
- }
-
- /* Second pass */
- infinite_area = dsf_canonify(dsf, INF_FACE);
- finite_area = -1;
- for (i = 0; i < g->num_edges; i++) {
- grid_edge *e = g->edges + i;
- int f1 = e->face1 ? e->face1 - g->faces : INF_FACE;
- int can1 = dsf_canonify(dsf, f1);
- int f2 = e->face2 ? e->face2 - g->faces : INF_FACE;
- int can2 = dsf_canonify(dsf, f2);
- if (state->lines[i] != LINE_YES) continue;
-
- if (can1 == can2) {
- /* Faces are equivalent, so this edge not part of a loop */
- found_edge_not_in_loop = TRUE;
- continue;
- }
- state->line_errors[i] = TRUE;
- if (loops_found == 0) loops_found = 1;
- /* Don't bother with further checks if we've already found 2 loops */
- if (loops_found == 2) continue;
+ dsf = snew_dsf(g->num_dots);
- if (finite_area == -1) {
- /* Found our first finite area */
- if (can1 != infinite_area)
- finite_area = can1;
- else
- finite_area = can2;
- }
-
- /* Have we found a second area? */
- if (finite_area != -1) {
- if (can1 != infinite_area && can1 != finite_area) {
- loops_found = 2;
- continue;
- }
- if (can2 != infinite_area && can2 != finite_area) {
- loops_found = 2;
- }
+ /* Build the dsf. */
+ for (i = 0; i < g->num_edges; i++) {
+ if (state->lines[i] == LINE_YES) {
+ grid_edge *e = g->edges + i;
+ int d1 = e->dot1 - g->dots, d2 = e->dot2 - g->dots;
+ dsf_merge(dsf, d1, d2);
}
}
-/*
- printf("loops_found = %d\n", loops_found);
- printf("found_edge_not_in_loop = %s\n",
- found_edge_not_in_loop ? "TRUE" : "FALSE");
-*/
-
- sfree(dsf); /* No longer need the dsf */
-
- /* Have we found a candidate loop? */
- if (loops_found == 1 && !found_edge_not_in_loop) {
- /* Yes, so check all clues are satisfied */
- int found_clue_violation = FALSE;
- for (i = 0; i < num_faces; i++) {
- int c = state->clues[i];
- if (c >= 0) {
- if (face_order(state, i, LINE_YES) != c) {
- found_clue_violation = TRUE;
- break;
- }
- }
- }
-
- if (!found_clue_violation) {
- /* The loop is good */
- memset(state->line_errors, 0, g->num_edges);
- return TRUE; /* No need to bother checking for dot violations */
- }
+ /* Initialise a state variable for each connected component. */
+ component_state = snewn(g->num_dots, int);
+ for (i = 0; i < g->num_dots; i++) {
+ if (dsf_canonify(dsf, i) == i)
+ component_state[i] = COMP_LOOP;
+ else
+ component_state[i] = COMP_NONE;
}
- /* Check for dot violations */
+ /* Check for dots with degree > 3. Here we also spot dots of
+ * degree 1 in which the user has marked all the non-edges as
+ * LINE_NO, because those are also clear vertex-level errors, so
+ * we give them the same treatment of excluding their connected
+ * component from the subsequent loop analysis. */
for (i = 0; i < g->num_dots; i++) {
+ int comp = dsf_canonify(dsf, i);
int yes = dot_order(state, i, LINE_YES);
int unknown = dot_order(state, i, LINE_UNKNOWN);
if ((yes == 1 && unknown == 0) || (yes >= 3)) {
if (state->lines[e] == LINE_YES)
state->line_errors[e] = TRUE;
}
+ /* And mark this component as not worthy of further
+ * consideration. */
+ component_state[comp] = COMP_SILLY;
+
+ } else if (yes == 0) {
+ /* A completely isolated dot must also be excluded it from
+ * the subsequent loop highlighting pass, but we tag it
+ * with a different enum value to avoid it counting
+ * towards the components that inhibit returning a win
+ * status. */
+ component_state[comp] = COMP_EMPTY;
+ } else if (yes == 1) {
+ /* A dot with degree 1 that didn't fall into the 'clearly
+ * erroneous' case above indicates that this connected
+ * component will be a path rather than a loop - unless
+ * something worse elsewhere in the component has
+ * classified it as silly. */
+ if (component_state[comp] != COMP_SILLY)
+ component_state[comp] = COMP_PATH;
}
}
- return FALSE;
+
+ /* Count up the components. Also, find the largest sensible
+ * component. (Tie-breaking condition is derived from the order of
+ * vertices in the grid data structure, which is fairly arbitrary
+ * but at least stays stable throughout the game.) */
+ nsilly = nloop = npath = 0;
+ total_pathsize = 0;
+ largest_comp = largest_size = -1;
+ for (i = 0; i < g->num_dots; i++) {
+ if (component_state[i] == COMP_SILLY) {
+ nsilly++;
+ } else if (component_state[i] == COMP_PATH) {
+ total_pathsize += dsf_size(dsf, i);
+ npath = 1;
+ } else if (component_state[i] == COMP_LOOP) {
+ int this_size;
+
+ nloop++;
+
+ if ((this_size = dsf_size(dsf, i)) > largest_size) {
+ largest_comp = i;
+ largest_size = this_size;
+ }
+ }
+ }
+ if (largest_size < total_pathsize) {
+ largest_comp = -1; /* means the paths */
+ largest_size = total_pathsize;
+ }
+
+ if (nloop > 0 && nloop + npath > 1) {
+ /*
+ * If there are at least two sensible components including at
+ * least one loop, highlight all edges in every sensible
+ * component that is not the largest one.
+ */
+ for (i = 0; i < g->num_edges; i++) {
+ if (state->lines[i] == LINE_YES) {
+ grid_edge *e = g->edges + i;
+ int d1 = e->dot1 - g->dots; /* either endpoint is good enough */
+ int comp = dsf_canonify(dsf, d1);
+ if ((component_state[comp] == COMP_PATH &&
+ -1 != largest_comp) ||
+ (component_state[comp] == COMP_LOOP &&
+ comp != largest_comp))
+ state->line_errors[i] = TRUE;
+ }
+ }
+ }
+
+ if (nloop == 1 && npath == 0 && nsilly == 0) {
+ /*
+ * If there is exactly one component and it is a loop, then
+ * the puzzle is potentially complete, so check the clues.
+ */
+ ret = TRUE;
+
+ for (i = 0; i < g->num_faces; i++) {
+ int c = state->clues[i];
+ if (c >= 0 && face_order(state, i, LINE_YES) != c) {
+ ret = FALSE;
+ break;
+ }
+ }
+
+ /*
+ * Also, whether or not the puzzle is actually complete, set
+ * the flag that says this game_state has exactly one loop and
+ * nothing else, which will be used to vary the semantics of
+ * clue highlighting at display time.
+ */
+ state->exactly_one_loop = TRUE;
+ } else {
+ ret = FALSE;
+ state->exactly_one_loop = FALSE;
+ }
+
+ sfree(component_state);
+ sfree(dsf);
+
+ return ret;
}
/* ----------------------------------------------------------------------
if (state->clues[i] < 0)
continue;
+ /*
+ * This code checks whether the numeric clue on a face is so
+ * large as to permit all its remaining LINE_UNKNOWNs to be
+ * filled in as LINE_YES, or alternatively so small as to
+ * permit them all to be filled in as LINE_NO.
+ */
+
if (state->clues[i] < current_yes) {
sstate->solver_status = SOLVER_MISTAKE;
return DIFF_EASY;
sstate->face_solved[i] = TRUE;
continue;
}
+
+ if (f->order - state->clues[i] == current_no + 1 &&
+ f->order - current_yes - current_no > 2) {
+ /*
+ * One small refinement to the above: we also look for any
+ * adjacent pair of LINE_UNKNOWNs around the face with
+ * some LINE_YES incident on it from elsewhere. If we find
+ * one, then we know that pair of LINE_UNKNOWNs can't
+ * _both_ be LINE_YES, and hence that pushes us one line
+ * closer to being able to determine all the rest.
+ */
+ int j, k, e1, e2, e, d;
+
+ for (j = 0; j < f->order; j++) {
+ e1 = f->edges[j] - g->edges;
+ e2 = f->edges[j+1 < f->order ? j+1 : 0] - g->edges;
+
+ if (g->edges[e1].dot1 == g->edges[e2].dot1 ||
+ g->edges[e1].dot1 == g->edges[e2].dot2) {
+ d = g->edges[e1].dot1 - g->dots;
+ } else {
+ assert(g->edges[e1].dot2 == g->edges[e2].dot1 ||
+ g->edges[e1].dot2 == g->edges[e2].dot2);
+ d = g->edges[e1].dot2 - g->dots;
+ }
+
+ if (state->lines[e1] == LINE_UNKNOWN &&
+ state->lines[e2] == LINE_UNKNOWN) {
+ for (k = 0; k < g->dots[d].order; k++) {
+ int e = g->dots[d].edges[k] - g->edges;
+ if (state->lines[e] == LINE_YES)
+ goto found; /* multi-level break */
+ }
+ }
+ }
+ continue;
+
+ found:
+ /*
+ * If we get here, we've found such a pair of edges, and
+ * they're e1 and e2.
+ */
+ for (j = 0; j < f->order; j++) {
+ e = f->edges[j] - g->edges;
+ if (state->lines[e] == LINE_UNKNOWN && e != e1 && e != e2) {
+ int r = solver_set_line(sstate, e, LINE_YES);
+ assert(r);
+ diff = min(diff, DIFF_EASY);
+ }
+ }
+ }
}
check_caches(sstate);
return sstate;
}
-static char *solve_game(game_state *state, game_state *currstate,
- char *aux, char **error)
+static char *solve_game(const game_state *state, const game_state *currstate,
+ const char *aux, char **error)
{
char *soln = NULL;
solver_state *sstate, *new_sstate;
* Drawing and mouse-handling
*/
-static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds,
+static char *interpret_move(const game_state *state, game_ui *ui,
+ const game_drawstate *ds,
int x, int y, int button)
{
grid *g = state->game_grid;
grid_edge *e;
int i;
- char *ret, buf[80];
+ char *movebuf;
+ int movelen, movesize;
char button_char = ' ';
enum line_state old_state;
return NULL;
}
+ movelen = 0;
+ movesize = 80;
+ movebuf = snewn(movesize, char);
+ movelen = sprintf(movebuf, "%d%c", i, (int)button_char);
+ {
+ static enum { OFF, FIXED, ADAPTIVE, DUNNO } autofollow = DUNNO;
+ if (autofollow == DUNNO) {
+ const char *env = getenv("LOOPY_AUTOFOLLOW");
+ if (env && !strcmp(env, "off"))
+ autofollow = OFF;
+ else if (env && !strcmp(env, "fixed"))
+ autofollow = FIXED;
+ else if (env && !strcmp(env, "adaptive"))
+ autofollow = ADAPTIVE;
+ else
+ autofollow = OFF;
+ }
- sprintf(buf, "%d%c", i, (int)button_char);
- ret = dupstr(buf);
+ if (autofollow != OFF) {
+ int dotid;
+ for (dotid = 0; dotid < 2; dotid++) {
+ grid_dot *dot = (dotid == 0 ? e->dot1 : e->dot2);
+ grid_edge *e_this = e;
+
+ while (1) {
+ int j, n_found;
+ grid_edge *e_next = NULL;
+
+ for (j = n_found = 0; j < dot->order; j++) {
+ grid_edge *e_candidate = dot->edges[j];
+ int i_candidate = e_candidate - g->edges;
+ if (e_candidate != e_this &&
+ (autofollow == FIXED ||
+ state->lines[i] == LINE_NO ||
+ state->lines[i_candidate] != LINE_NO)) {
+ e_next = e_candidate;
+ n_found++;
+ }
+ }
- return ret;
+ if (n_found != 1 ||
+ state->lines[e_next - g->edges] != state->lines[i])
+ break;
+
+ if (e_next == e) {
+ /*
+ * Special case: we might have come all the
+ * way round a loop and found our way back to
+ * the same edge we started from. In that
+ * situation, we must terminate not only this
+ * while loop, but the 'for' outside it that
+ * was tracing in both directions from the
+ * starting edge, because if we let it trace
+ * in the second direction then we'll only
+ * find ourself traversing the same loop in
+ * the other order and generate an encoded
+ * move string that mentions the same set of
+ * edges twice.
+ */
+ goto autofollow_done;
+ }
+
+ dot = (e_next->dot1 != dot ? e_next->dot1 : e_next->dot2);
+ if (movelen > movesize - 40) {
+ movesize = movesize * 5 / 4 + 128;
+ movebuf = sresize(movebuf, movesize, char);
+ }
+ e_this = e_next;
+ movelen += sprintf(movebuf+movelen, "%d%c",
+ (int)(e_this - g->edges), button_char);
+ }
+ }
+ autofollow_done:;
+ }
+ }
+
+ return sresize(movebuf, movelen+1, char);
}
-static game_state *execute_move(game_state *state, char *move)
+static game_state *execute_move(const game_state *state, const char *move)
{
int i;
game_state *newstate = dup_game(state);
/* Returns (into x,y) position of centre of face for rendering the text clue.
*/
static void face_text_pos(const game_drawstate *ds, const grid *g,
- const grid_face *f, int *x, int *y)
+ grid_face *f, int *xret, int *yret)
{
- int i;
+ int faceindex = f - g->faces;
- /* Simplest solution is the centroid. Might not work in some cases. */
+ /*
+ * Return the cached position for this face, if we've already
+ * worked it out.
+ */
+ if (ds->textx[faceindex] >= 0) {
+ *xret = ds->textx[faceindex];
+ *yret = ds->texty[faceindex];
+ return;
+ }
- /* Another algorithm to look into:
- * Find the midpoints of the sides, find the bounding-box,
- * then take the centre of that. */
+ /*
+ * Otherwise, use the incentre computed by grid.c and convert it
+ * to screen coordinates.
+ */
+ grid_find_incentre(f);
+ grid_to_screen(ds, g, f->ix, f->iy,
+ &ds->textx[faceindex], &ds->texty[faceindex]);
- /* Best solution probably involves incentres (inscribed circles) */
+ *xret = ds->textx[faceindex];
+ *yret = ds->texty[faceindex];
+}
- int sx = 0, sy = 0; /* sums */
- for (i = 0; i < f->order; i++) {
- grid_dot *d = f->dots[i];
- sx += d->x;
- sy += d->y;
- }
- sx /= f->order;
- sy /= f->order;
+static void face_text_bbox(game_drawstate *ds, grid *g, grid_face *f,
+ int *x, int *y, int *w, int *h)
+{
+ int xx, yy;
+ face_text_pos(ds, g, f, &xx, &yy);
+
+ /* There seems to be a certain amount of trial-and-error involved
+ * in working out the correct bounding-box for the text. */
- /* convert to screen coordinates */
- grid_to_screen(ds, g, sx, sy, x, y);
+ *x = xx - ds->tilesize/4 - 1;
+ *y = yy - ds->tilesize/4 - 3;
+ *w = ds->tilesize/2 + 2;
+ *h = ds->tilesize/2 + 5;
}
static void game_redraw_clue(drawing *dr, game_drawstate *ds,
- game_state *state, int i)
+ const game_state *state, int i)
{
grid *g = state->game_grid;
grid_face *f = g->faces + i;
int x, y;
- char c[3];
+ char c[20];
- if (state->clues[i] < 10) {
- c[0] = CLUE2CHAR(state->clues[i]);
- c[1] = '\0';
- } else {
- sprintf(c, "%d", state->clues[i]);
- }
+ sprintf(c, "%d", state->clues[i]);
face_text_pos(ds, g, f, &x, &y);
draw_text(dr, x, y,
ds->clue_satisfied[i] ? COL_SATISFIED : COL_FOREGROUND, c);
}
+static void edge_bbox(game_drawstate *ds, grid *g, grid_edge *e,
+ int *x, int *y, int *w, int *h)
+{
+ int x1 = e->dot1->x;
+ int y1 = e->dot1->y;
+ int x2 = e->dot2->x;
+ int y2 = e->dot2->y;
+ int xmin, xmax, ymin, ymax;
+
+ grid_to_screen(ds, g, x1, y1, &x1, &y1);
+ grid_to_screen(ds, g, x2, y2, &x2, &y2);
+ /* Allow extra margin for dots, and thickness of lines */
+ xmin = min(x1, x2) - 2;
+ xmax = max(x1, x2) + 2;
+ ymin = min(y1, y2) - 2;
+ ymax = max(y1, y2) + 2;
+
+ *x = xmin;
+ *y = ymin;
+ *w = xmax - xmin + 1;
+ *h = ymax - ymin + 1;
+}
+
+static void dot_bbox(game_drawstate *ds, grid *g, grid_dot *d,
+ int *x, int *y, int *w, int *h)
+{
+ int x1, y1;
+
+ grid_to_screen(ds, g, d->x, d->y, &x1, &y1);
+
+ *x = x1 - 2;
+ *y = y1 - 2;
+ *w = 5;
+ *h = 5;
+}
+
static const int loopy_line_redraw_phases[] = {
COL_FAINT, COL_LINEUNKNOWN, COL_FOREGROUND, COL_HIGHLIGHT, COL_MISTAKE
};
#define NPHASES lenof(loopy_line_redraw_phases)
static void game_redraw_line(drawing *dr, game_drawstate *ds,
- game_state *state, int i, int phase)
+ const game_state *state, int i, int phase)
{
grid *g = state->game_grid;
grid_edge *e = g->edges + i;
int x1, x2, y1, y2;
- int xmin, ymin, xmax, ymax;
int line_colour;
if (state->line_errors[i])
grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1);
grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2);
- xmin = min(x1, x2);
- xmax = max(x1, x2);
- ymin = min(y1, y2);
- ymax = max(y1, y2);
-
if (line_colour == COL_FAINT) {
static int draw_faint_lines = -1;
if (draw_faint_lines < 0) {
}
static void game_redraw_dot(drawing *dr, game_drawstate *ds,
- game_state *state, int i)
+ const game_state *state, int i)
{
grid *g = state->game_grid;
grid_dot *d = g->dots + i;
draw_circle(dr, x, y, 2, COL_FOREGROUND, COL_FOREGROUND);
}
-static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
- game_state *state, int dir, game_ui *ui,
+static int boxes_intersect(int x0, int y0, int w0, int h0,
+ int x1, int y1, int w1, int h1)
+{
+ /*
+ * Two intervals intersect iff neither is wholly on one side of
+ * the other. Two boxes intersect iff their horizontal and
+ * vertical intervals both intersect.
+ */
+ return (x0 < x1+w1 && x1 < x0+w0 && y0 < y1+h1 && y1 < y0+h0);
+}
+
+static void game_redraw_in_rect(drawing *dr, game_drawstate *ds,
+ const game_state *state,
+ int x, int y, int w, int h)
+{
+ grid *g = state->game_grid;
+ int i, phase;
+ int bx, by, bw, bh;
+
+ clip(dr, x, y, w, h);
+ draw_rect(dr, x, y, w, h, COL_BACKGROUND);
+
+ for (i = 0; i < g->num_faces; i++) {
+ if (state->clues[i] >= 0) {
+ face_text_bbox(ds, g, &g->faces[i], &bx, &by, &bw, &bh);
+ if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+ game_redraw_clue(dr, ds, state, i);
+ }
+ }
+ for (phase = 0; phase < NPHASES; phase++) {
+ for (i = 0; i < g->num_edges; i++) {
+ edge_bbox(ds, g, &g->edges[i], &bx, &by, &bw, &bh);
+ if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+ game_redraw_line(dr, ds, state, i, phase);
+ }
+ }
+ for (i = 0; i < g->num_dots; i++) {
+ dot_bbox(ds, g, &g->dots[i], &bx, &by, &bw, &bh);
+ if (boxes_intersect(x, y, w, h, bx, by, bw, bh))
+ game_redraw_dot(dr, ds, state, i);
+ }
+
+ unclip(dr);
+ draw_update(dr, x, y, w, h);
+}
+
+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)
{
#define REDRAW_OBJECTS_LIMIT 16 /* Somewhat arbitrary tradeoff */
grid *g = state->game_grid;
int border = BORDER(ds->tilesize);
- int i, phase;
+ int i;
int flash_changed;
int redraw_everything = FALSE;
* what needs doing, and the second actually does it.
*/
- if (!ds->started)
+ if (!ds->started) {
redraw_everything = TRUE;
- else {
-
- /* First, trundle through the faces. */
- for (i = 0; i < g->num_faces; i++) {
- grid_face *f = g->faces + i;
- int sides = f->order;
- int clue_mistake;
- int clue_satisfied;
- int n = state->clues[i];
- if (n < 0)
- continue;
-
- clue_mistake = (face_order(state, i, LINE_YES) > n ||
- face_order(state, i, LINE_NO ) > (sides-n));
- clue_satisfied = (face_order(state, i, LINE_YES) == n &&
- face_order(state, i, LINE_NO ) == (sides-n));
-
- if (clue_mistake != ds->clue_error[i] ||
- clue_satisfied != ds->clue_satisfied[i]) {
- ds->clue_error[i] = clue_mistake;
- ds->clue_satisfied[i] = clue_satisfied;
- if (nfaces == REDRAW_OBJECTS_LIMIT)
- redraw_everything = TRUE;
- else
- faces[nfaces++] = i;
- }
- }
+ /*
+ * But we must still go through the upcoming loops, so that we
+ * set up stuff in ds correctly for the initial redraw.
+ */
+ }
- /* Work out what the flash state needs to be. */
- if (flashtime > 0 &&
- (flashtime <= FLASH_TIME/3 ||
- flashtime >= FLASH_TIME*2/3)) {
- flash_changed = !ds->flashing;
- ds->flashing = TRUE;
- } else {
- flash_changed = ds->flashing;
- ds->flashing = FALSE;
- }
+ /* First, trundle through the faces. */
+ for (i = 0; i < g->num_faces; i++) {
+ grid_face *f = g->faces + i;
+ int sides = f->order;
+ int yes_order, no_order;
+ int clue_mistake;
+ int clue_satisfied;
+ int n = state->clues[i];
+ if (n < 0)
+ continue;
- /* Now, trundle through the edges. */
- for (i = 0; i < g->num_edges; i++) {
- char new_ds =
- state->line_errors[i] ? DS_LINE_ERROR : state->lines[i];
- if (new_ds != ds->lines[i] ||
- (flash_changed && state->lines[i] == LINE_YES)) {
- ds->lines[i] = new_ds;
- if (nedges == REDRAW_OBJECTS_LIMIT)
- redraw_everything = TRUE;
- else
- edges[nedges++] = i;
- }
- }
+ yes_order = face_order(state, i, LINE_YES);
+ if (state->exactly_one_loop) {
+ /*
+ * Special case: if the set of LINE_YES edges in the grid
+ * consists of exactly one loop and nothing else, then we
+ * switch to treating LINE_UNKNOWN the same as LINE_NO for
+ * purposes of clue checking.
+ *
+ * This is because some people like to play Loopy without
+ * using the right-click, i.e. never setting anything to
+ * LINE_NO. Without this special case, if a person playing
+ * in that style fills in what they think is a correct
+ * solution loop but in fact it has an underfilled clue,
+ * then we will display no victory flash and also no error
+ * highlight explaining why not. With this special case,
+ * we light up underfilled clues at the instant the loop
+ * is closed. (Of course, *overfilled* clues are fine
+ * either way.)
+ *
+ * (It might still be considered unfortunate that we can't
+ * warn this style of player any earlier, if they make a
+ * mistake very near the beginning which doesn't show up
+ * until they close the last edge of the loop. One other
+ * thing we _could_ do here is to treat any LINE_UNKNOWN
+ * as LINE_NO if either of its endpoints has yes-degree 2,
+ * reflecting the fact that setting that line to YES would
+ * be an obvious error. But I don't think even that could
+ * catch _all_ clue errors in a timely manner; I think
+ * there are some that won't be displayed until the loop
+ * is filled in, even so, and there's no way to avoid that
+ * with complete reliability except to switch to being a
+ * player who sets things to LINE_NO.)
+ */
+ no_order = sides - yes_order;
+ } else {
+ no_order = face_order(state, i, LINE_NO);
+ }
+
+ clue_mistake = (yes_order > n || no_order > (sides-n));
+ clue_satisfied = (yes_order == n && no_order == (sides-n));
+
+ if (clue_mistake != ds->clue_error[i] ||
+ clue_satisfied != ds->clue_satisfied[i]) {
+ ds->clue_error[i] = clue_mistake;
+ ds->clue_satisfied[i] = clue_satisfied;
+ if (nfaces == REDRAW_OBJECTS_LIMIT)
+ redraw_everything = TRUE;
+ else
+ faces[nfaces++] = i;
+ }
}
- /* Pass one is now done. Now we do the actual drawing. */
- if (redraw_everything) {
+ /* Work out what the flash state needs to be. */
+ if (flashtime > 0 &&
+ (flashtime <= FLASH_TIME/3 ||
+ flashtime >= FLASH_TIME*2/3)) {
+ flash_changed = !ds->flashing;
+ ds->flashing = TRUE;
+ } else {
+ flash_changed = ds->flashing;
+ ds->flashing = FALSE;
+ }
- /* This is the unsubtle version. */
+ /* Now, trundle through the edges. */
+ for (i = 0; i < g->num_edges; i++) {
+ char new_ds =
+ state->line_errors[i] ? DS_LINE_ERROR : state->lines[i];
+ if (new_ds != ds->lines[i] ||
+ (flash_changed && state->lines[i] == LINE_YES)) {
+ ds->lines[i] = new_ds;
+ if (nedges == REDRAW_OBJECTS_LIMIT)
+ redraw_everything = TRUE;
+ else
+ edges[nedges++] = i;
+ }
+ }
+ /* Pass one is now done. Now we do the actual drawing. */
+ if (redraw_everything) {
int grid_width = g->highest_x - g->lowest_x;
int grid_height = g->highest_y - g->lowest_y;
int w = grid_width * ds->tilesize / g->tilesize;
int h = grid_height * ds->tilesize / g->tilesize;
- draw_rect(dr, 0, 0, w + 2*border + 1, h + 2*border + 1,
- COL_BACKGROUND);
-
- for (i = 0; i < g->num_faces; i++)
- game_redraw_clue(dr, ds, state, i);
- for (phase = 0; phase < NPHASES; phase++)
- for (i = 0; i < g->num_edges; i++)
- game_redraw_line(dr, ds, state, i, phase);
- for (i = 0; i < g->num_dots; i++)
- game_redraw_dot(dr, ds, state, i);
-
- draw_update(dr, 0, 0, w + 2*border + 1, h + 2*border + 1);
+ game_redraw_in_rect(dr, ds, state,
+ 0, 0, w + 2*border + 1, h + 2*border + 1);
} else {
/* Right. Now we roll up our sleeves. */
for (i = 0; i < nfaces; i++) {
grid_face *f = g->faces + faces[i];
- int xx, yy;
int x, y, w, h;
- int j;
-
- /* There seems to be a certain amount of trial-and-error
- * involved in working out the correct bounding-box for
- * the text. */
- face_text_pos(ds, g, f, &xx, &yy);
-
- x = xx - ds->tilesize/4 - 1; w = ds->tilesize/2 + 2;
- y = yy - ds->tilesize/4 - 3; h = ds->tilesize/2 + 5;
- clip(dr, x, y, w, h);
- draw_rect(dr, x, y, w, h, COL_BACKGROUND);
-
- game_redraw_clue(dr, ds, state, faces[i]);
- for (phase = 0; phase < NPHASES; phase++)
- for (j = 0; j < f->order; j++)
- game_redraw_line(dr, ds, state, f->edges[j] - g->edges,
- phase);
- for (j = 0; j < f->order; j++)
- game_redraw_dot(dr, ds, state, f->dots[j] - g->dots);
- unclip(dr);
- draw_update(dr, x, y, w, h);
+
+ face_text_bbox(ds, g, f, &x, &y, &w, &h);
+ game_redraw_in_rect(dr, ds, state, x, y, w, h);
}
for (i = 0; i < nedges; i++) {
- grid_edge *e = g->edges + edges[i], *ee;
- int x1 = e->dot1->x;
- int y1 = e->dot1->y;
- int x2 = e->dot2->x;
- int y2 = e->dot2->y;
- int xmin, xmax, ymin, ymax;
- int j;
-
- grid_to_screen(ds, g, x1, y1, &x1, &y1);
- grid_to_screen(ds, g, x2, y2, &x2, &y2);
- /* Allow extra margin for dots, and thickness of lines */
- xmin = min(x1, x2) - 2;
- xmax = max(x1, x2) + 2;
- ymin = min(y1, y2) - 2;
- ymax = max(y1, y2) + 2;
- /* For testing, I find it helpful to change COL_BACKGROUND
- * to COL_SATISFIED here. */
- clip(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
- draw_rect(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1,
- COL_BACKGROUND);
-
- if (e->face1)
- game_redraw_clue(dr, ds, state, e->face1 - g->faces);
- if (e->face2)
- game_redraw_clue(dr, ds, state, e->face2 - g->faces);
-
- for (phase = 0; phase < NPHASES; phase++) {
- game_redraw_line(dr, ds, state, edges[i], phase);
- for (j = 0; j < e->dot1->order; j++) {
- ee = e->dot1->edges[j];
- if (ee != e)
- game_redraw_line(dr, ds, state, ee - g->edges, phase);
- }
- for (j = 0; j < e->dot2->order; j++) {
- ee = e->dot2->edges[j];
- if (ee != e)
- game_redraw_line(dr, ds, state, ee - g->edges, phase);
- }
- }
- game_redraw_dot(dr, ds, state, e->dot1 - g->dots);
- game_redraw_dot(dr, ds, state, e->dot2 - g->dots);
+ grid_edge *e = g->edges + edges[i];
+ int x, y, w, h;
- unclip(dr);
- draw_update(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1);
+ edge_bbox(ds, g, e, &x, &y, &w, &h);
+ game_redraw_in_rect(dr, ds, state, x, y, w, h);
}
}
ds->started = TRUE;
}
-static float game_flash_length(game_state *oldstate, game_state *newstate,
- int dir, game_ui *ui)
+static float game_flash_length(const game_state *oldstate,
+ const game_state *newstate, int dir, game_ui *ui)
{
if (!oldstate->solved && newstate->solved &&
!oldstate->cheated && !newstate->cheated) {
return 0.0F;
}
-static void game_print_size(game_params *params, float *x, float *y)
+static int game_status(const game_state *state)
+{
+ return state->solved ? +1 : 0;
+}
+
+static void game_print_size(const game_params *params, float *x, float *y)
{
int pw, ph;
*y = ph / 100.0F;
}
-static void game_print(drawing *dr, game_state *state, int tilesize)
+static void game_print(drawing *dr, const game_state *state, int tilesize)
{
int ink = print_mono_colour(dr, 0);
int i;
grid *g = state->game_grid;
ds->tilesize = tilesize;
+ ds->textx = snewn(g->num_faces, int);
+ ds->texty = snewn(g->num_faces, int);
+ for (i = 0; i < g->num_faces; i++)
+ ds->textx[i] = ds->texty[i] = -1;
for (i = 0; i < g->num_dots; i++) {
int x, y;
grid_face *f = g->faces + i;
int clue = state->clues[i];
if (clue >= 0) {
- char c[2];
+ char c[20];
int x, y;
- c[0] = CLUE2CHAR(clue);
- c[1] = '\0';
+ sprintf(c, "%d", state->clues[i]);
face_text_pos(ds, g, f, &x, &y);
draw_text(dr, x, y,
FONT_VARIABLE, ds->tilesize / 2,
}
}
}
+
+ sfree(ds->textx);
+ sfree(ds->texty);
}
#ifdef COMBINED
const struct game thegame = {
"Loopy", "games.loopy", "loopy",
default_params,
- game_fetch_preset,
+ NULL, game_preset_menu,
decode_params,
encode_params,
free_params,
game_redraw,
game_anim_length,
game_flash_length,
+ game_status,
TRUE, FALSE, game_print_size, game_print,
FALSE /* wants_statusbar */,
FALSE, game_timing_state,
}
#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */