#include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; static XrmDatabase db; static const char *defaults = "\ *Foreground: white\n\ *Background: black\n\ *BorderColour: white\n\ *BorderWidth: 1\n\ *Name: xflowfree\n\ *IconName: xflowfree\n\ *GridColour: #7f7f7f\n\ *DragPixels: 10\n\ *IgnoreDragTime: 125\n\ *MaxClickTime: 300\n\ *Link.1.Colour: #ff6d00\n\ *Link.1.Pattern: 1 1 1\n\ *Link.2.Colour: #0000ff\n\ *Link.2.Pattern: 6 6 001000 011100 110110 011100 001000 000001\n\ *Link.3.Colour: #00b655\n\ *Link.3.Pattern: 8 8 11111110 10000010 10111010 10101010 10111010 10000010 11111110 00000000\n\ *Link.4.Colour: #b6b600\n\ *Link.4.Pattern: 8 4 00001111 00111100 11110000 11000011\n\ *Link.5.Colour: #ff0000\n\ *Link.5.Pattern: 8 8 01111110 10111101 11011011 11100111 11100111 11011011 10111101 01111110\n\ *Link.6.Colour: #00ffff\n\ *Link.6.Pattern: 8 8 01111111 01111001 00111000 10001111 11110001 00011100 10011110 11111110\n\ *Link.7.Colour: #ff00ff\n\ *Link.7.Pattern: 6 6 111111 100001 100001 100001 100001 111111\n\ *Link.8.Colour: #492955\n\ *Link.8.Pattern: 8 8 11100111 11001111 10000111 00000011 01100000 11110000 11111001 11110011\n\ *Link.9.Colour: #6d0055\n\ *Link.9.Pattern: 8 4 11001001 10010011 10011100 00111001\n\ *Link.10.Colour: #ffffff\n\ *Link.10.Pattern: 8 8 00111000 01111100 11000110 11000110 11000110 01111100 00111000 00000000\n\ *Link.11.Colour: #6d6d55\n\ *Link.11.Pattern: 5 5 00010 01000 00001 00100 10000\n\ *Link.12.Colour: #00ff00\n\ *Link.12.Pattern: 6 6 001000 010100 100010 010100 001000 000000\n\ *Link.13.Colour: #ffff00\n\ *Link.13.Pattern: 8 8 11110000 11110000 11110000 11110000 00001111 00001111 00001111 00001111\n\ *Link.14.Colour: #0000aa\n\ *Link.14.Pattern: 5 5 01111 11011 11110 10111 11101\n\ *Link.15.Colour: #ff92aa\n\ *Link.15.Pattern: 3 3 011 101 110\n\ *Link.16.Colour: #9292ff\n\ *Link.16.Pattern: 4 4 0011 0011 1100 1100\n\ *Link.17.Colour: #b62455\n\ *Link.17.Pattern: 3 3 100 001 010\n\ *Link.18.Colour: #dbb6aa\n\ *Link.18.Pattern: 2 2 01 10\n\ "; static char *displayname = 0; static char *geometryspec = 0; static char *fontname = 0; static char *foreground = 0; static char *background = 0; static char *bordercstr = 0; static char *gridcstr = 0; static char *borderwstr = 0; static char *dragpixstr = 0; static char *igndragstr = 0; static char *maxclickstr = 0; static char *visualstr = 0; static char *magstr = 0; static int synch = 0; static int force_bw = 0; static char *puzzlestr = 0; static int trace = 0; static int argc; static char **argv; static Display *disp; static Screen *scr; static int scrwidth; static int scrheight; static int depth; static Window rootwin; static Colormap wincmap; static int defcmap; static XVisualInfo visinfo; static XColor fgcolour; static XColor bgcolour; static XColor bdcolour; static XColor gridcolour; static int first_expose; static int (*prev_error)(Display *, XErrorEvent *); static int (*prev_ioerror)(Display *); static int borderwidth; static int winw; static int winh; static Window topwin; static Window win; static XTextProperty wn_prop; static XTextProperty in_prop; static XSizeHints *normal_hints; static XWMHints *wm_hints; static XClassHint *class_hints; static GC wingc; static GC bitgc; typedef enum { CF_EMPTY = 1, CF_END, CF_LINK, } CELLFILL; typedef enum { DRAG_NOT = 1, DRAG_LIVE, DRAG_BROKEN, } DRAGSTATE; typedef struct link LINK; typedef struct cell CELL; typedef struct xy XY; typedef struct dir DIR; struct link { int ax; int ay; int bx; int by; union { struct { unsigned int flags; #define LCF_HAVE_PIXMAPS 0x00000001 XColor full; XColor half; Pixmap bare_end; Pixmap end_l; Pixmap end_r; Pixmap end_u; Pixmap end_d; Pixmap bare_link; Pixmap link_l; Pixmap link_r; Pixmap link_u; Pixmap link_d; Pixmap link_lr; Pixmap link_lu; Pixmap link_ld; Pixmap link_ru; Pixmap link_rd; Pixmap link_ud; } colour; struct { Pixmap pattern; } bw; } ; } ; struct cell { CELLFILL fill; unsigned char link; unsigned char bits; #define CB_L 0x01 #define CB_R 0x02 #define CB_U 0x04 #define CB_D 0x08 unsigned char flags; #define CF_DIRTY 0x01 #define CF_MARK 0x02 } ; struct xy { int x; int y; } ; struct dir { unsigned char bit; unsigned char obit; int dx; int dy; } ; static int bw; static XY grid; static CELL **board; static CELL **boardsave; static int boarddirty; static int nlinks; static LINK *links; static int curmag = 0; static XY pix; static int have_empty = 0; static Pixmap emptypm; static Pixmap bwmask1; static Pixmap bwmask2; static Pixmap bwmask3; static int have_circles = 0; // covers link_width too static Pixmap circle_end; static Pixmap circle_link; static int link_width; static int have_masks = 0; static Pixmap mask_l; static Pixmap mask_r; static Pixmap mask_u; static Pixmap mask_d; static Pixmap mask_tmp1; static Pixmap mask_tmp2; static DRAGSTATE drag = DRAG_NOT; static XY drag_end; static XY drag_loc = { -1, -1 }; static XY drag_last = { -1, -1 }; static int drag_last_w; static int drag_link; static XY down_loc; static Time down_time; static int down_click; static int drag_clicked; static int drag_pixels; static int ignore_drag_time; static int max_click_time; static Time motion_start = CurrentTime; static const DIR dirs[] = { { CB_L, CB_R, -1, 0 }, { CB_R, CB_L, 1, 0 }, { CB_U, CB_D, 0, -1 }, { CB_D, CB_U, 0, 1 } }; #define NDIRS 4 #define UNUSED __attribute__((__unused__)) static void trprintf(const char *fmt, ...) { va_list ap; if (! trace) return; va_start(ap,fmt); vprintf(fmt,ap); va_end(ap); } static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(rv)); return(rv); } static void saveargv(int ac, char **av) { int i; int nc; char *abuf; argc = ac; argv = (char **) malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;i 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: unrecognized argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-geometry")) { WANTARG(); geometryspec = av[skip]; continue; } if (!strcmp(*av,"-font") || !strcmp(*av,"-fn")) { WANTARG(); fontname = av[skip]; continue; } if (!strcmp(*av,"-foreground") || !strcmp(*av,"-fg")) { WANTARG(); foreground = av[skip]; continue; } if (!strcmp(*av,"-background") || !strcmp(*av,"-bg")) { WANTARG(); background = av[skip]; continue; } if (!strcmp(*av,"-bordercolour") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-gridcolour")) { WANTARG(); gridcstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-mag")) { WANTARG(); magstr = av[skip]; continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } if (!strcmp(*av,"-force-bw")) { force_bw = 1; continue; } if (!strcmp(*av,"-puzzle")) { WANTARG(); puzzlestr = av[skip]; continue; } if (!strcmp(*av,"-trace")) { trace = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void dirtycell(CELL *c) { c->flags |= CF_DIRTY; boarddirty = 1; } static void setup_puzzle(void) { const char *s; int num; int i; int link; int maxlink; auto void badpuzzle(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); auto void badpuzzle(const char *fmt, ...) { char *s; va_list ap; va_start(ap,fmt); vasprintf(&s,fmt,ap); va_end(ap); fprintf(stderr,"%s: bad puzzle spec (%s): %s\n",__progname,s,puzzlestr); exit(1); } int getnum(void) { unsigned long int v; char *ep; v = strtoul(s,&ep,10); if (ep == s) return(0); num = v; if ((num < 0) || (num != v)) badpuzzle("number `%.*s' out of range",(int)(ep-s),s); s = ep; if (*s == ',') s ++; return(1); } if (! puzzlestr) { fprintf(stderr,"%s: no puzzle! Use -puzzle.\n",__progname); exit(1); } s = puzzlestr; if (! getnum()) badpuzzle("no size"); if (num >= 100) badpuzzle("ridiculous grid size %d",num); grid.x = num; if (*s == 'x') { s ++; if (! getnum()) badpuzzle("no Y size"); if (num >= 100) badpuzzle("ridiculous grid size %d",num); grid.y = num; } else { grid.y = grid.x; } board = malloc(grid.x*sizeof(CELL *)); board[0] = malloc(grid.x*grid.y*sizeof(CELL)); for (i=1;i=0;i--) { board[0][i].fill = CF_EMPTY; dirtycell(&board[0][i]); } maxlink = grid.x * grid.y * grid.x * grid.y; link = 0; while (getnum()) { int ax; int ay; int bx; int by; if (num >= maxlink) badpuzzle("link value %d out of range",num); bx = num % grid.x; i = num / grid.x; by = i % grid.y; i /= grid.y; ax = i % grid.x; i /= grid.x; if (i >= grid.y) abort(); ay = i; if (board[ax][ay].fill != CF_EMPTY) badpuzzle("link %d uses already-filled cell (%d,%d)",num,ax,ay); board[ax][ay] = (CELL){.fill=CF_END,.link=link,.bits=0,.flags=CF_DIRTY}; if (board[bx][by].fill != CF_EMPTY) badpuzzle("link %d uses already-filled cell (%d,%d)",num,bx,by); board[bx][by] = (CELL){.fill=CF_END,.link=link,.bits=0,.flags=CF_DIRTY}; boarddirty = 1; link ++; } if (link < 1) badpuzzle("no links"); nlinks = link; } /* * We can use any visual. Thus, we always pick one of the visuals * returned by XGetVisualInfo (we check, of course, that it returned * at least one visual). * * We are happiest with * (a) TrueColor with bits-per-rgb >= 5 * (b) PseudoColor with bits-per-rgb >= 5 and colormap-size >= 25 * (c) DirectColor with bits-per-rgb >= 5 and colormap-size >= 25 * * We select a visual by, conceptually, checking these in this order: * * (1) If the default visual of the default screen is on the list and * fits one of (a)-(c), we use it. * * (2) If any visuals on the list fit one of (a)-(c), we pick one of * them, breaking ties by favouring visuals on the default screen, * then (a) over (b) or (c) and (b) over (c), then by preferring * larger bits-per-rgb, then by preferring larger colormap-size, with * any remaining ties broken arbitrarily. * * (3) If the default visual on the default screen is on the list, we * use it. * * (4) In order of preference, most to least, we (effectively) sort the * visuals by (most significant sort key first): * - Prefer visuals on the default screen. * - Prefer StaticGray over StaticColor over TrueColor over * GrayScale over PseudoColor over DirectColor. * - Prefer larger colormap-size. * - Prefer larger bits-per-rgb. * - Any remaining ties are broken arbitrarily. * * If the visual was chosen by rule 3 or 4, we force black-&-white * mode, which means using patterns rather than colours and * eliminating the reduced-intensity background fill. B&W mode can * also be forced from the command line, which suppresses cases (1) * and (2). * * B&W mode does not necessarily mean the program runs entirely in * black and white. The actualy colours can be changed (they are the * foreground and background colorus rather than black and white) and * this does not affect the colours of things other than the gameplay * board in any case - they are affected only in that the selected * visual, and its associated colourmap, are used for them. */ static void setup_visual(void) { XVisualInfo *xvi; int nvi; XVisualInfo template; int class; const char *classname; long int template_mask; int best; int i; int p; int visual_info_preference(XVisualInfo *vi) { switch (vi->class) { case TrueColor: if (vi->bits_per_rgb >= 5) return(3); break; case PseudoColor: if ( (vi->bits_per_rgb >= 5) && (vi->colormap_size >= 25) ) return(2); break; case DirectColor: if ( (vi->bits_per_rgb >= 5) && (vi->colormap_size >= 25) ) return(2); break; } return(0); } int visual_class_preference(int class) { switch (class) { case StaticGray: return(6); break; case StaticColor: return(5); break; case TrueColor: return(4); break; case GrayScale: return(3); break; case PseudoColor: return(2); break; case DirectColor: return(1); break; } abort(); } if (visualstr) #define FOO(c) if (!strcasecmp(visualstr,#c)) { class = c; classname = #c; } else { template_mask = VisualClassMask; if (! strcasecmp(visualstr,"default")) { template.visualid = XVisualIDFromVisual(XDefaultVisual(disp,XDefaultScreen(disp))); template_mask = VisualIDMask; } else FOO(StaticGray) FOO(StaticColor) FOO(TrueColor) FOO(GrayScale) FOO(PseudoColor) FOO(DirectColor) #undef FOO { unsigned long int id; char *cp; id = strtol(visualstr,&cp,0); if (*cp) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,visualstr); exit(1); } template.visualid = (VisualID) id; template_mask = VisualIDMask; } } else { template_mask = 0; } xvi = XGetVisualInfo(disp,template_mask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no %svisual found\n",visualstr?"matching ":"",__progname); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } bw = 0; do <"found"> { if (! force_bw) { /* Case (1). */ for (i=0;i; } } /* Case (2). */ best = -1; for (i=0;i visual_info_preference(&xvi[best])) || (xvi[i].bits_per_rgb > xvi[best].bits_per_rgb) || (xvi[i].colormap_size > xvi[best].colormap_size) ) best = i; } if (best >= 0) break <"found">; } /* If we haven't found a visual yet, we'll be running B&W. */ bw = 1; /* Case (3). */ for (i=0;i; } } /* Case (4). */ best = -1; for (i=0;i visual_class_preference(xvi[best].class)) || (xvi[i].colormap_size > xvi[best].colormap_size) || (xvi[i].bits_per_rgb > xvi[best].bits_per_rgb) ) best = i; } } while (0); if (best < 0) abort(); visinfo = xvi[best]; if ( (visinfo.screen != XDefaultScreen(disp)) && (template_mask != VisualIDMask) ) { fprintf(stderr,"%s: warning: using visual 0x%lx on screen %d\n",__progname,(unsigned long int)visinfo.visualid,(int)visinfo.screen); } } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = str; } static void setup_db(void) { char *str; char *home; char hostname[256]; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } else { home = getenv("HOME"); if (home) { str = malloc(strlen(home)+1+10+1); sprintf(str,"%s/.Xdefaults",home); db2 = XrmGetFileDatabase(str); if (db2) XrmMergeDatabases(db2,&db); free(str); gethostname(&hostname[0],(sizeof(hostname)/sizeof(hostname[0]))-1); hostname[(sizeof(hostname)/sizeof(hostname[0]))-1] = '\0'; str = malloc(strlen(home)+1+11+strlen(&hostname[0])+1); sprintf(str,"%s/.Xdefaults-%s",home,&hostname[0]); db2 = XrmGetFileDatabase(str); if (db2) XrmMergeDatabases(db2,&db); free(str); } } } static char *get_default_value(const char *name, const char *class) { char *type; XrmValue value; if (XrmGetResource(db,name,class,&type,&value) == False) return(0); return(value.addr); } static void setup_cmap(void) { if (visinfo.visual == XDefaultVisualOfScreen(scr)) { wincmap = XDefaultColormapOfScreen(scr); defcmap = 1; } else { wincmap = XCreateColormap(disp,rootwin,visinfo.visual,AllocNone); defcmap = 0; } } static void setup_gc(void) { Pixmap p; if (depth == XDefaultDepthOfScreen(scr)) { wingc = XDefaultGCOfScreen(scr); } else { p = XCreatePixmap(disp,rootwin,1,1,depth); wingc = XCreateGC(disp,p,0,0); XFreePixmap(disp,p); } p = XCreatePixmap(disp,rootwin,1,1,1); bitgc = XCreateGC(disp,p,0,0); XFreePixmap(disp,p); } static void setup_link_bw(int linkx, GC gc) { char *resname; char *resclass; const char *s0; const char *s; unsigned char *bits; unsigned long int pat_w; unsigned long int pat_h; char *ep; unsigned char *bitp; int i; Pixmap pm; int x; int y; void badpattern(const char *why) { fprintf(stderr,"%s: bad pattern for link #%d (%s): %s\n",__progname,linkx+1,why,s0); exit(1); } asprintf(&resname,"xflowfree.link.%d.pattern",linkx+1); asprintf(&resclass,"Game.Link.%d.Pattern",linkx+1); s = get_default_value(resname,resclass); if (! s) { fprintf(stderr,"%s: no pattern for link #%d\n",__progname,linkx+1); exit(1); } free(resname); free(resclass); s0 = s; pat_w = strtoul(s,&ep,10); if (ep == s) badpattern("no X size"); s = ep; pat_h = strtoul(s,&ep,10); if (ep == s) badpattern("no Y size"); s = ep; bits = malloc((pat_w*pat_h)+1); bitp = bits; i = pat_w * pat_h; while <"bitloop"> (1) { switch (*s++) { case '0': *bitp++ = 0; break; case '1': *bitp++ = 1; break; case ' ': continue; break; case '\0': if (i != 0) badpattern("too few bits"); break <"bitloop">; default: badpattern("bad char"); break; } if (i < 1) badpattern("too many bits"); i --; } pm = XCreatePixmap(disp,rootwin,pat_w,pat_h,1); for (i=1;i>=0;i--) { XSetForeground(disp,gc,i); bitp = bits + (pat_w * pat_h); for (y=pat_h-1;y>=0;y--) { for (x=pat_w-1;x>=0;x--) { if (*--bitp == i) XDrawPoint(disp,pm,gc,x,y); } } } links[linkx].bw.pattern = pm; } static void setup_link_colour(int linkx) { char *resname; char *resclass; char *s; // should be const, but XParseColor isn't const-poisoned links[linkx].colour.flags = 0; asprintf(&resname,"xflowfree.link.%d.colour",linkx+1); asprintf(&resclass,"Game.Link.%d.Colour",linkx+1); s = get_default_value(resname,resclass); if (! s) { fprintf(stderr,"%s: no colour for link #%d\n",__progname,linkx+1); exit(1); } free(resname); free(resclass); if (XParseColor(disp,wincmap,s,&links[linkx].colour.full) == 0) { fprintf(stderr,"%s: bad colour `%s' for link #%d\n",__progname,s,linkx+1); exit(1); } } static void setup_links(void) { int i; Pixmap pm; GC gc; int x; int y; CELL *c; LINK *l; links = malloc(nlinks*sizeof(LINK)); if (bw) { pm = XCreatePixmap(disp,rootwin,1,1,1); gc = XCreateGC(disp,pm,0,0); XFreePixmap(disp,pm); } for (i=nlinks-1;i>=0;i--) { if (bw) { setup_link_bw(i,gc); } else { setup_link_colour(i); } l = &links[i]; l->ax = -1; l->bx = -1; } if (bw) XFreeGC(disp,gc); for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if (c->fill == CF_END) { if (c->link >= nlinks) abort(); l = &links[c->link]; if (l->ax < 0) { l->ax = x; l->ay = y; } else if (l->bx < 0) { l->bx = x; l->by = y; } else { abort(); } } } for (i=nlinks-1;i>=0;i--) if (links[i].bx < 0) abort(); } static int alloc_colour(XColor *col) { if (XAllocColor(disp,wincmap,col) == 0) { if (! defcmap) return(0); wincmap = XCopyColormapAndFree(disp,wincmap); defcmap = 0; if (XAllocColor(disp,wincmap,col) == 0) return(0); } return(1); } static void setup_colour(char *str, XColor *col) { if (XParseColor(disp,wincmap,str,col) == 0) { fprintf(stderr,"%s: bad colour `%s'\n",__progname,str); exit(1); } if (! alloc_colour(col)) { fprintf(stderr,"%s: can't allocate colormap cell for colour `%s'\n",__progname,str); exit(1); } } static void setup_colours(void) { int i; LINK *l; setup_colour(foreground,&fgcolour); setup_colour(background,&bgcolour); setup_colour(bordercstr,&bdcolour); if (! bw) { setup_colour(gridcstr,&gridcolour); for (i=nlinks-1;i>=0;i--) { l = &links[i]; if (! alloc_colour(&l->colour.full)) { fprintf(stderr,"%s: can't allocate colormap cell for link #%d\n",__progname,i+1); exit(1); } l->colour.half.red = (l->colour.full.red + bgcolour.red) / 2; l->colour.half.green = (l->colour.full.green + bgcolour.green) / 2; l->colour.half.blue = (l->colour.full.blue + bgcolour.blue) / 2; l->colour.half.flags = DoRed | DoGreen | DoBlue; if (! alloc_colour(&l->colour.half)) { fprintf(stderr,"%s: can't allocate colormap cell for link #%d\n",__progname,i+1); exit(1); } } } } static void setup_numbers(void) { if (borderwstr) borderwidth = atoi(borderwstr); if (dragpixstr) drag_pixels = atoi(dragpixstr); if (igndragstr) ignore_drag_time = atoi(igndragstr); if (maxclickstr) max_click_time = atoi(maxclickstr); } static void uncache(void) { int i; LINK *l; for (i=nlinks-1;i>=0;i--) { l = &links[i]; if (bw) { } else { if (l->colour.flags & LCF_HAVE_PIXMAPS) { XFreePixmap(disp,l->colour.bare_end); XFreePixmap(disp,l->colour.end_l); XFreePixmap(disp,l->colour.end_r); XFreePixmap(disp,l->colour.end_u); XFreePixmap(disp,l->colour.end_d); XFreePixmap(disp,l->colour.link_l); XFreePixmap(disp,l->colour.link_r); XFreePixmap(disp,l->colour.link_u); XFreePixmap(disp,l->colour.link_d); XFreePixmap(disp,l->colour.link_lr); XFreePixmap(disp,l->colour.link_lu); XFreePixmap(disp,l->colour.link_ld); XFreePixmap(disp,l->colour.link_ru); XFreePixmap(disp,l->colour.link_rd); XFreePixmap(disp,l->colour.link_ud); l->colour.flags &= ~LCF_HAVE_PIXMAPS; } } } if (have_empty) { XFreePixmap(disp,emptypm); have_empty = 0; } if (have_circles) { XFreePixmap(disp,circle_end); XFreePixmap(disp,circle_link); have_circles = 0; } if (have_masks) { XFreePixmap(disp,mask_l); XFreePixmap(disp,mask_r); XFreePixmap(disp,mask_u); XFreePixmap(disp,mask_d); XFreePixmap(disp,mask_tmp1); XFreePixmap(disp,mask_tmp2); have_masks = 0; } } static void resize(void) { int magx; int magy; int mag; int x; int y; magx = (winw - 1) / grid.x; magy = (winh - 1) / grid.y; mag = (magx < magy) ? magx : magy; if (mag < 4) mag = 4; drag_last_w = (mag + 14) / 15; pix.x = (mag * grid.x) + 1; pix.y = (mag * grid.y) + 1; if (mag == curmag) { XMoveWindow(disp,win,(winw-pix.x)/2,(winh-pix.y)/2); return; } uncache(); curmag = mag; XMoveResizeWindow(disp,win,(winw-pix.x)/2,(winh-pix.y)/2,pix.x,pix.y); if (bw) { XFreePixmap(disp,bwmask1); bwmask1 = XCreatePixmap(disp,rootwin,pix.x,pix.y,1); XFreePixmap(disp,bwmask2); bwmask2 = XCreatePixmap(disp,rootwin,pix.x,pix.y,1); XFreePixmap(disp,bwmask3); bwmask3 = XCreatePixmap(disp,rootwin,pix.x,pix.y,1); } for (x=grid.x-1;x>=0;x--) for (y=grid.y-1;y>=0;y--) board[x][y].flags |= CF_DIRTY; boarddirty = 1; } static void setup_windows(void) { int magx; int magy; int mag; int x; int y; int w; int h; int bits; unsigned long int attrmask; XSetWindowAttributes attr; if (magstr) { mag = atoi(magstr); } else { magx = (scrwidth - 1 - (2*borderwidth)) / grid.x; magy = (scrheight - 1 - (2*borderwidth)) / grid.y; mag = (magx < magy) ? magx : magy; if (mag > 50) mag = 50; } if (mag < 4) mag = 4; w = (grid.x * mag) + 1 + (2 * borderwidth); h = (grid.y * mag) + 1 + (2 * borderwidth); x = (scrwidth - w) / 2; y = (scrheight - h) / 2; bits = XParseGeometry(geometryspec,&x,&y,&w,&h); if (bits & XNegative) x = scrwidth + x - w; if (bits & YNegative) y = scrheight + y - h; attrmask = 0; attr.background_pixel = fgcolour.pixel; attrmask |= CWBackPixel; attr.border_pixel = bdcolour.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = StructureNotifyMask; attrmask |= CWEventMask; attr.colormap = wincmap; attrmask |= CWColormap; w -= 2 * borderwidth; h -= 2 * borderwidth; winw = w; winh = h; topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visinfo.visual,attrmask,&attr); attrmask = 0; attr.background_pixel = bw ? fgcolour.pixel : gridcolour.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | PointerMotionHintMask | EnterWindowMask | LeaveWindowMask; attrmask |= CWEventMask; win = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,visinfo.visual,attrmask,&attr); XMapWindow(disp,win); wn_prop.value = (unsigned char *) deconst("XFlowFree"); wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen(wn_prop.value); in_prop.value = (unsigned char *) deconst("XFlowFree"); in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen(in_prop.value); normal_hints = XAllocSizeHints(); normal_hints->flags = PMinSize | PResizeInc; normal_hints->x = x; normal_hints->y = y; normal_hints->flags |= (bits & (XValue|YValue)) ? USPosition : PPosition; normal_hints->width = w; normal_hints->height = h; normal_hints->flags |= (bits & (WidthValue|HeightValue)) ? USSize : PSize; normal_hints->min_width = (grid.x * 4) + 1; normal_hints->min_height = (grid.y * 4) + 1; normal_hints->width_inc = grid.x; normal_hints->height_inc = grid.y; wm_hints = XAllocWMHints(); wm_hints->flags = InputHint; wm_hints->input = False; class_hints = XAllocClassHint(); class_hints->res_name = deconst("xflowfree"); class_hints->res_class = deconst("Game"); XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,normal_hints,wm_hints,class_hints); XMapRaised(disp,topwin); bwmask1 = XCreatePixmap(disp,rootwin,1,1,1); bwmask2 = XCreatePixmap(disp,rootwin,1,1,1); bwmask3 = XCreatePixmap(disp,rootwin,1,1,1); resize(); first_expose = 1; } static int error_handler(Display *d, XErrorEvent *e) { return((*prev_error)(d,e)); } static int ioerror_handler(Display *d) { return((*prev_ioerror)(d)); } static void setup_error(void) { prev_error = XSetErrorHandler(&error_handler); prev_ioerror = XSetIOErrorHandler(&ioerror_handler); } static void gen_circle(Pixmap pm, int r2, int size1) { int x; int y; int dx; int dy; int x0; int x1; for (y=size1-1;y>=0;y--) { dy = (y * 2) - (size1 - 1); x0 = -1; for (x=size1-1;x>=0;x--) { dx = (x * 2) - (size1 - 1); if ((dx*dx)+(dy*dy) <= r2) { if (x0 < 0) x0 = x; x1 = x; } } if (x0 >= 0) XDrawLine(disp,pm,bitgc,x0+1,y+1,x1+1,y+1); } } static void cache_circles(void) { int r2l; if (have_circles) return; circle_end = XCreatePixmap(disp,rootwin,curmag,curmag,1); circle_link = XCreatePixmap(disp,rootwin,curmag,curmag,1); XSetForeground(disp,bitgc,0); XFillRectangle(disp,circle_end,bitgc,0,0,curmag,curmag); XFillRectangle(disp,circle_link,bitgc,0,0,curmag,curmag); XSetForeground(disp,bitgc,1); gen_circle(circle_end,((curmag-1)*(curmag-1)*9)/16,curmag-1); r2l = ((curmag-1) * (curmag-1)) / 25; if (r2l < 3) r2l = 3; gen_circle(circle_link,r2l,curmag-1); if (curmag & 1) { link_width = (int)(sqrt(r2l-1)+1) & ~1; } else { link_width = (int)(sqrt(r2l)) | 1; } have_circles = 1; } static Pixmap gen_mask(unsigned int dbit) { Pixmap pm; pm = XCreatePixmap(disp,rootwin,curmag,curmag,1); XCopyArea(disp,circle_link,pm,bitgc,0,0,curmag,curmag,0,0); if (dbit & CB_L) { XSetForeground(disp,bitgc,1); XFillRectangle(disp,pm,bitgc,0,(curmag+1-link_width)/2,(curmag+1)/2,link_width); } if (dbit & CB_R) { XSetForeground(disp,bitgc,1); XFillRectangle(disp,pm,bitgc,(curmag+1)/2,(curmag+1-link_width)/2,curmag/2,link_width); } if (dbit & CB_U) { XSetForeground(disp,bitgc,1); XFillRectangle(disp,pm,bitgc,(curmag+1-link_width)/2,0,link_width,(curmag+1)/2); } if (dbit & CB_D) { XSetForeground(disp,bitgc,1); XFillRectangle(disp,pm,bitgc,(curmag+1-link_width)/2,(curmag+1)/2,link_width,curmag/2); } return(pm); } static void cache_masks(void) { if (have_masks) return; mask_l = gen_mask(CB_L); mask_r = gen_mask(CB_R); mask_u = gen_mask(CB_U); mask_d = gen_mask(CB_D); mask_tmp1 = XCreatePixmap(disp,rootwin,curmag,curmag,1); mask_tmp2 = XCreatePixmap(disp,rootwin,curmag,curmag,1); have_masks = 1; } static void cache_empty(unsigned long int gridpixel) { if (have_empty) return; emptypm = XCreatePixmap(disp,rootwin,curmag,curmag,depth); XSetFillStyle(disp,wingc,FillSolid); XSetForeground(disp,wingc,bgcolour.pixel); XFillRectangle(disp,emptypm,wingc,1,1,curmag-1,curmag-1); XSetForeground(disp,wingc,gridpixel); XFillRectangle(disp,emptypm,wingc,0,0,curmag,1); XFillRectangle(disp,emptypm,wingc,0,0,1,curmag); have_empty = 1; } static void render_bw(int x, int y, Pixmap circle, unsigned char bits) { XSetFunction(disp,bitgc,GXcopy); XSetForeground(disp,bitgc,1); XDrawPoint(disp,bwmask3,bitgc,x*curmag,y*curmag); if (bits & CB_L) { XFillRectangle(disp,bwmask1,bitgc,x*curmag,(y*curmag)+1,1,curmag-1); } else { XFillRectangle(disp,bwmask3,bitgc,x*curmag,y*curmag,1,curmag); } if (bits & CB_U) { XFillRectangle(disp,bwmask1,bitgc,(x*curmag)+1,y*curmag,curmag-1,1); } else { XFillRectangle(disp,bwmask3,bitgc,x*curmag,y*curmag,curmag,1); } XFillRectangle(disp,bwmask1,bitgc,(x*curmag)+1,(y*curmag)+1,curmag-1,curmag-1); XSetFunction(disp,bitgc,GXor); XCopyArea(disp,circle,bwmask2,bitgc,0,0,curmag,curmag,x*curmag,y*curmag); if (bits & CB_L) XCopyArea(disp,mask_l,bwmask2,bitgc,0,0,curmag,curmag,x*curmag,y*curmag); if (bits & CB_R) XCopyArea(disp,mask_r,bwmask2,bitgc,0,0,curmag,curmag,x*curmag,y*curmag); if (bits & CB_U) XCopyArea(disp,mask_u,bwmask2,bitgc,0,0,curmag,curmag,x*curmag,y*curmag); if (bits & CB_D) XCopyArea(disp,mask_d,bwmask2,bitgc,0,0,curmag,curmag,x*curmag,y*curmag); } static void redisplay_bw(void) { int x; int y; CELL *c; LINK *l; int lx; unsigned char wantlink[nlinks]; XSetFunction(disp,bitgc,GXcopy); XSetForeground(disp,bitgc,0); XFillRectangle(disp,bwmask3,bitgc,0,0,pix.x,pix.y); XSetForeground(disp,bitgc,1); bzero(&wantlink[0],nlinks); for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if (! (c->flags & CF_DIRTY)) continue; if ((x == drag_last.x) && (y == drag_last.y)) { XFillRectangle(disp,bwmask3,bitgc,(x*curmag)+1,(y*curmag)+1,drag_last_w,curmag-1); XFillRectangle(disp,bwmask3,bitgc,(x*curmag)+1,(y*curmag)+1,curmag-1,drag_last_w); XFillRectangle(disp,bwmask3,bitgc,((x+1)*curmag)-drag_last_w,(y*curmag)+1,drag_last_w,curmag-1); XFillRectangle(disp,bwmask3,bitgc,(x*curmag)+1,((y+1)*curmag)-drag_last_w,curmag-1,drag_last_w); } switch (c->fill) { default: abort(); break; case CF_EMPTY: cache_empty(fgcolour.pixel); XCopyArea(disp,emptypm,win,wingc,0,0,curmag,curmag,x*curmag,y*curmag); c->flags &= ~CF_DIRTY; break; case CF_END: case CF_LINK: if (c->link >= nlinks) abort(); wantlink[c->link] = 1; break; } } for (lx=nlinks-1;lx>=0;lx--) { if (! wantlink[lx]) continue; l = &links[lx]; cache_circles(); cache_masks(); XSetFunction(disp,bitgc,GXcopy); XSetForeground(disp,bitgc,0); XFillRectangle(disp,bwmask1,bitgc,0,0,pix.x,pix.y); XFillRectangle(disp,bwmask2,bitgc,0,0,pix.x,pix.y); for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if (! (c->flags & CF_DIRTY)) continue; switch (c->fill) { default: abort(); break; case CF_EMPTY: break; case CF_END: if (c->link != lx) break; render_bw(x,y,circle_end,c->bits); c->flags &= ~CF_DIRTY; break; case CF_LINK: if (c->link != lx) break; render_bw(x,y,circle_link,c->bits); c->flags &= ~CF_DIRTY; break; } } XSetFillStyle(disp,wingc,FillSolid); XSetClipMask(disp,wingc,bwmask1); XSetForeground(disp,wingc,bgcolour.pixel); XFillRectangle(disp,win,wingc,0,0,pix.x,pix.y); XSetClipMask(disp,wingc,bwmask2); XSetFillStyle(disp,wingc,FillOpaqueStippled); XSetStipple(disp,wingc,l->bw.pattern); XSetFunction(disp,wingc,GXcopy); XSetForeground(disp,wingc,fgcolour.pixel); XSetBackground(disp,wingc,bgcolour.pixel); XFillRectangle(disp,win,wingc,0,0,pix.x,pix.y); } XSetFillStyle(disp,wingc,FillSolid); XSetForeground(disp,wingc,fgcolour.pixel); XSetClipMask(disp,wingc,bwmask3); XFillRectangle(disp,win,wingc,0,0,pix.x,pix.y); XSetClipMask(disp,wingc,None); } static Pixmap gen_colour(Pixmap circle, unsigned int bits, unsigned long int fg, unsigned long int bg) { Pixmap pm; XSetForeground(disp,bitgc,0); XFillRectangle(disp,mask_tmp1,bitgc,0,0,curmag,curmag); XFillRectangle(disp,mask_tmp2,bitgc,0,0,curmag,curmag); XSetForeground(disp,bitgc,1); XFillRectangle(disp,mask_tmp1,bitgc,1,1,curmag-1,curmag-1); if (bits & CB_L) XDrawLine(disp,mask_tmp1,bitgc,0,1,0,curmag-1); if (bits & CB_U) XDrawLine(disp,mask_tmp1,bitgc,1,0,curmag-1,0); XCopyArea(disp,circle,mask_tmp2,bitgc,0,0,curmag,curmag,0,0); if (bits & CB_L) { XSetFunction(disp,bitgc,GXor); XCopyArea(disp,mask_l,mask_tmp2,bitgc,0,0,curmag,curmag,0,0); } if (bits & CB_R) { XSetFunction(disp,bitgc,GXor); XCopyArea(disp,mask_r,mask_tmp2,bitgc,0,0,curmag,curmag,0,0); } if (bits & CB_U) { XSetFunction(disp,bitgc,GXor); XCopyArea(disp,mask_u,mask_tmp2,bitgc,0,0,curmag,curmag,0,0); } if (bits & CB_D) { XSetFunction(disp,bitgc,GXor); XCopyArea(disp,mask_d,mask_tmp2,bitgc,0,0,curmag,curmag,0,0); } XSetFunction(disp,bitgc,GXcopy); pm = XCreatePixmap(disp,rootwin,curmag,curmag,depth); XCopyArea(disp,emptypm,pm,wingc,0,0,curmag,curmag,0,0); XSetStipple(disp,wingc,mask_tmp1); XSetFillStyle(disp,wingc,FillStippled); XSetForeground(disp,wingc,bg); XFillRectangle(disp,pm,wingc,0,0,curmag,curmag); XSetStipple(disp,wingc,mask_tmp2); XSetForeground(disp,wingc,fg); XFillRectangle(disp,pm,wingc,0,0,curmag,curmag); return(pm); } static void cache_colour(int lx) { LINK *l; if ((lx < 0) || (lx >= nlinks)) abort(); cache_empty(gridcolour.pixel); cache_circles(); cache_masks(); l = &links[lx]; l->colour.bare_end = gen_colour(circle_end,0,l->colour.full.pixel,bgcolour.pixel); l->colour.end_l = gen_colour(circle_end,CB_L,l->colour.full.pixel,l->colour.half.pixel); l->colour.end_r = gen_colour(circle_end,CB_R,l->colour.full.pixel,l->colour.half.pixel); l->colour.end_u = gen_colour(circle_end,CB_U,l->colour.full.pixel,l->colour.half.pixel); l->colour.end_d = gen_colour(circle_end,CB_D,l->colour.full.pixel,l->colour.half.pixel); l->colour.bare_link = gen_colour(circle_link,0,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_l = gen_colour(circle_link,CB_L,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_r = gen_colour(circle_link,CB_R,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_u = gen_colour(circle_link,CB_U,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_d = gen_colour(circle_link,CB_D,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_lr = gen_colour(circle_link,CB_L|CB_R,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_lu = gen_colour(circle_link,CB_L|CB_U,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_ld = gen_colour(circle_link,CB_L|CB_D,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_ru = gen_colour(circle_link,CB_R|CB_U,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_rd = gen_colour(circle_link,CB_R|CB_D,l->colour.full.pixel,l->colour.half.pixel); l->colour.link_ud = gen_colour(circle_link,CB_U|CB_D,l->colour.full.pixel,l->colour.half.pixel); l->colour.flags |= LCF_HAVE_PIXMAPS; } static void redisplay_colour(void) { int x; int y; CELL *c; Pixmap pm; LINK *l; for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if (! (c->flags & CF_DIRTY)) continue; switch (c->fill) { default: abort(); break; case CF_EMPTY: cache_empty(gridcolour.pixel); pm = emptypm; break; case CF_END: if (c->link >= nlinks) abort(); l = &links[c->link]; if (! (l->colour.flags & LCF_HAVE_PIXMAPS)) cache_colour(c->link); switch (c->bits) { case 0: pm = l->colour.bare_end; break; case CB_L: pm = l->colour.end_l; break; case CB_R: pm = l->colour.end_r; break; case CB_U: pm = l->colour.end_u; break; case CB_D: pm = l->colour.end_d; break; default: abort(); break; } break; case CF_LINK: if (c->link >= nlinks) abort(); l = &links[c->link]; if (! (l->colour.flags & LCF_HAVE_PIXMAPS)) cache_colour(c->link); switch (c->bits) { case 0: pm = l->colour.bare_link; break; case CB_L: pm = l->colour.link_l; break; case CB_R: pm = l->colour.link_r; break; case CB_U: pm = l->colour.link_u; break; case CB_D: pm = l->colour.link_d; break; case CB_L|CB_R: pm = l->colour.link_lr; break; case CB_L|CB_U: pm = l->colour.link_lu; break; case CB_L|CB_D: pm = l->colour.link_ld; break; case CB_R|CB_U: pm = l->colour.link_ru; break; case CB_R|CB_D: pm = l->colour.link_rd; break; case CB_U|CB_D: pm = l->colour.link_ud; break; default: abort(); break; } break; } XCopyArea(disp,pm,win,wingc,0,0,curmag,curmag,x*curmag,y*curmag); if ((x == drag_last.x) && (y == drag_last.y)) { XSetForeground(disp,wingc,fgcolour.pixel); XSetFillStyle(disp,wingc,FillSolid); XFillRectangle(disp,win,wingc,(x*curmag)+1,(y*curmag)+1,drag_last_w,curmag-1); XFillRectangle(disp,win,wingc,(x*curmag)+1,(y*curmag)+1,curmag-1,drag_last_w); XFillRectangle(disp,win,wingc,((x+1)*curmag)-drag_last_w,(y*curmag)+1,drag_last_w,curmag-1); XFillRectangle(disp,win,wingc,(x*curmag)+1,((y+1)*curmag)-drag_last_w,curmag-1,drag_last_w); } c->flags &= ~CF_DIRTY; } } static void redisplay(void) { if (! boarddirty) return; if (bw) redisplay_bw(); else redisplay_colour(); boarddirty = 0; } static void dirty_cells(int x, int y, int w, int h) { int x0; int x1; int y0; int y1; x0 = x / curmag; y0 = y / curmag; x1 = (x + w - 1) / curmag; y1 = (y + h - 1) / curmag; if (x0 < 0) x0 = 0; if (y0 < 0) y0 = 0; if (x1 >= grid.x) x1 = grid.x - 1; if (y1 >= grid.y) y1 = grid.y - 1; if ((x0 > x1) || (y0 > y1)) return; for (y=y0;y<=y1;y++) for (x=x0;x<=x1;x++) board[x][y].flags |= CF_DIRTY; boarddirty = 1; } static void save_board(void) { int x; int y; for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) boardsave[x][y] = board[x][y]; } static int walk_links(int x, int y, unsigned char dir, int (*fn)(XY, unsigned char, unsigned char)) { unsigned char pdir; CELL *c; int rv; int fnrv; pdir = 0; rv = 0; c = &board[x][y]; if (! (c->bits & dir)) { return((*fn)((XY){.x=x,.y=y},0,0)); } while (1) { fnrv = (*fn)((XY){.x=x,.y=y},pdir,dir); if (fnrv < 0) return(fnrv); rv += fnrv; switch (dir) { default: abort(); break; case CB_L: x --; pdir = CB_R; if (x < 0) abort(); break; case CB_R: x ++; pdir = CB_L; if (x >= grid.x) abort(); break; case CB_U: y --; pdir = CB_D; if (y < 0) abort(); break; case CB_D: y ++; pdir = CB_U; if (y >= grid.y) abort(); break; } c = &board[x][y]; if (! (c->bits & pdir)) abort(); dir = c->bits & ~pdir; if (dir == 0) { fnrv = (*fn)((XY){.x=x,.y=y},pdir,0); return((fnrv<0)?fnrv:(rv+fnrv)); } } } static int walk_links_2(int x, int y, unsigned char dirs, int (*fn1)(XY, unsigned char, unsigned char), int (*fn2)(XY, unsigned char, unsigned char)) { int rv1; int rv2; switch (dirs) { case CB_L: case CB_L | CB_R: case CB_L | CB_U: case CB_L | CB_D: rv1 = walk_links(x,y,CB_L,fn1); break; case CB_R: case CB_R | CB_U: case CB_R | CB_D: rv1 = walk_links(x,y,CB_R,fn1); break; case CB_U: case CB_U | CB_D: rv1 = walk_links(x,y,CB_U,fn1); break; case CB_D: rv1 = walk_links(x,y,CB_D,fn1); break; } if (rv1 < 0) return(rv1); switch (dirs) { case CB_L | CB_R: rv2 = walk_links(x,y,CB_R,fn2); break; case CB_L | CB_U: case CB_R | CB_U: rv2 = walk_links(x,y,CB_U,fn2); break; case CB_L | CB_D: case CB_R | CB_D: case CB_U | CB_D: rv2 = walk_links(x,y,CB_D,fn2); break; default: rv2 = 0; break; } if (rv2 < 0) return(rv2); else return(rv1+rv2); } static void clear_backlinks(int x, int y) { CELL *c; CELL *c2; int i; int x2; int y2; if ((x < 0) || (y < 0) || (x >= grid.x) || (y >= grid.y)) abort(); c = &board[x][y]; for (i=NDIRS-1;i>=0;i--) { if (c->bits & dirs[i].bit) { x2 = x + dirs[i].dx; y2 = y + dirs[i].dy; if ((x2 < 0) || (y2 < 0) || (x2 >= grid.x) || (y2 >= grid.y)) abort(); c2 = &board[x2][y2]; c2->bits &= ~dirs[i].obit; dirtycell(c2); } } } static void restore_backlinks(int x, int y) { CELL *c; CELL *c2; int i; int x2; int y2; if ((x < 0) || (y < 0) || (x >= grid.x) || (y >= grid.y)) abort(); c = &board[x][y]; switch (c->fill) { default: abort(); break; case CF_EMPTY: return; break; case CF_END: case CF_LINK: break; } for (i=NDIRS-1;i>=0;i--) { if (c->bits & dirs[i].bit) { x2 = x + dirs[i].dx; y2 = y + dirs[i].dy; if ((x2 < 0) || (y2 < 0) || (x2 >= grid.x) || (y2 >= grid.y)) abort(); c2 = &board[x2][y2]; c2->bits |= dirs[i].obit; dirtycell(c2); } } } static void unplace_link_all(int lx) { int x; int y; CELL *c; CELL *cs; if ((lx < 0) || (lx >= nlinks)) abort(); for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; switch (c->fill) { default: abort(); break; case CF_EMPTY: break; case CF_END: if (c->link == lx) { c->bits = 0; dirtycell(c); } break; case CF_LINK: if (c->link == lx) { cs = &boardsave[x][y]; if ((cs->fill == CF_EMPTY) || (cs->link != lx)) { c->fill = cs->fill; c->link = cs->link; c->bits = cs->bits; restore_backlinks(x,y); } else { c->fill = CF_EMPTY; } dirtycell(c); } break; } } } static void report_drag_state(void) { switch (drag) { default: abort(); break; case DRAG_NOT: trprintf("DRAG_NOT\n"); return; break; case DRAG_LIVE: trprintf("DRAG_LIVE "); break; case DRAG_BROKEN: trprintf("DRAG_BROKEN "); break; } trprintf("end(%d,%d) loc (%d,%d) link %d down_loc (%d,%d)\n",drag_end.x,drag_end.y,drag_loc.x,drag_loc.y,drag_link,down_loc.x,down_loc.y); } static int clear_link(XY at, unsigned char idir, unsigned char odir) { CELL *c; c = &board[at.x][at.y]; if (idir) { switch (c->fill) { default: abort(); break; case CF_LINK: c->fill = CF_EMPTY; break; case CF_END: if (odir) abort(); c->bits &= ~idir; break; } } else { if (! odir) abort(); c->bits &= ~odir; } dirtycell(c); return(1); } static int clear_or_revert_link(XY at, unsigned char idir, unsigned char odir) { CELL *c; CELL *cs; c = &board[at.x][at.y]; cs = &boardsave[at.x][at.y]; if (idir) { switch (c->fill) { default: abort(); break; case CF_LINK: if ((cs->fill == CF_LINK) && (cs->link == drag_link)) { c->fill = CF_EMPTY; } else { c->fill = cs->fill; c->link = cs->link; c->bits = cs->bits; } break; case CF_END: if (odir) abort(); c->bits &= ~idir; break; } } else { if (! odir) abort(); c->bits &= ~odir; } dirtycell(c); return(1); } static void start_drag(int clickx, int clicky) { int lx; CELL *c; unsigned char d1; int l1; XY e1; unsigned char d2; int l2; XY e2; int set_drag_end(XY at, unsigned char idir UNUSED, unsigned char odir) { if (! odir) drag_end = at; return(0); } int end_1(XY at, unsigned char idir, unsigned char odir) { l1 ++; trprintf("end_1 (%d,%d) i %d o %d\n",at.x,at.y,idir,odir); if (! idir) d1 = odir; if (! odir) e1 = at; return(0); } int end_2(XY at, unsigned char idir, unsigned char odir) { l2 ++; trprintf("end_2 (%d,%d) i %d o %d\n",at.x,at.y,idir,odir); if (! idir) d2 = odir; if (! odir) e2 = at; return(0); } trprintf("start_drag %d %d\n",clickx,clicky); c = &board[clickx][clicky]; switch <"started"> (c->fill) { default: abort(); break; case CF_EMPTY: trprintf("start_drag EMPTY\n"); return; break; case CF_END: drag = DRAG_LIVE; save_board(); lx = c->link; drag_end.x = clickx; drag_end.y = clicky; unplace_link_all(lx); break; case CF_LINK: drag = DRAG_LIVE; save_board(); c = &board[clickx][clicky]; if (c->fill != CF_LINK) abort(); l1 = 0; l2 = 0; switch (c->bits) { default: abort(); break; case CB_L: case CB_R: case CB_U: case CB_D: walk_links(clickx,clicky,c->bits,&set_drag_end); break <"started">; case CB_L | CB_R: case CB_L | CB_U: case CB_L | CB_D: case CB_R | CB_U: case CB_R | CB_D: case CB_U | CB_D: d1 = 0; d2 = 0; trprintf("start_drag CF_LINK %d\n",c->bits); walk_links_2(clickx,clicky,c->bits,&end_1,&end_2); trprintf("start_drag CF_LINK d1 %d d2 %d\n",d1,d2); if (!d1 || !d2) abort(); break; } if ( (board[e1.x][e1.y].fill == CF_END) && ( (board[e2.x][e2.y].fill == CF_LINK) || ( (board[e2.x][e2.y].fill == CF_END) && (l2 < l1) ) ) ) { walk_links(clickx,clicky,d2,&clear_link); drag_end = e1; } else if ( (board[e2.x][e2.y].fill == CF_END) && ( (board[e1.x][e1.y].fill == CF_LINK) || (board[e1.x][e1.y].fill == CF_END) ) ) { walk_links(clickx,clicky,d1,&clear_link); drag_end = e2; } else { abort(); } break; } drag_loc.x = clickx; drag_loc.y = clicky; drag_link = c->link; drag_clicked = 0; drag_last.x = clickx; drag_last.y = clicky; dirtycell(&board[clickx][clicky]); report_drag_state(); } static void clearmarks(void) { int x; int y; for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) board[x][y].flags &= ~CF_MARK; } static int mark_cell(XY loc, unsigned char idir UNUSED, unsigned char odir UNUSED) { board[loc.x][loc.y].flags |= CF_MARK; return(1); } static void end_drag(void) { int x; int y; CELL *c; trprintf("end_drag\n"); clearmarks(); for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if ((c->fill == CF_END) && c->bits) { walk_links(x,y,c->bits,&mark_cell); } } for (y=grid.y-1;y>=0;y--) for (x=grid.x-1;x>=0;x--) { c = &board[x][y]; if ((c->fill == CF_LINK) && !(c->flags & CF_MARK)) { if (c->link == drag_link) abort(); trprintf("clearing isolated LINK (%d,%d)\n",x,y); c->fill = CF_EMPTY; dirtycell(c); } } if (drag_last.x >= 0) { dirtycell(&board[drag_last.x][drag_last.y]); drag_last.x = -1; } drag = DRAG_NOT; } static void process_motion(int x, int y) { int cx; int cy; CELL *lc; CELL *c; unsigned char d; unsigned char db; int ix; int iy; trprintf("motion(%d,%d)",x,y); if (drag == DRAG_NOT) { trprintf(" NOT\n"); return; } if ( (abs(x-down_loc.x) > drag_pixels) || (abs(y-down_loc.y) > drag_pixels) ) { if (down_click) trprintf(" (declick)"); down_click = 0; } cx = x / curmag; cy = y / curmag; if (cx < 0) cx = 0; else if (cx >= grid.x) cx = grid.x - 1; if (cy < 0) cy = 0; else if (cy >= grid.y) cy = grid.y - 1; trprintf("->(%d,%d)",cx,cy); if ((cx != drag_last.x) || (cy != drag_last.y)) { if (drag_last.x >= 0) dirtycell(&board[drag_last.x][drag_last.y]); dirtycell(&board[cx][cy]); drag_last.x = cx; drag_last.y = cy; } c = &board[cx][cy]; if ((cx == drag_loc.x) && (cy == drag_loc.y)) { if ( (drag == DRAG_BROKEN) && (c->fill == CF_LINK) && !(c->bits & (c->bits-1)) ) { trprintf(" BROKEN->LIVE\n"); drag = DRAG_LIVE; } report_drag_state(); return; } if (drag == DRAG_BROKEN) { trprintf(" BROKEN\n"); return; } if (drag != DRAG_LIVE) abort(); if ((cx == drag_loc.x) && (cy == drag_loc.y-1)) { d = CB_U; db = CB_D; trprintf(" U"); } else if ((cx == drag_loc.x) && (cy == drag_loc.y+1)) { d = CB_D; db = CB_U; trprintf(" D"); } else if ((cy == drag_loc.y) && (cx == drag_loc.x-1)) { d = CB_L; db = CB_R; trprintf(" L"); } else if ((cy == drag_loc.y) && (cx == drag_loc.x+1)) { d = CB_R; db = CB_L; trprintf(" R"); } else { drag = DRAG_BROKEN; trprintf(" (%d,%d)->(%d,%d)? ->BROKEN\n",drag_loc.x,drag_loc.y,cx,cy); return; } lc = &board[drag_loc.x][drag_loc.y]; switch (c->fill) { default: abort(); break; case CF_EMPTY: c->fill = CF_LINK; c->link = drag_link; c->bits = db; dirtycell(c); lc->bits |= d; dirtycell(lc); trprintf(" EMPTY->LINK\n"); break; case CF_END: if (c->link != drag_link) { drag = DRAG_BROKEN; trprintf(" other-link END -> BROKEN\n"); return; } else if ((cx == drag_end.x) && (cy == drag_end.y)) { unplace_link_all(c->link); trprintf(" initial END\n"); } else { if (c->bits) walk_links(cx,cy,c->bits,&clear_link); lc->bits |= d; dirtycell(lc); c->bits |= db; dirtycell(c); trprintf(" completed END\n"); end_drag(); } break; case CF_LINK: if (c->link == drag_link) { unsigned char keepdir; int each(XY loc, unsigned char idir UNUSED, unsigned char odir) { CELL *c; c = &board[loc.x][loc.y]; if ((loc.x == cx) && (loc.y == cy)) { keepdir = odir; return(-1); } c->flags |= CF_MARK; if (c->fill == CF_END) { trprintf("END (%d,%d)\n",cx,cy); return(-2); } return(0); } trprintf(" same LINK (%d,%d)\n",cx,cy); clearmarks(); switch (walk_links(drag_loc.x,drag_loc.y,lc->bits,&each)) { default: abort(); break; case -1: for (iy=grid.y-1;iy>=0;iy--) for (ix=grid.x-1;ix>=0;ix--) { CELL *ic; CELL *ics; ic = &board[ix][iy]; if (ic->flags & CF_MARK) { ics = &boardsave[ix][iy]; if (ic->fill != CF_LINK) abort(); if ((ics->fill != CF_LINK) || (ics->link == drag_link)) { ic->fill = CF_EMPTY; } else { ic->fill = ics->fill; ic->link = ics->link; ic->bits = ics->bits; restore_backlinks(ix,iy); } dirtycell(ic); } } c->bits = keepdir; dirtycell(c); break; case -2: { int ends; unsigned char dn[2]; int find_end_1(XY loc, unsigned char idir, unsigned char odir) { if (! idir) dn[0] = odir; if (board[loc.x][loc.y].fill == CF_END) ends |= 1; return(0); } int find_end_2(XY loc, unsigned char idir, unsigned char odir) { if (! idir) dn[1] = odir; if (board[loc.x][loc.y].fill == CF_END) ends |= 2; return(0); } trprintf("thrown (%d,%d)\n",cx,cy); ends = 0; dn[0] = 0; dn[1] = 0; trprintf("walking (%d,%d)\n",cx,cy); walk_links_2(cx,cy,c->bits,&find_end_1,&find_end_2); if (!dn[0] || (!dn[1] && (ends & 2))) abort(); switch (ends) { default: abort(); break; case 1: if (dn[1]) walk_links(cx,cy,dn[1],&clear_or_revert_link); c->bits = dn[0] | db; break; case 2: walk_links(cx,cy,dn[0],&clear_or_revert_link); c->bits = dn[1] | db; break; } dirtycell(c); lc->bits |= d; dirtycell(lc); end_drag(); } } } else { trprintf(" other LINK\n"); clear_backlinks(cx,cy); c->link = drag_link; c->bits = db; dirtycell(c); lc->bits |= d; dirtycell(lc); } break; } if (drag_loc.x >= 0) dirtycell(&board[drag_loc.x][drag_loc.y]); dirtycell(&board[cx][cy]); drag_loc.x = cx; drag_loc.y = cy; } static void process_motion_upto(Time stamp) { XTimeCoord *hist; int n; int i; if (drag == DRAG_NOT) { if (stamp != CurrentTime) motion_start = stamp; } else { if (motion_start != CurrentTime) { hist = XGetMotionEvents(disp,win,motion_start,stamp,&n); for (i=0;i 0) motion_start = hist[n-1].time; } else { motion_start = stamp; } XFree(hist); } } } static void motion(int x, int y, Time stamp, int hint) { process_motion_upto(stamp); process_motion(x,y); if (hint) process_motion_upto(CurrentTime); redisplay(); } static void button_event(int x, int y, Time stamp, int downp) { int cx; int cy; Time dt; process_motion_upto(stamp); process_motion(x,y); switch (drag) { default: abort(); break; case DRAG_NOT: if (! downp) return; cx = x / curmag; cy = y / curmag; if (cx < 0) cx = 0; else if (cx >= grid.x) cx = grid.x - 1; if (cy < 0) cy = 0; else if (cy >= grid.y) cy = grid.y - 1; down_loc.x = x; down_loc.y = y; down_time = stamp; down_click = 1; start_drag(cx,cy); break; case DRAG_LIVE: if (stamp < down_time) { dt = 0xffffffff - (down_time - stamp - 1); } else { dt = stamp - down_time; } if ( (dt < ignore_drag_time) || ( down_click && (dt < max_click_time) ) ) { drag_clicked = 1; return; } process_motion(x,y); end_drag(); break; case DRAG_BROKEN: end_drag(); break; } redisplay(); } static void handle_event(XEvent *e) { switch (e->type) { default: break; case ConfigureNotify: /* XConfigureEvent - xconfigure */ winw = e->xconfigure.width; winh = e->xconfigure.height; resize(); break; case Expose: /* XExposeEvent - xexpose */ if (e->xexpose.window != win) break; dirty_cells(e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height); if (e->xexpose.count == 0) redisplay(); break; case ButtonPress: /* XButtonPressedEvent - XButtonEvent - xbutton */ if (e->xbutton.window == win) { button_event(e->xbutton.x,e->xbutton.y,e->xbutton.time,1); } break; case ButtonRelease: /* XButtonReleasedEvent - XButtonEvent - xbutton */ if (e->xbutton.window == win) { button_event(e->xbutton.x,e->xbutton.y,e->xbutton.time,0); } break; case MotionNotify: /* XPointerMovedEvent - XMotionEvent - xmotion */ if (e->xmotion.window == win) { motion(e->xmotion.x,e->xmotion.y,e->xmotion.time,e->xmotion.is_hint); } break; case EnterNotify: /* XEnterWindowEvent - XCrossingEvent - xcrossing */ if (e->xcrossing.window == win) { motion_start = e->xcrossing.time; } break; case LeaveNotify: /* XLeaveWindowEvent - XCrossingEvent - xcrossing */ if (e->xcrossing.window == win) { process_motion_upto(e->xcrossing.time); } break; } } static void run(void) { XEvent e; while (1) { XNextEvent(disp,&e); handle_event(&e); fflush(stdout); } } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac,av); setup_puzzle(); disp = XOpenDisplay(displayname); if (disp == 0) { fprintf(stderr,"%s: can't open display\n",__progname); exit(1); } if (synch) XSynchronize(disp,True); setup_visual(); scr = XScreenOfDisplay(disp,visinfo.screen); scrwidth = XWidthOfScreen(scr); scrheight = XHeightOfScreen(scr); depth = visinfo.depth; rootwin = XRootWindowOfScreen(scr); setup_db(); maybeset(&geometryspec,get_default_value("xflowfree.geometry","Game.Geometry")); maybeset(&fontname,get_default_value("xflowfree.font","Game.Font")); maybeset(&foreground,get_default_value("xflowfree.foreground","Game.Foreground")); maybeset(&background,get_default_value("xflowfree.background","Game.Background")); maybeset(&bordercstr,get_default_value("xflowfree.borderColour","Game.BorderColour")); maybeset(&borderwstr,get_default_value("xflowfree.borderWidth","Game.BorderWidth")); maybeset(&dragpixstr,get_default_value("dragPixels","DragPixels")); maybeset(&igndragstr,get_default_value("ignoreDragTime","IgnoreDragTime")); maybeset(&maxclickstr,get_default_value("maxClickTime","MaxClickTime")); if (! bw) maybeset(&gridcstr,get_default_value("xflowfree.gridColour","Game.GridColour")); setup_cmap(); setup_gc(); setup_links(); setup_colours(); setup_numbers(); setup_windows(); setup_error(); run(); return(0); }