Allow for trailing '\0' in game_text_format() in various games.
[sgt/puzzles] / rect.c
diff --git a/rect.c b/rect.c
index b9d7cda..ffb3725 100644 (file)
--- a/rect.c
+++ b/rect.c
 
 #include "puzzles.h"
 
-const char *const game_name = "Rectangles";
-const char *const game_winhelp_topic = "games.rectangles";
-const int game_can_configure = TRUE;
-
 enum {
     COL_BACKGROUND,
     COL_CORRECT,
@@ -86,10 +82,10 @@ struct game_state {
     int *grid;                        /* contains the numbers */
     unsigned char *vedge;             /* (w+1) x h */
     unsigned char *hedge;             /* w x (h+1) */
-    int completed;
+    int completed, cheated;
 };
 
-game_params *default_params(void)
+static game_params *default_params(void)
 {
     game_params *ret = snew(game_params);
 
@@ -99,7 +95,7 @@ game_params *default_params(void)
     return ret;
 }
 
-int game_fetch_preset(int i, char **name, game_params **params)
+static int game_fetch_preset(int i, char **name, game_params **params)
 {
     game_params *ret;
     int w, h;
@@ -122,19 +118,19 @@ int game_fetch_preset(int i, char **name, game_params **params)
     return TRUE;
 }
 
-void free_params(game_params *params)
+static void free_params(game_params *params)
 {
     sfree(params);
 }
 
-game_params *dup_params(game_params *params)
+static game_params *dup_params(game_params *params)
 {
     game_params *ret = snew(game_params);
     *ret = *params;                   /* structure copy */
     return ret;
 }
 
-game_params *decode_params(char const *string)
+static game_params *decode_params(char const *string)
 {
     game_params *ret = default_params();
 
@@ -154,7 +150,7 @@ game_params *decode_params(char const *string)
     return ret;
 }
 
-char *encode_params(game_params *params)
+static char *encode_params(game_params *params)
 {
     char data[256];
 
@@ -163,7 +159,7 @@ char *encode_params(game_params *params)
     return dupstr(data);
 }
 
-config_item *game_configure(game_params *params)
+static config_item *game_configure(game_params *params)
 {
     config_item *ret;
     char buf[80];
@@ -196,7 +192,7 @@ config_item *game_configure(game_params *params)
     return ret;
 }
 
-game_params *custom_params(config_item *cfg)
+static game_params *custom_params(config_item *cfg)
 {
     game_params *ret = snew(game_params);
 
@@ -207,7 +203,7 @@ game_params *custom_params(config_item *cfg)
     return ret;
 }
 
-char *validate_params(game_params *params)
+static char *validate_params(game_params *params)
 {
     if (params->w <= 0 && params->h <= 0)
        return "Width and height must both be greater than zero";
@@ -390,7 +386,14 @@ static void display_grid(game_params *params, int *grid, int *numbers, int all)
 }
 #endif
 
-char *new_game_seed(game_params *params, random_state *rs)
+struct game_aux_info {
+    int w, h;
+    unsigned char *vedge;             /* (w+1) x h */
+    unsigned char *hedge;             /* w x (h+1) */
+};
+
+static char *new_game_seed(game_params *params, random_state *rs,
+                          game_aux_info **aux)
 {
     int *grid, *numbers;
     struct rectlist *list;
@@ -832,6 +835,31 @@ char *new_game_seed(game_params *params, random_state *rs)
     }
 
     /*
+     * Store the rectangle data in the game_aux_info.
+     */
+    {
+       game_aux_info *ai = snew(game_aux_info);
+
+       ai->w = params->w;
+       ai->h = params->h;
+       ai->vedge = snewn(ai->w * ai->h, unsigned char);
+       ai->hedge = snewn(ai->w * ai->h, unsigned char);
+
+       for (y = 0; y < params->h; y++)
+           for (x = 1; x < params->w; x++) {
+               vedge(ai, x, y) =
+                   index(params, grid, x, y) != index(params, grid, x-1, y);
+           }
+       for (y = 1; y < params->h; y++)
+           for (x = 0; x < params->w; x++) {
+               hedge(ai, x, y) =
+                   index(params, grid, x, y) != index(params, grid, x, y-1);
+           }
+
+       *aux = ai;
+    }
+
+    /*
      * Place numbers.
      */
     numbers = snewn(params->w * params->h, int);
@@ -881,7 +909,13 @@ char *new_game_seed(game_params *params, random_state *rs)
                     run -= c - ('a' - 1);
                 }
             } else {
-                *p++ = '_';
+                /*
+                 * If there's a number in the very top left or
+                 * bottom right, there's no point putting an
+                 * unnecessary _ before or after it.
+                 */
+                if (p > seed && n > 0)
+                    *p++ = '_';
             }
             if (n > 0)
                 p += sprintf(p, "%d", n);
@@ -896,7 +930,14 @@ char *new_game_seed(game_params *params, random_state *rs)
     return seed;
 }
 
-char *validate_seed(game_params *params, char *seed)
+static void game_free_aux_info(game_aux_info *ai)
+{
+    sfree(ai->vedge);
+    sfree(ai->hedge);
+    sfree(ai);
+}
+
+static char *validate_seed(game_params *params, char *seed)
 {
     int area = params->w * params->h;
     int squares = 0;
@@ -924,7 +965,7 @@ char *validate_seed(game_params *params, char *seed)
     return NULL;
 }
 
-game_state *new_game(game_params *params, char *seed)
+static game_state *new_game(game_params *params, char *seed)
 {
     game_state *state = snew(game_state);
     int x, y, i, area;
@@ -937,7 +978,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;
+    state->completed = state->cheated = FALSE;
 
     i = 0;
     while (*seed) {
@@ -967,7 +1008,7 @@ game_state *new_game(game_params *params, char *seed)
     return state;
 }
 
-game_state *dup_game(game_state *state)
+static game_state *dup_game(game_state *state)
 {
     game_state *ret = snew(game_state);
 
@@ -979,6 +1020,7 @@ game_state *dup_game(game_state *state)
     ret->grid = snewn(state->w * state->h, int);
 
     ret->completed = state->completed;
+    ret->cheated = state->cheated;
 
     memcpy(ret->grid, state->grid, state->w * state->h * sizeof(int));
     memcpy(ret->vedge, state->vedge, state->w*state->h*sizeof(unsigned char));
@@ -987,7 +1029,7 @@ game_state *dup_game(game_state *state)
     return ret;
 }
 
-void free_game(game_state *state)
+static void free_game(game_state *state)
 {
     sfree(state->grid);
     sfree(state->vedge);
@@ -995,6 +1037,120 @@ void free_game(game_state *state)
     sfree(state);
 }
 
+static game_state *solve_game(game_state *state, game_aux_info *ai,
+                             char **error)
+{
+    game_state *ret;
+
+    if (!ai) {
+       *error = "Solution not known for this puzzle";
+       return NULL;
+    }
+
+    assert(state->w == ai->w);
+    assert(state->h == ai->h);
+
+    ret = dup_game(state);
+    memcpy(ret->vedge, ai->vedge, ai->w * ai->h * sizeof(unsigned char));
+    memcpy(ret->hedge, ai->hedge, ai->w * ai->h * sizeof(unsigned char));
+    ret->cheated = TRUE;
+
+    return ret;
+}
+
+static char *game_text_format(game_state *state)
+{
+    char *ret, *p, buf[80];
+    int i, x, y, col, maxlen;
+
+    /*
+     * First determine the number of spaces required to display a
+     * number. We'll use at least two, because one looks a bit
+     * silly.
+     */
+    col = 2;
+    for (i = 0; i < state->w * state->h; i++) {
+       x = sprintf(buf, "%d", state->grid[i]);
+       if (col < x) col = x;
+    }
+
+    /*
+     * Now we know the exact total size of the grid we're going to
+     * produce: it's got 2*h+1 rows, each containing w lots of col,
+     * w+1 boundary characters and a trailing newline.
+     */
+    maxlen = (2*state->h+1) * (state->w * (col+1) + 2);
+
+    ret = snewn(maxlen+1, char);
+    p = ret;
+
+    for (y = 0; y <= 2*state->h; y++) {
+       for (x = 0; x <= 2*state->w; x++) {
+           if (x & y & 1) {
+               /*
+                * Display a number.
+                */
+               int v = grid(state, x/2, y/2);
+               if (v)
+                   sprintf(buf, "%*d", col, v);
+               else
+                   sprintf(buf, "%*s", col, "");
+               memcpy(p, buf, col);
+               p += col;
+           } else if (x & 1) {
+               /*
+                * Display a horizontal edge or nothing.
+                */
+               int h = (y==0 || y==2*state->h ? 1 :
+                        HRANGE(state, x/2, y/2) && hedge(state, x/2, y/2));
+               int i;
+               if (h)
+                   h = '-';
+               else
+                   h = ' ';
+               for (i = 0; i < col; i++)
+                   *p++ = h;
+           } else if (y & 1) {
+               /*
+                * Display a vertical edge or nothing.
+                */
+               int v = (x==0 || x==2*state->w ? 1 :
+                        VRANGE(state, x/2, y/2) && vedge(state, x/2, y/2));
+               if (v)
+                   *p++ = '|';
+               else
+                   *p++ = ' ';
+           } else {
+               /*
+                * Display a corner, or a vertical edge, or a
+                * horizontal edge, or nothing.
+                */
+               int hl = (y==0 || y==2*state->h ? 1 :
+                         HRANGE(state, (x-1)/2, y/2) && hedge(state, (x-1)/2, y/2));
+               int hr = (y==0 || y==2*state->h ? 1 :
+                         HRANGE(state, (x+1)/2, y/2) && hedge(state, (x+1)/2, y/2));
+               int vu = (x==0 || x==2*state->w ? 1 :
+                         VRANGE(state, x/2, (y-1)/2) && vedge(state, x/2, (y-1)/2));
+               int vd = (x==0 || x==2*state->w ? 1 :
+                         VRANGE(state, x/2, (y+1)/2) && vedge(state, x/2, (y+1)/2));
+               if (!hl && !hr && !vu && !vd)
+                   *p++ = ' ';
+               else if (hl && hr && !vu && !vd)
+                   *p++ = '-';
+               else if (!hl && !hr && vu && vd)
+                   *p++ = '|';
+               else
+                   *p++ = '+';
+           }
+       }
+       *p++ = '\n';
+    }
+
+    assert(p - ret == maxlen);
+    *p = '\0';
+    return ret;
+}
+
 static unsigned char *get_correct(game_state *state)
 {
     unsigned char *ret;
@@ -1112,7 +1268,7 @@ struct game_ui {
     int dragged;
 };
 
-game_ui *new_ui(game_state *state)
+static game_ui *new_ui(game_state *state)
 {
     game_ui *ui = snew(game_ui);
     ui->drag_start_x = -1;
@@ -1123,12 +1279,12 @@ game_ui *new_ui(game_state *state)
     return ui;
 }
 
-void free_ui(game_ui *ui)
+static void free_ui(game_ui *ui)
 {
     sfree(ui);
 }
 
-void coord_round(float x, float y, int *xr, int *yr)
+static void coord_round(float x, float y, int *xr, int *yr)
 {
     float xs, ys, xv, yv, dx, dy, dist;
 
@@ -1259,7 +1415,8 @@ static void ui_draw_rect(game_state *state, game_ui *ui,
             }
 }
 
-game_state *make_move(game_state *from, game_ui *ui, int x, int y, int button)
+static game_state *make_move(game_state *from, game_ui *ui,
+                            int x, int y, int button)
 {
     int xc, yc;
     int startdrag = FALSE, enddrag = FALSE, active = FALSE;
@@ -1368,13 +1525,13 @@ struct game_drawstate {
     unsigned int *visible;
 };
 
-void game_size(game_params *params, int *x, int *y)
+static void game_size(game_params *params, int *x, int *y)
 {
     *x = params->w * TILE_SIZE + 2*BORDER + 1;
     *y = params->h * TILE_SIZE + 2*BORDER + 1;
 }
 
-float *game_colours(frontend *fe, game_state *state, int *ncolours)
+static float *game_colours(frontend *fe, game_state *state, int *ncolours)
 {
     float *ret = snewn(3 * NCOLOURS, float);
 
@@ -1404,7 +1561,7 @@ float *game_colours(frontend *fe, game_state *state, int *ncolours)
     return ret;
 }
 
-game_drawstate *game_new_drawstate(game_state *state)
+static game_drawstate *game_new_drawstate(game_state *state)
 {
     struct game_drawstate *ds = snew(struct game_drawstate);
     int i;
@@ -1419,13 +1576,13 @@ game_drawstate *game_new_drawstate(game_state *state)
     return ds;
 }
 
-void game_free_drawstate(game_drawstate *ds)
+static void game_free_drawstate(game_drawstate *ds)
 {
     sfree(ds->visible);
     sfree(ds);
 }
 
-void draw_tile(frontend *fe, game_state *state, int x, int y,
+static void draw_tile(frontend *fe, game_state *state, int x, int y,
                unsigned char *hedge, unsigned char *vedge,
               unsigned char *corners, int correct)
 {
@@ -1481,7 +1638,7 @@ void draw_tile(frontend *fe, game_state *state, int x, int y,
     draw_update(fe, cx, cy, TILE_SIZE+1, TILE_SIZE+1);
 }
 
-void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
+static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
                  game_state *state, int dir, game_ui *ui,
                  float animtime, float flashtime)
 {
@@ -1573,19 +1730,57 @@ void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate,
     sfree(correct);
 }
 
-float game_anim_length(game_state *oldstate, game_state *newstate, int dir)
+static float game_anim_length(game_state *oldstate,
+                             game_state *newstate, int dir)
 {
     return 0.0F;
 }
 
-float game_flash_length(game_state *oldstate, game_state *newstate, int dir)
+static float game_flash_length(game_state *oldstate,
+                              game_state *newstate, int dir)
 {
-    if (!oldstate->completed && newstate->completed)
+    if (!oldstate->completed && newstate->completed &&
+       !oldstate->cheated && !newstate->cheated)
         return FLASH_TIME;
     return 0.0F;
 }
 
-int game_wants_statusbar(void)
+static int game_wants_statusbar(void)
 {
     return FALSE;
 }
+
+#ifdef COMBINED
+#define thegame rect
+#endif
+
+const struct game thegame = {
+    "Rectangles", "games.rectangles",
+    default_params,
+    game_fetch_preset,
+    decode_params,
+    encode_params,
+    free_params,
+    dup_params,
+    TRUE, game_configure, custom_params,
+    validate_params,
+    new_game_seed,
+    game_free_aux_info,
+    validate_seed,
+    new_game,
+    dup_game,
+    free_game,
+    TRUE, solve_game,
+    TRUE, game_text_format,
+    new_ui,
+    free_ui,
+    make_move,
+    game_size,
+    game_colours,
+    game_new_drawstate,
+    game_free_drawstate,
+    game_redraw,
+    game_anim_length,
+    game_flash_length,
+    game_wants_statusbar,
+};