chiark / gitweb /
Loopy: be friendlier to right-click-less playing style.
authorSimon Tatham <anakin@pobox.com>
Wed, 24 Feb 2016 19:27:10 +0000 (19:27 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 24 Feb 2016 19:27:10 +0000 (19:27 +0000)
Some people don't bother to use the right-click UI action that marks a
line as 'definitely not' rather than the initial default yellow
'unknown'. Previously, Loopy gave those people a UI annoyance for some
classes of mistake they made during solving: it would reliably
highlight a clue square with too _many_ edges around it, but not one
with too few - because in normal right-click-ful play, a clue with too
few LINE_YES only becomes an error when there aren't enough
LINE_UNKNOWN around it to potentially become the remaining YESes in
future.

This change arranges that once the player closes the loop, _then_ we
light up underfilled clues, on the basis that adding any further edge
would be an obvious error, so it's no longer sensible to assume that
the user might be planning to come back and do so.

(It's not a very timely notification of errors - it's easy to imagine
someone making a mistake like this very near the start of play and
only finding out about it when they close the loop at the very end. I
discuss possible improvements in a comment, but I don't think any
improvement avoids that problem completely, so I think it may just be
a form of annoyance that right-click-less players have to live with.)

loopy.c

diff --git a/loopy.c b/loopy.c
index 136b9e9fa16c4ab1c8696b731b0b1accb4f0126c..ddb68b9bf52fbd8f38c80e7b8c7b398e228640d7 100644 (file)
--- a/loopy.c
+++ b/loopy.c
@@ -118,6 +118,7 @@ struct game_state {
     char *lines;
 
     unsigned char *line_errors;
     char *lines;
 
     unsigned char *line_errors;
+    int exactly_one_loop;
 
     int solved;
     int cheated;
 
     int solved;
     int cheated;
@@ -325,6 +326,7 @@ static game_state *dup_game(const game_state *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->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;
 
     ret->grid_type = state->grid_type;
     return ret;
@@ -1380,6 +1382,7 @@ static char *new_game_desc(const game_params *params, random_state *rs,
     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->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;
 
 
     state->grid_type = params->type;
 
@@ -1451,6 +1454,7 @@ static game_state *new_game(midend *me, const game_params *params,
     state->clues = snewn(num_faces, signed char);
     state->lines = snewn(num_edges, char);
     state->line_errors = snewn(num_edges, unsigned char);
     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;
 
 
     state->solved = state->cheated = FALSE;
 
@@ -1688,8 +1692,17 @@ static int check_completion(game_state *state)
                 break;
             }
         }
                 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;
     } else {
         ret = FALSE;
+        state->exactly_one_loop = FALSE;
     }
 
     sfree(component_state);
     }
 
     sfree(component_state);
@@ -3263,16 +3276,53 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
     for (i = 0; i < g->num_faces; i++) {
         grid_face *f = g->faces + i;
         int sides = f->order;
     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;
 
         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));
+        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]) {
 
         if (clue_mistake != ds->clue_error[i] ||
             clue_satisfied != ds->clue_satisfied[i]) {