Fix a memory leak.
[sgt/puzzles] / rect.c
diff --git a/rect.c b/rect.c
index 846811e..7fff9af 100644 (file)
--- a/rect.c
+++ b/rect.c
  *    selection to produce a few large rectangles more often
  *    than oodles of small ones? Unsure, but might be worth a
  *    try.
- * 
- *  - During redraw, do corner analysis centrally in game_redraw()
- *    itself so that we can take it into account when computing the
- *    `visible' array. If we can do this, we can actually _turn on_
- *    the `visible' processing and keep redraws to the minimum
- *    required.
  */
 
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <assert.h>
+#include <ctype.h>
 #include <math.h>
 
 #include "puzzles.h"
@@ -76,6 +71,11 @@ struct game_params {
 #define TILE_SIZE 24
 #define BORDER 18
 
+#define CORNER_TOLERANCE 0.15F
+#define CENTRE_TOLERANCE 0.15F
+
+#define FLASH_TIME 0.13F
+
 #define COORD(x) ( (x) * TILE_SIZE + BORDER )
 #define FROMCOORD(x) ( ((x) - BORDER) / TILE_SIZE )
 
@@ -84,6 +84,7 @@ struct game_state {
     int *grid;                        /* contains the numbers */
     unsigned char *vedge;             /* (w+1) x h */
     unsigned char *hedge;             /* w x (h+1) */
+    int completed;
 };
 
 game_params *default_params(void)
@@ -129,6 +130,29 @@ game_params *dup_params(game_params *params)
     return ret;
 }
 
+game_params *decode_params(char const *string)
+{
+    game_params *ret = default_params();
+
+    ret->w = ret->h = atoi(string);
+    while (*string && isdigit(*string)) string++;
+    if (*string == 'x') {
+        string++;
+        ret->h = atoi(string);
+    }
+
+    return ret;
+}
+
+char *encode_params(game_params *params)
+{
+    char data[256];
+
+    sprintf(data, "%dx%d", params->w, params->h);
+
+    return dupstr(data);
+}
+
 config_item *game_configure(game_params *params)
 {
     config_item *ret;
@@ -170,8 +194,8 @@ char *validate_params(game_params *params)
 {
     if (params->w <= 0 && params->h <= 0)
        return "Width and height must both be greater than zero";
-    if (params->w * params->h < 4)
-       return "Total area must be at least 4";
+    if (params->w < 2 && params->h < 2)
+       return "Grid area must be greater than one";
     return NULL;
 }
 
@@ -194,9 +218,13 @@ static struct rectlist *get_rectlist(game_params *params, int *grid)
     int nrects = 0, rectsize = 0;
 
     /*
-     * Maximum rectangle area is 1/6 of total grid size.
+     * Maximum rectangle area is 1/6 of total grid size, unless
+     * this means we can't place any rectangles at all in which
+     * case we set it to 2 at minimum.
      */
     maxarea = params->w * params->h / 6;
+    if (maxarea < 2)
+        maxarea = 2;
 
     for (rw = 1; rw <= params->w; rw++)
         for (rh = 1; rh <= params->h; rh++) {
@@ -206,25 +234,6 @@ static struct rectlist *get_rectlist(game_params *params, int *grid)
                 continue;
             for (x = 0; x <= params->w - rw; x++)
                 for (y = 0; y <= params->h - rh; y++) {
-                    /*
-                     * We have a candidate rectangle placement. See
-                     * if it's unobstructed.
-                     */
-                    int xx, yy;
-                    int ok;
-
-                    ok = TRUE;
-                    for (xx = x; xx < x+rw; xx++)
-                        for (yy = y; yy < y+rh; yy++)
-                            if (index(params, grid, xx, yy) >= 0) {
-                                ok = FALSE;
-                                goto break1;   /* break both loops at once */
-                            }
-                    break1:
-
-                    if (!ok)
-                        continue;
-
                     if (nrects >= rectsize) {
                         rectsize = nrects + 256;
                         rects = sresize(rects, rectsize, struct rect);
@@ -687,7 +696,7 @@ char *validate_seed(game_params *params, char *seed)
         } else if (n == '_') {
             /* do nothing */;
         } else if (n > '0' && n <= '9') {
-            squares += atoi(seed-1);
+            squares++;
             while (*seed >= '0' && *seed <= '9')
                 seed++;
         } else
@@ -716,6 +725,7 @@ game_state *new_game(game_params *params, char *seed)
     state->grid = snewn(area, int);
     state->vedge = snewn(area, unsigned char);
     state->hedge = snewn(area, unsigned char);
+    state->completed = FALSE;
 
     i = 0;
     while (*seed) {
@@ -756,6 +766,8 @@ game_state *dup_game(game_state *state)
     ret->hedge = snewn(state->w * state->h, unsigned char);
     ret->grid = snewn(state->w * state->h, int);
 
+    ret->completed = state->completed;
+
     memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
     memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
     memcpy(ret->hedge, state->hedge, state->w*state->h*sizeof(unsigned char));
@@ -904,29 +916,88 @@ void free_ui(game_ui *ui)
     sfree(ui);
 }
 
-int coord_round(float coord)
+void coord_round(float x, float y, int *xr, int *yr)
 {
-    int i;
-    float dist;
+    float xs, ys, xv, yv, dx, dy, dist;
+
+    /*
+     * Find the nearest square-centre.
+     */
+    xs = (float)floor(x) + 0.5F;
+    ys = (float)floor(y) + 0.5F;
 
     /*
-     * Find the nearest integer.
+     * And find the nearest grid vertex.
      */
-    i = (int)(coord + 0.5F);
+    xv = (float)floor(x + 0.5F);
+    yv = (float)floor(y + 0.5F);
 
     /*
-     * Find the distance from us to that integer.
+     * We allocate clicks in parts of the grid square to either
+     * corners, edges or square centres, as follows:
+     * 
+     *   +--+--------+--+
+     *   |  |        |  |
+     *   +--+        +--+
+     *   |   `.    ,'   |
+     *   |     +--+     |
+     *   |     |  |     |
+     *   |     +--+     |
+     *   |   ,'    `.   |
+     *   +--+        +--+
+     *   |  |        |  |
+     *   +--+--------+--+
+     * 
+     * (Not to scale!)
+     * 
+     * In other words: we measure the square distance (i.e.
+     * max(dx,dy)) from the click to the nearest corner, and if
+     * it's within CORNER_TOLERANCE then we return a corner click.
+     * We measure the square distance from the click to the nearest
+     * centre, and if that's within CENTRE_TOLERANCE we return a
+     * centre click. Failing that, we find which of the two edge
+     * centres is nearer to the click and return that edge.
      */
-    dist = (float)fabs(coord - (float)i);
 
     /*
-     * If we're within the tolerance limit, return the edge
-     * coordinate. Otherwise, return the centre coordinate.
+     * Check for corner click.
      */
-    if (dist < 0.3F)
-        return i * 2;
-    else
-        return 1 + 2 * (int)coord;
+    dx = (float)fabs(x - xv);
+    dy = (float)fabs(y - yv);
+    dist = (dx > dy ? dx : dy);
+    if (dist < CORNER_TOLERANCE) {
+        *xr = 2 * (int)xv;
+        *yr = 2 * (int)yv;
+    } else {
+        /*
+         * Check for centre click.
+         */
+        dx = (float)fabs(x - xs);
+        dy = (float)fabs(y - ys);
+        dist = (dx > dy ? dx : dy);
+        if (dist < CENTRE_TOLERANCE) {
+            *xr = 1 + 2 * (int)xs;
+            *yr = 1 + 2 * (int)ys;
+        } else {
+            /*
+             * Failing both of those, see which edge we're closer to.
+             * Conveniently, this is simply done by testing the relative
+             * magnitude of dx and dy (which are currently distances from
+             * the square centre).
+             */
+            if (dx > dy) {
+                /* Vertical edge: x-coord of corner,
+                 * y-coord of square centre. */
+                *xr = 2 * (int)xv;
+                *yr = 1 + 2 * (int)ys;
+            } else {
+                /* Horizontal edge: x-coord of square centre,
+                 * y-coord of corner. */
+                *xr = 1 + 2 * (int)xs;
+                *yr = 2 * (int)yv;
+            }
+        }
+    }
 }
 
 static void ui_draw_rect(game_state *state, game_ui *ui,
@@ -990,8 +1061,7 @@ game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
         return NULL;
     }
 
-    xc = coord_round(FROMCOORD((float)x));
-    yc = coord_round(FROMCOORD((float)y));
+    coord_round(FROMCOORD((float)x), FROMCOORD((float)y), &xc, &yc);
 
     if (startdrag) {
         ui->drag_start_x = xc;
@@ -1032,6 +1102,26 @@ game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
                free_game(ret);
                ret = NULL;
            }
+
+            /*
+             * We've made a real change to the grid. Check to see
+             * if the game has been completed.
+             */
+            if (ret && !ret->completed) {
+                int x, y, ok;
+                unsigned char *correct = get_correct(ret);
+
+                ok = TRUE;
+                for (x = 0; x < ret->w; x++)
+                    for (y = 0; y < ret->h; y++)
+                        if (!index(ret, correct, x, y))
+                            ok = FALSE;
+
+                sfree(correct);
+
+                if (ok)
+                    ret->completed = TRUE;
+            }
        }
 
        ui->drag_start_x = -1;
@@ -1054,7 +1144,7 @@ game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
  * Drawing routines.
  */
 
-#define CORRECT 256
+#define CORRECT 65536
 
 #define COLOUR(k) ( (k)==1 ? COL_LINE : COL_DRAG )
 #define MAX(x,y) ( (x)>(y) ? (x) : (y) )
@@ -1063,7 +1153,7 @@ game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
 struct game_drawstate {
     int started;
     int w, h;
-    unsigned short *visible;
+    unsigned int *visible;
 };
 
 void game_size(game_params *params, int *x, int *y)
@@ -1110,7 +1200,7 @@ game_drawstate *game_new_drawstate(game_state *state)
     ds->started = FALSE;
     ds->w = state->w;
     ds->h = state->h;
-    ds->visible = snewn(ds->w * ds->h, unsigned short);
+    ds->visible = snewn(ds->w * ds->h, unsigned int);
     for (i = 0; i < ds->w * ds->h; i++)
         ds->visible[i] = 0xFFFF;
 
@@ -1124,7 +1214,8 @@ void game_free_drawstate(game_drawstate *ds)
 }
 
 void draw_tile(frontend *fe, game_state *state, int x, int y,
-               unsigned char *hedge, unsigned char *vedge, int correct)
+               unsigned char *hedge, unsigned char *vedge,
+              unsigned char *corners, int correct)
 {
     int cx = COORD(x), cy = COORD(y);
     char str[80];
@@ -1162,34 +1253,18 @@ void draw_tile(frontend *fe, game_state *state, int x, int y,
     /*
      * Draw corners.
      */
-    if ((HRANGE(state,x-1,y) && index(state,hedge,x-1,y)) ||
-       (VRANGE(state,x,y-1) && index(state,vedge,x,y-1)))
+    if (index(state,corners,x,y))
        draw_rect(fe, cx, cy, 2, 2,
-                  COLOUR(MAX4(index(state,hedge,x-1,y),
-                              index(state,vedge,x,y-1),
-                              index(state,hedge,x,y),
-                              index(state,vedge,x,y))));
-    if ((HRANGE(state,x+1,y) && index(state,hedge,x+1,y)) ||
-       (VRANGE(state,x+1,y-1) && index(state,vedge,x+1,y-1)))
+                  COLOUR(index(state,corners,x,y)));
+    if (x+1 < state->w && index(state,corners,x+1,y))
        draw_rect(fe, cx+TILE_SIZE-1, cy, 2, 2,
-                  COLOUR(MAX4(index(state,hedge,x+1,y),
-                              index(state,vedge,x+1,y-1),
-                              index(state,hedge,x,y),
-                              index(state,vedge,x+1,y))));
-    if ((HRANGE(state,x-1,y+1) && index(state,hedge,x-1,y+1)) ||
-       (VRANGE(state,x,y+1) && index(state,vedge,x,y+1)))
+                  COLOUR(index(state,corners,x+1,y)));
+    if (y+1 < state->h && index(state,corners,x,y+1))
        draw_rect(fe, cx, cy+TILE_SIZE-1, 2, 2,
-                  COLOUR(MAX4(index(state,hedge,x-1,y+1),
-                              index(state,vedge,x,y+1),
-                              index(state,hedge,x,y+1),
-                              index(state,vedge,x,y))));
-    if ((HRANGE(state,x+1,y+1) && index(state,hedge,x+1,y+1)) ||
-       (VRANGE(state,x+1,y+1) && index(state,vedge,x+1,y+1)))
+                  COLOUR(index(state,corners,x,y+1)));
+    if (x+1 < state->w && y+1 < state->h && index(state,corners,x+1,y+1))
        draw_rect(fe, cx+TILE_SIZE-1, cy+TILE_SIZE-1, 2, 2,
-                  COLOUR(MAX4(index(state,hedge,x+1,y+1),
-                              index(state,vedge,x+1,y+1),
-                              index(state,hedge,x,y+1),
-                              index(state,vedge,x+1,y))));
+                  COLOUR(index(state,corners,x+1,y+1)));
 
     draw_update(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
 }
@@ -1200,7 +1275,7 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
 {
     int x, y;
     unsigned char *correct;
-    unsigned char *hedge, *vedge;
+    unsigned char *hedge, *vedge, *corners;
 
     correct = get_correct(state);
 
@@ -1215,6 +1290,28 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         vedge = state->vedge;
     }
 
+    corners = snewn(state->w * state->h, unsigned char);
+    memset(corners, 0, state->w * state->h);
+    for (x = 0; x < state->w; x++)
+       for (y = 0; y < state->h; y++) {
+           if (x > 0) {
+               int e = index(state, vedge, x, y);
+               if (index(state,corners,x,y) < e)
+                   index(state,corners,x,y) = e;
+               if (y+1 < state->h &&
+                   index(state,corners,x,y+1) < e)
+                   index(state,corners,x,y+1) = e;
+           }
+           if (y > 0) {
+               int e = index(state, hedge, x, y);
+               if (index(state,corners,x,y) < e)
+                   index(state,corners,x,y) = e;
+               if (x+1 < state->w &&
+                   index(state,corners,x+1,y) < e)
+                   index(state,corners,x+1,y) = e;
+           }
+       }
+
     if (!ds->started) {
        draw_rect(fe, 0, 0,
                  state->w * TILE_SIZE + 2*BORDER + 1,
@@ -1222,26 +1319,36 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
        draw_rect(fe, COORD(0)-1, COORD(0)-1,
                  ds->w*TILE_SIZE+3, ds->h*TILE_SIZE+3, COL_LINE);
        ds->started = TRUE;
+       draw_update(fe, 0, 0,
+                   state->w * TILE_SIZE + 2*BORDER + 1,
+                   state->h * TILE_SIZE + 2*BORDER + 1);
     }
 
     for (x = 0; x < state->w; x++)
        for (y = 0; y < state->h; y++) {
-           unsigned short c = 0;
+           unsigned int c = 0;
 
            if (HRANGE(state,x,y))
                 c |= index(state,hedge,x,y);
-           if (HRANGE(state,x+1,y))
-               c |= index(state,hedge,x+1,y) << 2;
+           if (HRANGE(state,x,y+1))
+               c |= index(state,hedge,x,y+1) << 2;
            if (VRANGE(state,x,y))
                c |= index(state,vedge,x,y) << 4;
-           if (VRANGE(state,x,y+1))
-               c |= index(state,vedge,x,y+1) << 6;
-           if (index(state, correct, x, y))
+           if (VRANGE(state,x+1,y))
+               c |= index(state,vedge,x+1,y) << 6;
+           c |= index(state,corners,x,y) << 8;
+           if (x+1 < state->w)
+               c |= index(state,corners,x+1,y) << 10;
+           if (y+1 < state->h)
+               c |= index(state,corners,x,y+1) << 12;
+           if (x+1 < state->w && y+1 < state->h)
+               c |= index(state,corners,x+1,y+1) << 14;
+           if (index(state, correct, x, y) && !flashtime)
                c |= CORRECT;
 
            if (index(ds,ds->visible,x,y) != c) {
-               draw_tile(fe, state, x, y, hedge, vedge, c & CORRECT);
-               /* index(ds,ds->visible,x,y) = c; */
+               draw_tile(fe, state, x, y, hedge, vedge, corners, c & CORRECT);
+               index(ds,ds->visible,x,y) = c;
            }
        }
 
@@ -1250,6 +1357,7 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
         sfree(vedge);
    }
 
+    sfree(corners);
     sfree(correct);
 }
 
@@ -1260,6 +1368,8 @@ float game_anim_length(game_state *oldstate, game_state *newstate)
 
 float game_flash_length(game_state *oldstate, game_state *newstate)
 {
+    if (!oldstate->completed && newstate->completed)
+        return FLASH_TIME;
     return 0.0F;
 }