From 8f632e5a3428101590e8cbe94025060c5c3b5ecf Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Fri, 23 Jul 2010 08:04:16 +0000 Subject: [PATCH] initial commit --- Makefile | 33 +++ hexmap.c | 637 ++++++++++++++++++++++++++++++++++++++++++++++++++ hexmap.h | 108 +++++++++ t/adjacency.c | 25 ++ t/cantor.c | 25 ++ t/center.c | 30 +++ t/distance.c | 25 ++ t/gridsize.c | 36 +++ t/hexbin.c | 25 ++ t/hextest.c | 88 +++++++ t/range.c | 67 ++++++ t/tap.3 | 368 +++++++++++++++++++++++++++++ t/tap.c | 436 ++++++++++++++++++++++++++++++++++ t/tap.h | 89 +++++++ t/within.c | 19 ++ 15 files changed, 2011 insertions(+) create mode 100644 Makefile create mode 100644 hexmap.c create mode 100644 hexmap.h create mode 100644 t/adjacency.c create mode 100644 t/cantor.c create mode 100644 t/center.c create mode 100644 t/distance.c create mode 100644 t/gridsize.c create mode 100644 t/hexbin.c create mode 100644 t/hextest.c create mode 100644 t/range.c create mode 100644 t/tap.3 create mode 100644 t/tap.c create mode 100644 t/tap.h create mode 100644 t/within.c diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20f36fc --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +# set ARCH in your environment if you want to do a multible architecture +# build. This means you Mac users. +# ARCH= -arch x386 -arch x86_64 + +LDFLAGS= $(ARCH) -lm +CFLAGS= -Wall -Wno-parentheses $(ARCH) -I. -I.. +OBJS= hexmap.o +SRC=$(OBJS:.o=.c) +TESTS= t/cantor.t t/distance.t t/adjacency.t t/range.t t/hexbin.t \ + t/gridsize.t t/center.t + +all: test libhexmap.a + +clean: + rm -f $(OBJS) libhexmap.a $(TESTS) + +t/%.t: t/%.o t/tap.o $(OBJS) + $(CC) -I.. -I. -o $@ $+ $(LDFLAGS) + +test: $(TESTS) + @prove --exec '' 2>/dev/null + +hextest: hextest.c libhexmap.a + $(CC) $(CFLAGS) -o $@ hextest.c -L. -lhexmap + +libhexmap.a: $(OBJS) + ar r $@ $+ + ranlib $@ + +.c.o: + $(CC) $(CFLAGS) -c -o $@ $< + +.SUFFIXES: .c .h diff --git a/hexmap.c b/hexmap.c new file mode 100644 index 0000000..bc20d8d --- /dev/null +++ b/hexmap.c @@ -0,0 +1,637 @@ +#include "hexmap.h" + +#include +#include +#include +#include + +static int inversecantor(int cantor, int *x, int *y); + +/* + * This file is written by Nathan Wagner and dedicated to the public + * domain + */ + +double HL_vertexv[] = { + .577350269189625764509148780502, 0.0, + .288675134594812882254574390251, 0.5, + -.288675134594812882254574390251, 0.5, + -.577350269189625764509148780502, 0.0, + -.288675134594812882254574390251, -0.5, + .288675134594812882254574390251, -0.5}; + +/* these all are for a hex one unit across */ +double hexptvd[6][2] = { + {.577350269189625764509148780502, 0.0}, /* 1.0/sqrt3 */ + {.288675134594812882254574390251, 0.5}, /* 0.5/sqrt3 */ + {-.288675134594812882254574390251, 0.5}, + {-.577350269189625764509148780502, 0.0}, + {-.288675134594812882254574390251, -0.5}, + {.288675134594812882254574390251, -0.5} +}; + +/* TODO how is this related? to the above? */ +double texptvd[6][2] = { + {1.154700538379251529018297561004, 0.5}, /* 2.0/sqrt3 */ + {.866025403784438646763723170753, 1.0}, /* 1.5/sqrt3 */ + {.288675134594812882254574390251, 1.0}, + {0.0, 0.5}, + {.288675134594812882254574390251, 0.0}, + {.866025403784438646763723170753, 0.0} +}; + +double hexpthd[6][2] = { + {0.0, .577350269189625764509148780502}, + {0.5, .288675134594812882254574390251}, + {0.5, -.288675134594812882254574390251}, + {0.0, -.577350269189625764509148780502}, + {-0.5, -.288675134594812882254574390251}, + {-0.5, .288675134594812882254574390251} +}; + +void HL_vertices(int cantor, double *vc) { + int i; + double xc, yc; + + HL_hexcenter(cantor, &xc, &yc); + + for (i=0; i<6; i++) { + *vc++ = hexptvd[i][0] + xc; + *vc++ = hexptvd[i][1] + yc; + } + *vc++ = hexptvd[0][0] + xc; + *vc++ = hexptvd[0][1] + yc; +} + +void HL_trianglefan(int cantor, double *vc) { + HL_hexcenter(cantor, vc, vc+1); + HL_vertices(cantor, vc+2); +} + +double HL_center_x(int cantor) { + double x; + + HL_hexcenter(cantor, &x, 0); + return x; +} + +double HL_center_y(int cantor) { + double y; + + HL_hexcenter(cantor, 0, &y); + return y; +} + +int HL_hexcenter(int cantor, double *xc, double *yc) { + int x, y; + double stride = 1.5/sqrt(3.0); + + inversecantor(cantor, &x, &y); + + if (xc) *xc = x * stride; + if (yc && x >= 0) *yc = y + ((x + 1) % 2) / 2.0 - 0.5; + if (yc && x < 0) *yc = y + ((-x + 1) % 2) / 2.0 - 0.5; + + return cantor; +} + +/* + * This function assumes that the hexes are one unit across, and vertically + * oriented. If that is not the case, you will need to transform + * your input coordinates first. + */ +int HL_cantor_bin(double x, double y) { + int i, j; + + HL_hexbin(1.0, x, y, &i, &j); + return HL_cantor_xy(i, j); +} + +static int xy2ijk(int x, int y, int *i, int *j, int *k) { + int pi, pj, pk; + + pi = x; + pj = -y; + if (x < 0) { + pj = pj + (-x + 1) / 2; + } else { + pj = pj - x/2; + } + pk = -pi - pj; + + if (i) *i = pi; + if (j) *j = pj; + if (k) *k = pk; + + return HL_cantor_xy(x,y); +} + +static int ijk2xy(int i, int j, int k, int *x, int *y) { + int px, py; + + px = i; + + /* py = -j - i/2; */ + py = -j; + + if (i < 0) { + py += (-i + 1)/2; + } else { + py -= i/2; + } + + if (x) *x = px; + if (y) *y = py; + + return HL_cantor_xy(px,py); +} + +int HL_cantor_ijk(int i, int j, int k) { + return ijk2xy(i,j,k,0,0); +} + +int HL_distance(int from, int to) { + int dist = 0, i;; + int fc[3], tc[3]; + + HL_cantor_arrays(from, 0, fc); + HL_cantor_arrays(to, 0, tc); + + for (i=0;i<=2;i++) { + dist += abs(fc[i] - tc[i]); + } + + return dist / 2; +} + +int HL_hexes_within_range(int hex, int range, int *list, int size) { + int count = 0; + int i; + + if (range == 0) { + return HL_hexes_at_range(hex, 0, list, size); + } + + for (i=1;i<=range;i++) { + count += HL_hexes_at_range(hex, i, count > size ? 0 : list+count, size-count); + } + return count; +} + +int HL_hexes_at_range(int hex, int range, int *list, int size) { + int q; /* p and q are count/loop vars */ + int c[3]; /* ijk coord array */ + + if (range == 0) { + if (list) { + list[0] = hex; + } + return 1; + } else if (range < 0) { + return 0; + } + + /* TODO don't bother to collect if the list isn't big enough? */ + /* i.e. if (!list || size < range * 6) */ + if (!list || size < 1) return range * 6; + + HL_cantor_arrays(hex, 0, c); + c[0] += range; + c[2] = -c[0] - c[1]; + hex = HL_cantor_ijkp(c); + + for(q=0; q INT_MAX - ydim - 1) + return 0; + + return 1; +} + +int HL_map_max_dimension(void) { + int low, high, try; + + low = 1; high = INT_MAX/2; + + while (low != high - 1) { + try = (low + high) / 2; + if (HL_map_bounds_ok(try,try)) { + low = try; + } else { + high = try; + } + } + + return low; +} + +#ifdef ASTAR + +struct astar_hex { + int hex; + int f, g, h; + int parent; +}; + +struct astar { + int open, closed; /* how many in open or closed */ + int closedroom, openroom; /* how much is malloced */ + struct astar_hex *openlist; + struct astar_hex *closedlist; + int known, index; + int error; +}; + +static void astar_init(struct astar *state) { + state->open = 0; + state->closed = 0; + state->closedroom = 0; + state->openroom = 0; + state->openlist = 0; + state->closedlist = 0; + state->error = 0; + state->known = 0; + state->index = 0; + + return; +} + +static void removeopen(struct astar *s, int hex) { + int index; + + if (s->error) return; + if (s->open == 0) return; + + if (s->known != hex) { + for (index = 0; index < s->open; index++) { + if (s->openlist[index].hex == hex) break; + } + } else { + index = s->index; + s->known = 0; s->index = 0; + } + s->openlist[index] = s->openlist[open]; + s->open--; +} + +static void addopen(struct astar *s, int hex) { + struct astar_hex *mem; + if (s->error) return; + + if (s->open + 1 > s->openroom) { + mem = realloc(s->openlist, s->openroom * 2 * sizeof *mem); + if (mem == NULL) { + s->error = errno; + return; + } + state->openlist = mem; + s->openroom = s->openroom * 2; + } + + s->openlist[s->open].hex = hex; + s->openlist[s->open].f = 0; + s->openlist[s->open].g = 0; + s->openlist[s->open].h = 0; + s->known = hex; + s->index = s->open; + s->open = s->open + 1; +} + +int lowrank(struct astar *s) { + int f; + int hex; + int i; + + if (!s || s->open = 0) return 0; + + hex = s->openlist[0].hex; + f = s->openlist[0].f; + + for(i=1; i < s->open; i++) { + if (s->openlist[i].f < f) { + hex = s->openlist[i].hex; + f = s->openlist[i].f; + } + /* TODO check s->openlist[i].tiebreak */ + } + + return hex; +} + +static int default_metric(int a, int b) { + return 1; +} + +/* + * find a path from start to end. we use A* + */ +int HL_findpath(int start, int end, int psize, int *path, + double (*metric)(int,int)) { + int neighbors[6]; + int count; + int cur = 0; + struct astar state; + int neighbor; + + if (start == end) return 0; + if (!metric) metric = default_metric; + + astar_init(&state); + addopen(&state, start); + + while (1) { + cur = lowrank(&state); + if (cur == end) { + break; + } + addclosed(&state, cur); + count = HL_hexes_at_range(cur->hex, 1, neighbors, 6); + for (i=0;iiso) return 1; + if (h->x >= 0) { + h->y = -h->y - (h->x+1)/2; + } else { + /* need to round toward -inf, not toward zero, so x-1 */ + h->y = -h->y - h->x/2; + } + h->iso = 0; + + return 1; +} + +static int hex_iso(struct hex *h) { + if (h->iso) return 1; + + if (h->x >= 0) { + h->y = (-h->y - (h->x+1)/2); + } else { + /* need to round toward -inf, not toward zero, so x-1 */ + h->y = (-h->y - (h->x)/2); + } + + h->z = -h->x - h->y; + h->iso = 1; + return 1; +} + +int HL_hexbin(double width, double x, double y, int *i, int *j) { + double z, rx, ry, rz; + double abs_dx, abs_dy, abs_dz; + int ix, iy, iz, s; + struct hex h; + + /* TODO just hard-code this cosine */ + x = x / cos(30 * M_PI / 180.0); /* rotated X coord */ + y = y - x / 2.0; /* adjustment for rotated X */ + + /* adjust for actual hexwidth */ + x /= width; + y /= width; + + z = -x - y; + + ix = rx = floor(x + 0.5); + iy = ry = floor(y + 0.5); + iz = rz = floor(z + 0.5); + + s = ix + iy + iz; + + if (s) { + abs_dx = fabs(rx - x); + abs_dy = fabs(ry - y); + abs_dz = fabs(rz - z); + + if (abs_dx >= abs_dy && abs_dx >= abs_dz) { + ix -= s; + } else if (abs_dy >= abs_dx && abs_dy >= abs_dz) { + iy -= s; + } else { + iz -= s; + } + } + h.x = ix; + h.y = iy; + h.z = iz; + h.iso = 1; + + hex_xy(&h); + if (i) *i = h.x; + if (j) *j = h.y; + return HL_cantor_xy(h.x, h.y); +} diff --git a/hexmap.h b/hexmap.h new file mode 100644 index 0000000..1058653 --- /dev/null +++ b/hexmap.h @@ -0,0 +1,108 @@ +#ifndef HEXLIB_H_ +#define HEXLIB_H_ 1 + +/* + * this library implements a generic library for hexgrids for games. + * it avoids display issues, other than some general provisions + * for data + * + * This file is written by Nathan Wagner and dedicated to the + * public domain. + */ + +/* max size for cantor numbers is 16383, or 32767 if non-negative, + * assuming a 32 bit int + */ + +/* map from cartesian coordinates to hex coords */ +/* cartesian coordinates are positive Y up, hex coords + * are positive Y down. You may need to pass in the + * negative of your Y coordinate + * + * width is the distance from one hexside to the opposite + * hexside. i and j are the cartesian coordinates of the hex + * the x,y point falls in. If a point happens to fall exactly + * on a hex boundary, the result will depend on how the machine + * does the floating point math, but should be consistent. + */ +int HL_hexbin(double width, double x, double y, int *i, int *j); + +/* generate the cantor mapping of a hexes x,y coordinate. + * See http://en.wikipedia.org/wiki/Cantor_pairing_function. + * We modify the basic formula to support negative coordinates + * + * The cantor mapping is *not* a contiguous mapping, as it maps + * onto a triangle, so you can't use the cantor mapping as + * an index into an array. Or, you can, but the array + * will be (I think) twice as large as it needs to be + * + * See also http://mathworld.wolfram.com/PairingFunction.html + * for a reference to a square mapping by Stein that doesn't + * have this problem, at least for square maps. + * + * We use the cantor mapping so that the generated integer + * is independent of the size of the grid. + */ + +/* Cartesian functions */ +int HL_cantor_xy(int x, int y); +int HL_cantor_xyp(int *xy); +int HL_cantor_x(int cantor); +int HL_cantor_y(int cantor); + +/* Isometric functions */ +int HL_cantor_ijk(int i, int j, int k); +int HL_cantor_ijkp(int *ijk); +int HL_cantor_i(int cantor); +int HL_cantor_k(int cantor); +int HL_cantor_j(int cantor); + +int HL_cantor_bin(double x, double y); + +/* A general decode function */ +int HL_cantor_decode(int can, int *x, int *y, int *i, int *j, int *k); +int HL_cantor_arrays(int can, int *xy, int *ijk); + +/* + * layout assistance functions + * + * HL_center_x(int cantor) returns the cartesian X coordinate + * of a given hex, assuming a width of 1.0. Scaling can + * be done by the caller. + * + * Similarly for HL_center_y() and HL_hexcenter() + */ +double HL_center_x(int cantor); +double HL_center_y(int cantor); +int HL_hexcenter(int cantor, double *x, double *y); + +/* how far is it on the hex grid */ +int HL_distance(int from_cantor, int to_cantor); + +int HL_adjhex(int start, int dir); +int HL_hexes_at_range(int hex, int range, int *list, int size); +int HL_hexes_within_range(int hex, int range, int *list, int size); + +typedef double (*HL_costfunc)(int start, int finish); + +/* writes up to len cantors to path. returns last + * value written + * perhaps it should return the last value not written, if the + * path is incomplete? + * either way, it can then be used in a loop + */ +int HL_path(int start, int end, int *path, int len); + +int HL_map_bounds_ok(int xdim, int ydim); +int HL_map_max_dimension(void); + +/* + * Plotting support + */ +void HL_trianglefan(int cantor, double *); + +struct HL_hex { + int x,y; /* hmm. would be nice to be able to just point to a generic */ +}; + +#endif diff --git a/t/adjacency.c b/t/adjacency.c new file mode 100644 index 0000000..95750cf --- /dev/null +++ b/t/adjacency.c @@ -0,0 +1,25 @@ +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int hex, start, dist; + int x, y, i; + + plan_tests(294); + + for (x = -3 ; x <= 3; x++) { + for (y = -3 ; y <= 3; y++) { + start = HL_cantor_xy(x, y); + for (i = 0; i < 6; i++) { + hex = HL_adjhex(start, i); + dist = HL_distance(hex, start); + ok(dist == 1, + "%d, %d direction %d distance one", + x, y, i); + } + } + } + + return exit_status(); +} diff --git a/t/cantor.c b/t/cantor.c new file mode 100644 index 0000000..a2e9290 --- /dev/null +++ b/t/cantor.c @@ -0,0 +1,25 @@ +#include + +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int xy[2]; + int ijk[3]; + int hex; + int x, y; + + plan_tests(81*2); + + for (x=-4;x<=4;x++) { + for (y=-4;y<=4;y++) { + hex = HL_cantor_xy(x,y); + HL_cantor_arrays(hex, xy, ijk); + ok(x == xy[0], "x check %d %d", x, y); + ok(y == xy[1], "y check %d %d", x, y); + } + } + + return exit_status(); +} diff --git a/t/center.c b/t/center.c new file mode 100644 index 0000000..00fdff2 --- /dev/null +++ b/t/center.c @@ -0,0 +1,30 @@ +#include + +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int start, x, y; + + plan_tests(6); + + x = 0; y = 0; + start = HL_cantor_xy(x,y); + ok(HL_center_x(start) == x * 1.5 / sqrt(3.0), "%d %d center X", x, y); + ok(HL_center_y(start) == 0.0, "%d %d center Y", x, y); + + x = 1; y = 1; + start = HL_cantor_xy(x,y); + ok(HL_center_x(start) == x * 1.5 / sqrt(3.0), "%d %d center X", x, y); + ok(HL_center_y(start) == 0.5, "%d %d center Y = %f", x, y, + HL_center_y(start)); + + x = 2; y = 2; + start = HL_cantor_xy(x,y); + ok(HL_center_x(start) == x * 1.5 / sqrt(3.0), "%d %d center X", x, y); + ok(HL_center_y(start) == 2.0, "%d %d center Y = %f", x, y, + HL_center_y(start)); + + return exit_status(); +} diff --git a/t/distance.c b/t/distance.c new file mode 100644 index 0000000..0f3ef97 --- /dev/null +++ b/t/distance.c @@ -0,0 +1,25 @@ +#include "hexmap.h" + +#include "tap.h" + +void dcheck(int x1, int y1, int x2, int y2, int expect) { + int from, to, dist; + from = HL_cantor_xy(x1,y1); + to = HL_cantor_xy(x2,y2); + dist = HL_distance(from,to); + ok(dist == expect, + "distance from (%02d, %02d) to (%02d, %02d) = %d (expect %d)\n", + x1, y1, x2, y2, dist, expect); +} + +int main(void) { + plan_tests(5); + + dcheck(1,1,2,1,1); + dcheck(1,1,2,2,2); + dcheck(2,2,2,1,1); + dcheck(1,1,2,3,3); + dcheck(3,3,3,3,0); + + return exit_status(); +} diff --git a/t/gridsize.c b/t/gridsize.c new file mode 100644 index 0000000..784e467 --- /dev/null +++ b/t/gridsize.c @@ -0,0 +1,36 @@ +#include "hexmap.h" + +#include "tap.h" + +int searchbound(int low, int high) { + int try; + + while (low != high - 1) { + try = (low + high) / 2; + if (HL_map_bounds_ok(try,try)) { + low = try; + } else { + high = try; + } + } + return low; +} + +int main(void) { + int start; + int x; + + plan_tests(2); + + x = 1; + do { + x = x * 2; + } while (HL_map_bounds_ok(x,x)); + x = searchbound(x/2, x); + ok(x == HL_map_max_dimension(), "checking HL_map_max_dimension()"); + + start = HL_cantor_xy(x,x); + ok(start > 0, "maximum cantor size is greater than zero"); + + return 0; +} diff --git a/t/hexbin.c b/t/hexbin.c new file mode 100644 index 0000000..e486bc8 --- /dev/null +++ b/t/hexbin.c @@ -0,0 +1,25 @@ +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int x, y; + + plan_tests(6); + + HL_hexbin(1.0, 0.444194, -4.639363, &x, &y); + ok(x == 1 && y == 4, "hexbin 0.444194, 4.639363 = 1, 4, %d %d", x, y); + + HL_hexbin(1.0, 0.0, 0.0, &x, &y); + ok(x == 0 && y == 0, "0.0 0.0 -> 0 0"); + + HL_hexbin(0.1111111, 0.288675, 0.500000, &x, &y); + ok(x == 3, "center bin X %d == 3", x); + ok(y == -5, "center bin Y %d == -5", y); + + HL_hexbin(0.1111111, 0.866025, 0.500000, &x, &y); + ok(x == 9, "vertex bin X %d == 9", x); + ok(y == -5, "vertex bin Y %d == -5", y); + + return exit_status(); +} diff --git a/t/hextest.c b/t/hextest.c new file mode 100644 index 0000000..f7b219b --- /dev/null +++ b/t/hextest.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int xy[2]; + int ijk[3]; + int hex, start, dist; + int x, y, i, count; + int range[32]; + + printf("checking within function\n"); + start = HL_cantor_xy(3,3); + count = HL_hexes_within_range(start, 2, range, 0); + printf("count = %d\n", count); + assert(count == 18); + count = HL_hexes_within_range(start, 2, range, count); + for (i = 0; i < count; i++) { + dumphex(range[i]); + } + + printf("checking hex center\n"); + start = HL_cantor_xy(0,0); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + start = HL_cantor_xy(1,1); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + start = HL_cantor_xy(-1,1); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + start = HL_cantor_xy(2,1); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + start = HL_cantor_xy(2,5); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + start = HL_cantor_xy(-2,5); + dumphex(start); + printf("center = %f, %f\n", HL_center_x(start), HL_center_y(start)); + + printf("checking hexbin -0.444194, 4.639363\n"); + start = HL_hexbin(0, -0.444194, 4.639363); + dumphex(start); + + printf("checking maximum grid size\n"); + x = 1; + do { + x = x * 2; + printf("checking %d...\n", x); + } while (HL_map_bounds_ok(x,x)); + x = searchbound(x/2, x); + printf("maximum size = %d\n", x); + assert(x = HL_map_max_dimension()); + + start = HL_cantor_xy(x,x); + assert(start>0); + dumphex(start); + + printf("trying max+1 = HL_cantor_xy(%d,%d)\n", x+1, x+1); + printf("the dump should be bogus\n"); + start = HL_cantor_xy(x+1,x+1); + assert(start<0); + dumphex(start); + + return 0; +} + +int searchbound(int low, int high) { + int try; + + while (low != high - 1) { + try = (low + high) / 2; + printf("searching, low = %d, high = %d, trying %d\n", low, high, try); + if (HL_map_bounds_ok(try,try)) { + low = try; + } else { + high = try; + } + } + return low; +} diff --git a/t/range.c b/t/range.c new file mode 100644 index 0000000..b40b86d --- /dev/null +++ b/t/range.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +#include "hexmap.h" + +#include "tap.h" + +int icmp(const void *ap, const void *bp) { + int a; + int b; + + a = *((int *)(ap)); + b = *((int *)(bp)); + + return a-b; +} + +void sort(int *r, int ct) { + qsort(r, ct, sizeof *r, icmp); +} + +int main(void) { + int start; + int i, count; + int range[32]; + int testrange[32]; + + plan_tests(7+4); + + printf("range = 1 from 3,3\n"); + start = HL_cantor_xy(3,3); + count = HL_hexes_at_range(start, 1, range, 0); + ok(count == 6, "6 hexes at range 1"); + count = HL_hexes_at_range(start, 1, range, count); + sort(range, 6); + testrange[0] = HL_cantor_xy(3,2); + testrange[1] = HL_cantor_xy(3,4); + testrange[2] = HL_cantor_xy(4,3); + testrange[3] = HL_cantor_xy(4,2); + testrange[4] = HL_cantor_xy(2,3); + testrange[5] = HL_cantor_xy(2,2); + sort(testrange, 6); + for (i = 0; i < count; i++) { + ok(range[i] == testrange[i], "3, 3 range 1 hex %d", testrange[i]); + } + + start = HL_cantor_xy(0,3); + count = HL_hexes_at_range(start, 1, range, 0); + ok(count == 6, "6 hexes at range1 from 0,3"); + + start = HL_cantor_xy(-1,5); + count = HL_hexes_at_range(start, 1, range, 0); + ok(count == 6, "6 hexes at range1 from -1,5"); + + start = HL_cantor_xy(-2,3); + count = HL_hexes_at_range(start, 1, range, 0); + ok(count == 6, "6 hexes at range1 from -2,3"); + + start = HL_cantor_xy(3,3); + count = HL_hexes_at_range(start, 2, range, 0); + ok(count == 12, "6 hexes at range1 from 3,3"); + + return exit_status(); +} diff --git a/t/tap.3 b/t/tap.3 new file mode 100644 index 0000000..4b23c24 --- /dev/null +++ b/t/tap.3 @@ -0,0 +1,368 @@ +.Dd December 20, 2004 +.Os +.Dt TAP 3 +.Sh NAME +.Nm tap +.Nd write tests that implement the Test Anything Protocol +.Sh SYNOPSIS +.In tap.h +.Sh DESCRIPTION +The +.Nm +library provides functions for writing test scripts that produce output +consistent with the Test Anything Protocol. A test harness that parses +this protocol can run these tests and produce useful reports indicating +their success or failure. +.Ss PRINTF STRINGS +In the descriptions that follow, for any function that takes as the +last two parameters +.Dq Fa char * , Fa ... +it can be assumed that the +.Fa char * +is a +.Fn printf +-like format string, and the optional arguments are values to be placed +in that string. +.Ss TEST PLANS +.Bl -tag -width indent +.It Xo +.Ft int +.Fn plan_tests "unsigned int" +.Xc +.It Xo +.Ft int +.Fn plan_no_plan "void" +.Xc +.It Xo +.Ft int +.Fn plan_skip_all "char *" "..." +.Xc +.El +.Pp +You must first specify a test plan. This indicates how many tests you +intend to run, and allows the test harness to notice if any tests were +missed, or if the test program exited prematurely. +.Pp +To do this, use +.Fn plan_tests , +which always returns 0. The function will cause your program to exit +prematurely if you specify 0 tests. +.Pp +In some situations you may not know how many tests you will be running, or +you are developing your test program, and do not want to update the +.Fn plan_tests +parameter every time you make a change. For those situations use +.Fn plan_no_plan . +It returns 0, and indicates to the test harness that an indeterminate number +of tests will be run. +.Pp +Both +.Fn plan_tests +and +.Fn plan_no_plan +will cause your test program to exit prematurely with a diagnostic +message if they are called more than once. +.Pp +If your test program detects at run time that some required functionality +is missing (for example, it relies on a database connection which is not +present, or a particular configuration option that has not been included +in the running kernel) use +.Fn plan_skip_all , +passing as parameters a string to display indicating the reason for skipping +the tests. +.Ss SIMPLE TESTS +.Bl -tag -width indent +.It Xo +.Ft unsigned int +.Fn ok "expression" "char *" "..." +.Xc +.It Xo +.Ft unsigned int +.Fn ok1 "expression" +.Xc +.It Xo +.Ft unsigned int +.Fn pass "char *" "..." +.Xc +.It Xo +.Ft unsigned int +.Fn fail "char *" "..." +.Xc +.El +.Pp +Tests are implemented as expressions checked by calls to the +.Fn ok +and +.Fn ok1 +macros. In both cases +.Fa expression +should evaluate to true if the test succeeded. +.Pp +.Fn ok +allows you to specify a name, or comment, describing the test which will +be included in the output. +.Fn ok1 +is for those times when the expression to be tested is self +explanatory and does not need an associated comment. In those cases +the test expression becomes the comment. +.Pp +These four calls are equivalent: +.Bd -literal -offset indent +int i = 5; + +ok(i == 5, "i equals 5"); /* Overly verbose */ +ok(i == 5, "i equals %d", i); /* Just to demonstrate printf-like + behaviour of the test name */ +ok(i == 5, "i == 5"); /* Needless repetition */ +ok1(i == 5); /* Just right */ +.Ed +.Pp +It is good practice to ensure that the test name describes the meaning +behind the test rather than what you are testing. Viz +.Bd -literal -offset indent +ok(db != NULL, "db is not NULL"); /* Not bad, but */ +ok(db != NULL, "Database conn. succeeded"); /* this is better */ +.Ed +.Pp +.Fn ok +and +.Fn ok1 +return 1 if the expression evaluated to true, and 0 if it evaluated to +false. This lets you chain calls from +.Fn ok +to +.Fn diag +to only produce diagnostic output if the test failed. For example, this +code will include diagnostic information about why the database connection +failed, but only if the test failed. +.Bd -literal -offset indent +ok(db != NULL, "Database conn. succeeded") || + diag("Database error code: %d", dberrno); +.Ed +.Pp +You also have +.Fn pass +and +.Fn fail . +From the Test::More documentation: +.Bd -literal -offset indent +Sometimes you just want to say that the tests have passed. +Usually the case is you've got some complicated condition +that is difficult to wedge into an ok(). In this case, +you can simply use pass() (to declare the test ok) or fail +(for not ok). + +Use these very, very, very sparingly. +.Ed +.Pp +These are synonyms for ok(1, ...) and ok(0, ...). +.Ss SKIPPING TESTS +.Bl -tag -width indent +.It Xo +.Ft int +.Fn skip "unsigned int" "char *" "..." +.Xc +.It Xo +.Fn skip_start "expression" "unsigned int" "char *" "..." +.Xc +.It Xo +.Sy skip_end +.Xc +.El +.Pp +Sets of tests can be skipped. Ordinarily you would do this because +the test can't be run in this particular testing environment. +.Pp +For example, suppose some tests should be run as root. If the test is +not being run as root then the tests should be skipped. In this +implementation, skipped tests are flagged as being ok, with a special +message indicating that they were skipped. It is your responsibility +to ensure that the number of tests skipped (the first parameter to +.Fn skip ) +is correct for the number of tests to skip. +.Pp +One way of implementing this is with a +.Dq do { } while(0); +loop, or an +.Dq if( ) { } else { } +construct, to ensure that there are no additional side effects from the +skipped tests. +.Bd -literal -offset indent +if(getuid() != 0) { + skip(1, "because test only works as root"); +} else { + ok(do_something_as_root() == 0, "Did something as root"); +} +.Ed +.Pp +Two macros are provided to assist with this. The previous example could +be re-written as follows. +.Bd -literal -offset indent +skip_start(getuid() != 0, 1, "because test only works as root"); + +ok(do_something_as_root() == 0, "Did something as root"); + +skip_end; /* It's a macro, no parentheses */ +.Ed +.Ss MARKING TESTS AS Dq TODO +.Bl -tag -width indent +.It Xo +.Ft void +.Fn todo_start "char *" "..." +.Xc +.It Xo +.Ft void +.Fn todo_end "void" +.Xc +.El +.Pp +Sets of tests can be flagged as being +.Dq TODO . +These are tests that you expect to fail, probably because you haven't +fixed a bug, or finished a new feature yet. These tests will still be +run, but with additional output that indicates that they are expected +to fail. Should a test start to succeed unexpectedly, tools like +.Xr prove 1 +will indicate this, and you can move the test out of the todo +block. This is much more useful than simply commenting out (or +.Dq #ifdef 0 ... #endif ) +the tests. +.Bd -literal -offset indent +todo_start("dwim() not returning true yet"); + +ok(dwim(), "Did what the user wanted"); + +todo_end(); +.Ed +.Pp +Should +.Fn dwim +ever start succeeding you will know about it as soon as you run the +tests. Note that +.Em unlike +the +.Fn skip_* +family, additional code between +.Fn todo_start +and +.Fn todo_end +.Em is +executed. +.Ss SKIP vs. TODO +From the Test::More documentation; +.Bd -literal -offset indent +If it's something the user might not be able to do, use SKIP. +This includes optional modules that aren't installed, running +under an OS that doesn't have some feature (like fork() or +symlinks), or maybe you need an Internet connection and one +isn't available. + +If it's something the programmer hasn't done yet, use TODO. +This is for any code you haven't written yet, or bugs you have +yet to fix, but want to put tests in your testing script +(always a good idea). +.Ed +.Ss DIAGNOSTIC OUTPUT +.Bl -tag -width indent +.It Xo +.Fr unsigned int +.Fn diag "char *" "..." +.Xc +.El +.Pp +If your tests need to produce diagnostic output, use +.Fn diag . +It ensures that the output will not be considered by the TAP test harness. +.Fn diag +adds the necessary trailing +.Dq \en +for you. +.Bd -literal -offset indent +diag("Expected return code 0, got return code %d", rcode); +.Ed +.Pp +.Fn diag +always returns 0. +.Ss EXIT STATUS +.Bl -tag -width indent +.It Xo +.Fr int +.Fn exit_status void +.Xc +.El +.Pp +For maximum compatability your test program should return a particular +exit code. This is calculated by +.Fn exit_status +so it is sufficient to always return from +.Fn main +with either +.Dq return exit_status(); +or +.Dq exit(exit_status()); +as appropriate. +.Sh EXAMPLES +The +.Pa tests +directory in the source distribution contains numerous tests of +.Nm +functionality, written using +.Nm . +Examine them for examples of how to construct test suites. +.Sh COMPATABILITY +.Nm +strives to be compatible with the Perl Test::More and Test::Harness +modules. The test suite verifies that +.Nm +is bug-for-bug compatible with their behaviour. This is why some +functions which would more naturally return nothing return constant +values. +.Pp +If the +.Lb libpthread +is found at compile time, +.Nm +.Em should +be thread safe. Indications to the contrary (and test cases that expose +incorrect behaviour) are very welcome. +.Sh SEE ALSO +.Xr Test::More 1 , +.Xr Test::Harness 1 , +.Xr prove 1 +.Sh STANDARDS +.Nm +requires a +.St -isoC-99 +compiler. Some of the +.Nm +functionality is implemented as variadic macros, and that functionality +was not formally codified until C99. Patches to use +.Nm +with earlier compilers that have their own implementation of variadic +macros will be gratefully received. +.Sh HISTORY +.Nm +was written to help improve the quality and coverage of the FreeBSD +regression test suite, and released in the hope that others find it +a useful tool to help improve the quality of their code. +.Sh AUTHORS +.An "Nik Clayton" Aq nik@ngo.org.uk , +.Aq nik@FreeBSD.org +.Pp +.Nm +would not exist without the efforts of +.An "Michael G Schwern" Aq schqern@pobox.com , +.An "Andy Lester" Aq andy@petdance.com , +and the countless others who have worked on the Perl QA programme. +.Sh BUGS +Ideally, running the tests would have no side effects on the behaviour +of the application you are testing. However, it is not always possible +to avoid them. The following side effects of using +.Nm +are known. +.Bl -bullet -offset indent +.It +stdout is set to unbuffered mode after calling any of the +.Fn plan_* +functions. +.El diff --git a/t/tap.c b/t/tap.c new file mode 100644 index 0000000..475c55e --- /dev/null +++ b/t/tap.c @@ -0,0 +1,436 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include + +#include "tap.h" + +static int no_plan = 0; +static int skip_all = 0; +static int have_plan = 0; +static unsigned int test_count = 0; /* Number of tests that have been run */ +static unsigned int e_tests = 0; /* Expected number of tests to run */ +static unsigned int failures = 0; /* Number of tests that failed */ +static char *todo_msg = NULL; +static char *todo_msg_fixed = "libtap malloc issue"; +static int todo = 0; +static int test_died = 0; + +/* Encapsulate the pthread code in a conditional. In the absence of + libpthread the code does nothing */ +#ifdef HAVE_LIBPTHREAD +#include +static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER; +# define LOCK pthread_mutex_lock(&M); +# define UNLOCK pthread_mutex_unlock(&M); +#else +# define LOCK +# define UNLOCK +#endif + +static void _expected_tests(unsigned int); +static void _tap_init(void); +static void _cleanup(void); + +/* + * Generate a test result. + * + * ok -- boolean, indicates whether or not the test passed. + * test_name -- the name of the test, may be NULL + * test_comment -- a comment to print afterwards, may be NULL + */ +unsigned int +_gen_result(int ok, const char *func, char *file, unsigned int line, + char *test_name, ...) +{ + va_list ap; + char *local_test_name = NULL; + char *c; + int name_is_digits; + + LOCK; + + test_count++; + + /* Start by taking the test name and performing any printf() + expansions on it */ + if(test_name != NULL) { + va_start(ap, test_name); + vasprintf(&local_test_name, test_name, ap); + va_end(ap); + + /* Make sure the test name contains more than digits + and spaces. Emit an error message and exit if it + does */ + if(local_test_name) { + name_is_digits = 1; + for(c = local_test_name; *c != '\0'; c++) { + if(!isdigit(*c) && !isspace(*c)) { + name_is_digits = 0; + break; + } + } + + if(name_is_digits) { + diag(" You named your test '%s'. You shouldn't use numbers for your test names.", local_test_name); + diag(" Very confusing."); + } + } + } + + if(!ok) { + printf("not "); + failures++; + } + + printf("ok %d", test_count); + + if(test_name != NULL) { + printf(" - "); + + /* Print the test name, escaping any '#' characters it + might contain */ + if(local_test_name != NULL) { + flockfile(stdout); + for(c = local_test_name; *c != '\0'; c++) { + if(*c == '#') + fputc('\\', stdout); + fputc((int)*c, stdout); + } + funlockfile(stdout); + } else { /* vasprintf() failed, use a fixed message */ + printf("%s", todo_msg_fixed); + } + } + + /* If we're in a todo_start() block then flag the test as being + TODO. todo_msg should contain the message to print at this + point. If it's NULL then asprintf() failed, and we should + use the fixed message. + + This is not counted as a failure, so decrement the counter if + the test failed. */ + if(todo) { + printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed); + if(!ok) + failures--; + } + + printf("\n"); + + if(!ok) + diag(" Failed %stest (%s:%s() at line %d)", + todo ? "(TODO) " : "", file, func, line); + + free(local_test_name); + + UNLOCK; + + /* We only care (when testing) that ok is positive, but here we + specifically only want to return 1 or 0 */ + return ok ? 1 : 0; +} + +/* + * Initialise the TAP library. Will only do so once, however many times it's + * called. + */ +void +_tap_init(void) +{ + static int run_once = 0; + + LOCK; + + if(!run_once) { + atexit(_cleanup); + + /* stdout needs to be unbuffered so that the output appears + in the same place relative to stderr output as it does + with Test::Harness */ + setbuf(stdout, 0); + run_once = 1; + } + + UNLOCK; +} + +/* + * Note that there's no plan. + */ +int +plan_no_plan(void) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + no_plan = 1; + + UNLOCK; + + return 0; +} + +/* + * Note that the plan is to skip all tests + */ +int +plan_skip_all(char *reason) +{ + + LOCK; + + _tap_init(); + + skip_all = 1; + + printf("1..0"); + + if(reason != NULL) + printf(" # Skip %s", reason); + + printf("\n"); + + UNLOCK; + + exit(0); +} + +/* + * Note the number of tests that will be run. + */ +int +plan_tests(unsigned int tests) +{ + + LOCK; + + _tap_init(); + + if(have_plan != 0) { + fprintf(stderr, "You tried to plan twice!\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + if(tests == 0) { + fprintf(stderr, "You said to run 0 tests! You've got to run something.\n"); + test_died = 1; + UNLOCK; + exit(255); + } + + have_plan = 1; + + _expected_tests(tests); + + UNLOCK; + + return 0; +} + +unsigned int +diag(char *fmt, ...) +{ + va_list ap; + + LOCK; + + fputs("# ", stderr); + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + fputs("\n", stderr); + + UNLOCK; + + return 0; +} + +void +_expected_tests(unsigned int tests) +{ + + LOCK; + + printf("1..%d\n", tests); + e_tests = tests; + + UNLOCK; +} + +int +skip(unsigned int n, char *fmt, ...) +{ + va_list ap; + char *skip_msg; + + LOCK; + + va_start(ap, fmt); + asprintf(&skip_msg, fmt, ap); + va_end(ap); + + while(n-- > 0) { + test_count++; + printf("ok %d # skip %s\n", test_count, + skip_msg != NULL ? + skip_msg : "libtap():malloc() failed"); + } + + free(skip_msg); + + UNLOCK; + + return 1; +} + +void +todo_start(char *fmt, ...) +{ + va_list ap; + + LOCK; + + va_start(ap, fmt); + vasprintf(&todo_msg, fmt, ap); + va_end(ap); + + todo = 1; + + UNLOCK; +} + +void +todo_end(void) +{ + + LOCK; + + todo = 0; + free(todo_msg); + + UNLOCK; +} + +int +exit_status(void) +{ + int r; + + LOCK; + + /* If there's no plan, just return the number of failures */ + if(no_plan || !have_plan) { + UNLOCK; + return failures; + } + + /* Ran too many tests? Return the number of tests that were run + that shouldn't have been */ + if(e_tests < test_count) { + r = test_count - e_tests; + UNLOCK; + return r; + } + + /* Return the number of tests that failed + the number of tests + that weren't run */ + r = failures + e_tests - test_count; + UNLOCK; + + return r; +} + +/* + * Cleanup at the end of the run, produce any final output that might be + * required. + */ +void +_cleanup(void) +{ + + LOCK; + + /* If plan_no_plan() wasn't called, and we don't have a plan, + and we're not skipping everything, then something happened + before we could produce any output */ + if(!no_plan && !have_plan && !skip_all) { + diag("Looks like your test died before it could output anything."); + UNLOCK; + return; + } + + if(test_died) { + diag("Looks like your test died just after %d.", test_count); + UNLOCK; + return; + } + + + /* No plan provided, but now we know how many tests were run, and can + print the header at the end */ + if(!skip_all && (no_plan || !have_plan)) { + printf("1..%d\n", test_count); + } + + if((have_plan && !no_plan) && e_tests < test_count) { + diag("Looks like you planned %d tests but ran %d extra.", + e_tests, test_count - e_tests); + UNLOCK; + return; + } + + if((have_plan || !no_plan) && e_tests > test_count) { + diag("Looks like you planned %d tests but only ran %d.", + e_tests, test_count); + UNLOCK; + return; + } + + if(failures) + diag("Looks like you failed %d tests of %d.", + failures, test_count); + + UNLOCK; +} diff --git a/t/tap.h b/t/tap.h new file mode 100644 index 0000000..e64f9d5 --- /dev/null +++ b/t/tap.h @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 2004 Nik Clayton + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting + and requires the caller to add the final comma if they've ommitted + the optional arguments */ +#ifdef __GNUC__ +# define ok(e, test, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + test, ## __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(test, ...) ok(1, test, ## __VA_ARGS__); +# define fail(test, ...) ok(0, test, ## __VA_ARGS__); + +# define skip_start(test, n, fmt, ...) \ + do { \ + if((test)) { \ + skip(n, fmt, ## __VA_ARGS__); \ + continue; \ + } +#elif __STDC_VERSION__ >= 199901L /* __GNUC__ */ +# define ok(e, ...) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, \ + __VA_ARGS__) : \ + _gen_result(0, __func__, __FILE__, __LINE__, \ + __VA_ARGS__)) + +# define ok1(e) ((e) ? \ + _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \ + _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e)) + +# define pass(...) ok(1, __VA_ARGS__); +# define fail(...) ok(0, __VA_ARGS__); + +# define skip_start(test, n, ...) \ + do { \ + if((test)) { \ + skip(n, __VA_ARGS__); \ + continue; \ + } +#else /* __STDC_VERSION__ */ +# error "Needs gcc or C99 compiler for variadic macros." +#endif /* __STDC_VERSION__ */ + +# define skip_end } while(0); + +unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...); + +int plan_no_plan(void); +int plan_skip_all(char *); +int plan_tests(unsigned int); + +unsigned int diag(char *, ...); + +int skip(unsigned int, char *, ...); + +void todo_start(char *, ...); +void todo_end(void); + +int exit_status(void); diff --git a/t/within.c b/t/within.c new file mode 100644 index 0000000..143b7a8 --- /dev/null +++ b/t/within.c @@ -0,0 +1,19 @@ +#include "hexmap.h" + +#include "tap.h" + +int main(void) { + int xy[2]; + int ijk[3]; + int hex, start, dist; + int x, y, i, count; + int range[32]; + + plan_tests(1); + + start = HL_cantor_xy(3,3); + count = HL_hexes_within_range(start, 2, range, 0); + ok(count == 18, "18 hexes within 2 hexes of 3,3"); + + return exit_status(); +} -- 2.40.0