X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/93b1da3d0f613d616b930419354558365847bc7d..59dae0db4e2110631e1c7f2ea5990f6b406aff6e:/net.c diff --git a/net.c b/net.c index 5ef1ba7..767ef31 100644 --- a/net.c +++ b/net.c @@ -43,7 +43,8 @@ #define COUNT(x) ( (((x) & 0x08) >> 3) + (((x) & 0x04) >> 2) + \ (((x) & 0x02) >> 1) + ((x) & 0x01) ) -#define TILE_SIZE 32 +#define PREFERRED_TILE_SIZE 32 +#define TILE_SIZE (ds->tilesize) #define TILE_BORDER 1 #define WINDOW_OFFSET 16 @@ -76,11 +77,6 @@ struct game_params { float barrier_probability; }; -struct game_aux_info { - int width, height; - unsigned char *tiles; -}; - struct game_state { int width, height, wrapping, completed; int last_rotate_x, last_rotate_y, last_rotate_dir; @@ -120,7 +116,7 @@ static int xyd_cmp(const void *av, const void *bv) { if (a->direction > b->direction) return +1; return 0; -}; +} static int xyd_cmp_nc(void *av, void *bv) { return xyd_cmp(av, bv); } @@ -149,32 +145,29 @@ static game_params *default_params(void) return ret; } +static const struct game_params net_presets[] = { + {5, 5, FALSE, TRUE, 0.0}, + {7, 7, FALSE, TRUE, 0.0}, + {9, 9, FALSE, TRUE, 0.0}, + {11, 11, FALSE, TRUE, 0.0}, + {13, 11, FALSE, TRUE, 0.0}, + {5, 5, TRUE, TRUE, 0.0}, + {7, 7, TRUE, TRUE, 0.0}, + {9, 9, TRUE, TRUE, 0.0}, + {11, 11, TRUE, TRUE, 0.0}, + {13, 11, TRUE, TRUE, 0.0}, +}; + static int game_fetch_preset(int i, char **name, game_params **params) { game_params *ret; char str[80]; - static const struct { int x, y, wrap; } values[] = { - {5, 5, FALSE}, - {7, 7, FALSE}, - {9, 9, FALSE}, - {11, 11, FALSE}, - {13, 11, FALSE}, - {5, 5, TRUE}, - {7, 7, TRUE}, - {9, 9, TRUE}, - {11, 11, TRUE}, - {13, 11, TRUE}, - }; - - if (i < 0 || i >= lenof(values)) + + if (i < 0 || i >= lenof(net_presets)) return FALSE; ret = snew(game_params); - ret->width = values[i].x; - ret->height = values[i].y; - ret->wrapping = values[i].wrap; - ret->unique = TRUE; - ret->barrier_probability = 0.0; + *ret = net_presets[i]; sprintf(str, "%dx%d%s", ret->width, ret->height, ret->wrapping ? " wrapping" : ""); @@ -302,12 +295,8 @@ static game_params *custom_params(config_item *cfg) static char *validate_params(game_params *params) { - if (params->width <= 0 && params->height <= 0) + if (params->width <= 0 || params->height <= 0) return "Width and height must both be greater than zero"; - if (params->width <= 0) - return "Width must be greater than zero"; - if (params->height <= 0) - return "Height must be greater than zero"; if (params->width <= 1 && params->height <= 1) return "At least one of width and height must be greater than one"; if (params->barrier_probability < 0) @@ -968,6 +957,7 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping, break; } + sfree(perim2); if (i == nperim) return; /* nothing we can do! */ @@ -1144,7 +1134,7 @@ static void perturb(int w, int h, unsigned char *tiles, int wrapping, } static char *new_game_desc(game_params *params, random_state *rs, - game_aux_info **aux, int interactive) + char **aux, int interactive) { tree234 *possibilities, *barriertree; int w, h, x, y, cx, cy, nbarriers; @@ -1235,7 +1225,7 @@ static char *new_game_desc(game_params *params, random_state *rs, OFFSET(x2, y2, x1, y1, d1, params); d2 = F(d1); -#ifdef DEBUG +#ifdef GENERATION_DIAGNOSTICS printf("picked (%d,%d,%c) <-> (%d,%d,%c)\n", x1, y1, "0RU3L567D9abcdef"[d1], x2, y2, "0RU3L567D9abcdef"[d2]); #endif @@ -1262,7 +1252,7 @@ static char *new_game_desc(game_params *params, random_state *rs, xydp = find234(possibilities, &xyd1, NULL); if (xydp) { -#ifdef DEBUG +#ifdef GENERATION_DIAGNOSTICS printf("T-piece; removing (%d,%d,%c)\n", xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); #endif @@ -1289,7 +1279,7 @@ static char *new_game_desc(game_params *params, random_state *rs, xydp = find234(possibilities, &xyd1, NULL); if (xydp) { -#ifdef DEBUG +#ifdef GENERATION_DIAGNOSTICS printf("Loop avoidance; removing (%d,%d,%c)\n", xydp->x, xydp->y, "0RU3L567D9abcdef"[xydp->direction]); #endif @@ -1324,7 +1314,7 @@ static char *new_game_desc(game_params *params, random_state *rs, if (index(params, tiles, x3, y3)) continue; /* this would create a loop */ -#ifdef DEBUG +#ifdef GENERATION_DIAGNOSTICS printf("New frontier; adding (%d,%d,%c)\n", x2, y2, "0RU3L567D9abcdef"[d]); #endif @@ -1406,16 +1396,16 @@ static char *new_game_desc(game_params *params, random_state *rs, } /* - * Save the unshuffled grid in an aux_info. + * Save the unshuffled grid in aux. */ { - game_aux_info *solution; + char *solution; + int i; - solution = snew(game_aux_info); - solution->width = w; - solution->height = h; - solution->tiles = snewn(w * h, unsigned char); - memcpy(solution->tiles, tiles, w * h); + solution = snewn(w * h + 1, char); + for (i = 0; i < w * h; i++) + solution[i] = "0123456789abcdef"[tiles[i] & 0xF]; + solution[w*h] = '\0'; *aux = solution; } @@ -1520,12 +1510,6 @@ static char *new_game_desc(game_params *params, random_state *rs, return desc; } -static void game_free_aux_info(game_aux_info *aux) -{ - sfree(aux->tiles); - sfree(aux); -} - static char *validate_desc(game_params *params, char *desc) { int w = params->width, h = params->height; @@ -1671,28 +1655,96 @@ static void free_game(game_state *state) sfree(state); } -static game_state *solve_game(game_state *state, game_aux_info *aux, - char **error) +static char *solve_game(game_state *state, game_state *currstate, + char *aux, char **error) { - game_state *ret; + unsigned char *tiles; + char *ret; + int retlen, retsize; + int i; + + tiles = snewn(state->width * state->height, unsigned char); if (!aux) { /* * Run the internal solver on the provided grid. This might * not yield a complete solution. */ - ret = dup_game(state); - net_solver(ret->width, ret->height, ret->tiles, - ret->barriers, ret->wrapping); + memcpy(tiles, state->tiles, state->width * state->height); + net_solver(state->width, state->height, tiles, + state->barriers, state->wrapping); } else { - assert(aux->width == state->width); - assert(aux->height == state->height); - ret = dup_game(state); - memcpy(ret->tiles, aux->tiles, ret->width * ret->height); - ret->used_solve = ret->just_used_solve = TRUE; - ret->completed = TRUE; + for (i = 0; i < state->width * state->height; i++) { + int c = aux[i]; + + if (c >= '0' && c <= '9') + tiles[i] = c - '0'; + else if (c >= 'a' && c <= 'f') + tiles[i] = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') + tiles[i] = c - 'A' + 10; + } } + /* + * Now construct a string which can be passed to execute_move() + * to transform the current grid into the solved one. + */ + retsize = 256; + ret = snewn(retsize, char); + retlen = 0; + ret[retlen++] = 'S'; + + for (i = 0; i < state->width * state->height; i++) { + int from = currstate->tiles[i], to = tiles[i]; + int ft = from & (R|L|U|D), tt = to & (R|L|U|D); + int x = i % state->width, y = i / state->width; + int chr = '\0'; + char buf[80], *p = buf; + + if (from == to) + continue; /* nothing needs doing at all */ + + /* + * To transform this tile into the desired tile: first + * unlock the tile if it's locked, then rotate it if + * necessary, then lock it if necessary. + */ + if (from & LOCKED) + p += sprintf(p, ";L%d,%d", x, y); + + if (tt == A(ft)) + chr = 'A'; + else if (tt == C(ft)) + chr = 'C'; + else if (tt == F(ft)) + chr = 'F'; + else { + assert(tt == ft); + chr = '\0'; + } + if (chr) + p += sprintf(p, ";%c%d,%d", chr, x, y); + + if (to & LOCKED) + p += sprintf(p, ";L%d,%d", x, y); + + if (p > buf) { + if (retlen + (p - buf) >= retsize) { + retsize = retlen + (p - buf) + 512; + ret = sresize(ret, retsize, char); + } + memcpy(ret+retlen, buf, p - buf); + retlen += p - buf; + } + } + + assert(retlen < retsize); + ret[retlen] = '\0'; + ret = sresize(ret, retlen+1, char); + + sfree(tiles); + return ret; } @@ -1792,13 +1844,44 @@ static void free_ui(game_ui *ui) sfree(ui); } +static char *encode_ui(game_ui *ui) +{ + char buf[120]; + /* + * We preserve the origin and centre-point coordinates over a + * serialise. + */ + sprintf(buf, "O%d,%d;C%d,%d", ui->org_x, ui->org_y, ui->cx, ui->cy); + return dupstr(buf); +} + +static void decode_ui(game_ui *ui, 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) +{ +} + +struct game_drawstate { + int started; + int width, height; + int org_x, org_y; + int tilesize; + unsigned char *visible; +}; + /* ---------------------------------------------------------------------- * Process a move. */ -static game_state *make_move(game_state *state, game_ui *ui, - game_drawstate *ds, int x, int y, int button) { - game_state *ret, *nullret; - int tx, ty, orig; +static char *interpret_move(game_state *state, game_ui *ui, + game_drawstate *ds, int x, int y, int button) +{ + char *nullret; + int tx, ty; int shift = button & MOD_SHFT, ctrl = button & MOD_CTRL; button &= ~MOD_MASK; @@ -1810,7 +1893,7 @@ static game_state *make_move(game_state *state, game_ui *ui, if (ui->cur_visible) { ui->cur_visible = FALSE; - nullret = state; + nullret = ""; } /* @@ -1861,12 +1944,13 @@ static game_state *make_move(game_state *state, game_ui *ui, OFFSET(ui->cur_x, ui->cur_y, ui->cur_x, ui->cur_y, dir, state); ui->cur_visible = TRUE; } - return state; /* UI activity has occurred */ + return ""; /* UI activity has occurred */ } else if (button == 'a' || button == 's' || button == 'd' || - button == 'A' || button == 'S' || button == 'D') { + button == 'A' || button == 'S' || button == 'D' || + button == CURSOR_SELECT) { tx = ui->cur_x; ty = ui->cur_y; - if (button == 'a' || button == 'A') + if (button == 'a' || button == 'A' || button == CURSOR_SELECT) button = LEFT_BUTTON; else if (button == 's' || button == 'S') button = MIDDLE_BUTTON; @@ -1891,14 +1975,11 @@ static game_state *make_move(game_state *state, game_ui *ui, * unlocks it.) */ if (button == MIDDLE_BUTTON) { - - ret = dup_game(state); - ret->just_used_solve = FALSE; - tile(ret, tx, ty) ^= LOCKED; - ret->last_rotate_dir = ret->last_rotate_x = ret->last_rotate_y = 0; - return ret; - + char buf[80]; + sprintf(buf, "L%d,%d", tx, ty); + return dupstr(buf); } else if (button == LEFT_BUTTON || button == RIGHT_BUTTON) { + char buf[80]; /* * The left and right buttons have no effect if clicked on a @@ -1911,46 +1992,114 @@ static game_state *make_move(game_state *state, game_ui *ui, * Otherwise, turn the tile one way or the other. Left button * turns anticlockwise; right button turns clockwise. */ - ret = dup_game(state); - ret->just_used_solve = FALSE; - orig = tile(ret, tx, ty); - if (button == LEFT_BUTTON) { - tile(ret, tx, ty) = A(orig); - ret->last_rotate_dir = +1; - } else { - tile(ret, tx, ty) = C(orig); - ret->last_rotate_dir = -1; - } - ret->last_rotate_x = tx; - ret->last_rotate_y = ty; - + sprintf(buf, "%c%d,%d", (button == LEFT_BUTTON ? 'A' : 'C'), tx, ty); + return dupstr(buf); } else if (button == 'J') { - /* * Jumble all unlocked tiles to random orientations. */ - int jx, jy; - ret = dup_game(state); - ret->just_used_solve = FALSE; - for (jy = 0; jy < ret->height; jy++) { - for (jx = 0; jx < ret->width; jx++) { - if (!(tile(ret, jx, jy) & LOCKED)) { + + int jx, jy, maxlen; + char *ret, *p; + + /* + * Maximum string length assumes no int can be converted to + * decimal and take more than 11 digits! + */ + maxlen = state->width * state->height * 25 + 3; + + ret = snewn(maxlen, char); + p = ret; + *p++ = 'J'; + + for (jy = 0; jy < state->height; jy++) { + for (jx = 0; jx < state->width; jx++) { + if (!(tile(state, jx, jy) & LOCKED)) { int rot = random_upto(ui->rs, 4); - orig = tile(ret, jx, jy); - tile(ret, jx, jy) = ROT(orig, rot); + if (rot) { + p += sprintf(p, ";%c%d,%d", "AFC"[rot-1], jx, jy); + } } } } - ret->last_rotate_dir = 0; /* suppress animation */ - ret->last_rotate_x = ret->last_rotate_y = 0; + *p++ = '\0'; + assert(p - ret < maxlen); + ret = sresize(ret, p - ret, char); + + return ret; + } else { + return NULL; + } +} + +static game_state *execute_move(game_state *from, char *move) +{ + game_state *ret; + int tx, ty, n, noanim, orig; + + ret = dup_game(from); + ret->just_used_solve = FALSE; - } else assert(0); + if (move[0] == 'J' || move[0] == 'S') { + if (move[0] == 'S') + ret->just_used_solve = ret->used_solve = TRUE; + + move++; + if (*move == ';') + move++; + noanim = TRUE; + } else + noanim = FALSE; + + ret->last_rotate_dir = 0; /* suppress animation */ + ret->last_rotate_x = ret->last_rotate_y = 0; + + while (*move) { + if ((move[0] == 'A' || move[0] == 'C' || + move[0] == 'F' || move[0] == 'L') && + sscanf(move+1, "%d,%d%n", &tx, &ty, &n) >= 2 && + tx >= 0 && tx < from->width && ty >= 0 && ty < from->height) { + orig = tile(ret, tx, ty); + if (move[0] == 'A') { + tile(ret, tx, ty) = A(orig); + if (!noanim) + ret->last_rotate_dir = +1; + } else if (move[0] == 'F') { + tile(ret, tx, ty) = F(orig); + if (!noanim) { + free_game(ret); + return NULL; + } + } else if (move[0] == 'C') { + tile(ret, tx, ty) = C(orig); + if (!noanim) + ret->last_rotate_dir = -1; + } else { + assert(move[0] == 'L'); + tile(ret, tx, ty) ^= LOCKED; + } + + move += 1 + n; + if (*move == ';') move++; + } else { + free_game(ret); + return NULL; + } + } + if (!noanim) { + ret->last_rotate_x = tx; + ret->last_rotate_y = ty; + } /* * Check whether the game has been completed. + * + * For this purpose it doesn't matter where the source square + * is, because we can start from anywhere and correctly + * determine whether the game is completed. */ { - unsigned char *active = compute_active(ret, ui->cx, ui->cy); + unsigned char *active = compute_active(ret, 0, 0); int x1, y1; int complete = TRUE; @@ -1971,17 +2120,11 @@ static game_state *make_move(game_state *state, game_ui *ui, return ret; } + /* ---------------------------------------------------------------------- * Routines for drawing the game position on the screen. */ -struct game_drawstate { - int started; - int width, height; - int org_x, org_y; - unsigned char *visible; -}; - static game_drawstate *game_new_drawstate(game_state *state) { game_drawstate *ds = snew(game_drawstate); @@ -1991,6 +2134,7 @@ static game_drawstate *game_new_drawstate(game_state *state) ds->height = state->height; ds->org_x = ds->org_y = -1; ds->visible = snewn(state->width * state->height, unsigned char); + ds->tilesize = 0; /* undecided yet */ memset(ds->visible, 0xFF, state->width * state->height); return ds; @@ -2002,8 +2146,23 @@ static void game_free_drawstate(game_drawstate *ds) sfree(ds); } -static void game_size(game_params *params, int *x, int *y) +static void game_size(game_params *params, game_drawstate *ds, int *x, int *y, + int expand) { + int tsx, tsy, ts; + /* + * Each window dimension equals the tile size times the grid + * dimension, plus TILE_BORDER, plus twice WINDOW_OFFSET. + */ + tsx = (*x - 2*WINDOW_OFFSET - TILE_BORDER) / params->width; + tsy = (*y - 2*WINDOW_OFFSET - TILE_BORDER) / params->height; + ts = min(tsx, tsy); + + if (expand) + ds->tilesize = ts; + else + ds->tilesize = min(ts, PREFERRED_TILE_SIZE); + *x = WINDOW_OFFSET * 2 + TILE_SIZE * params->width + TILE_BORDER; *y = WINDOW_OFFSET * 2 + TILE_SIZE * params->height + TILE_BORDER; } @@ -2090,8 +2249,8 @@ static void draw_rect_coords(frontend *fe, int x1, int y1, int x2, int y2, /* * draw_barrier_corner() and draw_barrier() are passed physical coords */ -static void draw_barrier_corner(frontend *fe, int x, int y, int dx, int dy, - int phase) +static void draw_barrier_corner(frontend *fe, game_drawstate *ds, + int x, int y, int dx, int dy, int phase) { int bx = WINDOW_OFFSET + TILE_SIZE * x; int by = WINDOW_OFFSET + TILE_SIZE * y; @@ -2114,7 +2273,8 @@ static void draw_barrier_corner(frontend *fe, int x, int y, int dx, int dy, } } -static void draw_barrier(frontend *fe, int x, int y, int dir, int phase) +static void draw_barrier(frontend *fe, game_drawstate *ds, + int x, int y, int dir, int phase) { int bx = WINDOW_OFFSET + TILE_SIZE * x; int by = WINDOW_OFFSET + TILE_SIZE * y; @@ -2336,7 +2496,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds, * At least one barrier terminates here. Draw a * corner. */ - draw_barrier_corner(fe, x, y, + draw_barrier_corner(fe, ds, x, y, X(dir)+X(A(dir)), Y(dir)+Y(A(dir)), phase); } @@ -2344,7 +2504,7 @@ static void draw_tile(frontend *fe, game_state *state, game_drawstate *ds, for (dir = 1; dir < 0x10; dir <<= 1) if (barrier(state, GX(x), GY(y)) & dir) - draw_barrier(fe, x, y, dir, phase); + draw_barrier(fe, ds, x, y, dir, phase); } unclip(fe); @@ -2386,38 +2546,38 @@ static void game_redraw(frontend *fe, game_drawstate *ds, game_state *oldstate, for (x = 0; x < ds->width; x++) { if (x+1 < ds->width) { if (barrier(state, GX(x), GY(0)) & R) - draw_barrier_corner(fe, x, -1, +1, +1, phase); + draw_barrier_corner(fe, ds, x, -1, +1, +1, phase); if (barrier(state, GX(x), GY(ds->height-1)) & R) - draw_barrier_corner(fe, x, ds->height, +1, -1, phase); + draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase); } if (barrier(state, GX(x), GY(0)) & U) { - draw_barrier_corner(fe, x, -1, -1, +1, phase); - draw_barrier_corner(fe, x, -1, +1, +1, phase); - draw_barrier(fe, x, -1, D, phase); + draw_barrier_corner(fe, ds, x, -1, -1, +1, phase); + draw_barrier_corner(fe, ds, x, -1, +1, +1, phase); + draw_barrier(fe, ds, x, -1, D, phase); } if (barrier(state, GX(x), GY(ds->height-1)) & D) { - draw_barrier_corner(fe, x, ds->height, -1, -1, phase); - draw_barrier_corner(fe, x, ds->height, +1, -1, phase); - draw_barrier(fe, x, ds->height, U, phase); + draw_barrier_corner(fe, ds, x, ds->height, -1, -1, phase); + draw_barrier_corner(fe, ds, x, ds->height, +1, -1, phase); + draw_barrier(fe, ds, x, ds->height, U, phase); } } for (y = 0; y < ds->height; y++) { if (y+1 < ds->height) { if (barrier(state, GX(0), GY(y)) & D) - draw_barrier_corner(fe, -1, y, +1, +1, phase); + draw_barrier_corner(fe, ds, -1, y, +1, +1, phase); if (barrier(state, GX(ds->width-1), GY(y)) & D) - draw_barrier_corner(fe, ds->width, y, -1, +1, phase); + draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase); } if (barrier(state, GX(0), GY(y)) & L) { - draw_barrier_corner(fe, -1, y, +1, -1, phase); - draw_barrier_corner(fe, -1, y, +1, +1, phase); - draw_barrier(fe, -1, y, R, phase); + draw_barrier_corner(fe, ds, -1, y, +1, -1, phase); + draw_barrier_corner(fe, ds, -1, y, +1, +1, phase); + draw_barrier(fe, ds, -1, y, R, phase); } if (barrier(state, GX(ds->width-1), GY(y)) & R) { - draw_barrier_corner(fe, ds->width, y, -1, -1, phase); - draw_barrier_corner(fe, ds->width, y, -1, +1, phase); - draw_barrier(fe, ds->width, y, L, phase); + draw_barrier_corner(fe, ds, ds->width, y, -1, -1, phase); + draw_barrier_corner(fe, ds, ds->width, y, -1, +1, phase); + draw_barrier(fe, ds, ds->width, y, L, phase); } } } @@ -2585,7 +2745,6 @@ const struct game thegame = { TRUE, game_configure, custom_params, validate_params, new_game_desc, - game_free_aux_info, validate_desc, new_game, dup_game, @@ -2594,7 +2753,11 @@ const struct game thegame = { FALSE, game_text_format, new_ui, free_ui, - make_move, + encode_ui, + decode_ui, + game_changed_state, + interpret_move, + execute_move, game_size, game_colours, game_new_drawstate,