chiark / gitweb /
Fix completion checking in Killer Solo.
[sgt-puzzles.git] / net.c
diff --git a/net.c b/net.c
index fa48a869cb4fd78568d150e837f494f47323c04a..349b13dfb216edfde678f50cf8d701c4ead2e91e 100644 (file)
--- a/net.c
+++ b/net.c
 #define D 0x08
 #define LOCKED 0x10
 #define ACTIVE 0x20
+#define RLOOP (R << 6)
+#define ULOOP (U << 6)
+#define LLOOP (L << 6)
+#define DLOOP (D << 6)
+#define LOOP(dir) ((dir) << 6)
 
 /* Rotations: Anticlockwise, Clockwise, Flip, general rotate */
 #define A(x) ( (((x) & 0x07) << 1) | (((x) & 0x08) >> 3) )
@@ -85,6 +90,7 @@ enum {
     COL_ENDPOINT,
     COL_POWERED,
     COL_BARRIER,
+    COL_LOOP,
     NCOLOURS
 };
 
@@ -1126,6 +1132,10 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping,
     sfree(perimeter);
 }
 
+static int *compute_loops_inner(int w, int h, int wrapping,
+                                const unsigned char *tiles,
+                                const unsigned char *barriers);
+
 static char *new_game_desc(const game_params *params, random_state *rs,
                           char **aux, int interactive)
 {
@@ -1424,10 +1434,21 @@ static char *new_game_desc(const game_params *params, random_state *rs,
      * connectedness. However, that would take more effort, and
      * it's easier to simply make sure every grid is _obviously_
      * not solved.)
+     *
+     * We also require that our shuffle produces no loops in the
+     * initial grid state, because it's a bit rude to light up a 'HEY,
+     * YOU DID SOMETHING WRONG!' indicator when the user hasn't even
+     * had a chance to do _anything_ yet. This also is possible just
+     * by retrying the whole shuffle on failure, because it's clear
+     * that at least one non-solved shuffle with no loops must exist.
+     * (Proof: take the _solved_ state of the puzzle, and rotate one
+     * endpoint.)
      */
     while (1) {
-        int mismatches;
+        int mismatches, prev_loopsquares, this_loopsquares, i;
+        int *loops;
 
+      shuffle:
         for (y = 0; y < h; y++) {
             for (x = 0; x < w; x++) {
                 int orig = index(params, tiles, x, y);
@@ -1436,6 +1457,35 @@ static char *new_game_desc(const game_params *params, random_state *rs,
             }
         }
 
+        /*
+         * Check for loops, and try to fix them by reshuffling just
+         * the squares involved.
+         */
+        prev_loopsquares = w*h+1;
+        while (1) {
+            loops = compute_loops_inner(w, h, params->wrapping, tiles, NULL);
+            this_loopsquares = 0;
+            for (i = 0; i < w*h; i++) {
+                if (loops[i]) {
+                    int orig = tiles[i];
+                    int rot = random_upto(rs, 4);
+                    tiles[i] = ROT(orig, rot);
+                    this_loopsquares++;
+                }
+            }
+            sfree(loops);
+            if (this_loopsquares > prev_loopsquares) {
+                /*
+                 * We're increasing rather than reducing the number of
+                 * loops. Give up and go back to the full shuffle.
+                 */
+                goto shuffle;
+            }
+            if (this_loopsquares == 0)
+                break;
+            prev_loopsquares = this_loopsquares;
+        }
+
         mismatches = 0;
         /*
          * I can't even be bothered to check for mismatches across
@@ -1452,8 +1502,11 @@ static char *new_game_desc(const game_params *params, random_state *rs,
                 mismatches++;
         }
 
-        if (mismatches > 0)
-            break;
+        if (mismatches == 0)
+            continue;
+
+        /* OK. */
+        break;
     }
 
     /*
@@ -1856,6 +1909,87 @@ static unsigned char *compute_active(const game_state *state, int cx, int cy)
     return active;
 }
 
+struct net_neighbour_ctx {
+    int w, h;
+    const unsigned char *tiles, *barriers;
+    int i, n, neighbours[4];
+};
+static int net_neighbour(int vertex, void *vctx)
+{
+    struct net_neighbour_ctx *ctx = (struct net_neighbour_ctx *)vctx;
+
+    if (vertex >= 0) {
+        int x = vertex % ctx->w, y = vertex / ctx->w;
+        int tile, dir, x1, y1, v1;
+
+        ctx->i = ctx->n = 0;
+
+        tile = ctx->tiles[vertex];
+        if (ctx->barriers)
+            tile &= ~ctx->barriers[vertex];
+
+        for (dir = 1; dir < 0x10; dir <<= 1) {
+            if (!(tile & dir))
+                continue;
+            OFFSETWH(x1, y1, x, y, dir, ctx->w, ctx->h);
+            v1 = y1 * ctx->w + x1;
+            if (ctx->tiles[v1] & F(dir))
+                ctx->neighbours[ctx->n++] = v1;
+        }
+    }
+
+    if (ctx->i < ctx->n)
+        return ctx->neighbours[ctx->i++];
+    else
+        return -1;
+}
+
+static int *compute_loops_inner(int w, int h, int wrapping,
+                                const unsigned char *tiles,
+                                const unsigned char *barriers)
+{
+    struct net_neighbour_ctx ctx;
+    struct findloopstate *fls;
+    int *loops;
+    int x, y;
+
+    fls = findloop_new_state(w*h);
+    ctx.w = w;
+    ctx.h = h;
+    ctx.tiles = tiles;
+    ctx.barriers = barriers;
+    findloop_run(fls, w*h, net_neighbour, &ctx);
+
+    loops = snewn(w*h, int);
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            int x1, y1, dir;
+            int flags = 0;
+
+            for (dir = 1; dir < 0x10; dir <<= 1) {
+                if ((tiles[y*w+x] & dir) &&
+                    !(barriers && (barriers[y*w+x] & dir))) {
+                    OFFSETWH(x1, y1, x, y, dir, w, h);
+                    if ((tiles[y1*w+x1] & F(dir)) &&
+                        findloop_is_loop_edge(fls, y*w+x, y1*w+x1))
+                        flags |= LOOP(dir);
+                }
+            }
+            loops[y*w+x] = flags;
+        }
+    }
+
+    findloop_free_state(fls);
+    return loops;
+}
+
+static int *compute_loops(const game_state *state)
+{
+    return compute_loops_inner(state->width, state->height, state->wrapping,
+                               state->tiles, state->barriers);
+}
+
 struct game_ui {
     int org_x, org_y; /* origin */
     int cx, cy;       /* source tile (game coordinates) */
@@ -1916,7 +2050,7 @@ struct game_drawstate {
     int width, height;
     int org_x, org_y;
     int tilesize;
-    unsigned char *visible;
+    int *visible;
 };
 
 /* ----------------------------------------------------------------------
@@ -2290,14 +2424,16 @@ static game_state *execute_move(const game_state *from, const char *move)
 static game_drawstate *game_new_drawstate(drawing *dr, const game_state *state)
 {
     game_drawstate *ds = snew(game_drawstate);
+    int i;
 
     ds->started = FALSE;
     ds->width = state->width;
     ds->height = state->height;
     ds->org_x = ds->org_y = -1;
-    ds->visible = snewn(state->width * state->height, unsigned char);
+    ds->visible = snewn(state->width * state->height, int);
     ds->tilesize = 0;                  /* undecided yet */
-    memset(ds->visible, 0xFF, state->width * state->height);
+    for (i = 0; i < state->width * state->height; i++)
+        ds->visible[i] = -1;
 
     return ds;
 }
@@ -2355,6 +2491,13 @@ static float *game_colours(frontend *fe, int *ncolours)
     ret[COL_BARRIER * 3 + 1] = 0.0F;
     ret[COL_BARRIER * 3 + 2] = 0.0F;
 
+    /*
+     * Highlighted loops are red as well.
+     */
+    ret[COL_LOOP * 3 + 0] = 1.0F;
+    ret[COL_LOOP * 3 + 1] = 0.0F;
+    ret[COL_LOOP * 3 + 2] = 0.0F;
+
     /*
      * Unpowered endpoints are blue.
      */
@@ -2527,9 +2670,14 @@ static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
             ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir);
             MATMUL(tx, ty, matrix, ex, ey);
             draw_line(dr, bx+(int)cx, by+(int)cy,
-                     bx+(int)(cx+tx), by+(int)(cy+ty), col);
+                     bx+(int)(cx+tx), by+(int)(cy+ty),
+                      (tile & LOOP(dir)) ? COL_LOOP : col);
         }
     }
+    /* If we've drawn any loop-highlighted arms, make sure the centre
+     * point is loop-coloured rather than a later arm overwriting it. */
+    if (tile & (RLOOP | ULOOP | LLOOP | DLOOP))
+        draw_rect(dr, bx+(int)cx, by+(int)cy, 1, 1, COL_LOOP);
 
     /*
      * Draw the box in the middle. We do this in blue if the tile
@@ -2598,7 +2746,9 @@ static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds,
              */
             draw_rect_coords(dr, px-vx, py-vy, px+lx+vx, py+ly+vy, COL_WIRE);
             draw_rect_coords(dr, px, py, px+lx, py+ly,
-                             (tile & ACTIVE) ? COL_POWERED : COL_WIRE);
+                             ((tile & LOOP(dir)) ? COL_LOOP :
+                              (tile & ACTIVE) ? COL_POWERED :
+                              COL_WIRE));
         } else {
             /*
              * The other tile extends into our border, but isn't
@@ -2672,6 +2822,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
 {
     int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE;
     unsigned char *active;
+    int *loops;
     float angle = 0.0;
 
     /*
@@ -2765,11 +2916,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
      * Draw any tile which differs from the way it was last drawn.
      */
     active = compute_active(state, ui->cx, ui->cy);
+    loops = compute_loops(state);
 
     for (x = 0; x < ds->width; x++)
         for (y = 0; y < ds->height; y++) {
-            unsigned char c = tile(state, GX(x), GY(y)) |
-                              index(state, active, GX(x), GY(y));
+            int c = tile(state, GX(x), GY(y)) |
+                index(state, active, GX(x), GY(y)) |
+                index(state, loops, GX(x), GY(y));
             int is_src = GX(x) == ui->cx && GY(y) == ui->cy;
             int is_anim = GX(x) == tx && GY(y) == ty;
             int is_cursor = ui->cur_visible &&
@@ -2796,12 +2949,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
 
             if (moved_origin ||
                 index(state, ds->visible, x, y) != c ||
-                index(state, ds->visible, x, y) == 0xFF ||
+                index(state, ds->visible, x, y) == -1 ||
                 is_src || is_anim || is_cursor) {
                 draw_tile(dr, state, ds, x, y, c,
                           is_src, (is_anim ? angle : 0.0F), is_cursor);
                 if (is_src || is_anim || is_cursor)
-                    index(state, ds->visible, x, y) = 0xFF;
+                    index(state, ds->visible, x, y) = -1;
                 else
                     index(state, ds->visible, x, y) = c;
             }
@@ -2830,6 +2983,7 @@ static void game_redraw(drawing *dr, game_drawstate *ds,
     }
 
     sfree(active);
+    sfree(loops);
 }
 
 static float game_anim_length(const game_state *oldstate,