X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ian/git?a=blobdiff_plain;f=net.c;h=349b13dfb216edfde678f50cf8d701c4ead2e91e;hb=3ce69e84cad15844282d691fa03e711c5353c05e;hp=ad970b6a2d783edf53f631ea96f036250f451347;hpb=a3b837c69845e5ccfd3bc29ee72c9b3e6ea9adec;p=sgt-puzzles.git diff --git a/net.c b/net.c index ad970b6..349b13d 100644 --- a/net.c +++ b/net.c @@ -12,6 +12,21 @@ #include "puzzles.h" #include "tree234.h" +/* + * The standard user interface for Net simply has left- and + * right-button mouse clicks in a square rotate it one way or the + * other. We also provide, by #ifdef, a separate interface based on + * rotational dragging motions. I initially developed this for the + * Mac on the basis that it might work better than the click + * interface with only one mouse button available, but in fact + * found it to be quite strange and unintuitive. Apparently it + * works better on stylus-driven platforms such as Palm and + * PocketPC, though, so we enable it by default there. + */ +#ifdef STYLUS_BASED +#define USE_DRAGGING +#endif + #define MATMUL(xr,yr,m,x,y) do { \ float rx, ry, xx = (x), yy = (y), *mat = (m); \ rx = mat[0] * xx + mat[2] * yy; \ @@ -26,6 +41,11 @@ #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) ) @@ -46,7 +66,11 @@ #define PREFERRED_TILE_SIZE 32 #define TILE_SIZE (ds->tilesize) #define TILE_BORDER 1 +#ifdef SMALL_SCREEN +#define WINDOW_OFFSET 4 +#else #define WINDOW_OFFSET 16 +#endif #define ROTATE_TIME 0.13F #define FLASH_FRAME 0.07F @@ -66,6 +90,7 @@ enum { COL_ENDPOINT, COL_POWERED, COL_BARRIER, + COL_LOOP, NCOLOURS }; @@ -150,12 +175,16 @@ static const struct game_params net_presets[] = { {7, 7, FALSE, TRUE, 0.0}, {9, 9, FALSE, TRUE, 0.0}, {11, 11, FALSE, TRUE, 0.0}, +#ifndef SMALL_SCREEN {13, 11, FALSE, TRUE, 0.0}, +#endif {5, 5, TRUE, TRUE, 0.0}, {7, 7, TRUE, TRUE, 0.0}, {9, 9, TRUE, TRUE, 0.0}, {11, 11, TRUE, TRUE, 0.0}, +#ifndef SMALL_SCREEN {13, 11, TRUE, TRUE, 0.0}, +#endif }; static int game_fetch_preset(int i, char **name, game_params **params) @@ -182,7 +211,7 @@ static void free_params(game_params *params) sfree(params); } -static game_params *dup_params(game_params *params) +static game_params *dup_params(const game_params *params) { game_params *ret = snew(game_params); *ret = *params; /* structure copy */ @@ -209,7 +238,7 @@ static void decode_params(game_params *ret, char const *string) ret->wrapping = TRUE; } else if (*p == 'b') { p++; - ret->barrier_probability = atof(p); + ret->barrier_probability = (float)atof(p); while (*p && (*p == '.' || isdigit((unsigned char)*p))) p++; } else if (*p == 'a') { p++; @@ -219,7 +248,7 @@ static void decode_params(game_params *ret, char const *string) } } -static char *encode_params(game_params *params, int full) +static char *encode_params(const game_params *params, int full) { char ret[400]; int len; @@ -237,7 +266,7 @@ static char *encode_params(game_params *params, int full) return dupstr(ret); } -static config_item *game_configure(game_params *params) +static config_item *game_configure(const game_params *params) { config_item *ret; char buf[80]; @@ -280,7 +309,7 @@ static config_item *game_configure(game_params *params) return ret; } -static game_params *custom_params(config_item *cfg) +static game_params *custom_params(const config_item *cfg) { game_params *ret = snew(game_params); @@ -293,7 +322,7 @@ static game_params *custom_params(config_item *cfg) return ret; } -static char *validate_params(game_params *params, int full) +static char *validate_params(const game_params *params, int full) { if (params->width <= 0 || params->height <= 0) return "Width and height must both be greater than zero"; @@ -521,9 +550,7 @@ static int net_solver(int w, int h, unsigned char *tiles, * classes) by finding the representative of each tile and * setting equivalence[one]=the_other. */ - equivalence = snewn(w * h, int); - for (i = 0; i < w*h; i++) - equivalence[i] = i; /* initially all distinct */ + equivalence = snew_dsf(w * h); /* * On a non-wrapping grid, we instantly know that all the edges @@ -929,8 +956,10 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping, } sfree(perim2); - if (i == nperim) + if (i == nperim) { + sfree(perimeter); return; /* nothing we can do! */ + } /* * Now we've constructed a new link, we need to find the entire @@ -1103,7 +1132,11 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping, sfree(perimeter); } -static char *new_game_desc(game_params *params, random_state *rs, +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) { tree234 *possibilities, *barriertree; @@ -1382,13 +1415,98 @@ static char *new_game_desc(game_params *params, random_state *rs, /* * Now shuffle the grid. + * + * In order to avoid accidentally generating an already-solved + * grid, we will reshuffle as necessary to ensure that at least + * one edge has a mismatched connection. + * + * This can always be done, since validate_params() enforces a + * grid area of at least 2 and our generator never creates + * either type of rotationally invariant tile (cross and + * blank). Hence there must be at least one edge separating + * distinct tiles, and it must be possible to find orientations + * of those tiles such that one tile is trying to connect + * through that edge and the other is not. + * + * (We could be more subtle, and allow the shuffle to generate + * a grid in which all tiles match up locally and the only + * criterion preventing the grid from being already solved is + * 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.) */ - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - int orig = index(params, tiles, x, y); - int rot = random_upto(rs, 4); - index(params, tiles, x, y) = ROT(orig, rot); - } + while (1) { + 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); + int rot = random_upto(rs, 4); + index(params, tiles, x, y) = ROT(orig, rot); + } + } + + /* + * 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 + * a wrapping edge, so I'm just going to enforce that there + * must be a mismatch across a non-wrapping edge, which is + * still always possible. + */ + for (y = 0; y < h; y++) for (x = 0; x < w; x++) { + if (x+1 < w && ((ROT(index(params, tiles, x, y), 2) ^ + index(params, tiles, x+1, y)) & L)) + mismatches++; + if (y+1 < h && ((ROT(index(params, tiles, x, y), 2) ^ + index(params, tiles, x, y+1)) & U)) + mismatches++; + } + + if (mismatches == 0) + continue; + + /* OK. */ + break; } /* @@ -1480,7 +1598,7 @@ static char *new_game_desc(game_params *params, random_state *rs, return desc; } -static char *validate_desc(game_params *params, char *desc) +static char *validate_desc(const game_params *params, const char *desc) { int w = params->width, h = params->height; int i; @@ -1510,7 +1628,8 @@ static char *validate_desc(game_params *params, char *desc) * Construct an initial game state, given a description and parameters. */ -static game_state *new_game(midend *me, game_params *params, char *desc) +static game_state *new_game(midend *me, const game_params *params, + const char *desc) { game_state *state; int w, h, x, y; @@ -1587,7 +1706,7 @@ static game_state *new_game(midend *me, game_params *params, char *desc) if (!(barrier(state, x, 0) & U) || !(barrier(state, x, state->height-1) & D)) state->wrapping = TRUE; - for (y = 0; y < state->width; y++) + for (y = 0; y < state->height; y++) if (!(barrier(state, 0, y) & L) || !(barrier(state, state->width-1, y) & R)) state->wrapping = TRUE; @@ -1596,7 +1715,7 @@ static game_state *new_game(midend *me, game_params *params, char *desc) return state; } -static game_state *dup_game(game_state *state) +static game_state *dup_game(const game_state *state) { game_state *ret; @@ -1624,8 +1743,8 @@ static void free_game(game_state *state) sfree(state); } -static char *solve_game(game_state *state, game_state *currstate, - char *aux, char **error) +static char *solve_game(const game_state *state, const game_state *currstate, + const char *aux, char **error) { unsigned char *tiles; char *ret; @@ -1719,7 +1838,12 @@ static char *solve_game(game_state *state, game_state *currstate, return ret; } -static char *game_text_format(game_state *state) +static int game_can_format_as_text_now(const game_params *params) +{ + return TRUE; +} + +static char *game_text_format(const game_state *state) { return NULL; } @@ -1735,7 +1859,7 @@ static char *game_text_format(game_state *state) * completed - just call this function and see whether every square * is marked active. */ -static unsigned char *compute_active(game_state *state, int cx, int cy) +static unsigned char *compute_active(const game_state *state, int cx, int cy) { unsigned char *active; tree234 *todo; @@ -1785,15 +1909,99 @@ static unsigned char *compute_active(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) */ int cur_x, cur_y; int cur_visible; random_state *rs; /* used for jumbling */ +#ifdef USE_DRAGGING + int dragtilex, dragtiley, dragstartx, dragstarty, dragged; +#endif }; -static game_ui *new_ui(game_state *state) +static game_ui *new_ui(const game_state *state) { void *seed; int seedsize; @@ -1815,7 +2023,7 @@ static void free_ui(game_ui *ui) sfree(ui); } -static char *encode_ui(game_ui *ui) +static char *encode_ui(const game_ui *ui) { char buf[120]; /* @@ -1826,14 +2034,14 @@ static char *encode_ui(game_ui *ui) return dupstr(buf); } -static void decode_ui(game_ui *ui, char *encoding) +static void decode_ui(game_ui *ui, const char *encoding) { sscanf(encoding, "O%d,%d;C%d,%d", &ui->org_x, &ui->org_y, &ui->cx, &ui->cy); } -static void game_changed_state(game_ui *ui, game_state *oldstate, - game_state *newstate) +static void game_changed_state(game_ui *ui, const game_state *oldstate, + const game_state *newstate) { } @@ -1842,14 +2050,15 @@ struct game_drawstate { int width, height; int org_x, org_y; int tilesize; - unsigned char *visible; + int *visible; }; /* ---------------------------------------------------------------------- * Process a move. */ -static char *interpret_move(game_state *state, game_ui *ui, - game_drawstate *ds, int x, int y, int button) +static char *interpret_move(const game_state *state, game_ui *ui, + const game_drawstate *ds, + int x, int y, int button) { char *nullret; int tx = -1, ty = -1, dir = 0; @@ -1865,6 +2074,12 @@ static char *interpret_move(game_state *state, game_ui *ui, if (button == LEFT_BUTTON || button == MIDDLE_BUTTON || +#ifdef USE_DRAGGING + button == LEFT_DRAG || + button == LEFT_RELEASE || + button == RIGHT_DRAG || + button == RIGHT_RELEASE || +#endif button == RIGHT_BUTTON) { if (ui->cur_visible) { @@ -1890,10 +2105,113 @@ static char *interpret_move(game_state *state, game_ui *ui, y % TILE_SIZE >= TILE_SIZE - TILE_BORDER) return nullret; - action = button == LEFT_BUTTON ? ROTATE_LEFT : - button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK; - } else if (button == CURSOR_UP || button == CURSOR_DOWN || - button == CURSOR_RIGHT || button == CURSOR_LEFT) { +#ifdef USE_DRAGGING + + if (button == MIDDLE_BUTTON +#ifdef STYLUS_BASED + || button == RIGHT_BUTTON /* with a stylus, `right-click' locks */ +#endif + ) { + /* + * Middle button never drags: it only toggles the lock. + */ + action = TOGGLE_LOCK; + } else if (button == LEFT_BUTTON +#ifndef STYLUS_BASED + || button == RIGHT_BUTTON /* (see above) */ +#endif + ) { + /* + * Otherwise, we note down the start point for a drag. + */ + ui->dragtilex = tx; + ui->dragtiley = ty; + ui->dragstartx = x % TILE_SIZE; + ui->dragstarty = y % TILE_SIZE; + ui->dragged = FALSE; + return nullret; /* no actual action */ + } else if (button == LEFT_DRAG +#ifndef STYLUS_BASED + || button == RIGHT_DRAG +#endif + ) { + /* + * Find the new drag point and see if it necessitates a + * rotation. + */ + int x0,y0, xA,yA, xC,yC, xF,yF; + int mx, my; + int d0, dA, dC, dF, dmin; + + tx = ui->dragtilex; + ty = ui->dragtiley; + + mx = x - (ui->dragtilex * TILE_SIZE); + my = y - (ui->dragtiley * TILE_SIZE); + + x0 = ui->dragstartx; + y0 = ui->dragstarty; + xA = ui->dragstarty; + yA = TILE_SIZE-1 - ui->dragstartx; + xF = TILE_SIZE-1 - ui->dragstartx; + yF = TILE_SIZE-1 - ui->dragstarty; + xC = TILE_SIZE-1 - ui->dragstarty; + yC = ui->dragstartx; + + d0 = (mx-x0)*(mx-x0) + (my-y0)*(my-y0); + dA = (mx-xA)*(mx-xA) + (my-yA)*(my-yA); + dF = (mx-xF)*(mx-xF) + (my-yF)*(my-yF); + dC = (mx-xC)*(mx-xC) + (my-yC)*(my-yC); + + dmin = min(min(d0,dA),min(dF,dC)); + + if (d0 == dmin) { + return nullret; + } else if (dF == dmin) { + action = ROTATE_180; + ui->dragstartx = xF; + ui->dragstarty = yF; + ui->dragged = TRUE; + } else if (dA == dmin) { + action = ROTATE_LEFT; + ui->dragstartx = xA; + ui->dragstarty = yA; + ui->dragged = TRUE; + } else /* dC == dmin */ { + action = ROTATE_RIGHT; + ui->dragstartx = xC; + ui->dragstarty = yC; + ui->dragged = TRUE; + } + } else if (button == LEFT_RELEASE +#ifndef STYLUS_BASED + || button == RIGHT_RELEASE +#endif + ) { + if (!ui->dragged) { + /* + * There was a click but no perceptible drag: + * revert to single-click behaviour. + */ + tx = ui->dragtilex; + ty = ui->dragtiley; + + if (button == LEFT_RELEASE) + action = ROTATE_LEFT; + else + action = ROTATE_RIGHT; + } else + return nullret; /* no action */ + } + +#else /* USE_DRAGGING */ + + action = (button == LEFT_BUTTON ? ROTATE_LEFT : + button == RIGHT_BUTTON ? ROTATE_RIGHT : TOGGLE_LOCK); + +#endif /* USE_DRAGGING */ + + } else if (IS_CURSOR_MOVE(button)) { switch (button) { case CURSOR_UP: dir = U; break; case CURSOR_DOWN: dir = D; break; @@ -1908,12 +2226,12 @@ static char *interpret_move(game_state *state, game_ui *ui, } else if (button == 'a' || button == 's' || button == 'd' || button == 'A' || button == 'S' || button == 'D' || button == 'f' || button == 'F' || - button == CURSOR_SELECT) { + IS_CURSOR_SELECT(button)) { tx = ui->cur_x; ty = ui->cur_y; if (button == 'a' || button == 'A' || button == CURSOR_SELECT) action = ROTATE_LEFT; - else if (button == 's' || button == 'S') + else if (button == 's' || button == 'S' || button == CURSOR_SELECT2) action = TOGGLE_LOCK; else if (button == 'd' || button == 'D') action = ROTATE_RIGHT; @@ -2012,10 +2330,10 @@ static char *interpret_move(game_state *state, game_ui *ui, } } -static game_state *execute_move(game_state *from, char *move) +static game_state *execute_move(const game_state *from, const char *move) { game_state *ret; - int tx, ty, n, noanim, orig; + int tx = -1, ty = -1, n, noanim, orig; ret = dup_game(from); @@ -2064,6 +2382,7 @@ static game_state *execute_move(game_state *from, char *move) } } if (!noanim) { + if (tx == -1 || ty == -1) { free_game(ret); return NULL; } ret->last_rotate_x = tx; ret->last_rotate_y = ty; } @@ -2102,17 +2421,19 @@ static game_state *execute_move(game_state *from, char *move) * Routines for drawing the game position on the screen. */ -static game_drawstate *game_new_drawstate(drawing *dr, game_state *state) +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; } @@ -2123,15 +2444,15 @@ static void game_free_drawstate(drawing *dr, game_drawstate *ds) sfree(ds); } -static void game_compute_size(game_params *params, int tilesize, - int *x, int *y) +static void game_compute_size(const game_params *params, int tilesize, + int *x, int *y) { *x = WINDOW_OFFSET * 2 + tilesize * params->width + TILE_BORDER; *y = WINDOW_OFFSET * 2 + tilesize * params->height + TILE_BORDER; } static void game_set_size(drawing *dr, game_drawstate *ds, - game_params *params, int tilesize) + const game_params *params, int tilesize) { ds->tilesize = tilesize; } @@ -2170,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. */ @@ -2194,8 +2522,8 @@ static float *game_colours(frontend *fe, int *ncolours) return ret; } -static void draw_thick_line(drawing *dr, int x1, int y1, int x2, int y2, - int colour) +static void draw_filled_line(drawing *dr, int x1, int y1, int x2, int y2, + int colour) { draw_line(dr, x1-1, y1, x2-1, y2, COL_WIRE); draw_line(dr, x1+1, y1, x2+1, y2, COL_WIRE); @@ -2264,7 +2592,7 @@ static void draw_barrier(drawing *dr, game_drawstate *ds, /* * draw_tile() is passed physical coordinates */ -static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds, +static void draw_tile(drawing *dr, const game_state *state, game_drawstate *ds, int x, int y, int tile, int src, float angle, int cursor) { int bx = WINDOW_OFFSET + TILE_SIZE * x; @@ -2331,9 +2659,9 @@ static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds, ex = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * X(dir); ey = (TILE_SIZE - TILE_BORDER - 1.0F) / 2.0F * Y(dir); MATMUL(tx, ty, matrix, ex, ey); - draw_thick_line(dr, bx+(int)cx, by+(int)cy, - bx+(int)(cx+tx), by+(int)(cy+ty), - COL_WIRE); + draw_filled_line(dr, bx+(int)cx, by+(int)cy, + bx+(int)(cx+tx), by+(int)(cy+ty), + COL_WIRE); } } for (dir = 1; dir < 0x10; dir <<= 1) { @@ -2342,9 +2670,14 @@ static void draw_tile(drawing *dr, 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 @@ -2413,7 +2746,9 @@ static void draw_tile(drawing *dr, 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 @@ -2480,11 +2815,14 @@ static void draw_tile(drawing *dr, game_state *state, game_drawstate *ds, draw_update(dr, bx, by, TILE_SIZE+TILE_BORDER, TILE_SIZE+TILE_BORDER); } -static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, - game_state *state, int dir, game_ui *ui, float t, float ft) +static void game_redraw(drawing *dr, game_drawstate *ds, + const game_state *oldstate, const game_state *state, + int dir, const game_ui *ui, + float t, float ft) { int x, y, tx, ty, frame, last_rotate_dir, moved_origin = FALSE; unsigned char *active; + int *loops; float angle = 0.0; /* @@ -2578,11 +2916,13 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, * 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 && @@ -2609,12 +2949,12 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, 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; } @@ -2643,10 +2983,11 @@ static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, } sfree(active); + sfree(loops); } -static float game_anim_length(game_state *oldstate, - game_state *newstate, int dir, game_ui *ui) +static float game_anim_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) { int last_rotate_dir; @@ -2661,8 +3002,8 @@ static float game_anim_length(game_state *oldstate, return 0.0F; } -static float game_flash_length(game_state *oldstate, - game_state *newstate, int dir, game_ui *ui) +static float game_flash_length(const game_state *oldstate, + const game_state *newstate, int dir, game_ui *ui) { /* * If the game has just been completed, we display a completion @@ -2681,12 +3022,17 @@ static float game_flash_length(game_state *oldstate, return 0.0F; } -static int game_timing_state(game_state *state, game_ui *ui) +static int game_status(const game_state *state) +{ + return state->completed ? +1 : 0; +} + +static int game_timing_state(const game_state *state, game_ui *ui) { return TRUE; } -static void game_print_size(game_params *params, float *x, float *y) +static void game_print_size(const game_params *params, float *x, float *y) { int pw, ph; @@ -2694,8 +3040,8 @@ static void game_print_size(game_params *params, float *x, float *y) * I'll use 8mm squares by default. */ game_compute_size(params, 800, &pw, &ph); - *x = pw / 100.0; - *y = ph / 100.0; + *x = pw / 100.0F; + *y = ph / 100.0F; } static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y, @@ -2744,7 +3090,7 @@ static void draw_diagram(drawing *dr, game_drawstate *ds, int x, int y, } } -static void game_print(drawing *dr, game_state *state, int tilesize) +static void game_print(drawing *dr, const game_state *state, int tilesize) { int w = state->width, h = state->height; int ink = print_mono_colour(dr, 0); @@ -2826,7 +3172,7 @@ static void game_print(drawing *dr, game_state *state, int tilesize) #endif const struct game thegame = { - "Net", "games.net", + "Net", "games.net", "net", default_params, game_fetch_preset, decode_params, @@ -2841,7 +3187,7 @@ const struct game thegame = { 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, @@ -2856,6 +3202,7 @@ const struct game thegame = { game_redraw, game_anim_length, game_flash_length, + game_status, TRUE, FALSE, game_print_size, game_print, TRUE, /* wants_statusbar */ FALSE, game_timing_state,