Stop the analysis pass in Loopy's redraw routine from being
[sgt/puzzles] / net.c
diff --git a/net.c b/net.c
index bf0f7e7..2e19f1f 100644 (file)
--- a/net.c
+++ b/net.c
 #include "puzzles.h"
 #include "tree234.h"
 
+/*
+ * The standard user interface for Net simply has left- and
+ * right-button mouse clicks in a square rotate it one way or the
+ * other. We also provide, by #ifdef, a separate interface based on
+ * rotational dragging motions. I initially developed this for the
+ * Mac on the basis that it might work better than the click
+ * interface with only one mouse button available, but in fact
+ * found it to be quite strange and unintuitive. Apparently it
+ * works better on stylus-driven platforms such as Palm and
+ * PocketPC, though, so we enable it by default there.
+ */
+#ifdef STYLUS_BASED
+#define USE_DRAGGING
+#endif
+
 #define MATMUL(xr,yr,m,x,y) do { \
     float rx, ry, xx = (x), yy = (y), *mat = (m); \
     rx = mat[0] * xx + mat[2] * yy; \
@@ -217,7 +232,7 @@ static void decode_params(game_params *ret, char const *string)
            ret->wrapping = TRUE;
        } else if (*p == 'b') {
            p++;
-            ret->barrier_probability = atof(p);
+            ret->barrier_probability = (float)atof(p);
            while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++;
        } else if (*p == 'a') {
             p++;
@@ -935,8 +950,10 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping,
     }
     sfree(perim2);
 
-    if (i == nperim)
+    if (i == nperim) {
+        sfree(perimeter);
        return;                        /* nothing we can do! */
+    }
 
     /*
      * Now we've constructed a new link, we need to find the entire
@@ -1388,13 +1405,55 @@ static char *new_game_desc(game_params *params, random_state *rs,
 
     /*
      * Now shuffle the grid.
+     * 
+     * In order to avoid accidentally generating an already-solved
+     * grid, we will reshuffle as necessary to ensure that at least
+     * one edge has a mismatched connection.
+     *
+     * This can always be done, since validate_params() enforces a
+     * grid area of at least 2 and our generator never creates
+     * either type of rotationally invariant tile (cross and
+     * blank). Hence there must be at least one edge separating
+     * distinct tiles, and it must be possible to find orientations
+     * of those tiles such that one tile is trying to connect
+     * through that edge and the other is not.
+     * 
+     * (We could be more subtle, and allow the shuffle to generate
+     * a grid in which all tiles match up locally and the only
+     * criterion preventing the grid from being already solved is
+     * connectedness. However, that would take more effort, and
+     * it's easier to simply make sure every grid is _obviously_
+     * not solved.)
      */
-    for (y = 0; y < h; y++) {
-       for (x = 0; x < w; x++) {
-           int orig = index(params, tiles, x, y);
-           int rot = random_upto(rs, 4);
-           index(params, tiles, x, y) = ROT(orig, rot);
-       }
+    while (1) {
+        int mismatches;
+
+        for (y = 0; y < h; y++) {
+            for (x = 0; x < w; x++) {
+                int orig = index(params, tiles, x, y);
+                int rot = random_upto(rs, 4);
+                index(params, tiles, x, y) = ROT(orig, rot);
+            }
+        }
+
+        mismatches = 0;
+        /*
+         * I can't even be bothered to check for mismatches across
+         * a wrapping edge, so I'm just going to enforce that there
+         * must be a mismatch across a non-wrapping edge, which is
+         * still always possible.
+         */
+        for (y = 0; y < h; y++) for (x = 0; x < w; x++) {
+            if (x+1 < w && ((ROT(index(params, tiles, x, y), 2) ^ 
+                             index(params, tiles, x+1, y)) & L))
+                mismatches++;
+            if (y+1 < h && ((ROT(index(params, tiles, x, y), 2) ^ 
+                             index(params, tiles, x, y+1)) & U))
+                mismatches++;
+        }
+
+        if (mismatches > 0)
+            break;
     }
 
     /*
@@ -1593,7 +1652,7 @@ static game_state *new_game(midend *me, game_params *params, char *desc)
             if (!(barrier(state, x, 0) & U) ||
                 !(barrier(state, x, state->height-1) & D))
                 state->wrapping = TRUE;
-        for (y = 0; y < state->width; y++)
+        for (y = 0; y < state->height; y++)
             if (!(barrier(state, 0, y) & L) ||
                 !(barrier(state, state->width-1, y) & R))
                 state->wrapping = TRUE;
@@ -1725,6 +1784,11 @@ static char *solve_game(game_state *state, game_state *currstate,
     return ret;
 }
 
+static int game_can_format_as_text_now(game_params *params)
+{
+    return TRUE;
+}
+
 static char *game_text_format(game_state *state)
 {
     return NULL;
@@ -1797,6 +1861,9 @@ struct game_ui {
     int cur_x, cur_y;
     int cur_visible;
     random_state *rs; /* used for jumbling */
+#ifdef USE_DRAGGING
+    int dragtilex, dragtiley, dragstartx, dragstarty, dragged;
+#endif
 };
 
 static game_ui *new_ui(game_state *state)
@@ -1855,7 +1922,7 @@ struct game_drawstate {
  * Process a move.
  */
 static char *interpret_move(game_state *state, game_ui *ui,
-                           game_drawstate *ds, int x, int y, int button)
+                           const game_drawstate *ds, int x, int y, int button)
 {
     char *nullret;
     int tx = -1, ty = -1, dir = 0;
@@ -1871,6 +1938,12 @@ static char *interpret_move(game_state *state, game_ui *ui,
 
     if (button == LEFT_BUTTON ||
        button == MIDDLE_BUTTON ||
+#ifdef USE_DRAGGING
+       button == LEFT_DRAG ||
+       button == LEFT_RELEASE ||
+       button == RIGHT_DRAG ||
+       button == RIGHT_RELEASE ||
+#endif
        button == RIGHT_BUTTON) {
 
        if (ui->cur_visible) {
@@ -1896,10 +1969,113 @@ static char *interpret_move(game_state *state, game_ui *ui,
            y % TILE_SIZE >= TILE_SIZE - TILE_BORDER)
            return nullret;
 
-        action = button == LEFT_BUTTON ? ROTATE_LEFT :
-                 button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK;
-    } else if (button == CURSOR_UP || button == CURSOR_DOWN ||
-              button == CURSOR_RIGHT || button == CURSOR_LEFT) {
+#ifdef USE_DRAGGING
+
+        if (button == MIDDLE_BUTTON
+#ifdef STYLUS_BASED
+           || button == RIGHT_BUTTON  /* with a stylus, `right-click' locks */
+#endif
+           ) {
+            /*
+             * Middle button never drags: it only toggles the lock.
+             */
+            action = TOGGLE_LOCK;
+        } else if (button == LEFT_BUTTON
+#ifndef STYLUS_BASED
+                   || button == RIGHT_BUTTON /* (see above) */
+#endif
+                  ) {
+            /*
+             * Otherwise, we note down the start point for a drag.
+             */
+            ui->dragtilex = tx;
+            ui->dragtiley = ty;
+            ui->dragstartx = x % TILE_SIZE;
+            ui->dragstarty = y % TILE_SIZE;
+            ui->dragged = FALSE;
+            return nullret;            /* no actual action */
+        } else if (button == LEFT_DRAG
+#ifndef STYLUS_BASED
+                   || button == RIGHT_DRAG
+#endif
+                  ) {
+            /*
+             * Find the new drag point and see if it necessitates a
+             * rotation.
+             */
+            int x0,y0, xA,yA, xC,yC, xF,yF;
+            int mx, my;
+            int d0, dA, dC, dF, dmin;
+
+            tx = ui->dragtilex;
+            ty = ui->dragtiley;
+
+            mx = x - (ui->dragtilex * TILE_SIZE);
+            my = y - (ui->dragtiley * TILE_SIZE);
+
+            x0 = ui->dragstartx;
+            y0 = ui->dragstarty;
+            xA = ui->dragstarty;
+            yA = TILE_SIZE-1 - ui->dragstartx;
+            xF = TILE_SIZE-1 - ui->dragstartx;
+            yF = TILE_SIZE-1 - ui->dragstarty;
+            xC = TILE_SIZE-1 - ui->dragstarty;
+            yC = ui->dragstartx;
+
+            d0 = (mx-x0)*(mx-x0) + (my-y0)*(my-y0);
+            dA = (mx-xA)*(mx-xA) + (my-yA)*(my-yA);
+            dF = (mx-xF)*(mx-xF) + (my-yF)*(my-yF);
+            dC = (mx-xC)*(mx-xC) + (my-yC)*(my-yC);
+
+            dmin = min(min(d0,dA),min(dF,dC));
+
+            if (d0 == dmin) {
+                return nullret;
+            } else if (dF == dmin) {
+                action = ROTATE_180;
+                ui->dragstartx = xF;
+                ui->dragstarty = yF;
+                ui->dragged = TRUE;
+            } else if (dA == dmin) {
+                action = ROTATE_LEFT;
+                ui->dragstartx = xA;
+                ui->dragstarty = yA;
+                ui->dragged = TRUE;
+            } else /* dC == dmin */ {
+                action = ROTATE_RIGHT;
+                ui->dragstartx = xC;
+                ui->dragstarty = yC;
+                ui->dragged = TRUE;
+            }
+        } else if (button == LEFT_RELEASE
+#ifndef STYLUS_BASED
+                   || button == RIGHT_RELEASE
+#endif
+                  ) {
+            if (!ui->dragged) {
+                /*
+                 * There was a click but no perceptible drag:
+                 * revert to single-click behaviour.
+                 */
+                tx = ui->dragtilex;
+                ty = ui->dragtiley;
+
+                if (button == LEFT_RELEASE)
+                    action = ROTATE_LEFT;
+                else
+                    action = ROTATE_RIGHT;
+            } else
+                return nullret;        /* no action */
+        }
+
+#else /* USE_DRAGGING */
+
+       action = (button == LEFT_BUTTON ? ROTATE_LEFT :
+                 button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK);
+
+#endif /* USE_DRAGGING */
+
+    } else if (IS_CURSOR_MOVE(button)) {
         switch (button) {
           case CURSOR_UP:       dir = U; break;
           case CURSOR_DOWN:     dir = D; break;
@@ -1914,12 +2090,12 @@ static char *interpret_move(game_state *state, game_ui *ui,
     } else if (button == 'a' || button == 's' || button == 'd' ||
               button == 'A' || button == 'S' || button == 'D' ||
                button == 'f' || button == 'F' ||
-              button == CURSOR_SELECT) {
+               IS_CURSOR_SELECT(button)) {
        tx = ui->cur_x;
        ty = ui->cur_y;
        if (button == 'a' || button == 'A' || button == CURSOR_SELECT)
            action = ROTATE_LEFT;
-       else if (button == 's' || button == 'S')
+       else if (button == 's' || button == 'S' || button == CURSOR_SELECT2)
            action = TOGGLE_LOCK;
        else if (button == 'd' || button == 'D')
            action = ROTATE_RIGHT;
@@ -2021,7 +2197,7 @@ static char *interpret_move(game_state *state, game_ui *ui,
 static game_state *execute_move(game_state *from, char *move)
 {
     game_state *ret;
-    int tx, ty, n, noanim, orig;
+    int tx = -1, ty = -1, n, noanim, orig;
 
     ret = dup_game(from);
 
@@ -2070,6 +2246,7 @@ static game_state *execute_move(game_state *from, char *move)
        }
     }
     if (!noanim) {
+        if (tx == -1 || ty == -1) { free_game(ret); return NULL; }
        ret->last_rotate_x = tx;
        ret->last_rotate_y = ty;
     }
@@ -2200,8 +2377,8 @@ static float *game_colours(frontend *fe, int *ncolours)
     return ret;
 }
 
-static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2,
-                            int colour)
+static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2,
+                            int colour)
 {
     draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE);
     draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE);
@@ -2337,9 +2514,9 @@ static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds,
             ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir);
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
-            draw_thick_line(dr, bx+(int)cx, by+(int)cy,
-                           bx+(int)(cx+tx), by+(int)(cy+ty),
-                            COL_WIRE);
+            draw_filled_line(dr, bx+(int)cx, by+(int)cy,
+                            bx+(int)(cx+tx), by+(int)(cy+ty),
+                            COL_WIRE);
         }
     }
     for (dir = 1; dir < 0x10; dir <<= 1) {
@@ -2687,6 +2864,11 @@ static float game_flash_length(game_state *oldstate,
     return 0.0F;
 }
 
+static int game_status(game_state *state)
+{
+    return state->completed ? +1 : 0;
+}
+
 static int game_timing_state(game_state *state, game_ui *ui)
 {
     return TRUE;
@@ -2700,8 +2882,8 @@ static void game_print_size(game_params *params, float *x, float *y)
      * I'll use 8mm squares by default.
      */
     game_compute_size(params, 800, &pw, &ph);
-    *x = pw / 100.0;
-    *y = ph / 100.0;
+    *x = pw / 100.0F;
+    *y = ph / 100.0F;
 }
 
 static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y,
@@ -2847,7 +3029,7 @@ const struct game thegame = {
     dup_game,
     free_game,
     TRUE, solve_game,
-    FALSE, game_text_format,
+    FALSE, game_can_format_as_text_now, game_text_format,
     new_ui,
     free_ui,
     encode_ui,
@@ -2862,6 +3044,7 @@ const struct game thegame = {
     game_redraw,
     game_anim_length,
     game_flash_length,
+    game_status,
     TRUE, FALSE, game_print_size, game_print,
     TRUE,                             /* wants_statusbar */
     FALSE, game_timing_state,