#include #include #include #include #include #include #include "puzzles.h" #define COL_BACKGROUND 0 #define COL_LOCKED 1 #define COL_BORDER 2 #define COL_WIRE 3 #define COL_ENDPOINT 4 #define COL_POWERED 5 #define COL_ERR 6 #define COL_LOOP 7 #define NCOLOURS 8 #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; 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; } ; /* * 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<= size) { return(x>=asz-(1+y-size)); } else { return(xsize * 2) - 1; y = cell / asz; x = cell % asz; if ((y < 0) || (x < 0) || (y >= asz) || (x >= asz)) 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 ((y < 0) || (x < 0) || (y >= asz) || (x >= asz)) return(-1); if (unused_xy(p->size,x,y)) return(-1); return((y*asz)+x); } 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 > 6)) return(false); p = snew(PARAMS); p->size = i + 3; asprintf(name,"Size %d",p->size); *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; } static char *encode_params(const PARAMS *p, bool full) { char *s; (void)full; asprintf(&s,"%d",p->size); 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); return(p); } static const char *validate_params(const PARAMS *p, bool full) { (void)full; if (p->size < 2) return("Size must be at least 2"); return(0); } 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 char *new_game_desc(const PARAMS *p, RANDOM_STATE *rs, char **aux, bool interactive) { CELL *net; int asz; int asz2; int i; int j; int x; int y; CDQ q; CDQE e; char *desc; (void)aux; (void)interactive; asz = (p->size * 2) - 1; asz2 = asz * asz; net = smalloc(asz2*sizeof(*net)); bzero(net,asz2*sizeof(*net)); for (j=p->size-2;j>=0;j--) { for (i=p->size-2-j;i>=0;i--) { x = (j * asz) + i; net[x] = UNUSED; net[asz2-1-x] = UNUSED; } } x = (p->size - 1) * (asz + 1); cdq_init(&q); for (i=5;i>=0;i--) cdq_add(&q,(CDQE){.x=x,.dir=i}); 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; y = connect_to(p,e.x,e.dir); // If line points outside the grid, skip. if ((y < 0) || (net[y] & UNUSED)) continue; // If the target has already been included, skip. // (We depend on "included" == "has >=1 line(s)".) if (net[y] != 0) continue; // Add the line. net[e.x] |= 1U << e.dir; net[y] = 1U << otherway(e.dir); // Queue all lines from the target. for (i=5;i>=0;i--) cdq_add(&q,(CDQE){.x=y,.dir=i}); } cdq_done(&q); for (i=0;i=0;j--) net[i] = rot_r(net[i]); } desc = smalloc(asz2); j = 0; for (i=0;isize * (p->size - 1) * 3) + 1; if (strlen(desc) != nc) return("Game deescription length wrong"); if (strspn(desc,&b64chars[0]) != nc) return("Game description contains unexpected character"); return(0); } static int loop_neighbour(int v, void *vc) { LOOPCTX *c; CELL *net; const PARAMS *p; int d; int v2; c = vc; net = c->s->net; p = &c->s->par; 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(p,v,d); 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 find_loops(STATE *s) { int i; int d; int j; int n; LOOPCTX ctx; FINDLOOPSTATE *fls; n = 0; fls = findloop_new_state(s->asz2); ctx.s = s; findloop_run(fls,s->asz2,&loop_neighbour,&ctx); for (i=s->asz2-1;i>=0;i--) s->net[i] &= ~LOOP_ALL; for (i=s->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->par,i,d); 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; int asz; int x; int y; int i; const char *inx; (void)me; asz = (p->size * 2) - 1; s = snew(STATE); s->par = *p; s->asz = asz; s->asz2 = asz * asz; s->lastrot_loc = -1; s->flags = 0; s->net = smalloc(s->asz2*sizeof(*s->net)); for (y=0;ysize,x,y)) { s->net[i] = UNUSED; } else { if (! *desc) abort(); inx = index(&b64chars[0],*desc++); if (! inx) abort(); s->net[i] = inx - &b64chars[0]; } } } find_loops(s); return(s); } static STATE *dup_game(const STATE *s) { STATE *n; n = snew(STATE); *n = *s; n->net = smalloc(s->asz2*sizeof(*n->net)); bcopy(s->net,n->net,s->asz2*sizeof(*n->net)); return(n); } static void free_game(STATE *s) { 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->par.size * (s->par.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->par.size - 1)) * 2) + y - (ds->par.size - 1)) * ds->hexr3)); } static int cell_cy_xy(const DRAWSTATE *ds, int x, int y) { (void)x; return((ds->h / 2) + ((y - (ds->par.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 (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. * * 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->par.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->asz)) return(-1); // Compensate for the offset. y --; // Convert x to cell index. x = cellx_for_pixx(ds,px,y); // Range-check. if (x >= ds->asz) return(-1); // (x,y) is in the allocated grid, but is it an unused cell? if (unused_xy(ds->par.size,x,y)) return(-1); // All good! return((y*ds->asz)+x); } // 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->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 ((x < 0) || (x >= ds->asz) || (y < 0) || (y >= ds->asz)) return(-1); if (unused_xy(ds->par.size,x,y)) return(-1); return((y*ds->asz)+x); } 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->asz2) abort(); 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->par,u->light,dir); 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; } (void)u; return(m); } static void find_err(STATE *s) { int x; int y; int inx; int i; int to; for (inx=s->asz2-1;inx>=0;inx--) s->net[inx] &= ~ERR_ALL; x = 0; y = s->asz; for (inx=s->asz2-1;inx>=0;inx--) { if (x < 1) { x = s->asz - 1; y --; } else { x --; } if (s->net[inx] & UNUSED) continue; if (! (s->net[inx] & LOCKED)) continue; for (i=5;i>=0;i--) { if ((s->net[inx] >> i) & LINK_BASE) { to = connect_to(&s->par,inx,i); 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->asz2-1;i>=0;i--) s->net[i] &= ~MARK; n = 0; if (cdq_n(&q) != 0) abort(); cdq_add(&q,(CDQE){.x=s->asz-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->par,x,i); if ( (x2 >= 0) && ((s->net[x2] >> otherway(i)) & LINK_BASE) ) { cdq_add(&q,(CDQE){.x=x2,.dir=0}); } } } } if (n == (3 * s->par.size * (s->par.size - 1)) + 1) 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->asz2)) 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->asz2)) 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->asz2) || (n->net[inx] & 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->asz2) || (n->net[inx] & 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,.3,.3,0); #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->par = s->par; ds->asz = s->asz; ds->asz2 = s->asz2; ds->first = 1; ds->hexsize = 0; ds->hexr3 = 0; ds->animloc = -1; ds->live = smalloc(ds->asz2*sizeof(*ds->live)); ds->cur = smalloc(ds->asz2*sizeof(*ds->cur)); ds->want = smalloc(ds->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); } 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->par,x,i); 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; } 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 ) { int i; int j; int k; int cx; int cy; int x; int y; int kx; int ky; int oj; int phex[12]; 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; 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=ds->asz2-1;i>=0;i--) ds->cur[i] = new->net[i] ^ 1; } bcopy(new->net,ds->live,ds->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. * * 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 cells get redrawn, first pass. bzero(ds->want,ds->asz2); for (i=ds->asz2-1;i>=0;i--) { if (ds->live[i] & UNUSED) continue; if ( (ds->cur[i] == ds->live[i]) && !( (i == rotloc) || (i == ds->animloc) || ( (ds->curlight != u->light) && ( (i == ds->curlight) || (i == u->light) ) ) ) ) continue; ds->want[i] = 1; } // Find (relevant) bidirectional links. for (i=ds->asz2-1;i>=0;i--) ds->live[i] &= ~BIDIR_ALL; for (i=ds->asz2-1;i>=0;i--) { if (ds->want[i]) { for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & LINK_BASE) { k = connect_to(&ds->par,i,j); if ( (k >= 0) && (i != rotloc) && (k != rotloc) && ((ds->live[k] >> otherway(j)) & LINK_BASE) ) { ds->live[k] |= BIDIR_BASE << otherway(j); ds->live[i] |= BIDIR_BASE << j; } } } } } // We want to draw both ends of bidirectional links. for (i=ds->asz2-1;i>=0;i--) { if (ds->live[i] & BIDIR_ALL) ds->want[i] = 1; } // Draw (1) cell backgrounds. i = ds->asz2; for (y=ds->asz-1;y>=0;y--) for (x=ds->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,(ds->live[i]&LOCKED)?COL_LOCKED:COL_BACKGROUND,COL_BORDER); } // Draw (2) bindirectional links. i = ds->asz2; for (y=ds->asz-1;y>=0;y--) for (x=ds->asz-1;x>=0;x--) { i --; if (! ds->want[i]) continue; if (! (ds->live[i] & BIDIR_ALL)) continue; 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->par,i,j); if (i < k) // draw it only once! { col = ((ds->live[i] >> j) & ERR_BASE) ? COL_ERR : ((ds->live[i] >> j) & LOOP_BASE) ? COL_LOOP : (ds->live[i] & LIT) ? COL_POWERED : COL_WIRE; kx = k % ds->asz; ky = k / ds->asz; l = cell_cx_xy(ds,kx,ky); ky = cell_cy_xy(ds,kx,ky); kx = 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 = ds->asz2; for (y=ds->asz-1;y>=0;y--) for (x=ds->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); for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & LINK_BASE) { if (! ((ds->live[i] >> j) & BIDIR_BASE)) { col = ((ds->live[i] >> j) & ERR_BASE) ? COL_ERR : ((ds->live[i] >> j) & LOOP_BASE) ? COL_LOOP : (ds->live[i] & LIT) ? COL_POWERED : COL_WIRE; if (ds->linew2 < 1) { if (i == 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 (i == 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 = ds->asz2; for (y=ds->asz-1;y>=0;y--) for (x=ds->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); if (only_one(ds->live[i]&0x3f)) { col = (ds->live[i] & 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[i] >> ERR_S) & ERR_M) ? COL_ERR : ((ds->live[i] >> LOOP_S) & LOOP_M) ? COL_LOOP : (ds->live[i] & LIT) ? COL_POWERED : COL_WIRE; draw_circle(dr,cx,cy,ds->nepcr,col,col); } if (i == 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 = ds->asz2; for (y=ds->asz-1;y>=0;y--) for (x=ds->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=ds->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, 20, // 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 };