X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/puzzles/blobdiff_plain/80e7e37cd954d03f10a6a69f857c9033d05d1809..b0a2ee969ddd95665e0cbfd18b50c9d69e6346e0:/loopy.c?ds=sidebyside diff --git a/loopy.c b/loopy.c index afa362c..64c45da 100644 --- a/loopy.c +++ b/loopy.c @@ -253,7 +253,10 @@ static void check_caches(const solver_state* sstate); A(Cairo,grid_new_cairo,3,4) \ A(Great-Hexagonal,grid_new_greathexagonal,3,3) \ A(Octagonal,grid_new_octagonal,3,3) \ - A(Kites,grid_new_kites,3,3) + A(Kites,grid_new_kites,3,3) \ + A(Floret,grid_new_floret,1,2) \ + A(Dodecagonal,grid_new_dodecagonal,2,2) \ + A(Great-Dodecagonal,grid_new_greatdodecagonal,2,2) #define GRID_NAME(title,fn,amin,omin) #title, #define GRID_CONFIG(title,fn,amin,omin) ":" #title @@ -300,7 +303,7 @@ static void params_generate_grid(game_params *params) ((field) &= ~(1<<(bit)), TRUE) : FALSE) #define CLUE2CHAR(c) \ - ((c < 0) ? ' ' : c + '0') + ((c < 0) ? ' ' : c < 10 ? c + '0' : c - 10 + 'A') /* ---------------------------------------------------------------------- * General struct manipulation and other straightforward code @@ -504,6 +507,9 @@ static const game_params presets[] = { { 5, 4, DIFF_HARD, 5, NULL }, { 5, 5, DIFF_HARD, 6, NULL }, { 5, 5, DIFF_HARD, 7, NULL }, + { 3, 3, DIFF_HARD, 8, NULL }, + { 3, 3, DIFF_HARD, 9, NULL }, + { 3, 3, DIFF_HARD, 10, NULL }, #else { 7, 7, DIFF_EASY, 0, NULL }, { 10, 10, DIFF_EASY, 0, NULL }, @@ -518,6 +524,9 @@ static const game_params presets[] = { { 5, 4, DIFF_HARD, 5, NULL }, { 7, 7, DIFF_HARD, 6, NULL }, { 5, 5, DIFF_HARD, 7, NULL }, + { 5, 5, DIFF_HARD, 8, NULL }, + { 5, 4, DIFF_HARD, 9, NULL }, + { 5, 4, DIFF_HARD, 10, NULL }, #endif }; @@ -702,7 +711,7 @@ static char *validate_desc(game_params *params, char *desc) g = params->game_grid; for (; *desc; ++desc) { - if (*desc >= '0' && *desc <= '9') { + if ((*desc >= '0' && *desc <= '9') || (*desc >= 'A' && *desc <= 'Z')) { count++; continue; } @@ -1819,6 +1828,7 @@ static char *new_game_desc(game_params *params, random_state *rs, grid *g; game_state *state = snew(game_state); game_state *state_new; + int count = 0; params_generate_grid(params); state->game_grid = g = params->game_grid; g->refcount++; @@ -1840,6 +1850,7 @@ static char *new_game_desc(game_params *params, random_state *rs, * preventing games smaller than 4x4 seems to stop this happening */ do { add_full_clues(state, rs); + if (++count%100 == 0) printf("tried %d times to make a unique board\n", count); } while (!game_has_unique_soln(state, params->diff)); state_new = remove_clues(state, rs, params->diff); @@ -1868,7 +1879,7 @@ static game_state *new_game(midend *me, game_params *params, char *desc) int i; game_state *state = snew(game_state); int empties_to_make = 0; - int n; + int n,n2; const char *dp = desc; grid *g; int num_faces, num_edges; @@ -1896,8 +1907,11 @@ static game_state *new_game(midend *me, game_params *params, char *desc) assert(*dp); n = *dp - '0'; + n2 = *dp - 'A' + 10; if (n >= 0 && n < 10) { state->clues[i] = n; + } else if (n2 >= 10 && n2 < 36) { + state->clues[i] = n2; } else { n = *dp - 'a' + 1; assert(n > 0); @@ -2531,7 +2545,7 @@ static int dline_deductions(solver_state *sstate) * on that. We check this with an assertion, in case someone decides to * make a grid which has larger faces than this. Note, this algorithm * could get quite expensive if there are many large faces. */ -#define MAX_FACE_SIZE 8 +#define MAX_FACE_SIZE 12 for (i = 0; i < g->num_faces; i++) { int maxs[MAX_FACE_SIZE][MAX_FACE_SIZE]; @@ -3347,235 +3361,290 @@ static void face_text_pos(const game_drawstate *ds, const grid *g, grid_to_screen(ds, g, sx, sy, x, y); } +static void game_redraw_clue(drawing *dr, game_drawstate *ds, + game_state *state, int i) +{ + grid *g = state->game_grid; + grid_face *f = g->faces + i; + int x, y; + char c[3]; + + if (state->clues[i] < 10) { + c[0] = CLUE2CHAR(state->clues[i]); + c[1] = '\0'; + } else { + sprintf(c, "%d", state->clues[i]); + } + + face_text_pos(ds, g, f, &x, &y); + draw_text(dr, x, y, + FONT_VARIABLE, ds->tilesize/2, + ALIGN_VCENTRE | ALIGN_HCENTRE, + ds->clue_error[i] ? COL_MISTAKE : + ds->clue_satisfied[i] ? COL_SATISFIED : COL_FOREGROUND, c); +} + +static const int loopy_line_redraw_phases[] = { + COL_FAINT, COL_LINEUNKNOWN, COL_FOREGROUND, COL_HIGHLIGHT, COL_MISTAKE +}; +#define NPHASES lenof(loopy_line_redraw_phases) + +static void game_redraw_line(drawing *dr, game_drawstate *ds, + game_state *state, int i, int phase) +{ + grid *g = state->game_grid; + grid_edge *e = g->edges + i; + int x1, x2, y1, y2; + int xmin, ymin, xmax, ymax; + int line_colour; + + if (state->line_errors[i]) + line_colour = COL_MISTAKE; + else if (state->lines[i] == LINE_UNKNOWN) + line_colour = COL_LINEUNKNOWN; + else if (state->lines[i] == LINE_NO) + line_colour = COL_FAINT; + else if (ds->flashing) + line_colour = COL_HIGHLIGHT; + else + line_colour = COL_FOREGROUND; + if (line_colour != loopy_line_redraw_phases[phase]) + return; + + /* Convert from grid to screen coordinates */ + grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1); + grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2); + + xmin = min(x1, x2); + xmax = max(x1, x2); + ymin = min(y1, y2); + ymax = max(y1, y2); + + if (line_colour == COL_FAINT) { + static int draw_faint_lines = -1; + if (draw_faint_lines < 0) { + char *env = getenv("LOOPY_FAINT_LINES"); + draw_faint_lines = (!env || (env[0] == 'y' || + env[0] == 'Y')); + } + if (draw_faint_lines) + draw_line(dr, x1, y1, x2, y2, line_colour); + } else { + draw_thick_line(dr, 3.0, + x1 + 0.5, y1 + 0.5, + x2 + 0.5, y2 + 0.5, + line_colour); + } +} + +static void game_redraw_dot(drawing *dr, game_drawstate *ds, + game_state *state, int i) +{ + grid *g = state->game_grid; + grid_dot *d = g->dots + i; + int x, y; + + grid_to_screen(ds, g, d->x, d->y, &x, &y); + draw_circle(dr, x, y, 2, COL_FOREGROUND, COL_FOREGROUND); +} + static void game_redraw(drawing *dr, game_drawstate *ds, game_state *oldstate, game_state *state, int dir, game_ui *ui, float animtime, float flashtime) { +#define REDRAW_OBJECTS_LIMIT 16 /* Somewhat arbitrary tradeoff */ + grid *g = state->game_grid; int border = BORDER(ds->tilesize); - int i, n; - char c[2]; - int line_colour, flash_changed; - int clue_mistake; - int clue_satisfied; + int i, phase; + int flash_changed; + int redraw_everything = FALSE; - if (!ds->started) { - /* - * The initial contents of the window are not guaranteed and - * can vary with front ends. To be on the safe side, all games - * should start by drawing a big background-colour rectangle - * covering the whole window. - */ - int grid_width = g->highest_x - g->lowest_x; - int grid_height = g->highest_y - g->lowest_y; - int w = grid_width * ds->tilesize / g->tilesize; - int h = grid_height * ds->tilesize / g->tilesize; - draw_rect(dr, 0, 0, w + 2 * border + 1, h + 2 * border + 1, - COL_BACKGROUND); + int edges[REDRAW_OBJECTS_LIMIT], nedges = 0; + int faces[REDRAW_OBJECTS_LIMIT], nfaces = 0; - /* Draw clues */ - for (i = 0; i < g->num_faces; i++) { - grid_face *f; - int x, y; + /* Redrawing is somewhat involved. + * + * An update can theoretically affect an arbitrary number of edges + * (consider, for example, completing or breaking a cycle which doesn't + * satisfy all the clues -- we'll switch many edges between error and + * normal states). On the other hand, redrawing the whole grid takes a + * while, making the game feel sluggish, and many updates are actually + * quite well localized. + * + * This redraw algorithm attempts to cope with both situations gracefully + * and correctly. For localized changes, we set a clip rectangle, fill + * it with background, and then redraw (a plausible but conservative + * guess at) the objects which intersect the rectangle; if several + * objects need redrawing, we'll do them individually. However, if lots + * of objects are affected, we'll just redraw everything. + * + * The reason for all of this is that it's just not safe to do the redraw + * piecemeal. If you try to draw an antialiased diagonal line over + * itself, you get a slightly thicker antialiased diagonal line, which + * looks rather ugly after a while. + * + * So, we take two passes over the grid. The first attempts to work out + * what needs doing, and the second actually does it. + */ - c[0] = CLUE2CHAR(state->clues[i]); - c[1] = '\0'; - f = g->faces + i; - face_text_pos(ds, g, f, &x, &y); - draw_text(dr, x, y, FONT_VARIABLE, ds->tilesize/2, - ALIGN_VCENTRE | ALIGN_HCENTRE, COL_FOREGROUND, c); - } - draw_update(dr, 0, 0, w + 2 * border, h + 2 * border); - } + if (!ds->started) + redraw_everything = TRUE; + else { + + /* First, trundle through the faces. */ + for (i = 0; i < g->num_faces; i++) { + grid_face *f = g->faces + i; + int sides = f->order; + int clue_mistake; + int clue_satisfied; + int n = state->clues[i]; + if (n < 0) + continue; + + clue_mistake = (face_order(state, i, LINE_YES) > n || + face_order(state, i, LINE_NO ) > (sides-n)); + clue_satisfied = (face_order(state, i, LINE_YES) == n && + face_order(state, i, LINE_NO ) == (sides-n)); + + if (clue_mistake != ds->clue_error[i] || + clue_satisfied != ds->clue_satisfied[i]) { + ds->clue_error[i] = clue_mistake; + ds->clue_satisfied[i] = clue_satisfied; + if (nfaces == REDRAW_OBJECTS_LIMIT) + redraw_everything = TRUE; + else + faces[nfaces++] = i; + } + } - if (flashtime > 0 && - (flashtime <= FLASH_TIME/3 || - flashtime >= FLASH_TIME*2/3)) { - flash_changed = !ds->flashing; - ds->flashing = TRUE; - } else { - flash_changed = ds->flashing; - ds->flashing = FALSE; - } + /* Work out what the flash state needs to be. */ + if (flashtime > 0 && + (flashtime <= FLASH_TIME/3 || + flashtime >= FLASH_TIME*2/3)) { + flash_changed = !ds->flashing; + ds->flashing = TRUE; + } else { + flash_changed = ds->flashing; + ds->flashing = FALSE; + } - /* Some platforms may perform anti-aliasing, which may prevent clean - * repainting of lines when the colour is changed. - * If a line needs to be over-drawn in a different colour, erase a - * bounding-box around the line, then flag all nearby objects for redraw. - */ - if (ds->started) { - const char redraw_flag = (char)(1<<7); - for (i = 0; i < g->num_edges; i++) { - char prev_ds = (ds->lines[i] & ~redraw_flag); - char new_ds = state->lines[i]; - if (state->line_errors[i]) - new_ds = DS_LINE_ERROR; - - /* If we're changing state, AND - * the previous state was a coloured line */ - if ((prev_ds != new_ds) && (prev_ds != LINE_NO)) { - grid_edge *e = g->edges + i; - int x1 = e->dot1->x; - int y1 = e->dot1->y; - int x2 = e->dot2->x; - int y2 = e->dot2->y; - int xmin, xmax, ymin, ymax; - int j; - grid_to_screen(ds, g, x1, y1, &x1, &y1); - grid_to_screen(ds, g, x2, y2, &x2, &y2); - /* Allow extra margin for dots, and thickness of lines */ - xmin = min(x1, x2) - 2; - xmax = max(x1, x2) + 2; - ymin = min(y1, y2) - 2; - ymax = max(y1, y2) + 2; - /* For testing, I find it helpful to change COL_BACKGROUND - * to COL_SATISFIED here. */ - draw_rect(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1, - COL_BACKGROUND); - draw_update(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); - - /* Mark nearby lines for redraw */ - for (j = 0; j < e->dot1->order; j++) - ds->lines[e->dot1->edges[j] - g->edges] |= redraw_flag; - for (j = 0; j < e->dot2->order; j++) - ds->lines[e->dot2->edges[j] - g->edges] |= redraw_flag; - /* Mark nearby clues for redraw. Use a value that is - * neither TRUE nor FALSE for this. */ - if (e->face1) - ds->clue_error[e->face1 - g->faces] = 2; - if (e->face2) - ds->clue_error[e->face2 - g->faces] = 2; - } - } + /* Now, trundle through the edges. */ + for (i = 0; i < g->num_edges; i++) { + char new_ds = + state->line_errors[i] ? DS_LINE_ERROR : state->lines[i]; + if (new_ds != ds->lines[i] || + (flash_changed && state->lines[i] == LINE_YES)) { + ds->lines[i] = new_ds; + if (nedges == REDRAW_OBJECTS_LIMIT) + redraw_everything = TRUE; + else + edges[nedges++] = i; + } + } } - /* Redraw clue colours if necessary */ - for (i = 0; i < g->num_faces; i++) { - grid_face *f = g->faces + i; - int sides = f->order; - int j; - n = state->clues[i]; - if (n < 0) - continue; + /* Pass one is now done. Now we do the actual drawing. */ + if (redraw_everything) { - c[0] = CLUE2CHAR(n); - c[1] = '\0'; + /* This is the unsubtle version. */ - clue_mistake = (face_order(state, i, LINE_YES) > n || - face_order(state, i, LINE_NO ) > (sides-n)); + int grid_width = g->highest_x - g->lowest_x; + int grid_height = g->highest_y - g->lowest_y; + int w = grid_width * ds->tilesize / g->tilesize; + int h = grid_height * ds->tilesize / g->tilesize; - clue_satisfied = (face_order(state, i, LINE_YES) == n && - face_order(state, i, LINE_NO ) == (sides-n)); + draw_rect(dr, 0, 0, w + 2*border + 1, h + 2*border + 1, + COL_BACKGROUND); - if (clue_mistake != ds->clue_error[i] - || clue_satisfied != ds->clue_satisfied[i]) { - int x, y; - face_text_pos(ds, g, f, &x, &y); - /* There seems to be a certain amount of trial-and-error - * involved in working out the correct bounding-box for - * the text. */ - draw_rect(dr, x - ds->tilesize/4 - 1, y - ds->tilesize/4 - 3, - ds->tilesize/2 + 2, ds->tilesize/2 + 5, - COL_BACKGROUND); - draw_text(dr, x, y, - FONT_VARIABLE, ds->tilesize/2, - ALIGN_VCENTRE | ALIGN_HCENTRE, - clue_mistake ? COL_MISTAKE : - clue_satisfied ? COL_SATISFIED : COL_FOREGROUND, c); - draw_update(dr, x - ds->tilesize/4 - 1, y - ds->tilesize/4 - 3, - ds->tilesize/2 + 2, ds->tilesize/2 + 5); - - ds->clue_error[i] = clue_mistake; - ds->clue_satisfied[i] = clue_satisfied; - - /* Sometimes, the bounding-box encroaches into the surrounding - * lines (particularly if the window is resized fairly small). - * So redraw them. */ - for (j = 0; j < f->order; j++) - ds->lines[f->edges[j] - g->edges] = -1; - } - } + for (i = 0; i < g->num_faces; i++) + game_redraw_clue(dr, ds, state, i); + for (phase = 0; phase < NPHASES; phase++) + for (i = 0; i < g->num_edges; i++) + game_redraw_line(dr, ds, state, i, phase); + for (i = 0; i < g->num_dots; i++) + game_redraw_dot(dr, ds, state, i); - /* Lines */ - for (i = 0; i < g->num_edges; i++) { - grid_edge *e = g->edges + i; - int x1, x2, y1, y2; - int xmin, ymin, xmax, ymax; - char new_ds, need_draw; - new_ds = state->lines[i]; - if (state->line_errors[i]) - new_ds = DS_LINE_ERROR; - need_draw = (new_ds != ds->lines[i]) ? TRUE : FALSE; - if (flash_changed && (state->lines[i] == LINE_YES)) - need_draw = TRUE; - if (!ds->started) - need_draw = TRUE; /* draw everything at the start */ - ds->lines[i] = new_ds; - if (!need_draw) - continue; - if (state->line_errors[i]) - line_colour = COL_MISTAKE; - else if (state->lines[i] == LINE_UNKNOWN) - line_colour = COL_LINEUNKNOWN; - else if (state->lines[i] == LINE_NO) - line_colour = COL_FAINT; - else if (ds->flashing) - line_colour = COL_HIGHLIGHT; - else - line_colour = COL_FOREGROUND; + draw_update(dr, 0, 0, w + 2*border + 1, h + 2*border + 1); + } else { - /* Convert from grid to screen coordinates */ - grid_to_screen(ds, g, e->dot1->x, e->dot1->y, &x1, &y1); - grid_to_screen(ds, g, e->dot2->x, e->dot2->y, &x2, &y2); + /* Right. Now we roll up our sleeves. */ + + for (i = 0; i < nfaces; i++) { + grid_face *f = g->faces + faces[i]; + int xx, yy; + int x, y, w, h; + int j; + + /* There seems to be a certain amount of trial-and-error + * involved in working out the correct bounding-box for + * the text. */ + face_text_pos(ds, g, f, &xx, &yy); + + x = xx - ds->tilesize/4 - 1; w = ds->tilesize/2 + 2; + y = yy - ds->tilesize/4 - 3; h = ds->tilesize/2 + 5; + clip(dr, x, y, w, h); + draw_rect(dr, x, y, w, h, COL_BACKGROUND); + + game_redraw_clue(dr, ds, state, faces[i]); + for (phase = 0; phase < NPHASES; phase++) + for (j = 0; j < f->order; j++) + game_redraw_line(dr, ds, state, f->edges[j] - g->edges, + phase); + for (j = 0; j < f->order; j++) + game_redraw_dot(dr, ds, state, f->dots[j] - g->dots); + unclip(dr); + draw_update(dr, x, y, w, h); + } - xmin = min(x1, x2); - xmax = max(x1, x2); - ymin = min(y1, y2); - ymax = max(y1, y2); - - if (line_colour == COL_FAINT) { - static int draw_faint_lines = -1; - if (draw_faint_lines < 0) { - char *env = getenv("LOOPY_FAINT_LINES"); - draw_faint_lines = (!env || (env[0] == 'y' || - env[0] == 'Y')); + for (i = 0; i < nedges; i++) { + grid_edge *e = g->edges + edges[i], *ee; + int x1 = e->dot1->x; + int y1 = e->dot1->y; + int x2 = e->dot2->x; + int y2 = e->dot2->y; + int xmin, xmax, ymin, ymax; + int j; + + grid_to_screen(ds, g, x1, y1, &x1, &y1); + grid_to_screen(ds, g, x2, y2, &x2, &y2); + /* Allow extra margin for dots, and thickness of lines */ + xmin = min(x1, x2) - 2; + xmax = max(x1, x2) + 2; + ymin = min(y1, y2) - 2; + ymax = max(y1, y2) + 2; + /* For testing, I find it helpful to change COL_BACKGROUND + * to COL_SATISFIED here. */ + clip(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); + draw_rect(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1, + COL_BACKGROUND); + + if (e->face1) + game_redraw_clue(dr, ds, state, e->face1 - g->faces); + if (e->face2) + game_redraw_clue(dr, ds, state, e->face2 - g->faces); + + for (phase = 0; phase < NPHASES; phase++) { + game_redraw_line(dr, ds, state, edges[i], phase); + for (j = 0; j < e->dot1->order; j++) { + ee = e->dot1->edges[j]; + if (ee != e) + game_redraw_line(dr, ds, state, ee - g->edges, phase); + } + for (j = 0; j < e->dot2->order; j++) { + ee = e->dot2->edges[j]; + if (ee != e) + game_redraw_line(dr, ds, state, ee - g->edges, phase); + } } - if (draw_faint_lines) - draw_line(dr, x1, y1, x2, y2, line_colour); - } else { - /* (dx, dy) points roughly from (x1, y1) to (x2, y2). - * The line is then "fattened" in a (roughly) perpendicular - * direction to create a thin rectangle. */ - int dx = (x1 > x2) ? -1 : ((x1 < x2) ? 1 : 0); - int dy = (y1 > y2) ? -1 : ((y1 < y2) ? 1 : 0); - int points[8]; - points[0] = x1 + dy; - points[1] = y1 - dx; - points[2] = x1 - dy; - points[3] = y1 + dx; - points[4] = x2 - dy; - points[5] = y2 + dx; - points[6] = x2 + dy; - points[7] = y2 - dx; - draw_polygon(dr, points, 4, line_colour, line_colour); - } - if (ds->started) { - /* Draw dots at ends of the line */ - draw_circle(dr, x1, y1, 2, COL_FOREGROUND, COL_FOREGROUND); - draw_circle(dr, x2, y2, 2, COL_FOREGROUND, COL_FOREGROUND); - } - draw_update(dr, xmin-2, ymin-2, xmax - xmin + 4, ymax - ymin + 4); - } + game_redraw_dot(dr, ds, state, e->dot1 - g->dots); + game_redraw_dot(dr, ds, state, e->dot2 - g->dots); - /* Draw dots */ - if (!ds->started) { - for (i = 0; i < g->num_dots; i++) { - grid_dot *d = g->dots + i; - int x, y; - grid_to_screen(ds, g, d->x, d->y, &x, &y); - draw_circle(dr, x, y, 2, COL_FOREGROUND, COL_FOREGROUND); - } + unclip(dr); + draw_update(dr, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); + } } + ds->started = TRUE; } @@ -3609,7 +3678,7 @@ static void game_print(drawing *dr, game_state *state, int tilesize) game_drawstate ads, *ds = &ads; grid *g = state->game_grid; - game_set_size(dr, ds, NULL, tilesize); + ds->tilesize = tilesize; for (i = 0; i < g->num_dots; i++) { int x, y;