#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_BARRIER 6 #define COL_ERR 7 #define NCOLOURS 8 #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 unsigned int CELL; typedef struct cdq CDQ; typedef struct cdqe CDQE; typedef struct ixy IXY; typedef struct fxy FXY; struct ixy { int x; int y; } ; struct fxy { double x; double y; } ; struct game_params { int size; } ; /* * 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(x= 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(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 = random_upto(rs,q->n); 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 = 4; 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(2,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 = 0; cv[1].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 spot, skip. if (all_but_one(net[e.x])) continue; y = connect_to(p->size,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 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->size = p->size; s->asz = asz; s->asz2 = asz * asz; s->lastrot_loc = -1; 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]; } } } 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); 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->gsz - 1)) * 2) + y - (ds->gsz - 1)) * ds->hexr3)); } static int cell_cy_xy(const DRAWSTATE *ds, int x, int y) { (void)x; return((ds->h / 2) + ((y - (ds->gsz - 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->gsz * 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->gsz,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); 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; tx = tile_for_loc(ds,x,y); if (tx < 0) return(0); if (tx >= ds->asz2) abort(); 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; default: return(0); break; } (void)u; return(m); } 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; 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; 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]); 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]); 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; compute_size(p,tilesize,&ds->w,&ds->h); if (ds->linew2 < 1) { ds->lineoff[0][0].x = ds->hexr3; ds->lineoff[0][0].y = 0; rn = 1; } else { ds->lineoff[0][0].x = ds->hexr3; 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); // Barriers are red. SET(COL_BARRIER,1,0,0); // Errors are red also. SET(COL_ERR,1,0,0); // 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->gsz = s->size; ds->asz = s->asz; ds->asz2 = s->asz2; ds->first = 1; ds->hexsize = 0; ds->hexr3 = 0; ds->live = smalloc(ds->asz2*sizeof(*ds->live)); ds->cur = smalloc(ds->asz2*sizeof(*ds->cur)); // ds->cur[] initialized in first redraw() return(ds); } static void free_drawstate(drawing *dr, DRAWSTATE *ds) { (void)dr; sfree(ds->cur); sfree(ds); } /* * This functions is a factoring-out of the loading of an array of ints * for draw_polygon to draw the hexagon for a cell. It exists because * we want to draw that hexagon more than once, but draw_polygon * doesn't promise it won't mutate the array(!). */ static void load_hexagon(int *pp, DRAWSTATE *ds, int cx, int cy) { pp[0] = cx; pp[1] = cy - (2 * ds->hexsize); pp[2] = cx + ds->hexr3; pp[3] = cy - ds->hexsize; pp[4] = cx + ds->hexr3; pp[5] = cy + ds->hexsize; pp[6] = cx; pp[7] = cy + (2 * ds->hexsize); pp[8] = cx - ds->hexr3; pp[9] = cy + ds->hexsize; pp[10] = cx - ds->hexr3; pp[11] = cy - ds->hexsize; } static void find_err(DRAWSTATE *ds) { int x; int y; int inx; int i; int to; x = 0; y = ds->asz; for (inx=ds->asz2-1;inx>=0;inx--) { if (x < 1) { x = ds->asz - 1; y --; } else { x --; } if (ds->live[inx] & UNUSED) continue; if (! (ds->live[inx] & LOCKED)) continue; for (i=5;i>=0;i--) { if ((ds->live[inx] >> i) & LINK_BASE) { to = connect_to(ds->gsz,inx,i); if ((to < 0) || ((ds->live[to] & LOCKED) && ! ((ds->live[to] >> OTHERWAY(i)) & 1))) { ds->live[inx] |= ERR_BASE << i; } } } } } static void find_lit(DRAWSTATE *ds) { (void)ds; } 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 phex[12]; int pwire[8]; int px; int l; int col; if (ds->first) { draw_rect(dr,0,0,ds->w,ds->h,COL_BACKGROUND); draw_update(dr,0,0,ds->w,ds->h); ds->first = 0; for (i=ds->asz2-1;i>=0;i--) ds->cur[i] = new->net[i] ^ 1; } (void)old; (void)dir; (void)u; (void)animtime; (void)flashtime; bcopy(new->net,ds->live,ds->asz2*sizeof(CELL)); find_err(ds); find_lit(ds); x = 0; y = ds->asz; for (i=ds->asz2-1;i>=0;i--) { if (x < 1) { x = ds->asz - 1; y --; } else { x --; } if (ds->live[i] & UNUSED) continue; if (ds->cur[i] == ds->live[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); for (j=5;j>=0;j--) { if ((ds->live[i] >> j) & LINK_BASE) { col = ((ds->live[i] >> j) & ERR_BASE) ? COL_ERR : COL_WIRE; k = connect_to(ds->gsz,i,j); if (ds->linew2 < 1) { draw_line(dr,cx,cy,cx+ds->lineoff[j][0].x,cy+ds->lineoff[j][0].y,col); } else { px = 0; for (l=0;l<4;l++) { pwire[px++] = cx + ds->lineoff[j][l].x; pwire[px++] = cy + ds->lineoff[j][l].y; } draw_polygon(dr,&pwire[0],4,col,col); } } } if (only_one(ds->live[i]&0x3f)) { draw_circle(dr,cx,cy,ds->epcr,COL_WIRE,COL_WIRE); draw_circle(dr,cx,cy,ds->epcr-ds->linew2,COL_ENDPOINT,COL_ENDPOINT); } else { draw_circle(dr,cx,cy,ds->nepcr,COL_WIRE,COL_WIRE); } load_hexagon(&phex[0],ds,cx,cy); draw_polygon(dr,&phex[0],6,-1,COL_BORDER); draw_update(dr,cx-ds->hexr3-1,cy-(2*ds->hexsize)-1,(ds->hexr3+1)*2,((2*ds->hexsize)+1)*2); ds->cur[i] = ds->live[i]; } } static float anim_length(const STATE *old, const STATE *new, int dir, UI *u) { (void)old; (void)new; (void)dir; (void)u; return(0); } static float flash_length(const STATE *old, const STATE *new, int dir, UI *u) { (void)old; (void)new; (void)dir; (void)u; 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, &status, false, // can_print false, // can_print_in_colour &print_size, &print, false, // wants_statusbar false, // is_timed &timing_state, 0, // flags };