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;
}
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];
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);
* 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
* 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
}
}
+static int game_can_format_as_text_now(game_params *params)
+{
+ return TRUE;
+}
+
static char *game_text_format(game_state *state)
{
int w = state->p.w, h = state->p.h;
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 */
+
+ int cx, cy, cdisp; /* cursor position, and ?display. */
+};
+
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;
+ ui->cx = ui->cy = ui->cdisp = 0;
+ return ui;
}
static void free_ui(game_ui *ui)
{
+ sfree(ui);
}
static char *encode_ui(game_ui *ui)
int started;
game_params p;
char *drawn;
+ int cx, cy; /* last-drawn cursor pos, or (-1,-1) if absent. */
};
#define PREFERRED_TILESIZE 32
#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;
+ char tmpbuf[80];
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;
+ ui->cdisp = 0;
+ return ""; /* ui updated */
+ }
+
+ if ((IS_MOUSE_DRAG(button) || IS_MOUSE_RELEASE(button)) &&
+ ui->drag_button > 0) {
+ int xmin, ymin, xmax, ymax;
+ char *buf, *sep;
+ int buflen, bufsize, tmplen;
+
+ x = FROMCOORD(x);
+ y = FROMCOORD(y);
+ if (x < 0 || y < 0 || x >= w || y >= h) {
+ ui->drag_ok = FALSE;
+ } else {
+ /*
+ * 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 */
+ }
+
+ 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 (button == LEFT_BUTTON) {
- v = (state->grid[y*w+x] == BLANK ? TENT : BLANK);
+ if (buflen == 0) {
+ sfree(buf);
+ return ""; /* ui updated (drag was terminated) */
} else {
- v = (state->grid[y*w+x] == BLANK ? NONTENT : BLANK);
+ buf[buflen] = '\0';
+ return buf;
+ }
+ }
+
+ if (IS_CURSOR_MOVE(button)) {
+ move_cursor(button, &ui->cx, &ui->cy, w, h, 0);
+ ui->cdisp = 1;
+ return "";
+ }
+ if (ui->cdisp) {
+ char rep = 0;
+ int v = state->grid[ui->cy*w+ui->cx];
+
+ if (v != TREE) {
+#ifdef SINGLE_CURSOR_SELECT
+ if (button == CURSOR_SELECT)
+ /* SELECT cycles T, N, B */
+ rep = v == BLANK ? 'T' : v == TENT ? 'N' : 'B';
+#else
+ if (button == CURSOR_SELECT)
+ rep = v == BLANK ? 'T' : 'B';
+ else if (button == CURSOR_SELECT2)
+ rep = v == BLANK ? 'N' : 'B';
+ else if (button == 'T' || button == 'N' || button == 'B')
+ rep = (char)button;
+#endif
}
- sprintf(buf, "%c%d,%d", (int)(v==BLANK ? 'B' :
- v==TENT ? 'T' : 'N'), x, y);
- return dupstr(buf);
+ if (rep) {
+ sprintf(tmpbuf, "%c%d,%d", (int)rep, ui->cx, ui->cy);
+ return dupstr(tmpbuf);
+ }
+ } else if (IS_CURSOR_SELECT(button)) {
+ ui->cdisp = 1;
+ return "";
}
return NULL;
int *x, int *y)
{
/* fool the macros */
- struct dummy { int tilesize; } dummy = { tilesize }, *ds = &dummy;
+ struct dummy { int tilesize; } dummy, *ds = &dummy;
+ dummy.tilesize = tilesize;
*x = TLBORDER + BRBORDER + TILESIZE * params->w;
*y = TLBORDER + BRBORDER + TILESIZE * params->h;
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);
ds->p = state->p; /* structure copy */
ds->drawn = snewn(w*h, char);
memset(ds->drawn, MAGIC, w*h);
+ ds->cx = ds->cy = -1;
return ds;
}
}
static void draw_tile(drawing *dr, game_drawstate *ds,
- int x, int y, int v, int printing)
+ int x, int y, int v, int cur, int printing)
{
int tx = COORD(x), ty = COORD(y);
int cx = tx + TILESIZE/2, cy = ty + TILESIZE/2;
draw_polygon(dr, coords, 3, (printing ? -1 : COL_TENT), COL_TENT);
}
+ if (cur) {
+ int coff = TILESIZE/8;
+ draw_rect_outline(dr, tx + coff, ty + coff,
+ TILESIZE - coff*2 + 1, TILESIZE - coff*2 + 1,
+ COL_GRID);
+ }
+
unclip(dr);
draw_update(dr, tx+1, ty+1, TILESIZE-1, TILESIZE-1);
}
{
int w = state->p.w, h = state->p.h;
int x, y, flashing;
+ int cx = -1, cy = -1;
+ int cmoved = 0;
+
+ if (ui) {
+ if (ui->cdisp) { cx = ui->cx; cy = ui->cy; }
+ if (cx != ds->cx || cy != ds->cy) cmoved = 1;
+ }
if (printing || !ds->started) {
if (!printing) {
for (y = 0; y < h; y++)
for (x = 0; x < w; x++) {
int v = state->grid[y*w+x];
+ int credraw = 0;
+
+ /*
+ * 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;
- if (printing || ds->drawn[y*w+x] != v) {
- draw_tile(dr, ds, x, y, v, printing);
+ if (cmoved) {
+ if ((x == cx && y == cy) ||
+ (x == ds->cx && y == ds->cy)) credraw = 1;
+ }
+
+ if (printing || ds->drawn[y*w+x] != v || credraw) {
+ draw_tile(dr, ds, x, y, v, (x == cx && y == cy), printing);
if (!printing)
ds->drawn[y*w+x] = v;
}
}
+ if (cmoved) { ds->cx = cx; ds->cy = cy; }
}
static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate,
return 0.0F;
}
-static int game_wants_statusbar(void)
-{
- return FALSE;
-}
-
static int game_timing_state(game_state *state, game_ui *ui)
{
return TRUE;
* I'll use 6mm squares by default.
*/
game_compute_size(params, 600, &pw, &ph);
- *x = pw / 100.0;
- *y = ph / 100.0;
+ *x = pw / 100.0F;
+ *y = ph / 100.0F;
}
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,
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,
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 */
+ REQUIRE_RBUTTON, /* flags */
};
#ifdef STANDALONE_SOLVER
}
#endif
+
+/* vim: set shiftwidth=4 tabstop=8: */