+/* Calculates the line_errors data, and checks if the current state is a
+ * solution */
+static int check_completion(game_state *state)
+{
+ grid *g = state->game_grid;
+ 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);
+
+ /*
+ * 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.
+ *
+ * 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.
+ *
+ * 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!
+ *
+ * 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).
+ *
+ * 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.
+ *
+ * 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.
+ */
+
+ dsf = snew_dsf(g->num_dots);
+
+ /* 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);
+ }
+ }
+
+ /* 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 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)) {
+ /* violation, so mark all YES edges as errors */
+ grid_dot *d = g->dots + i;
+ int j;
+ for (j = 0; j < d->order; j++) {
+ int e = d->edges[j] - g->edges;
+ 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;
+ }
+ }
+
+ /* 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;
+}