/* * Copyright info: this file is in the public domain. */ #include #include #include #include #include #include #include "puzzles.h" #define COL_BACKGROUND 1 #define COL_LOCKED 2 #define COL_BORDER 3 #define COL_WIRE 4 #define COL_ENDPOINT 5 #define COL_POWERED 6 #define COL_ERR 7 #define COL_LOOP 8 #define COL_BARRIER 9 #define COL_BGWRAP 10 #define COL_LOCKWRAP 11 #define NCOLOURS 12 #define ROTATE_TIME .2 #define FLASH_TIME .75 #define FLASH_FRAME .1 #define BORDER_PIXELS 10 typedef config_item CONFIG_ITEM; typedef random_state RANDOM_STATE; typedef struct game_params PARAMS; typedef struct game_state STATE; typedef struct game_ui UI; typedef struct game_drawstate DRAWSTATE; typedef struct findloopstate FINDLOOPSTATE; typedef unsigned int CELL; typedef struct cdq CDQ; typedef struct cdqe CDQE; typedef struct ixy IXY; typedef struct fxy FXY; typedef struct loopctx LOOPCTX; typedef struct updrect UPDRECT; typedef struct dpar DPAR; struct updrect { int minx; int maxx; int miny; int maxy; } ; struct loopctx { STATE *s; int v; int d; } ; struct ixy { int x; int y; } ; struct fxy { double x; double y; } ; struct game_params { int size; int wrap; } ; struct dpar { PARAMS p; int asz; int asz2; int hszd; int hszg; void *fv[6]; const int *ix; const int *iy; const int * const *xyi; const int *dupof; const unsigned char *flags; #define FCF_UNUSED 0x01 #define FCF_DUP 0x02 #define FCF_WRAP 0x3c #define FCF_WRAP_NONE 0x00 // _NONE must be 0 #define FCF_WRAP_0 0x04 #define FCF_WRAP_01 0x08 #define FCF_WRAP_1 0x0c #define FCF_WRAP_12 0x10 #define FCF_WRAP_2 0x14 #define FCF_WRAP_23 0x18 #define FCF_WRAP_3 0x1c #define FCF_WRAP_34 0x20 #define FCF_WRAP_4 0x24 #define FCF_WRAP_45 0x28 #define FCF_WRAP_5 0x2c #define FCF_WRAP_50 0x30 int refs; } ; /* * net is a vector of unsigned chars. Its subscripts for various sizes * go like this, where lines mark off cells that aren't actually used: * * size=2 * 0 / 1 2 * * 3 4 5 * * 6 7 / 8 * * * size=3 * 0 1 / 2 3 4 * * 5 / 6 7 8 9 * * 10 11 12 13 14 * * 15 16 17 18 / 19 * * 20 21 22 / 23 24 * * size=4 * 0 1 2 / 3 4 5 6 * * 7 8 / 9 10 11 12 13 * * 14 / 15 16 17 18 19 20 * * 21 22 23 24 25 26 27 * * 28 29 30 31 32 33 / 34 * * 35 36 37 38 39 / 40 41 * * 42 43 44 45 / 46 47 48 * * A given cell's bits indicate connectivity thus: * * 2 1 * \ / * 3--*--0 * / \ * 4 5 * * The low six bits indicate connectivity: Bit 1<= d->asz) || (y >= d->asz) || (d->flags[d->xyi[x][y]] & ((disp||!d->p.wrap) ? FCF_UNUSED : (FCF_UNUSED|FCF_DUP))) ); } /* * Given a cell index and a direction, return the cell index of the * next cell in that direction, or -1 if that step would take us * outside the board - the effective board, not the allocated board. * * If disp is true, this operates on the displayed board, with * boundaries at the edges and p->size cells along each edge. If disp * is false, this operates on the game board. When d->p.wrap is * false, the game baord is the same as the displayed board; when * true, the right and bottom edges of the displayed board are an * alternative view of the left and top edges. Either way, the input * cell must be valid for wrap and disp. */ static int connect_to(const DPAR *d, int cell, int dir, int disp) { int x; int y; if ((cell < 0) || (cell >= d->asz2)) abort(); x = d->ix[cell]; y = d->iy[cell]; if (unused_xy(d,x,y,disp)) abort(); switch (dir) { case 0: x ++; break; case 1: x ++; y --; break; case 2: y --; break; case 3: x --; break; case 4: x --; y ++; break; case 5: y ++; break; default: abort(); break; } if (d->p.wrap && !disp) { if (y == -1) { x -= d->p.size - 1; y = d->asz - 2; } if (x == -1) { x = d->asz - 2; y -= d->p.size - 1; } if ((y < 0) || (x < 0) || (y >= d->asz) || (x >= d->asz)) abort(); if (x == d->p.size-2-y) { x += d->p.size - 1; y += d->p.size - 1; } return(d->dupof[d->xyi[x][y]]); } else { if ((y < 0) || (x < 0) || (y >= d->asz) || (x >= d->asz)) return(-1); if (unused_xy(d,x,y,1)) return(-1); return(d->xyi[x][y]); } } static void cdq_init(CDQ *q) { q->v = 0; q->a = 0; q->n = 0; } static void cdq_add(CDQ *q, CDQE e) { if (q->n >= q->a) { q->v = srealloc(q->v,(q->a=q->n+8)*sizeof(CDQE)); } q->v[q->n++] = e; } static int cdq_n(CDQ *q) { return(q->n); } static CDQE cdq_get(CDQ *q, RANDOM_STATE *rs) { int i; CDQE e; if (q->n < 1) abort(); i = rs ? random_upto(rs,q->n) : (q->n - 1); e = q->v[i]; q->n --; if (i != q->n) q->v[i] = q->v[q->n]; return(e); } static void cdq_done(CDQ *q) { sfree(q->v); } static PARAMS *default_params(void) { PARAMS *p; p = snew(PARAMS); p->size = 5; p->wrap = 0; return(p); } static bool fetch_preset(int i, char **name, PARAMS **pp) { PARAMS *p; if ((i < 0) || (i > 9)) return(false); p = snew(PARAMS); p->size = ((i % 5) * 2) + 3; p->wrap = i > 4; asprintf(name,"Size %d%s",p->size,p->wrap?", wrapping":""); *pp = p; return(true); } static void free_params(PARAMS *p) { sfree(p); } static PARAMS *dup_params(const PARAMS *p) { PARAMS *n; n = snew(PARAMS); *n = *p; return(n); } static void decode_params(PARAMS *p, const char *s) { char *e; long int szl; int sz; szl = strtol(s,&e,0); if (e == s) szl = 0; sz = szl; if (sz != szl) sz = 0; p->size = sz; if (*e == 'w') { p->wrap = 1; e ++; } else { p->wrap = 0; } } static char *encode_params(const PARAMS *p, bool full) { char *s; (void)full; asprintf(&s,"%d%s",p->size,p->wrap?"w":""); return(s); } static CONFIG_ITEM *configure(const PARAMS *p) { CONFIG_ITEM *cv; char *s; cv = snewn(3,CONFIG_ITEM); cv[0].name = "Size"; cv[0].type = C_STRING; asprintf(&s,"%d",p->size); cv[0].u.string.sval = s; cv[1].name = "Wraparound"; cv[1].type = C_BOOLEAN; cv[1].u.boolean.bval = p->wrap; cv[2].name = 0; cv[2].type = C_END; return(cv); } static PARAMS *custom_params(const CONFIG_ITEM *cfg) { PARAMS *p; p = snew(PARAMS); p->size = atoi(cfg[0].u.string.sval); p->wrap = cfg[1].u.boolean.bval; return(p); } static const char *validate_params(const PARAMS *p, bool full) { (void)full; if (p->size < 3) return("Size must be at least 3"); return(0); } static DPAR *dpar_new(const PARAMS *p) { DPAR *d; int *v; int i; int x; int y; int *ix; int *iy; int **xyi; int *dupof; unsigned char *cf; unsigned char f; d = snew(DPAR); d->p = *p; d->asz = (p->size * 2) - 1; d->asz2 = d->asz * d->asz; d->hszd = (3 * p->size * (p->size - 1)) + 1; d->hszg = p->wrap ? d->hszd - (3 * p->size) + 2 : d->hszd; ix = snewn(d->asz2,int); iy = snewn(d->asz2,int); xyi = snewn(d->asz,int *); dupof = snewn(d->asz2,int); v = snewn(d->asz2,int); cf = snewn(d->asz2,char); d->fv[0] = ix; d->fv[1] = iy; d->fv[2] = xyi; d->fv[3] = dupof; d->fv[4] = v; d->fv[5] = cf; for (i=d->asz-1;i>=0;i--) { xyi[i] = v; v += d->asz; } i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; ix[i] = x; iy[i] = y; xyi[x][y] = i; f = 0; if (y < p->size) { if (x < p->size-1-y) { f = FCF_UNUSED; } else if (x == p->size-1-y) { if (y == 0) { f = FCF_WRAP_2; } else if (y == p->size-1) { f = FCF_WRAP_3; } else { f = FCF_WRAP_23; } } else { if (x == d->asz-1) { f = FCF_DUP; if (y == 0) { f |= FCF_WRAP_1; } else if (y == p->size-1) { f |= FCF_WRAP_0; } else { f |= FCF_WRAP_01; } } else if (y == 0) { f = FCF_WRAP_12; } } } else { int m; m = d->asz - (2 + y - p->size); if (x > m) { f = FCF_UNUSED; } else { if (y == d->asz-1) { if (x == 0) { f = FCF_DUP | FCF_WRAP_4; } else if (x == p->size-1) { f = FCF_DUP | FCF_WRAP_5; } else { f = FCF_DUP | FCF_WRAP_45; } } else if (x == 0) { f = FCF_WRAP_34; } else if (x == m) { f = FCF_DUP | FCF_WRAP_50; } } } if (! p->wrap) f &= FCF_UNUSED; cf[i] = f; } #if 0 printf("cf\n"); i = 0; for (y=0;yasz;y++) { printf("%*s",y,""); for (x=0;xasz;x++) { putchar(' '); switch (cf[i]) { case 0: f = '.'; break; case FCF_UNUSED: f = 'u'; break; case FCF_DUP | FCF_WRAP_0: f = '0'; break; case FCF_DUP | FCF_WRAP_01: f = 'a'; break; case FCF_DUP | FCF_WRAP_1: f = '1'; break; case FCF_WRAP_12: f = 'b'; break; case FCF_WRAP_2: f = '2'; break; case FCF_WRAP_23: f = 'c'; break; case FCF_WRAP_3: f = '3'; break; case FCF_WRAP_34: f = 'd'; break; case FCF_DUP | FCF_WRAP_4: f = '4'; break; case FCF_DUP | FCF_WRAP_45: f = 'e'; break; case FCF_DUP | FCF_WRAP_5: f = '5'; break; case FCF_DUP | FCF_WRAP_50: f = 'f'; break; default: f = '\0'; printf("<%d>",cf[i]); break; } if (f) putchar(f); i ++; } putchar('\n'); } if (d->asz2) exit(0); #endif i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; if (cf[i] & FCF_UNUSED) { dupof[i] = -1; } else if (cf[i] & FCF_DUP) { if (x == d->asz-1) { if (y == d->p.size - 1) { dupof[i] = d->p.size - 1; } else { dupof[i] = i + ((d->p.size - 2) * d->asz) + 1; } } else if (y == d->asz-1) { if (x == d->p.size - 1) { dupof[i] = (d->p.size - 1) * d->asz; } else { dupof[i] = i - ((d->asz-1) * d->asz) + d->p.size - 1; } } else { dupof[i] = i - ((d->p.size * d->asz) - 1) + d->p.size - 1; } } else { dupof[i] = i; } } d->ix = ix; d->iy = iy; d->xyi = (void *) xyi; d->dupof = dupof; d->flags = cf; d->refs = 1; return(d); } static DPAR *dpar_ref(DPAR *d) { if (d->refs < 1) abort(); d->refs ++; return(d); } static void dpar_deref(DPAR *d) { d->refs --; if (d->refs < 1) { free(d->fv[0]); free(d->fv[1]); free(d->fv[2]); free(d->fv[3]); free(d->fv[4]); free(d->fv[5]); free(d); } } static int all_but_one(unsigned char v) { switch (v) { case 0x3e: case 0x3d: case 0x3b: case 0x37: case 0x2f: case 0x1f: return(1); break; } return(0); } static int only_one(unsigned char v) { switch (v) { case 0x01: case 0x02: case 0x04: case 0x08: case 0x10: case 0x20: return(1); break; } return(0); } static unsigned char rot_l(unsigned char c) { return(((c << 1) & 0x3e) | ((c >> 5) & 0x01)); } static unsigned char rot_r(unsigned char c) { return(((c << 5) & 0x20) | ((c >> 1) & 0x1f)); } static void dump_cells(const DPAR *, const CELL *, CELL) __attribute__((__unused__)); static void dump_cells(const DPAR *d, const CELL *v, CELL bit) { int x; int y; int i; char c[8]; i = 0; for (y=0;yasz;y++) { printf("%*s",y*4,""); for (x=0;xasz;x++) { printf(" %c %c ", ((v[i+x] >> 2) & bit) ? '\\' : ' ', ((v[i+x] >> 1) & bit) ? '/' : ' ' ); } printf("\n"); printf("%*s",y*4,""); for (x=0;xasz;x++) { c[0] = ((v[i+x] >> 3) & bit) ? '-' : ' '; c[1] = c[0]; c[2] = c[0]; sprintf(&c[3],"%02d",(i+x)%100); c[7] = ((v[i+x] >> 0) & bit) ? '-' : ' '; c[6] = c[7]; c[5] = c[7]; if (v[i+x] & UNUSED) { c[3] = '.'; c[4] = '.'; } if (v[i+x] & DUP) { c[2] = '('; c[5] = ')'; } if (v[i+x] & LIT) { c[1] = '*'; c[6] = '*'; } printf("%.8s",&c[0]); } printf("\n"); printf("%*s",y*4,""); for (x=0;xasz;x++) { printf(" %c %c ", ((v[i+x] >> 4) & bit) ? '/' : ' ', ((v[i+x] >> 5) & bit) ? '\\' : ' ' ); } printf("\n"); i += d->asz;; } } static char *new_game_desc(const PARAMS *p, RANDOM_STATE *rs, char **aux, bool interactive) { CELL *net; int i; int j; CDQ q; CDQE e; char *desc; static DPAR *dp = 0; (void)aux; (void)interactive; /* * This is ugly. * * At the time we're called, all we have is a PARAMS. This means we * don't, at least initially, have a DPAR for that PARAMS. We could * create one here, but we have nowhere to hang it - no STATE or * DRAWSTATE or any such. * * So we sleaze it, creating our own, which we cache in a cache of * size one (this is what dp is for). */ if (dp && ((dp->p.size != p->size) || (dp->p.wrap != p->wrap))) { dpar_deref(dp); dp = 0; } if (! dp) dp = dpar_new(p); net = snewn(dp->asz2,CELL); for (i=dp->asz2-1;i>=0;i--) { if (dp->flags[i] & FCF_UNUSED) { net[i] = UNUSED; } else if ((dp->flags[i] & FCF_DUP) && dp->p.wrap) { net[i] = DUP; } else { net[i] = 0; } } i = (dp->p.size - 1) * (dp->asz + 1); cdq_init(&q); for (j=5;j>=0;j--) cdq_add(&q,(CDQE){.x=i,.dir=j}); while (cdq_n(&q)) { e = cdq_get(&q,rs); // If this would be the last line for this node, skip. if (all_but_one(net[e.x])) continue; i = connect_to(dp,e.x,e.dir,0); // If line points to unused space, skip. if (unused_xy(dp,dp->ix[i],dp->iy[i],0)) continue; // If the target has already been included, skip. // (We depend on "included" == "has >=1 line(s)".) if (net[i] != 0) continue; // Add the line. net[e.x] |= 1U << e.dir; net[i] = 1U << otherway(e.dir); // Queue all lines from the target. for (j=5;j>=0;j--) cdq_add(&q,(CDQE){.x=i,.dir=j}); } cdq_done(&q); for (i=dp->asz2-1;i>=0;i--) { if (net[i] & (UNUSED|DUP)) continue; for (j=random_upto(rs,6);j>0;j--) net[i] = rot_r(net[i]); } desc = smalloc(dp->hszg+1); j = 0; for (i=0;iasz2;i++) { if (net[i] & (UNUSED|DUP)) continue; if (j >= dp->hszg) abort(); #if LINK_M != 63 #error "Game encoding doesn't work for this value of LINK_M" #endif desc[j++] = b64chars[(net[i]>>LINK_S)&LINK_M]; } if (j != dp->hszg) abort(); desc[j++] = '\0'; return(desc); } /* * More ugly. * * Given how constrained the API here is, I see no way to get the DPAR * for a game here. This means we have to compute anything we need * ourselves. * * So I'm punting on most of the verification here. */ static const char *validate_desc(const PARAMS *p, const char *desc) { (void)p; if (strspn(desc,&b64chars[0]) != strlen(desc)) return("Game description contains unexpected character"); return(0); } static int loop_neighbour_(int v, void *vc) { LOOPCTX *c; CELL *net; const DPAR *dp; int d; int v2; c = vc; net = c->s->net; dp = c->s->d; if (v >= 0) { if (net[v] & UNUSED) return(-1); c->v = v; d = 0; } else { v = c->v; d = c->d; if (d >= 6) return(-1); } while (1) { if ((net[v] >> d) & LINK_BASE) { v2 = connect_to(dp,v,d,0); if ( (v2 >= 0) && ((net[v2] >> otherway(d)) & LINK_BASE) ) { c->d = d + 1; return(v2); } } d ++; if (d >= 6) { c->d = d; return(-1); } } } static int (*lb)[3]; static int la = 0; static int ln; static int lv; static void lrecord(int v1, int v2, int v3) { if (ln >= la) abort(); lb[ln][0] = v1; lb[ln][1] = v2; lb[ln][2] = v3; ln ++; } static int loop_neighbour(int v, void *vc) { int r; r = loop_neighbour_(v,vc); if (v < 0) { if (lv < 0) abort(); lrecord(-1,lv,r); } else { lrecord(v,v,r); lv = v; } return(r); } static int find_loops(STATE *s) { int i; int d; int j; int n; LOOPCTX ctx; FINDLOOPSTATE *fls; la = s->d->asz2*7; lb = smalloc(la*sizeof(*lb)); ln = 0; lv = -1; n = 0; fls = findloop_new_state(s->d->asz2); ctx.s = s; findloop_run(fls,s->d->asz2,&loop_neighbour,&ctx); for (i=s->d->asz2-1;i>=0;i--) s->net[i] &= ~LOOP_ALL; for (i=s->d->asz2-1;i>=0;i--) { if (s->net[i] & UNUSED) continue; for (d=5;d>=0;d--) { if ((s->net[i] >> d) & LINK_BASE) { j = connect_to(s->d,i,d,0); if ( (j >= 0) && ((s->net[j] >> otherway(d)) & LINK_BASE) && findloop_is_loop_edge(fls,i,j) ) { s->net[i] |= LOOP_BASE << d; s->net[j] |= LOOP_BASE << otherway(d); n ++; } } } } findloop_free_state(fls); return(n); } static STATE *new_game(midend *me, const PARAMS *p, const char *desc) { STATE *s; DPAR *dp; int i; const char *inx; (void)me; s = snew(STATE); dp = dpar_new(p); s->d = dp; s->lastrot_loc = -1; s->flags = 0; s->net = smalloc(dp->asz2*sizeof(*s->net)); for (i=0;iasz2;i++) { if (dp->flags[i] & FCF_UNUSED) { s->net[i] = UNUSED; } else if (dp->p.wrap && (dp->flags[i] & FCF_DUP)) { s->net[i] = DUP; } else { if (! *desc) { s->net[i] = 0; } else { inx = index(&b64chars[0],*desc++); if (! inx) abort(); s->net[i] = (inx - &b64chars[0]) << LINK_S; } } } find_loops(s); return(s); } static STATE *dup_game(const STATE *s) { STATE *n; n = snew(STATE); *n = *s; n->d = dpar_ref(s->d); n->net = smalloc(n->d->asz2*sizeof(*n->net)); bcopy(s->net,n->net,n->d->asz2*sizeof(*n->net)); return(n); } static void free_game(STATE *s) { dpar_deref(s->d); sfree(s->net); sfree(s); } static char *solve_game(const STATE *state, const STATE *curstate, const char *aux, const char **error) { (void)state; (void)curstate; (void)aux; *error = "Not implemented"; return(0); } static bool can_format_as_text_now(const PARAMS *p) { (void)p; return(false); } static char *text_format(const STATE *s) { (void)s; return(0); } static UI *new_ui(const STATE *s) { UI *u; (void)s; u = snew(UI); u->light = 2 * s->d->p.size * (s->d->p.size - 1); u->lighton = 1; return(u); } static void free_ui(UI *u) { sfree(u); } static char *encode_ui(const UI *u) { (void)u; return(dupstr("")); } static void decode_ui(UI *u, const char *enc) { (void)u; (void)enc; } static void changed_state(UI *u, const STATE *old, const STATE *new) { (void)u; (void)old; (void)new; } /* * Because if the way they're used by tile_for_loc, below, it's * important that cell_c[xy]_xy work for out-of-range x and y, * returning what the coordinates would be if the grid were extended * far enough to cover the (x,y) passed in. */ static int cell_cx_xy(const DRAWSTATE *ds, int x, int y) { return((ds->w / 2) + ((((x - (ds->d->p.size - 1)) * 2) + y - (ds->d->p.size - 1)) * ds->hexr3)); } static int cell_cy_xy(const DRAWSTATE *ds, int x, int y) { (void)x; return((ds->h / 2) + ((y - (ds->d->p.size - 1)) * 3 * ds->hexsize)); } /* * Convert a pixel X coordinate to a grid X coordinate, assuming it * belongs to the row of hexagons with grid Y coordinate y. It is * important that this return an out-of-range grid coordinate (<0 or * >=ds->asz) for out-of-range input coordinates. */ static int cellx_for_pixx(const DRAWSTATE *ds, int x, int y) { // Make x relative to the x=0 left edge for this row. x -= cell_cx_xy(ds,0,y) - ds->hexr3; // If x is negative, outside the allocated grid. if (x < 0) return(-1); // Convert x from pixels to grid index and return. return(x/(2*ds->hexr3)); } /* * Return the square of the distance between (x1,y1) and (x2,y2). We * use this to tell which of two points is closer to a third point, so * all that really matters is that this is strictly monotonic in the * distance between the points. This is why we can return the square * of the distance, thereby saving ourselves a comparatively expensive * square-root computation. */ static int id2(int x1, int y1, int x2, int y2) { return(((x1-x2)*(x1-x2))+((y1-y2)*(y1-y2))); } /* * Return the index of the tile pixel (px,py) falls into. * * This is the tile (in the infinite grid of which the game board is a * part) whose centre is closest to (px,py), except that if that tile * isn't part of the actual game board, we return -1 instead. This * returns display cell numbers even when wrapping is enabled. * * If y is close enough to a hexagon row's centreline that every point * with that y coordinate falls into that line of hexagons, it's easy. * Otherwise, we compute the closest point in the band above and the * band below, then check to see which one is closer to (px,py). */ static int tile_for_loc(const DRAWSTATE *ds, int px, int py) { int x; int y; int xa; int xb; int cxa; int cya; int cxb; int cyb; // Make y relative to the top of the `easy' band for the (nonexistent) // row just above the grid. y = py + (((ds->d->p.size * 3) + 1) * ds->hexsize) - (ds->h / 2); // If y is now negative, definitely off-grid. if (y < 0) return(-1); // Do we fall into an easy band? if ((y % (3 * ds->hexsize)) < (2 * ds->hexsize)) { // Yes. y /= 3 * ds->hexsize; // Because of the offset, valid is 1..ds->asz. if ((y < 1) || (y > ds->d->asz)) return(-1); // Compensate for the offset. y --; // Convert x to cell index. x = cellx_for_pixx(ds,px,y); // unused_xy() range-checks as well as testing for unused cells. if (unused_xy(ds->d,x,y,1)) return(-1); // All good! return(ds->d->xyi[x][y]); } // No easy band. Compute the y of the upper possible band. y /= 3 * ds->hexsize; // Compensate for the offset, same as above. y --; // If the _upper_ band is off the bottom edge, off-grid. if (y >= ds->d->asz) return(-1); // Get grid X coordinates for above and below. xa = cellx_for_pixx(ds,px,y); xb = cellx_for_pixx(ds,px,y+1); // Get pixel coordinates of cell centres. cxa = cell_cx_xy(ds,xa,y); cya = cell_cy_xy(ds,xa,y); cxb = cell_cx_xy(ds,xb,y+1); cyb = cell_cy_xy(ds,xb,y+1); // Which is closer? if (id2(cxa,cya,px,py) < id2(cxb,cyb,px,py)) { x = xa; } else { x = xb; y ++; } // Return -1 if out of range, else the index. if (unused_xy(ds->d,x,y,1)) return(-1); return(ds->d->xyi[x][y]); } static char *interpret_move( const STATE *s, UI *u, const DRAWSTATE *ds, int x, int y, int button ) { int tx; char *m; int mods; mods = button & MOD_MASK; button &= ~MOD_MASK; switch (button) { case LEFT_BUTTON: case MIDDLE_BUTTON: case RIGHT_BUTTON: tx = tile_for_loc(ds,x,y); if (tx < 0) return(0); if (tx >= ds->d->asz2) abort(); tx = s->d->dupof[tx]; break; } m = 0; switch (button) { case MIDDLE_BUTTON: asprintf(&m,".%d",tx); m[0] = (s->net[tx] & LOCKED) ? 'U' : 'L'; break; case LEFT_BUTTON: { char rotch; rotch = 'l'; if (0) { case RIGHT_BUTTON: rotch = 'r'; } if (s->net[tx] & LOCKED) return(0); asprintf(&m,"%c%d",rotch,tx); } break; { int dir; case 'd': dir = 0; if (0) { case 'e': dir = 1; } if (0) { case 'w': dir = 2; } if (0) { case 'a': dir = 3; } if (0) { case 'z': dir = 4; } if (0) { case 'x': dir = 5; } if (mods & MOD_CTRL) { int new; new = connect_to(ds->d,u->light,dir,0); if (new < 0) return(0); u->light = new; return(UI_UPDATE); } } break; case 's': if (mods & MOD_CTRL) { u->lighton = ! u->lighton; return(UI_UPDATE); } break; default: return(0); break; } return(m); } static void find_err(STATE *s) { int inx; int i; int to; for (inx=s->d->asz2-1;inx>=0;inx--) s->net[inx] &= ~ERR_ALL; for (inx=s->d->asz2-1;inx>=0;inx--) { if ((s->net[inx] & (UNUSED|DUP|LOCKED)) != LOCKED) continue; for (i=5;i>=0;i--) { if ((s->net[inx] >> i) & LINK_BASE) { to = connect_to(s->d,inx,i,0); if ((to < 0) || ((s->net[to] & LOCKED) && ! ((s->net[to] >> otherway(i)) & 1))) { s->net[inx] |= ERR_BASE << i; } } } } } static void check_solved(STATE *s) { static CDQ q = CDQ_STATIC_INIT; int x; int i; int x2; int n; for (i=s->d->asz2-1;i>=0;i--) s->net[i] &= ~MARK; n = 0; if (cdq_n(&q) != 0) abort(); cdq_add(&q,(CDQE){.x=s->d->p.size-1,.dir=0}); while (cdq_n(&q) > 0) { x = cdq_get(&q,0).x; if (s->net[x] & MARK) continue; s->net[x] |= MARK; n ++; for (i=5;i>=0;i--) { if ((s->net[x] >> i) & LINK_BASE) { x2 = connect_to(s->d,x,i,0); if ( (x2 >= 0) && ((s->net[x2] >> otherway(i)) & LINK_BASE) ) { cdq_add(&q,(CDQE){.x=x2,.dir=0}); } } } } if (n == s->d->hszg) s->flags |= SF_SOLVED; else s->flags &= ~SF_SOLVED; } /* * redraw() also knows how lastrot_loc and lastrot_dir work. */ static STATE *execute_move(const STATE *s, const char *move) { STATE *n; int inx; int n1; int n2; char c; n = dup_game(s); n1 = -1; n2 = -1; switch (move[0]) { case 'L': sscanf(move+1,"%d%n%c%n",&inx,&n1,&c,&n2); if ((n1 < 0) || (n2 >= 0) || (inx < 0) || (inx >= s->d->asz2) || (n->net[inx] & (UNUSED|DUP))) abort(); n->net[inx] |= LOCKED; n->lastrot_loc = -1; find_err(n); break; case 'U': sscanf(move+1,"%d%n%c%n",&inx,&n1,&c,&n2); if ((n1 < 0) || (n2 >= 0) || (inx < 0) || (inx >= s->d->asz2) || (n->net[inx] & (UNUSED|DUP))) abort(); n->net[inx] &= ~LOCKED; n->lastrot_loc = -1; find_err(n); break; case 'r': sscanf(move+1,"%d%n%c%n",&inx,&n1,&c,&n2); if ((n1 < 0) || (n2 >= 0) || (inx < 0) || (inx >= s->d->asz2) || (n->net[inx] & (UNUSED|DUP|LOCKED))) abort(); n->net[inx] = rot_r(n->net[inx]); n->lastrot_loc = inx; n->lastrot_dir = 1; find_loops(n); check_solved(n); break; case 'l': sscanf(move+1,"%d%n%c%n",&inx,&n1,&c,&n2); if ((n1 < 0) || (n2 >= 0) || (inx < 0) || (inx >= s->d->asz2) || (n->net[inx] & (UNUSED|DUP|LOCKED))) abort(); n->net[inx] = rot_l(n->net[inx]); n->lastrot_loc = inx; n->lastrot_dir = -1; find_loops(n); check_solved(n); break; default: abort(); break; } return(n); } // ---------------------------------------------------------------------- // Drawing routines. /* * Y size is ((3 * p->size) - 1) * tilesize. * X size is (((2 * p->size) - 1) * 2 * ROOT_3_4) * tilesize. * * There are two sources of discrepancy between these two numbers. * * One is that, for the large game hexagon, the X distance is between * opposite vertices while the Y distance is between opposite parallel * sides. * * The other is that, to put it a little loosely, the X distance is * measured between parallel sides of cell hexagons while the Y * distance is measured between opposite corners. * * Rather than try to come up with an analytic comparison between the * two, I just compute both, especially since the rest of the system * is perfectly fine with non-square drawing areas. * * I do, though, round tilesize * ROOT_3_4 to an integer before * computing sizes based on it. Note that our computation of this * must exactly match set_size()'s. * * The +1 on each coordinate is because otherwise the last pixel falls * just outside, rather than just inside, the size. */ static void compute_size(const PARAMS *p, int tilesize, int *x, int *y) { int hexr3; hexr3 = (tilesize * ROOT_3) + .5; *x = (((2 * p->size) - 1) * 2 * hexr3) + (2 * BORDER_PIXELS) + 1; *y = (((3 * p->size) - 1) * 2 * tilesize) + (2 * BORDER_PIXELS) + 1; } static void rotate_points(const IXY *src, IXY *dst, int npts, double xx, double xy, double yx, double yy) { for (;npts>0;npts--) { dst->x = rint((src->x * xx) + (src->y * yx)); dst->y = rint((src->x * xy) + (src->y * yy)); src ++; dst ++; } } /* * Note that our computation of hexr3 must exactly match * compute_size()'s. */ static void set_size(drawing *dr, DRAWSTATE *ds, const PARAMS *p, int tilesize) { int rn; (void)dr; (void)p; ds->hexsize = tilesize; ds->hexr3 = (tilesize * ROOT_3) + .5; ds->linew2 = tilesize / 10; ds->epcr = (tilesize * 2) / 3; ds->nepcr = tilesize / 3; ds->litcr = tilesize; compute_size(p,tilesize,&ds->w,&ds->h); if (ds->linew2 < 1) { ds->lineoff[0][0].x = ds->hexr3 - 1; ds->lineoff[0][0].y = 0; rn = 1; } else { /* * Drawing bidirectional lines, in redraw(), that knows that the * [2] and [3] points are the ones at the centre end. */ ds->lineoff[0][0].x = ds->hexr3 - 1; ds->lineoff[0][0].y = ds->linew2; ds->lineoff[0][1].x = ds->lineoff[0][0].x; ds->lineoff[0][1].y = - ds->lineoff[0][0].y; ds->lineoff[0][2].x = 0; ds->lineoff[0][2].y = ds->lineoff[0][1].y; ds->lineoff[0][3].x = 0; ds->lineoff[0][3].y = ds->lineoff[0][0].y; rn = 4; } rotate_points(&ds->lineoff[0][0],&ds->lineoff[1][0],rn,.5,-ROOT_3/2,ROOT_3/2,.5); rotate_points(&ds->lineoff[0][0],&ds->lineoff[2][0],rn,-.5,-ROOT_3/2,ROOT_3/2,-.5); rotate_points(&ds->lineoff[0][0],&ds->lineoff[3][0],rn,-1,0,0,-1); rotate_points(&ds->lineoff[0][0],&ds->lineoff[4][0],rn,-.5,ROOT_3/2,-ROOT_3/2,-.5); rotate_points(&ds->lineoff[0][0],&ds->lineoff[5][0],rn,.5,ROOT_3/2,-ROOT_3/2,.5); } static float *colours(frontend *fe, int *ncolours) { float *cv; cv = snewn(NCOLOURS*3,float); *ncolours = NCOLOURS; #define SET(inx,r,g,b) \ do { \ cv[((inx)*3)+0] = (r); \ cv[((inx)*3)+1] = (g); \ cv[((inx)*3)+2] = (b); \ } while (0) // Background is black. SET(COL_BACKGROUND,0,0,0); // (Unpowered) wires are blue. SET(COL_WIRE,0,.5,1); // Powered wires/endpoints are white. SET(COL_POWERED,1,1,1); // Errors are red also. SET(COL_ERR,1,0,0); // Loops are magenta. SET(COL_LOOP,1,0,1); // Unpowered endpoints are grey. SET(COL_ENDPOINT,.6,.6,.6); // Tile borders are a darker grey. SET(COL_BORDER,.4,.4,.4); // Locked-tile backgrounds are brown (dim yellow). SET(COL_LOCKED,.2,.2,0); // The surrounding barrier is red. SET(COL_BARRIER,1,0,0); // Wrap-indicating background is a dim cyan SET(COL_BGWRAP,0,.2,.2); // A cross between COL_LOCKED and COL_BGWRAP. SET(COL_LOCKWRAP,.2,.3,.2); #undef SET (void)fe; return(cv); } static DRAWSTATE *new_drawstate(drawing *dr, const STATE *s) { DRAWSTATE *ds; ds = snew(DRAWSTATE); (void)dr; (void)s; ds->d = dpar_ref(s->d); ds->first = 1; ds->hexsize = 0; ds->hexr3 = 0; ds->animloc = -1; ds->live = smalloc(ds->d->asz2*sizeof(*ds->live)); ds->cur = smalloc(ds->d->asz2*sizeof(*ds->cur)); ds->want = smalloc(ds->d->asz2*sizeof(*ds->want)); ds->curlight = -1; ds->lastflash = -1; ds->flashing = 0; return(ds); } static void free_drawstate(drawing *dr, DRAWSTATE *ds) { (void)dr; sfree(ds->live); sfree(ds->cur); sfree(ds->want); sfree(ds); } static void find_lit(DRAWSTATE *ds, int light) { static CDQ q = CDQ_STATIC_INIT; int x; int i; int x2; int n; n = 0; if (cdq_n(&q) != 0) abort(); cdq_add(&q,(CDQE){.x=light,.dir=0}); while (cdq_n(&q) > 0) { x = cdq_get(&q,0).x; if (ds->live[x] & LIT) continue; ds->live[x] |= LIT; n ++; for (i=5;i>=0;i--) { if ((ds->live[x] >> i) & LINK_BASE) { x2 = connect_to(ds->d,x,i,0); if ( (x2 >= 0) && ((ds->live[x2] >> otherway(i)) & LINK_BASE) ) { cdq_add(&q,(CDQE){.x=x2,.dir=0}); } } } } } /* * Set up for a draw_polygon() call for a cell's hexagon. This is * factored out because it's used multiple times in redraw(). */ static void load_hexagon(int *phex, const DRAWSTATE *ds, int cx, int cy) { phex[0] = cx; phex[1] = cy - (2 * ds->hexsize); phex[2] = cx + ds->hexr3; phex[3] = cy - ds->hexsize; phex[4] = cx + ds->hexr3; phex[5] = cy + ds->hexsize; phex[6] = cx; phex[7] = cy + (2 * ds->hexsize); phex[8] = cx - ds->hexr3; phex[9] = cy + ds->hexsize; phex[10] = cx - ds->hexr3; phex[11] = cy - ds->hexsize; } /* * Set up for a draw_polygon() call for one cell's portion of the * wrap-mark ring. cx and cy are the centre of the cell; cf is one of * the FCF_WRAP values, indicating which portions of the cell to draw. * Return value is the number of points to draw. */ static int load_wrap_region(int *pvec, const DRAWSTATE *ds, int cx, int cy, unsigned char cf) { int x; #define STORE(dx,dy) do { pvec[x++] = cx + (dx); pvec[x++] = cy + (dy); } while (0) x = 0; switch (cf) { case FCF_WRAP_0: STORE(0,0); STORE(-ds->hexr3/2,-(3*ds->hexsize)/2); STORE(0,-2*ds->hexsize); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,ds->hexsize); STORE(0,2*ds->hexsize); STORE(-ds->hexr3/2,(3*ds->hexsize)/2); return(7); break; case FCF_WRAP_01: STORE(-ds->hexr3/2,-(3*ds->hexsize)/2); STORE(0,-2*ds->hexsize); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,ds->hexsize); STORE(ds->hexr3/2,(3*ds->hexsize)/2); return(5); break; case FCF_WRAP_1: STORE(0,0); STORE(-ds->hexr3,0); STORE(-ds->hexr3,-ds->hexsize); STORE(0,-2*ds->hexsize); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,ds->hexsize); STORE(ds->hexr3/2,(3*ds->hexsize)/2); return(7); break; case FCF_WRAP_12: STORE(-ds->hexr3,0); STORE(-ds->hexr3,-ds->hexsize); STORE(0,-2*ds->hexsize); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,0); return(5); break; case FCF_WRAP_2: STORE(0,0); STORE(-ds->hexr3/2,(3*ds->hexsize)/2); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,-ds->hexsize); STORE(0,-2*ds->hexsize); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,0); return(7); break; case FCF_WRAP_23: STORE(-ds->hexr3/2,(3*ds->hexsize)/2); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,-ds->hexsize); STORE(0,-2*ds->hexsize); STORE(ds->hexr3/2,-(3*ds->hexsize)/2); return(5); break; case FCF_WRAP_3: STORE(0,0); STORE(ds->hexr3/2,(3*ds->hexsize)/2); STORE(0,2*ds->hexsize); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,-ds->hexsize); STORE(0,-2*ds->hexsize); STORE(ds->hexr3/2,-(3*ds->hexsize)/2); return(7); break; case FCF_WRAP_34: STORE(ds->hexr3/2,(3*ds->hexsize)/2); STORE(0,2*ds->hexsize); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,-ds->hexsize); STORE(-ds->hexr3/2,-(3*ds->hexsize)/2); return(5); break; case FCF_WRAP_4: STORE(0,0); STORE(ds->hexr3,0); STORE(ds->hexr3,ds->hexsize); STORE(0,2*ds->hexsize); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,-ds->hexsize); STORE(-ds->hexr3/2,-(3*ds->hexsize)/2); return(7); break; case FCF_WRAP_45: STORE(ds->hexr3,0); STORE(ds->hexr3,ds->hexsize); STORE(0,2*ds->hexsize); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,0); return(5); break; case FCF_WRAP_5: STORE(0,0); STORE(ds->hexr3/2,-(3*ds->hexsize)/2); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,ds->hexsize); STORE(0,2*ds->hexsize); STORE(-ds->hexr3,ds->hexsize); STORE(-ds->hexr3,0); return(7); break; case FCF_WRAP_50: STORE(ds->hexr3/2,-(3*ds->hexsize)/2); STORE(ds->hexr3,-ds->hexsize); STORE(ds->hexr3,ds->hexsize); STORE(0,2*ds->hexsize); STORE(-ds->hexr3/2,(3*ds->hexsize)/2); return(5); break; } abort(); } static void updr_init(UPDRECT *ur, const DRAWSTATE *ds) { ur->minx = ds->w; ur->maxx = 0; ur->miny = ds->h; ur->maxy = 0; } static void updr_add(UPDRECT *ur, int x, int y, int w, int h) { if (x < ur->minx) ur->minx = x; if (y < ur->miny) ur->miny = y; x += w; if (x > ur->maxx) ur->maxx = x; y += h; if (y > ur->maxy) ur->maxy = y; } static void updr_draw_update(UPDRECT *ur, drawing *dr) { draw_update(dr,ur->minx,ur->miny,ur->maxx-ur->minx,ur->maxy-ur->miny); } /* * Redraw the game board. * * In order to get all the occlusion correct, we order our drawing very * carefully. * * First we determine which cells we want to draw. This includes: * 1) Cells which have changed. * 2) The cell being animated, if any. * 3) The cell we just stopped animating, if any. * 4) The current and former light source cells, if they differ. * 5) Any cell which shares a bidirectional link with a cell in * classes 1, 3, or 4. * * The reason for (5) is that we want to draw such a link as a single * polygon (or, if thin, line) so as to avoid a glitch at the cell * boundary due to coordinate rounding. * * We draw things in this order, where a cell being animated is never * considered to participate in bidirectional links: * 1) Cell backgrounds. * 2) Bidirectional links. * 3) Unidirectional links. * 4) Cell-centre circles. * 5) Grid hexagons. * * For each of the items in the list above, we draw that thing for all * cells being redrawn before going on to the next thing. This is * semi-necessary to make occlusion work right. To avoid flicker, * though, we want to do _one_ draw_update call, at the end, covering * all cells which got redrawn, rather than doing them as we go. */ static void redraw( drawing *dr, DRAWSTATE *ds, const STATE *old, const STATE *new, int dir, const UI *u, float animtime, float flashtime ) { DPAR *d; int i; int ii; int j; int k; int cx; int cy; int x; int y; int kx; int ky; int oj; int phex[14]; int pwire[8]; int px; int l; int col; int rotloc; double rotang; double racos; double rasin; UPDRECT upd; int lighton; (void)u; (void)flashtime; d = ds->d; if (!old || !dir) { // Not animating. rotloc = -1; } else { /* * Animating. animtime runs from 0 to ROTATE_TIME here. old is * the former state, new is the state we're moving to. That would * be all we'd need, except that we want to do the move specified. * For example, if we have a 0x15 piece being changed into a 0x2a * piece, we have to rotate it the way the user expects, and we * can't tell that just from 0x15 and 0x2a. So execute_move sets * lastrot_loc and lastrot_dir when it does rotations. Undo and * redo are done by saving states and moving between them, not by * re-executing moves. This means that, when animating an undo, * we have to animate by reversing the move in the old state * rather than performing the move in the new state, whereas for a * move or redo, we have to do the obvious thing and perform the * move in the new state. * * Also, we have to be careful that animtime=0 corresponds to old * and animtime=ROTATE_TIME to new, visually, even though our base * position is always the new one. This is why rotang is * sometimes negative below. * * Our execute_move() uses lastrot_dir=1 for rotates right, * lastrot_dir=-1 for rotates left. And we're in fourth quadrant, * not first quadrant. So we have to be very careful about * angular directions here. * * We do not always get a final animation call with * animtime==ROTATE_TIME. We get a final call, but it's a * non-animating call. Making sure we finish the rotation is what * ds->animloc is for. */ if (dir > 0) { // Move or redo. rotloc = new->lastrot_loc; rotang = - new->lastrot_dir * M_PI / 3; } else { // Undo. rotloc = old->lastrot_loc; rotang = old->lastrot_dir * M_PI / 3; } rotang *= (ROTATE_TIME - animtime) / ROTATE_TIME; racos = cos(rotang); rasin = sin(rotang); } lighton = u->lighton; if (flashtime > 0) { if (ds->lastflash < 0) { ds->flashing = 1; ds->lastflash = 0; } else if (flashtime > ds->lastflash + FLASH_FRAME) { ds->flashing = ! ds->flashing; ds->lastflash += FLASH_FRAME; } if (ds->flashing) lighton = ! lighton; } else if (ds->lastflash >= 0) { ds->lastflash = -1; ds->flashing = 0; } if (ds->first) { ds->first = 0; for (i=d->asz2-1;i>=0;i--) ds->cur[i] = new->net[i] ^ 1; if (! d->p.wrap) { /* * Generate the barrier surround by drawing offset border * hexagons. Doing it this way does O(n^2) work instead of * O(n) work, but n is small, and this is done just once. In * my opinion, the code complexity of arranging to do just the * border hexagons outweighs the run-time cost of this way. */ for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { if (unused_xy(d,x,y,0)) continue; cx = cell_cx_xy(ds,x,y); cy = cell_cy_xy(ds,x,y); load_hexagon(&phex[0],ds,cx+1,cy); draw_polygon(dr,&phex[0],6,-1,COL_BARRIER); load_hexagon(&phex[0],ds,cx-1,cy); draw_polygon(dr,&phex[0],6,-1,COL_BARRIER); load_hexagon(&phex[0],ds,cx,cy+1); draw_polygon(dr,&phex[0],6,-1,COL_BARRIER); load_hexagon(&phex[0],ds,cx,cy-1); draw_polygon(dr,&phex[0],6,-1,COL_BARRIER); } } } bcopy(new->net,ds->live,d->asz2*sizeof(CELL)); if (lighton) { if (rotloc >= 0) ds->live[rotloc] = old->net[rotloc]; find_lit(ds,u->light); if (rotloc >= 0) ds->live[rotloc] = (ds->live[rotloc] & LIT) | (new->net[rotloc] & ~LIT); } /* * Find bidirectional links and set BIDIR on the affected links (which * may or may not be getting redrawn anyway; that doesn't matter). * But if a cell is being animated, it never participates in * bidirectional links (for graphical purposes, which are the only * purposes for which bidirectional links exist anyway). * * BIDIR depends on the way we clear BIDIR when saving ds->live[] into * ds->cur[]. Set BIDIR on both links so we can use it to identify * which cells need such attention. * * Note that all per-line markings (ERR, LOOP, etc) are such that they * will always be present on both or neither of the two half-lines. * We could check this, but the code would be a bit messy and, if * things are broken badly enough for that check to trip, a display * glitch is the least of our worries. */ // Determine which (display) cells get redrawn, first cut. bzero(ds->want,d->asz2*sizeof(*ds->want)); for (i=d->asz2-1;i>=0;i--) { if (ds->live[i] & UNUSED) continue; ii = d->dupof[i]; if ( (ds->cur[ii] == ds->live[ii]) && !( (ii == rotloc) || (ii == ds->animloc) || ( (ds->curlight != u->light) && ( (ii == ds->curlight) || (ii == u->light) ) ) ) ) continue; ds->want[i] = 1; } // Find (relevant) bidirectional links. // This sets bits on game cells; we fix that below. for (i=d->asz2-1;i>=0;i--) ds->live[i] &= ~BIDIR_ALL; for (i=d->asz2-1;i>=0;i--) { if (ds->live[i] & (UNUSED|DUP)) continue; if (ds->want[i]) { for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & LINK_BASE) { k = connect_to(d,i,j,0); if ( (k >= 0) && (d->dupof[i] != rotloc) && (d->dupof[k] != rotloc) && ((ds->live[k] >> otherway(j)) & LINK_BASE) ) { ds->live[k] |= BIDIR_BASE << otherway(j); ds->live[i] |= BIDIR_BASE << j; } } } } } // Now copy BIDIR bits to display cells. for (i=d->asz2-1;i>=0;i--) { if (ds->live[i] & UNUSED) continue; ii = ds->d->dupof[i]; if (ii == i) continue; ds->live[i] |= ds->live[ii] & BIDIR_ALL; } // Clear BIDIR bits on (display) links that wrap. for (i=d->asz2-1;i>=0;i--) { if (ds->live[i] & UNUSED) continue; if (! (ds->live[i] & BIDIR_ALL)) continue; for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & BIDIR_BASE) { k = connect_to(d,i,j,1); if (k < 0) ds->live[i] &= ~ (BIDIR_BASE << j); } } } // We want to draw both ends of bidirectional links. // We set BIDIR on both ends above. for (i=d->asz2-1;i>=0;i--) { if (ds->live[i] & UNUSED) continue; if (ds->live[i] & BIDIR_ALL) ds->want[i] = 1; } // Draw (1) cell backgrounds. for (i=d->asz2-1;i>=0;i--) { if (! ds->want[i]) continue; cx = cell_cx_xy(ds,d->ix[i],d->iy[i]); cy = cell_cy_xy(ds,d->ix[i],d->iy[i]); load_hexagon(&phex[0],ds,cx,cy); draw_polygon(dr,&phex[0],6,(ds->live[d->dupof[i]]&LOCKED)?COL_LOCKED:COL_BACKGROUND,COL_BACKGROUND); if (ds->d->flags[i] & FCF_WRAP) { j = load_wrap_region(&phex[0],ds,cx,cy,ds->d->flags[i]&FCF_WRAP); col = (ds->live[d->dupof[i]] & LOCKED) ? COL_LOCKWRAP : COL_BGWRAP; draw_polygon(dr,&phex[0],j,col,col); } } // Draw (2) bidirectional links. i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; if (! ds->want[i]) continue; if (ds->live[i] & UNUSED) continue; if (! (ds->live[i] & BIDIR_ALL)) continue; ii = d->dupof[i]; cx = cell_cx_xy(ds,x,y); cy = cell_cy_xy(ds,x,y); for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & BIDIR_BASE) { k = connect_to(ds->d,i,j,1); if (k < 0) abort(); if (i < k) { col = ((ds->live[i] >> j) & ERR_BASE) ? COL_ERR : ((ds->live[i] >> j) & LOOP_BASE) ? COL_LOOP : (ds->live[ii] & LIT) ? COL_POWERED : COL_WIRE; l = k / ds->d->asz; k %= ds->d->asz; kx = cell_cx_xy(ds,k,l); ky = cell_cy_xy(ds,k,l); if (ds->linew2 < 1) { draw_line(dr,cx,cy,kx,ky,col); } else { px = 0; oj = otherway(j); // This code depends on how set_size() sets up lineoff. pwire[px++] = kx + ds->lineoff[oj][2].x; pwire[px++] = ky + ds->lineoff[oj][2].y; pwire[px++] = kx + ds->lineoff[oj][3].x; pwire[px++] = ky + ds->lineoff[oj][3].y; pwire[px++] = cx + ds->lineoff[j][2].x; pwire[px++] = cy + ds->lineoff[j][2].y; pwire[px++] = cx + ds->lineoff[j][3].x; pwire[px++] = cy + ds->lineoff[j][3].y; draw_polygon(dr,&pwire[0],4,col,col); } } } } } // Draw (3) unidirectional links (including animated ones). i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; if (! ds->want[i]) continue; ii = d->dupof[i]; cx = cell_cx_xy(ds,x,y); cy = cell_cy_xy(ds,x,y); for (j=5;j>=0;j--) { if ( ((ds->live[ii] >> j) & LINK_BASE) && !((ds->live[i] >> j) & BIDIR_BASE) ) { col = ((ds->live[ii] >> j) & ERR_BASE) ? COL_ERR : ((ds->live[ii] >> j) & LOOP_BASE) ? COL_LOOP : (ds->live[ii] & LIT) ? COL_POWERED : COL_WIRE; if (ds->linew2 < 1) { if (ii == rotloc) { draw_line(dr,cx,cy, cx + rint((ds->lineoff[j][0].x * racos) - (ds->lineoff[j][0].y * rasin)), cy + rint((ds->lineoff[j][0].x * rasin) + (ds->lineoff[j][0].y * racos)), col); } else { draw_line(dr,cx,cy,cx+ds->lineoff[j][0].x,cy+ds->lineoff[j][0].y,col); } } else { px = 0; for (l=4-1;l>=0;l--) { if (ii == rotloc) { pwire[px++] = cx + rint((ds->lineoff[j][l].x * racos) - (ds->lineoff[j][l].y * rasin)); pwire[px++] = cy + rint((ds->lineoff[j][l].x * rasin) + (ds->lineoff[j][l].y * racos)); } else { pwire[px++] = cx + ds->lineoff[j][l].x; pwire[px++] = cy + ds->lineoff[j][l].y; } } draw_polygon(dr,&pwire[0],4,col,col); } } } } // Draw (4) cell-centre circles. i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; if (! ds->want[i]) continue; ii = d->dupof[i]; cx = cell_cx_xy(ds,x,y); cy = cell_cy_xy(ds,x,y); if (only_one(ds->live[ii]&0x3f)) { col = (ds->live[ii] & LIT) ? COL_POWERED : COL_ENDPOINT; draw_circle(dr,cx,cy,ds->epcr,COL_WIRE,COL_WIRE); draw_circle(dr,cx,cy,ds->epcr-ds->linew2,col,col); } else { col = ((ds->live[ii] >> ERR_S) & ERR_M) ? COL_ERR : ((ds->live[ii] >> LOOP_S) & LOOP_M) ? COL_LOOP : (ds->live[ii] & LIT) ? COL_POWERED : COL_WIRE; draw_circle(dr,cx,cy,ds->nepcr,col,col); } if (ii == u->light) draw_circle(dr,cx,cy,ds->litcr,-1,COL_POWERED); } // Draw (5) grid hexagons. Also compute update box. updr_init(&upd,ds); i = d->asz2; for (y=d->asz-1;y>=0;y--) for (x=d->asz-1;x>=0;x--) { i --; if (! ds->want[i]) continue; cx = cell_cx_xy(ds,x,y); cy = cell_cy_xy(ds,x,y); load_hexagon(&phex[0],ds,cx,cy); draw_polygon(dr,&phex[0],6,-1,COL_BORDER); updr_add(&upd,cx-ds->hexr3-1,cy-(2*ds->hexsize)-1,(ds->hexr3+1)*2,((2*ds->hexsize)+1)*2); } // Push the updates. updr_draw_update(&upd,dr); // Update ds->cur. for (i=d->asz2-1;i>=0;i--) ds->cur[i] = ds->live[i] & ~BIDIR_ALL; ds->animloc = rotloc; ds->curlight = u->light; } static float anim_length(const STATE *old, const STATE *new, int dir, UI *u) { int rdir; (void)u; if (dir < 0) { rdir = (old->lastrot_loc < 0) ? 0 : old->lastrot_dir; } else { rdir = (new->lastrot_loc < 0) ? 0 : new->lastrot_dir; } if (! rdir) return(0); return(ROTATE_TIME); } static float flash_length(const STATE *old, const STATE *new, int dir, UI *u) { (void)dir; (void)u; if ((new->flags & ~old->flags) & SF_SOLVED) return(FLASH_TIME); return(0); } static int status(const STATE *s) { (void)s; return(0); } static bool timing_state(const STATE *s, UI *u) { (void)s; (void)u; return(false); } static void print_size(const PARAMS *p, float *x, float *y) { (void)p; (void)x; (void)y; abort(); } static void print(drawing *dr, const STATE *s, int tilesize) { (void)dr; (void)s; (void)tilesize; abort(); } #ifdef COMBINED #define thegame hexnetgame #endif const struct game thegame = { "Hex Net Game", 0, // winhelp_topic 0, // htmlhelp_topic &default_params, &fetch_preset, 0, // preset_menu &decode_params, &encode_params, &free_params, &dup_params, true, // can_configure &configure, &custom_params, &validate_params, &new_game_desc, &validate_desc, &new_game, &dup_game, &free_game, false, // can_solve &solve_game, false, // can_format_as_text_ever &can_format_as_text_now, &text_format, &new_ui, &free_ui, &encode_ui, &decode_ui, 0, // request_keys &changed_state, &interpret_move, &execute_move, 10, // preferred_tilesize &compute_size, &set_size, &colours, &new_drawstate, &free_drawstate, &redraw, &anim_length, &flash_length, 0, // get_cursor_location &status, false, // can_print false, // can_print_in_colour &print_size, &print, false, // wants_statusbar false, // is_timed &timing_state, 0, // flags };