]> pd.if.org Git - hexagon/commitdiff
initial commit
authorNathan Wagner <nw@hydaspes.if.org>
Fri, 23 Jul 2010 08:04:16 +0000 (08:04 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Fri, 23 Jul 2010 08:04:16 +0000 (08:04 +0000)
15 files changed:
Makefile [new file with mode: 0644]
hexmap.c [new file with mode: 0644]
hexmap.h [new file with mode: 0644]
t/adjacency.c [new file with mode: 0644]
t/cantor.c [new file with mode: 0644]
t/center.c [new file with mode: 0644]
t/distance.c [new file with mode: 0644]
t/gridsize.c [new file with mode: 0644]
t/hexbin.c [new file with mode: 0644]
t/hextest.c [new file with mode: 0644]
t/range.c [new file with mode: 0644]
t/tap.3 [new file with mode: 0644]
t/tap.c [new file with mode: 0644]
t/tap.h [new file with mode: 0644]
t/within.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
index 0000000..bc20d8d
--- /dev/null
+++ b/hexmap.c
@@ -0,0 +1,637 @@
+#include "hexmap.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <limits.h>
+
+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<size && q < range * 6; q++) { 
+               list[q] = hex;
+               hex = HL_adjhex(hex, q/range+2);
+       }
+
+       return range * 6;
+}
+
+/* direction 0 is positive X , counter clockwise from there */
+int HL_adjhex(int start, int dir) {
+       int c[3];
+
+       HL_cantor_arrays(start, 0, c);
+
+       switch (dir%6) {
+               case 2:
+                       c[0]--; c[1]++; break;
+               case 1:
+                               c[1]++; c[2]--; break;
+               case 0:
+                       c[0]++;         c[2]--; break;
+               case 5:
+                       c[0]++; c[1]--; break;
+               case 4:
+                               c[1]--; c[2]++; break;
+               case 3:
+                       c[0]--;       ; c[2]++; break;
+       }
+
+       return HL_cantor_ijkp(c);
+}
+
+/* returns last hex in found path */
+int HL_findpath(int start, int end, int *path, int size) {
+       return 0;
+}
+
+int HL_cantor_xyp(int *xy) {
+       return HL_cantor_xy(xy[0], xy[1]);
+}
+
+int HL_cantor_ijkp(int *ijk) {
+       return HL_cantor_ijk(ijk[0], ijk[1], ijk[2]);
+}
+
+int HL_cantor_arrays(int can, int *xy, int *ijk) {
+       return HL_cantor_decode(can, xy, xy ? xy+1 : 0,
+                       ijk, ijk ? ijk+1 : 0, ijk ? ijk+2 : 0);
+}
+
+int HL_cantor_decode(int can, int *x, int *y, int *i, int *j, int *k) {
+       int px, py;
+
+       inversecantor(can, &px, &py);
+       if (x) *x = px;
+       if (y) *y = py;
+
+       xy2ijk(px, py, i, j, k);
+
+       return can;
+}
+
+int HL_cantor_i(int cantor) {
+       int i;
+
+       HL_cantor_decode(cantor, 0,0, &i,0,0);
+       return i;
+}
+
+int HL_cantor_j(int cantor) {
+       int j;
+
+       HL_cantor_decode(cantor, 0,0, 0,&j,0);
+       return j;
+}
+
+int HL_cantor_k(int cantor) {
+       int k;
+
+       HL_cantor_decode(cantor, 0,0, 0,0,&k);
+       return k;
+}
+
+int HL_cantor_x(int cantor) {
+       int x;
+       inversecantor(cantor, &x, 0);
+       return x;
+}
+
+int HL_cantor_y(int cantor) {
+       int y;
+       inversecantor(cantor, 0, &y);
+       return y;
+}
+
+/* Determine if a map with these dimensions will overflow */
+int HL_map_bounds_ok(int xdim, int ydim) {
+       
+       /* return (x+y) * (x + y + 1) / 2 + y+1; */
+
+       if (INT_MAX - xdim - 1 < ydim) return 0;
+       if (INT_MAX / (xdim+ydim) < (xdim+ydim+1)) return 0;
+       if ( (xdim+ydim) * (xdim+ydim+1) / 2 > 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;i<count;i++) {
+                       neighbor = neighbors[i];
+                       cost = g(&state, current) + metric(current, neighbor);
+                       if (inopen(&state, neighbor) && cost < g(&state, neighbor)) {
+                               removeopen(&state, neighbor);
+                       } else
+                       if (inclosed(&state, neighbor) && cost < g(&state, neighbor)) {
+                               /* should never happen with
+                                * admissible heuristic
+                                */
+                               removeclosed(&state, neighbor);
+                       } else
+                       if (!inopen(&state, neighbor) && !inclosed(&state, neighbor)) {
+                               addopen(&state, neighbor);
+                               setg(&state, neighbor, cost);
+                               setrank(&state, neighbor, cost + h(neighbor,end));
+                               setparent(&state, neighbor, current);
+                       } else {
+                               abort();
+                       }
+               }
+       }
+
+       count = 0;
+       while (parent(&state, cur) != start) {
+               count++;
+               cur = parent(&state, cur);
+       }
+       cur = end;
+       while (parent(&state, cur) != start) {
+               path[count--] = cur;
+               cur = parent(&state, cur);
+       }
+
+}
+
+#endif
+
+static int inversenatcantor(int cantor, int *x, int *y) {
+       int w, t, py;
+
+       cantor -= 1;
+
+       w = (int)floor((sqrt(8.0 * cantor + 1.0) - 1.0)/2.0);
+       t = (w * w + w)/2;
+
+       py = cantor - t;
+
+       if (y) *y = py;
+       if (x) *x = w - py;
+
+       return cantor;
+}
+
+/*
+ * map non negative integer pairs to their cantor pairing function
+ * number, plus one.  We add one so that the result is never zero,
+ * leaving zero to be "invalid" or "none" or what have you.
+ */
+
+static int natcantor(int x, int y) {
+       return (x+y) * (x + y + 1) / 2 + y+1;
+}
+
+/* See http://en.wikipedia.org/wiki/Cantor_pairing_function */
+/* see also http://szudzik.com/ElegantPairing.pdf */
+/*
+ * if either coordinate is negative, map the integers onto the
+ * whole numbers, and then return the negative of the adjusted
+ * cantor number.  As for most grids negative coordinates will
+ * be invalid, this will allow for a <= 0 test for invalid
+ * or out of bounds (on the negative side anyway, you'll
+ * still have to test for out of range on the positive side).
+ *
+ * TODO figure out what the maximum supported coordinates are
+ * for given integer sizes.
+ */
+int HL_cantor_xy(int x, int y) {
+       if (x < 0 || y < 0) {
+               x = abs(2 * x) - (x < 0);
+               y = abs(2 * y) - (y < 0);
+               return -natcantor(x, y);
+       }
+       return natcantor(x,y);
+}
+
+static int inversecantor(int cantor, int *x, int *y) {
+       if (cantor < 0) {
+               inversenatcantor(-cantor, x, y);
+               if (x) {
+                       if (*x % 2) {
+                               *x = -(*x+1)/2;
+                       } else {
+                               *x = *x / 2;
+                       }
+               }
+               if (y) {
+                       if (*y % 2) {
+                               *y =  -(*y+1)/2;
+                       } else {
+                               *y = *y/2;
+                       }
+               }
+       } else {
+               inversenatcantor(cantor, x, y);
+       }
+
+       return cantor;
+}
+
+struct hex {
+        int iso;
+        int x, y, z;
+};
+
+/* y *must* be positive down as the xy /iso conversion assumes this */
+static int hex_xy(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->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 (file)
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 (file)
index 0000000..95750cf
--- /dev/null
@@ -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 (file)
index 0000000..a2e9290
--- /dev/null
@@ -0,0 +1,25 @@
+#include <stdio.h>
+
+#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 (file)
index 0000000..00fdff2
--- /dev/null
@@ -0,0 +1,30 @@
+#include <math.h>
+
+#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 (file)
index 0000000..0f3ef97
--- /dev/null
@@ -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 (file)
index 0000000..784e467
--- /dev/null
@@ -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 (file)
index 0000000..e486bc8
--- /dev/null
@@ -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 (file)
index 0000000..f7b219b
--- /dev/null
@@ -0,0 +1,88 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#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 (file)
index 0000000..b40b86d
--- /dev/null
+++ b/t/range.c
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <limits.h>
+#include <math.h>
+
+#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 (file)
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 (file)
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 <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <pthread.h>
+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 (file)
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 (file)
index 0000000..143b7a8
--- /dev/null
@@ -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();
+}