diff --git a/grid.c b/grid.c index cb62eb7..eeecde3 100644 --- a/grid.c +++ b/grid.c @@ -1525,15 +1525,34 @@ static void grid_size_triangular(int width, int height, int vec_y = TRIANGLE_VEC_Y; *tilesize = TRIANGLE_TILESIZE; - *xextent = width * 2 * vec_x + vec_x; + *xextent = (width+1) * 2 * vec_x; *yextent = height * vec_y; } +static char *grid_validate_desc_triangular(grid_type type, int width, + int height, char *desc) +{ + /* + * Triangular grids: an absent description is valid (indicating + * the old-style approach which had 'ears', i.e. triangles + * connected to only one other face, at some grid corners), and so + * is a description reading just "0" (indicating the new-style + * approach in which those ears are trimmed off). Anything else is + * illegal. + */ + + if (!desc || !strcmp(desc, "0")) + return NULL; + + return "Unrecognised grid description."; +} + /* Doesn't use the previous method of generation, it pre-dates it! * A triangular grid is just about simple enough to do by "brute force" */ static grid *grid_new_triangular(int width, int height, char *desc) { int x,y; + int version = (desc == NULL ? -1 : atoi(desc)); /* Vector for side of triangle - ratio is close to sqrt(3) */ int vec_x = TRIANGLE_VEC_X; @@ -1547,61 +1566,144 @@ static grid *grid_new_triangular(int width, int height, char *desc) grid *g = grid_empty(); g->tilesize = TRIANGLE_TILESIZE; - g->num_faces = width * height * 2; - g->num_dots = (width + 1) * (height + 1); - g->faces = snewn(g->num_faces, grid_face); - g->dots = snewn(g->num_dots, grid_dot); + if (version == -1) { + /* + * Old-style triangular grid generation, preserved as-is for + * backwards compatibility with old game ids, in which it's + * just a little asymmetric and there are 'ears' (faces linked + * to only one other face) at two grid corners. + * + * Example old-style game ids, which should still work, and in + * which you should see the ears in the TL/BR corners on the + * first one and in the TL/BL corners on the second: + * + * 5x5t1:2c2a1a2a201a1a1a1112a1a2b1211f0b21a2a2a0a + * 5x6t1:a022a212h1a1d1a12c2b11a012b1a20d1a0a12e + */ - /* generate dots */ - index = 0; - for (y = 0; y <= height; y++) { - for (x = 0; x <= width; x++) { - grid_dot *d = g->dots + index; - /* odd rows are offset to the right */ - d->order = 0; - d->edges = NULL; - d->faces = NULL; - d->x = x * 2 * vec_x + ((y % 2) ? vec_x : 0); - d->y = y * vec_y; - index++; - } - } - - /* generate faces */ - index = 0; - for (y = 0; y < height; y++) { - for (x = 0; x < width; x++) { - /* initialise two faces for this (x,y) */ - grid_face *f1 = g->faces + index; - grid_face *f2 = f1 + 1; - f1->edges = NULL; - f1->order = 3; - f1->dots = snewn(f1->order, grid_dot*); - f1->has_incentre = FALSE; - f2->edges = NULL; - f2->order = 3; - f2->dots = snewn(f2->order, grid_dot*); - f2->has_incentre = FALSE; + g->num_faces = width * height * 2; + g->num_dots = (width + 1) * (height + 1); + g->faces = snewn(g->num_faces, grid_face); + g->dots = snewn(g->num_dots, grid_dot); - /* face descriptions depend on whether the row-number is - * odd or even */ - if (y % 2) { - f1->dots[0] = g->dots + y * w + x; - f1->dots[1] = g->dots + (y + 1) * w + x + 1; - f1->dots[2] = g->dots + (y + 1) * w + x; - f2->dots[0] = g->dots + y * w + x; - f2->dots[1] = g->dots + y * w + x + 1; - f2->dots[2] = g->dots + (y + 1) * w + x + 1; - } else { - f1->dots[0] = g->dots + y * w + x; - f1->dots[1] = g->dots + y * w + x + 1; - f1->dots[2] = g->dots + (y + 1) * w + x; - f2->dots[0] = g->dots + y * w + x + 1; - f2->dots[1] = g->dots + (y + 1) * w + x + 1; - f2->dots[2] = g->dots + (y + 1) * w + x; + /* generate dots */ + index = 0; + for (y = 0; y <= height; y++) { + for (x = 0; x <= width; x++) { + grid_dot *d = g->dots + index; + /* odd rows are offset to the right */ + d->order = 0; + d->edges = NULL; + d->faces = NULL; + d->x = x * 2 * vec_x + ((y % 2) ? vec_x : 0); + d->y = y * vec_y; + index++; } - index += 2; } + + /* generate faces */ + index = 0; + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + /* initialise two faces for this (x,y) */ + grid_face *f1 = g->faces + index; + grid_face *f2 = f1 + 1; + f1->edges = NULL; + f1->order = 3; + f1->dots = snewn(f1->order, grid_dot*); + f1->has_incentre = FALSE; + f2->edges = NULL; + f2->order = 3; + f2->dots = snewn(f2->order, grid_dot*); + f2->has_incentre = FALSE; + + /* face descriptions depend on whether the row-number is + * odd or even */ + if (y % 2) { + f1->dots[0] = g->dots + y * w + x; + f1->dots[1] = g->dots + (y + 1) * w + x + 1; + f1->dots[2] = g->dots + (y + 1) * w + x; + f2->dots[0] = g->dots + y * w + x; + f2->dots[1] = g->dots + y * w + x + 1; + f2->dots[2] = g->dots + (y + 1) * w + x + 1; + } else { + f1->dots[0] = g->dots + y * w + x; + f1->dots[1] = g->dots + y * w + x + 1; + f1->dots[2] = g->dots + (y + 1) * w + x; + f2->dots[0] = g->dots + y * w + x + 1; + f2->dots[1] = g->dots + (y + 1) * w + x + 1; + f2->dots[2] = g->dots + (y + 1) * w + x; + } + index += 2; + } + } + } else { + /* + * New-style approach, in which there are never any 'ears', + * and if height is even then the grid is nicely 4-way + * symmetric. + * + * Example new-style grids: + * + * 5x5t1:0_21120b11a1a01a1a00c1a0b211021c1h1a2a1a0a + * 5x6t1:0_a1212c22c2a02a2f22a0c12a110d0e1c0c0a101121a1 + */ + tree234 *points = newtree234(grid_point_cmp_fn); + /* Upper bounds - don't have to be exact */ + int max_faces = height * (2*width+1); + int max_dots = (height+1) * (width+1) * 4; + + g->faces = snewn(max_faces, grid_face); + g->dots = snewn(max_dots, grid_dot); + + for (y = 0; y < height; y++) { + /* + * Each row contains (width+1) triangles one way up, and + * (width) triangles the other way up. Which way up is + * which varies with parity of y. Also, the dots around + * each face will flip direction with parity of y, so we + * set up n1 and n2 to cope with that easily. + */ + int y0, y1, n1, n2; + y0 = y1 = y * vec_y; + if (y % 2) { + y1 += vec_y; + n1 = 2; n2 = 1; + } else { + y0 += vec_y; + n1 = 1; n2 = 2; + } + + for (x = 0; x <= width; x++) { + int x0 = 2*x * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x; + + /* + * If the grid has odd height, then we skip the first + * and last triangles on this row, otherwise they'll + * end up as ears. + */ + if (height % 2 == 1 && y == height-1 && (x == 0 || x == width)) + continue; + + grid_face_add_new(g, 3); + grid_face_set_dot(g, grid_get_dot(g, points, x0, y0), 0); + grid_face_set_dot(g, grid_get_dot(g, points, x1, y1), n1); + grid_face_set_dot(g, grid_get_dot(g, points, x2, y0), n2); + } + + for (x = 0; x < width; x++) { + int x0 = (2*x+1) * vec_x, x1 = x0 + vec_x, x2 = x1 + vec_x; + + grid_face_add_new(g, 3); + grid_face_set_dot(g, grid_get_dot(g, points, x0, y1), 0); + grid_face_set_dot(g, grid_get_dot(g, points, x1, y0), n2); + grid_face_set_dot(g, grid_get_dot(g, points, x2, y1), n1); + } + } + + freetree234(points); + assert(g->num_faces <= max_faces); + assert(g->num_dots <= max_dots); } grid_make_consistent(g); @@ -2708,21 +2810,26 @@ static void(*(grid_sizes[]))(int, int, int*, int*, int*) = { GRIDGEN_LIST(FNSZ) char *grid_new_desc(grid_type type, int width, int height, random_state *rs) { - if (type != GRID_PENROSE_P2 && type != GRID_PENROSE_P3) + if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) { + return grid_new_desc_penrose(type, width, height, rs); + } else if (type == GRID_TRIANGULAR) { + return dupstr("0"); /* up-to-date version of triangular grid */ + } else { return NULL; - - return grid_new_desc_penrose(type, width, height, rs); + } } char *grid_validate_desc(grid_type type, int width, int height, char *desc) { - if (type != GRID_PENROSE_P2 && type != GRID_PENROSE_P3) { + if (type == GRID_PENROSE_P2 || type == GRID_PENROSE_P3) { + return grid_validate_desc_penrose(type, width, height, desc); + } else if (type == GRID_TRIANGULAR) { + return grid_validate_desc_triangular(type, width, height, desc); + } else { if (desc != NULL) return "Grid description strings not used with this grid type"; return NULL; } - - return grid_validate_desc_penrose(type, width, height, desc); } grid *grid_new(grid_type type, int width, int height, char *desc)