X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/86e60e3d907db72ccb60783b2bdab4fc3890f169..cccb7f09e28f348e9273467cc8e410ad13283165:/tents.c diff --git a/tents.c b/tents.c index 113fa17..e5e8c6f 100644 --- a/tents.c +++ b/tents.c @@ -415,8 +415,12 @@ static game_params *custom_params(config_item *cfg) static char *validate_params(game_params *params, int full) { - if (params->w < 2 || params->h < 2) - return "Width and height must both be at least two"; + /* + * Generating anything under 4x4 runs into trouble of one kind + * or another. + */ + if (params->w < 4 || params->h < 4) + return "Width and height must both be at least four"; return NULL; } @@ -875,7 +879,7 @@ static int tents_solve(int w, int h, const char *grid, int *numbers, printf("%s %d forces %s at %d,%d\n", step==1 ? "row" : "column", step==1 ? start/w : start, - mrow[j] == TENT ? "tent" : "non-tent", + mthis[j] == TENT ? "tent" : "non-tent", pos % w, pos / w); #endif soln[pos] = mthis[j]; @@ -916,7 +920,7 @@ static char *new_game_desc(game_params *params, random_state *rs, char *puzzle = snewn(w*h, char); int *numbers = snewn(w+h, int); char *soln = snewn(w*h, char); - int *temp = snewn(2*w*h, int), *itemp = temp + w*h; + int *temp = snewn(2*w*h, int); int maxedges = ntrees*4 + w*h; int *edges = snewn(2*maxedges, int); int *capacity = snewn(maxedges, int); @@ -962,9 +966,9 @@ static char *new_game_desc(game_params *params, random_state *rs, * The maxflow algorithm is not randomised, so employed naively * it would give rise to grids with clear structure and * directional bias. Hence, I assign the network nodes as seen - * by maxflow to be a _random_ permutation the squares of the - * grid, so that any bias shown by maxflow towards low-numbered - * nodes is turned into a random bias. + * by maxflow to be a _random_ permutation of the squares of + * the grid, so that any bias shown by maxflow towards + * low-numbered nodes is turned into a random bias. * * This generation strategy can fail at many points, including * as early as tent placement (if you get a bad random order in @@ -979,16 +983,16 @@ static char *new_game_desc(game_params *params, random_state *rs, * trouble. */ + if (params->diff > DIFF_EASY && params->w <= 4 && params->h <= 4) + params->diff = DIFF_EASY; /* downgrade to prevent tight loop */ + while (1) { /* - * Arrange the grid squares into a random order, and invert - * that order so we can find a square's index as well. + * Arrange the grid squares into a random order. */ for (i = 0; i < w*h; i++) temp[i] = i; shuffle(temp, w*h, sizeof(*temp), rs); - for (i = 0; i < w*h; i++) - itemp[temp[i]] = i; /* * The first `ntrees' entries in temp which we can get @@ -1400,13 +1404,26 @@ static char *game_text_format(game_state *state) return ret; } +struct game_ui { + int dsx, dsy; /* coords of drag start */ + int dex, dey; /* coords of drag end */ + int drag_button; /* -1 for none, or a button code */ + int drag_ok; /* dragged off the window, to cancel */ +}; + static game_ui *new_ui(game_state *state) { - return NULL; + game_ui *ui = snew(game_ui); + ui->dsx = ui->dsy = -1; + ui->dex = ui->dey = -1; + ui->drag_button = -1; + ui->drag_ok = FALSE; + return ui; } static void free_ui(game_ui *ui) { + sfree(ui); } static char *encode_ui(game_ui *ui) @@ -1439,32 +1456,151 @@ struct game_drawstate { #define FLASH_TIME 0.30F +static int drag_xform(game_ui *ui, int x, int y, int v) +{ + int xmin, ymin, xmax, ymax; + + xmin = min(ui->dsx, ui->dex); + xmax = max(ui->dsx, ui->dex); + ymin = min(ui->dsy, ui->dey); + ymax = max(ui->dsy, ui->dey); + + /* + * Left-dragging has no effect, so we treat a left-drag as a + * single click on dsx,dsy. + */ + if (ui->drag_button == LEFT_BUTTON) { + xmin = xmax = ui->dsx; + ymin = ymax = ui->dsy; + } + + if (x < xmin || x > xmax || y < ymin || y > ymax) + return v; /* no change outside drag area */ + + if (v == TREE) + return v; /* trees are inviolate always */ + + if (xmin == xmax && ymin == ymax) { + /* + * Results of a simple click. Left button sets blanks to + * tents; right button sets blanks to non-tents; either + * button clears a non-blank square. + */ + if (ui->drag_button == LEFT_BUTTON) + v = (v == BLANK ? TENT : BLANK); + else + v = (v == BLANK ? NONTENT : BLANK); + } else { + /* + * Results of a drag. Left-dragging has no effect. + * Right-dragging sets all blank squares to non-tents and + * has no effect on anything else. + */ + if (ui->drag_button == RIGHT_BUTTON) + v = (v == BLANK ? NONTENT : v); + else + /* do nothing */; + } + + return v; +} + static char *interpret_move(game_state *state, game_ui *ui, game_drawstate *ds, int x, int y, int button) { int w = state->p.w, h = state->p.h; if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { - int v; - char buf[80]; - x = FROMCOORD(x); y = FROMCOORD(y); if (x < 0 || y < 0 || x >= w || y >= h) return NULL; - if (state->grid[y*w+x] == TREE) - return NULL; + ui->drag_button = button; + ui->dsx = ui->dex = x; + ui->dsy = ui->dey = y; + ui->drag_ok = TRUE; + return ""; /* ui updated */ + } - if (button == LEFT_BUTTON) { - v = (state->grid[y*w+x] == BLANK ? TENT : BLANK); + if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) && + ui->drag_button > 0) { + int xmin, ymin, xmax, ymax; + char *buf, *sep, tmpbuf[80]; + int buflen, bufsize, tmplen; + + x = FROMCOORD(x); + y = FROMCOORD(y); + if (x < 0 || y < 0 || x >= w || y >= h) { + ui->drag_ok = FALSE; } else { - v = (state->grid[y*w+x] == BLANK ? NONTENT : BLANK); + /* + * Drags are limited to one row or column. Hence, we + * work out which coordinate is closer to the drag + * start, and move it _to_ the drag start. + */ + if (abs(x - ui->dsx) < abs(y - ui->dsy)) + x = ui->dsx; + else + y = ui->dsy; + + ui->dex = x; + ui->dey = y; + + ui->drag_ok = TRUE; + } + + if (IS_MOUSE_DRAG(button)) + return ""; /* ui updated */ + + /* + * The drag has been released. Enact it. + */ + if (!ui->drag_ok) { + ui->drag_button = -1; + return ""; /* drag was just cancelled */ } - sprintf(buf, "%c%d,%d", (int)(v==BLANK ? 'B' : - v==TENT ? 'T' : 'N'), x, y); - return dupstr(buf); + xmin = min(ui->dsx, ui->dex); + xmax = max(ui->dsx, ui->dex); + ymin = min(ui->dsy, ui->dey); + ymax = max(ui->dsy, ui->dey); + assert(0 <= xmin && xmin <= xmax && xmax < w); + assert(0 <= ymin && ymin <= ymax && ymax < h); + + buflen = 0; + bufsize = 256; + buf = snewn(bufsize, char); + sep = ""; + for (y = ymin; y <= ymax; y++) + for (x = xmin; x <= xmax; x++) { + int v = drag_xform(ui, x, y, state->grid[y*w+x]); + if (state->grid[y*w+x] != v) { + tmplen = sprintf(tmpbuf, "%s%c%d,%d", sep, + (int)(v == BLANK ? 'B' : + v == TENT ? 'T' : 'N'), + x, y); + sep = ";"; + + if (buflen + tmplen >= bufsize) { + bufsize = buflen + tmplen + 256; + buf = sresize(buf, bufsize, char); + } + + strcpy(buf+buflen, tmpbuf); + buflen += tmplen; + } + } + + ui->drag_button = -1; /* drag is terminated */ + + if (buflen == 0) { + sfree(buf); + return ""; /* ui updated (drag was terminated) */ + } else { + buf[buflen] = '\0'; + return buf; + } } return NULL; @@ -1674,7 +1810,7 @@ static void game_set_size(drawing *dr, game_drawstate *ds, ds->tilesize = tilesize; } -static float *game_colours(frontend *fe, game_state *state, int *ncolours) +static float *game_colours(frontend *fe, int *ncolours) { float *ret = snewn(3 * NCOLOURS, float); @@ -1834,6 +1970,15 @@ static void int_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, for (x = 0; x < w; x++) { int v = state->grid[y*w+x]; + /* + * We deliberately do not take drag_ok into account + * here, because user feedback suggests that it's + * marginally nicer not to have the drag effects + * flickering on and off disconcertingly. + */ + if (ui && ui->drag_button >= 0) + v = drag_xform(ui, x, y, v); + if (flashing && (v == TREE || v == TENT)) v = NONTENT; @@ -1868,11 +2013,6 @@ static float game_flash_length(game_state *oldstate, game_state *newstate, return 0.0F; } -static int game_wants_statusbar(void) -{ - return FALSE; -} - static int game_timing_state(game_state *state, game_ui *ui) { return TRUE; @@ -1913,7 +2053,7 @@ static void game_print(drawing *dr, game_state *state, int tilesize) #endif const struct game thegame = { - "Tents", "games.tents", + "Tents", "games.tents", "tents", default_params, game_fetch_preset, decode_params, @@ -1944,9 +2084,9 @@ const struct game thegame = { game_anim_length, game_flash_length, TRUE, FALSE, game_print_size, game_print, - game_wants_statusbar, + FALSE, /* wants_statusbar */ FALSE, game_timing_state, - 0, /* mouse_priorities */ + 0, /* flags */ }; #ifdef STANDALONE_SOLVER