#include #include #include #include #include #include #include #include #include #include #include #include #include #include #define UNUSED_ARG(x) x __attribute__((__unused__)) #include "cards.h" #if defined(XlibSpecificationRelease) && (XlibSpecificationRelease >= 5) #define HAVE_R5 #endif #define FULL_WIDTH_GRAY /* kludge around bug in some R4 servers */ #define CARD_MARGIN_X 20 /* pixels used as spacing around cards, horizontally */ #define CARD_MARGIN_Y 17 /* pixels used as spacing around cards, vertically */ #define CARD_DOWN_OVL_Y 5 /* pixels to show of face-down card underneath another, vertically */ #define CARD_UP_OVL_Y 25 /* pixels to show of face-up card underneath another, vertically */ #define CARD_UP_OVL_X 15 /* pixels to show of face-up card underneath another, horizontally */ #define BOUNCE_TICK 50000 /* usec per tick when bouncing */ #define GRAVITY 2 /* arbitrary units */ #define LOSS_MUL .9 /* factor to multiply speed by when bouncing */ #define ANIM_TICK 50000 /* usec per tick when moving things automatically */ #define ANIM_SPEED 25 /* pixels per tick */ #define MAX(a,b) (((a)>(b)) ? (a) : (b)) #define MIN(a,b) (((a)<(b)) ? (a) : (b)) #define SECUSEC 1000000 #define FILEVERS 1 #ifdef __GNUC__ #define flowsink __attribute__ ((noreturn)) #endif #if !defined(flowsink) #define flowsink #endif extern const char *__progname; static XrmDatabase db; static const char *defaults = "\ *Foreground: white\n\ *Background: black\n\ *BorderColor: white\n\ *BorderWidth: 1\n\ *BorderMargin: 0\n\ *Gamma: 1.0\n\ *Font: fixed\n\ *Name: xklondike\n\ *IconName: xklondike\n\ *DeckPath: /Users/mouse/games/cards/.deck\n\ *BackNumber: 0\n\ *DragPixels: 10\n\ *IgnoreDragTime: 125\n\ *MaxClickTime: 300\n\ *DoubleClickIdle: 500\n\ *RememberScore: true\n\ *BounceGame: false\n\ *ScoreMemoryFile: .klondike.score\n\ "; static char *xrmstring; static char *displayname; static char *geometryspec; static char *foreground; static char *background; static char *bordercstr; static char *borderwstr; static char *bordermstr; static char *margincstr; static char *fontname; static char *name; static char *iconname; static char *deckpath; static char *backnostr; static char *dragpixstr; static char *igndragstr; static char *maxclickstr; static char *dblclickstr; static char *gammastr; static char *scorefilename; static int runsync; static int quickfinish; static int justscore; static int rememberscore = -1; static int bouncegame = -1; static int argc; static char **argv; static Display *disp; static Screen *scr; static int width; static int height; static int depth; static Window rootwin; static Colormap defcmap; static Visual *visual; static int (*oldioerr)(Display *); static int (*olderr)(Display *, XErrorEvent *); static XColor fgcolor; static XColor bgcolor; static XColor bdcolor; static XColor mgcolor; typedef struct button BUTTON; struct button { const char *label; void (*clickfn)(BUTTON *); BUTTON *flink; BUTTON *blink; char *private; Window win; int textw; int winw; XCharStruct textsize; int pressed; int disabled; } ; typedef struct scorestate SCORESTATE; struct scorestate { int score; int total; int deals; } ; static GC drawgc; static GC xorgc; static Window topwin; static int desw; static int desh; static Window iwin; static Window cwin; static Window linewin; static Window gamewin; static Window menuwin; static Window donewin; static Window scoreparent; static Window scorewin; static Window flipwin; static Window pilesize_deck; static Window pilesize_up; static Window pileszp_deck; static Window pileszp_up; static int pileszp_mapped; static int piles_mapped_deck; static int piles_mapped_up; static SCORESTATE scores[2]; static SCORESTATE *score; static SCORESTATE startscore; static int gametouched; static int scoredirty; static int scorex; static int scorey; static int scorew; static int scoreh; static int gotbuttonpress; static Window buttonpress_win; static int buttonpress_x; static int buttonpress_y; static int gamedone; static Colormap wincmap; static int margin; static int borderwidth; static XFontStruct *font; static int deffont; static int baselineskip; static int gwheight; static int drag_pixels; static int ignore_drag_time; static int max_click_time; static int dbl_click_time; static int scorefd; static double gammafactor; static void newgame_click(BUTTON *); static void undo_click(BUTTON *); static void finish_click(BUTTON *); static void quit_click(BUTTON *); static BUTTON newgamebutton = { "New game", &newgame_click }; static BUTTON undobutton = { "Undo", &undo_click }; static BUTTON finishbutton = { "Finish", &finish_click }; static BUTTON quitbutton = { "Quit", &quit_click }; static BUTTON *buttons[] = { &newgamebutton, &undobutton, &finishbutton, &quitbutton }; #define NBUTTONS (sizeof(buttons)/sizeof(buttons[0])) static int button_w; static int backno; static int decks = -1; #define MAXDECKS 2 static int decksize; #define MAXDECKSIZE (MAXDECKS*52) static int suitpiles; #define MAXPILESPERSUIT MAXDECKS #define MAXSUITPILES (4*MAXPILESPERSUIT) static int workpiles; #define MAXWORKPILES 9 static int deal_cost; static int card_win; static int deck_cards[MAXDECKSIZE]; static Window deck_wins[MAXDECKSIZE]; static XColor *deck_colors; static Pixmap deck_face[52]; static Pixmap *deck_back; static int c_x[MAXDECKSIZE]; static int c_y[MAXDECKSIZE]; static int c_up[MAXDECKSIZE]; static int color[MAXDECKSIZE]; static int n_deck; static int deck[MAXDECKSIZE]; static int n_up; static int up[MAXDECKSIZE]; static int upx[MAXDECKSIZE]; static int suitcards[MAXSUITPILES][13]; static int suitheight[MAXSUITPILES]; static int suitsuit[MAXSUITPILES]; static int ndown[MAXWORKPILES]; static int downpiles[MAXWORKPILES][MAXWORKPILES]; static int nup[MAXWORKPILES]; static int uppiles[MAXWORKPILES][13]; struct whereis { unsigned char where; #define WHERE_DECK 1 #define WHERE_UP 2 #define WHERE_SUIT 3 #define WHERE_DPILE 4 #define WHERE_UPILE 5 unsigned char pile; unsigned char cwp; } ; typedef struct whereis WHEREIS; static WHEREIS whereis[MAXDECKSIZE]; static int pile_x[MAXWORKPILES]; static int pile_y[MAXWORKPILES]; static int suit_x[MAXSUITPILES]; static int suit_y[MAXSUITPILES]; static int ndeckflips; static int undo_score; static int undo_type; #define U_NIL 0 #define U_FLIPDECK 1 #define U_FLIPPILE 2 #define U_MOVE 3 static int undo_n; static int undo_cards[13]; static WHEREIS undo_where[13]; static int *undo_vecn; static Window carrywin; static Pixmap carrybg; static int carrybgn; static int ncarry; static int carrycards[13]; static int *carryvec; static int *carryvecn; static void (*carryafter)(void); static int carryx; static int carryy; static int carryoffx; static int carryoffy; static int carrystartt; static int carrystartx; static int carrystarty; static int carryclick; static int carrying_basepile; static int gotmotion; static BUTTON *allbuttons; static Pixmap gray_pm; static Pixmap outline_pm; struct spaceloc { char flags; #define SL_PTR_X 0x01 #define SL_PTR_Y 0x02 int x; int y; int *xp; int *yp; #define SL_FIXED(x,y) { 0, x, y, 0, 0 } #define SL_PTRS(x,y) { SL_PTR_X|SL_PTR_Y, 0, 0, x, y, } } ; typedef struct spaceloc SPACELOC; #define DECKSPACE 0 /* index of deck entry in spaceloc[] */ static SPACELOC spaceloc1[] = { SL_FIXED(CARD_MARGIN_X,CARD_MARGIN_Y), /* deck */ SL_PTRS(&suit_x[0],&suit_y[0]), /* suit #0 */ SL_PTRS(&suit_x[1],&suit_y[1]), /* suit #1 */ SL_PTRS(&suit_x[2],&suit_y[2]), /* suit #2 */ SL_PTRS(&suit_x[3],&suit_y[3]), /* suit #3 */ SL_PTRS(&pile_x[0],&pile_y[0]), /* work #0 */ SL_PTRS(&pile_x[1],&pile_y[1]), /* work #1 */ SL_PTRS(&pile_x[2],&pile_y[2]), /* work #2 */ SL_PTRS(&pile_x[3],&pile_y[3]), /* work #3 */ SL_PTRS(&pile_x[4],&pile_y[4]), /* work #4 */ SL_PTRS(&pile_x[5],&pile_y[5]), /* work #5 */ SL_PTRS(&pile_x[6],&pile_y[6]), /* work #6 */ }; #define NSPACE1 (sizeof(spaceloc1)/sizeof(spaceloc1[0])) static SPACELOC spaceloc2[] = { SL_FIXED(CARD_MARGIN_X,CARD_MARGIN_Y), /* deck */ SL_PTRS(&suit_x[0],&suit_y[0]), /* suit #0 */ SL_PTRS(&suit_x[1],&suit_y[1]), /* suit #1 */ SL_PTRS(&suit_x[2],&suit_y[2]), /* suit #2 */ SL_PTRS(&suit_x[3],&suit_y[3]), /* suit #3 */ SL_PTRS(&suit_x[4],&suit_y[4]), /* suit #4 */ SL_PTRS(&suit_x[5],&suit_y[5]), /* suit #5 */ SL_PTRS(&suit_x[6],&suit_y[6]), /* suit #6 */ SL_PTRS(&suit_x[7],&suit_y[7]), /* suit #7 */ SL_PTRS(&pile_x[0],&pile_y[0]), /* work #0 */ SL_PTRS(&pile_x[1],&pile_y[1]), /* work #1 */ SL_PTRS(&pile_x[2],&pile_y[2]), /* work #2 */ SL_PTRS(&pile_x[3],&pile_y[3]), /* work #3 */ SL_PTRS(&pile_x[4],&pile_y[4]), /* work #4 */ SL_PTRS(&pile_x[5],&pile_y[5]), /* work #5 */ SL_PTRS(&pile_x[6],&pile_y[6]), /* work #6 */ SL_PTRS(&pile_x[7],&pile_y[7]), /* work #7 */ SL_PTRS(&pile_x[8],&pile_y[8]), /* work #8 */ }; #define NSPACE2 (sizeof(spaceloc2)/sizeof(spaceloc2[0])) #define NSPACEMAX ((NSPACE1>NSPACE2)?NSPACE1:NSPACE2) static int nspace; static SPACELOC *spacelocs; static Window spacewins[NSPACEMAX]; static int junk_direction; static int junk_font_ascent; static int junk_font_descent; #define XTE_JUNK &junk_direction,&junk_font_ascent,&junk_font_descent static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(char *)); return(rv); } static int roundint(double x) { return((x<0)?(x-.5):(x+.5)); } static unsigned long int rnd(void) { #define N 63 static unsigned long int state[N]; static int h1 = -1; static int h2; if (h1 < 0) { struct timeval tv; struct stat stb; int i; stat(".",&stb); gettimeofday(&tv,0); bcopy(&stb,&state[0],MIN(sizeof(state),sizeof(stb))); for (i=0;i0;i--) { if (++h1 >= N) h1 = 0; if (++h2 >= N) h2 = 0; state[h2] += state[h1]; } } if (++h1 >= N) h1 = 0; if (++h2 >= N) h2 = 0; return(state[h2]+=state[h1]); #undef N } static void strappend(char **strp, ...) { va_list ap; char *str; int oldl; int newl; char *new; char *np; va_start(ap,strp); strp = va_arg(ap,char **); if (! *strp) { *strp = malloc(1); **strp = '\0'; } oldl = strlen(*strp); newl = 0; while (1) { str = va_arg(ap,char *); if (str == 0) break; newl += strlen(str); } va_end(ap); new = malloc(oldl+newl+1); np = new; strcpy(np,*strp); np += strlen(np); va_start(ap,strp); (void) va_arg(ap,char **); while (1) { str = va_arg(ap,char *); if (str == 0) break; strcpy(np,str); np += strlen(np); } va_end(ap); free(*strp); *strp = new; } static void panic(const char *, ...) flowsink; static void panic(const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); abort(); } 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) && (*ip < 0)) *ip = i; } static int yn3(char *str) { if (str == 0) return(-1); if ( !strcasecmp(str,"true") || !strcasecmp(str,"t") || !strcasecmp(str,"vrai") || !strcasecmp(str,"v") || !strcasecmp(str,"yes") || !strcasecmp(str,"y") || !strcasecmp(str,"oui") || !strcasecmp(str,"o") || !strcasecmp(str,"ja") || !strcasecmp(str,"j") || !strcasecmp(str,"1") ) { return(1); } if ( !strcasecmp(str,"false") || !strcasecmp(str,"f") || !strcasecmp(str,"faux") || !strcasecmp(str,"no") || !strcasecmp(str,"n") || !strcasecmp(str,"non") || !strcasecmp(str,"nil") || !strcasecmp(str,"nei") || !strcasecmp(str,"0") ) { return(0); } return(-1); } static void handleargs(int ac, char **av) { int skip; int errs; skip = 0; errs = 0; for (ac--,av++;ac;ac--,av++) { if (skip > 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,"-foreground") || !strcmp(*av,"-fg")) { WANTARG(); foreground = av[skip]; continue; } if (!strcmp(*av,"-background") || !strcmp(*av,"-bg")) { WANTARG(); background = av[skip]; continue; } if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-bordermargin") || !strcmp(*av,"-bm")) { WANTARG(); bordermstr = av[skip]; continue; } if (!strcmp(*av,"-margincolor") || !strcmp(*av,"-mc")) { WANTARG(); margincstr = av[skip]; continue; } if (!strcmp(*av,"-gamma")) { WANTARG(); gammastr = av[skip]; continue; } if (!strcmp(*av,"-name")) { WANTARG(); name = av[skip]; continue; } if (!strcmp(*av,"-iconname")) { WANTARG(); iconname = av[skip]; continue; } if (!strcmp(*av,"-font") || !strcmp(*av,"-fn")) { WANTARG(); fontname = av[skip]; continue; } if (!strcmp(*av,"-deck")) { WANTARG(); deckpath = av[skip]; continue; } if (!strcmp(*av,"-back")) { WANTARG(); backnostr = av[skip]; continue; } if (!strcmp(*av,"-sync")) { runsync = 1; continue; } if (!strcmp(*av,"-quickfinish")) { quickfinish = 1; continue; } if (!strcmp(*av,"-score")) { justscore = 1; continue; } if (!strcmp(*av,"-scorefile")) { WANTARG(); scorefilename = av[skip]; continue; } if (!strcmp(*av,"-scorememory")) { WANTARG(); maybeseti3(&rememberscore,yn3(av[skip])); continue; } if (!strcmp(*av,"-bouncegame")) { WANTARG(); maybeseti3(&bouncegame,yn3(av[skip])); continue; } if (!strcmp(*av,"-1")) { decks = 1; continue; } if (!strcmp(*av,"-2")) { decks = 2; continue; } if (!strcmp(*av,"-xrm")) { WANTARG(); strappend(&xrmstring,"\n",av[skip],(char *)0); continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = str; } static void initbutton(BUTTON *b) { XTextExtents(font,b->label,strlen(b->label),XTE_JUNK,&b->textsize); b->textw = b->textsize.rbearing - b->textsize.lbearing; b->winw = b->textw; b->pressed = 0; b->flink = allbuttons; b->blink = 0; if (b->flink) b->flink->blink = b; allbuttons = b; } static void makebuttonwindow(BUTTON *b, Window parent) { unsigned long int attrmask; XSetWindowAttributes attr; attrmask = 0; attr.background_pixel = bgcolor.pixel; attrmask |= CWBackPixel; attr.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask; attrmask |= CWEventMask; b->win = XCreateWindow(disp,parent,0,0,1,1,0,depth,InputOutput,CopyFromParent,attrmask,&attr); XMapWindow(disp,b->win); } static void setbuttonenable(BUTTON *b, int enb) { if (b->disabled == !enb) return; b->disabled = ! enb; if (enb) XMapWindow(disp,b->win); else XUnmapWindow(disp,b->win); } static void setup_db(void) { char *str; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #ifdef HAVE_R5 str = XScreenResourceString(scr); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } #endif if (xrmstring) { db2 = XrmGetStringDatabase(xrmstring); XrmMergeDatabases(db2,&db); } } static const char *resname(void) { switch (decks) { case -1: case 1: return("xklondike"); break; case 2: return("xklondike2"); break; default: return("?""?""?"); break; } } static const char *classname(void) { return("XCardGame"); } static char *get_default_value(const char *n, const char *c) { char *type; XrmValue value; char *nt; char *ct; char *rv; asprintf(&nt,"%s.%s",resname(),n); asprintf(&ct,"%s.%s",classname(),c); rv = (XrmGetResource(db,nt,ct,&type,&value) == False) ? 0 : value.addr; free(nt); free(ct); return(rv); } static Pixmap pmsetup(unsigned char bmc[CARD_YSIZE][CARD_XSIZE], unsigned char bmbw[CARD_YSIZE][CARD_BW_XBYTES]) { Pixmap rv; if (depth == 1) { static XImage *i = 0; static unsigned char buf[((CARD_XSIZE+7)>>3)*CARD_YSIZE]; #if CARD_BW_XBYTES != ((CARD_XSIZE+7)>>3) #error CARD_BW_XBYTES and CARD_XSIZE do not agree #endif static GC gc; int x; int y; int n; int acc; unsigned char *bp; if (i == 0) { i = XCreateImage(disp,visual,1,XYBitmap,0,(char *)&buf[0],CARD_XSIZE,CARD_YSIZE,8,(CARD_XSIZE+7)>>3); i->bitmap_unit = 8; i->bitmap_bit_order = MSBFirst; rv = XCreatePixmap(disp,rootwin,1,1,1); gc = XCreateGC(disp,rv,0L,(XGCValues *)0); XSetBackground(disp,gc,0L); XSetForeground(disp,gc,1L); XFreePixmap(disp,rv); } rv = XCreatePixmap(disp,rootwin,CARD_XSIZE,CARD_YSIZE,1); if (bmbw) { bcopy((char *)&bmbw[0][0],(char *)&buf[0],CARD_YSIZE*CARD_BW_XBYTES); } else { acc = 0; bp = &buf[0]; for (y=0;y 7) { *bp++ = acc; n = 0; } acc <<= 1; if (deck_colors[bmc[y][x]].pixel) acc |= 1; n ++; } if (n > 0) { *bp++ = acc << (8 - n); } } } XPutImage(disp,rv,gc,i,0,0,0,0,CARD_XSIZE,CARD_YSIZE); } else if (depth == 8) { static XImage *i = 0; static unsigned char buf[CARD_YSIZE*CARD_XSIZE]; static GC gc; int x; int y; unsigned char *bp; if (i == 0) { i = XCreateImage(disp,visual,8,ZPixmap,0,(char *)&buf[0],CARD_XSIZE,CARD_YSIZE,8,CARD_XSIZE); i->bitmap_unit = 8; rv = XCreatePixmap(disp,rootwin,1,1,8); gc = XCreateGC(disp,rv,0L,(XGCValues *)0); XFreePixmap(disp,rv); } bp = &buf[0]; for (y=0;y>3)*CARD_YSIZE]; static GC gc1; static GC gcn; static Pixmap tmp; int x; int y; int n; int acc; int m; int c; unsigned char *bp; if (i == 0) { i = XCreateImage(disp,visual,1,XYBitmap,0,(char *)&buf[0],CARD_XSIZE,CARD_YSIZE,8,(CARD_XSIZE+7)>>3); i->bitmap_unit = 8; i->bitmap_bit_order = MSBFirst; tmp = XCreatePixmap(disp,rootwin,CARD_XSIZE,CARD_YSIZE,1); gc1 = XCreateGC(disp,tmp,0L,(XGCValues *)0); XSetBackground(disp,gc1,0L); XSetForeground(disp,gc1,1L); rv = XCreatePixmap(disp,rootwin,1,1,depth); gcn = XCreateGC(disp,rv,0L,(XGCValues *)0); XSetBackground(disp,gcn,0L); XFreePixmap(disp,rv); } rv = XCreatePixmap(disp,rootwin,CARD_XSIZE,CARD_YSIZE,depth); XSetForeground(disp,gcn,0L); XSetFunction(disp,gcn,GXcopy); XFillRectangle(disp,rv,gcn,0,0,CARD_XSIZE,CARD_YSIZE); XSetFunction(disp,gcn,GXor); for (c=0;c 7) { *bp++ = acc; n = 0; } acc <<= 1; if (bmc[y][x] == c) { acc |= 1; m ++; } n ++; } if (n > 0) { *bp++ = acc << (8 - n); } } if (m > 0) { XPutImage(disp,tmp,gc1,i,0,0,0,0,CARD_XSIZE,CARD_YSIZE); XSetForeground(disp,gcn,deck_colors[c].pixel); XCopyPlane(disp,tmp,rv,gcn,0,0,CARD_XSIZE,CARD_YSIZE,0,0,1L); } } } return(rv); } static void get_back(int n) { if (deck_back[n] != None) return; deck_back[n] = pmsetup(card_backs_color[n],card_backs_bw[n]); } static Pixmap get_face(int n) { n = deck_cards[n]; if (deck_face[n] == None) deck_face[n] = pmsetup(card_cards_color[n],card_cards_bw[n]); return(deck_face[n]); } static void setup_xcolor(XColor *col, const char *what) { while (1) { if (XAllocColor(disp,wincmap,col) == 0) { if (wincmap != defcmap) { fprintf(stderr,"%s: can't allocate colormap cell for color %s\n",__progname,what); exit(1); } wincmap = XCopyColormapAndFree(disp,wincmap); continue; } break; } } static void setup_color(char *str, XColor *col) { if (XParseColor(disp,wincmap,str,col) == 0) { fprintf(stderr,"%s: bad color `%s'\n",__progname,str); exit(1); } setup_xcolor(col,str); } static void setup_deck(void) { int i; switch (decks) { case -1: decks = 1; /* fall through */ case 1: workpiles = 7; for (i=0;i<52;i++) deck_cards[i] = i; nspace = NSPACE1; spacelocs = &spaceloc1[0]; deal_cost = 52; card_win = 5; break; case 2: workpiles = 9; for (i=0;i<52;i++) { deck_cards[i] = i; deck_cards[i+52] = i; } nspace = NSPACE2; spacelocs = &spaceloc2[0]; deal_cost = 0; card_win = 1; break; default: fprintf(stderr,"%s: bad deck count %d\n",__progname,decks); exit(1); break; } decksize = 52 * decks; suitpiles = 4 * decks; score = &scores[decks-1]; startscore = *score; if (card_loaddeck(deckpath) < 0) { perror(deckpath); exit(1); } deck_colors = (XColor *) malloc(card_ncol*sizeof(XColor)); for (i=0;i<52;i++) deck_face[i] = None; deck_back = (Pixmap *) malloc(card_nbacks*sizeof(Pixmap)); for (i=0;iascent + font->descent; } static unsigned short int gammamap(unsigned short int x) { signed long int v; if (gammafactor == 1) return(x); v = 65535.9999 * pow(x/65535.0,gammafactor); if (v < 0) v = 0; if (v > 65535) v = 65535; return(v); } static void setup_colors(void) { int i; wincmap = defcmap; setup_color(foreground,&fgcolor); setup_color(background,&bgcolor); setup_color(bordercstr,&bdcolor); setup_color(margincstr,&mgcolor); for (i=0;i= card_nbacks)) { fprintf(stderr,"%s: invalid back number %d (valid numbers for this deck 0-%d), using 0\n",__progname,backno,card_nbacks-1); backno = 0; } } static void clearscore(void) { scores[0].score = 0; scores[0].total = 0; scores[0].deals = 0; scores[1] = scores[0]; } static void scorefile_load(void) { unsigned char fbuf[256]; unsigned long int uli; unsigned char buf[65]; int i; int v; SCORESTATE ss[2]; lseek(scorefd,0L,L_SET); if (read(scorefd,(char *)&fbuf[0],256) != 256) goto badf; uli = FILEVERS; for (i=0;i<252;i++) uli = (uli << 1) ^ (uli >> 31) ^ fbuf[i]; if ( (fbuf[252] != ((uli ) & 0xff)) || (fbuf[253] != ((uli >> 8) & 0xff)) || (fbuf[254] != ((uli >> 16) & 0xff)) || (fbuf[255] != ((uli >> 24) & 0xff)) ) goto badf; for (i=64-1;i>=0;i--) buf[i] = fbuf[(i*7)%251] ^ (fbuf[((i*7)+4)%251] + 'w'); buf[64] = '\0'; if (sscanf(&buf[0],"%d%d%d%d%d%d%d",&v,&ss[0].score,&ss[0].total,&ss[0].deals,&ss[1].score,&ss[1].total,&ss[1].deals) != 7) goto badf; switch (v) { case FILEVERS: scores[0] = ss[0]; scores[1] = ss[1]; break; default: badf:; clearscore(); break; } } static void scorefile_save(void) { unsigned char fbuf[256]; unsigned long int uli; unsigned char buf[65]; int i; i = sprintf((char *)&buf[0],"%d %d %d %d %d %d %d",FILEVERS,scores[0].score,scores[0].total,scores[0].deals,scores[1].score,scores[1].total,scores[1].deals); buf[i] = '\0'; for (i=251;i>=0;i--) fbuf[i] ^= fbuf[i+1]; for (i=0;i<64;i++) fbuf[(i*7)%251] = buf[i] ^ (fbuf[((i*7)+4)%251] + 'w'); uli = FILEVERS; for (i=0;i<252;i++) uli = (uli << 1) ^ (uli >> 31) ^ fbuf[i]; fbuf[252] = (uli ) & 0xff; fbuf[253] = (uli >> 8) & 0xff; fbuf[254] = (uli >> 16) & 0xff; fbuf[255] = (uli >> 24) & 0xff; lseek(scorefd,0L,L_SET); write(scorefd,(char *)&fbuf[0],256); } static void setup_score(void) { if (rememberscore < 0) rememberscore = 0; if (bouncegame < 0) bouncegame = 0; scorefd = -1; if (rememberscore && scorefilename) { if (scorefilename[0] != '/') { char *home; char *buf; home = getenv("HOME"); if (home == 0) { fprintf(stderr,"%s: no HOME\n",__progname); exit(1); } buf = malloc(strlen(home)+1+strlen(scorefilename)+1); sprintf(buf,"%s/%s",home,scorefilename); scorefilename = buf; } scorefd = open(scorefilename,O_RDWR|O_CREAT,0600); if (scorefd >= 0) scorefile_load(); } if (scorefd < 0) clearscore(); } static void setup_buttons(void) { int i; button_w = 0; for (i=0;itextw; } } static void reset_scorewin(void) { XMoveResizeWindow(disp,scoreparent,scorex,scorey,scorew+(2*margin),scoreh+(2*margin)); XMoveResizeWindow(disp,scorewin,margin,margin,scorew,scoreh); } static void resize(int topw, int toph) { int x; int y; int hm; int dx; int i; y = margin; XMoveResizeWindow(disp,menuwin,margin,y,topw-(2*margin),baselineskip); hm = topw - (2 * margin) - button_w; x = 0; for (i=0;iwin,x,0,buttons[i]->winw,baselineskip); x += buttons[i]->winw; } y += baselineskip + margin; XMoveResizeWindow(disp,linewin,0,y,topw,borderwidth); scorew = 1; scoreh = baselineskip; scorex = topw - borderwidth - margin - scorew - margin; scorey = y; reset_scorewin(); y += borderwidth + margin + baselineskip + margin + borderwidth + margin; x = (topw - desw) / 2; gwheight = toph - y - margin; XResizeWindow(disp,cwin,desw,gwheight); XResizeWindow(disp,donewin,desw,gwheight); XMoveResizeWindow(disp,gamewin,x,y,desw,gwheight); } static void setup_windows(void) { int i; int x; int y; int w; int h; int bits; XSetWindowAttributes attr; XTextProperty wn_prop; XTextProperty in_prop; XSizeHints normal_hints; XWMHints wm_hints; XClassHint class_hints; desw = MAX( (CARD_XSIZE*workpiles) + (CARD_MARGIN_X*(workpiles+1)), (CARD_XSIZE*(2+suitpiles)) + (CARD_MARGIN_X*(2+suitpiles+1)) + (CARD_UP_OVL_X*2) ); desh = (CARD_YSIZE * 2) + (CARD_MARGIN_Y * 3) + (CARD_DOWN_OVL_Y * (workpiles-1)) + (CARD_UP_OVL_Y * workpiles); w = borderwidth + margin + desw + margin + borderwidth; h = borderwidth /* top border */ + margin + baselineskip + margin /* menu bar */ + borderwidth /* line */ + margin + baselineskip + margin /* score window */ + borderwidth /* score bottom border */ + margin + desh + margin /* game proper */ + borderwidth; /* bottom border */ x = (width - w) / 2; y = (height - h) / 2; bits = XParseGeometry(geometryspec,&x,&y,&w,&h); if (bits & XNegative) x = width + x - w; if (bits & YNegative) y = height + y - h; attr.background_pixel = mgcolor.pixel; attr.border_pixel = bdcolor.pixel; attr.event_mask = StructureNotifyMask; attr.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonMotionMask | KeyPressMask | KeyReleaseMask; w -= 2 * borderwidth; h -= 2 * borderwidth; topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,CopyFromParent,CWBackPixel|CWBorderPixel|CWEventMask|CWDontPropagate,&attr); wn_prop.value = (unsigned char *) name; wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen((char *)wn_prop.value); in_prop.value = (unsigned char *) iconname; in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen((char *)in_prop.value); normal_hints.flags = PMinSize | PResizeInc; normal_hints.x = x; normal_hints.y = y; normal_hints.flags |= (bits & (XValue|YValue)) ? USPosition : 0; normal_hints.width = w; normal_hints.height = h; normal_hints.flags |= (bits & (WidthValue|HeightValue)) ? USSize : PSize; normal_hints.min_width = 1; normal_hints.min_height = 1; normal_hints.width_inc = 1; normal_hints.height_inc = 1; wm_hints.flags = InputHint; wm_hints.input = True; class_hints.res_name = deconst(resname()); class_hints.res_class = deconst(classname()); XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,&normal_hints,&wm_hints,&class_hints); XSetWindowColormap(disp,topwin,wincmap); gamewin = XCreateWindow(disp,topwin,0,0,desw,desh,0,depth,InputOutput,CopyFromParent,0UL,&attr); XMapWindow(disp,gamewin); attr.background_pixel = bgcolor.pixel; menuwin = XCreateWindow(disp,topwin,0,0,1,1,0,depth,InputOutput,CopyFromParent,CWBackPixel,&attr); XMapWindow(disp,menuwin); for (i=0;ifid); } } XSetForeground(disp,drawgc,fgcolor.pixel); XSetBackground(disp,drawgc,bgcolor.pixel); xorgc = XCreateGC(disp,topwin,0L,(XGCValues *)0); XSetFunction(disp,xorgc,GXxor); XSetForeground(disp,xorgc,fgcolor.pixel^bgcolor.pixel); XRaiseWindow(disp,iwin); XMapRaised(disp,topwin); resize(w,h); } static void setfaceup(int c, int u) { if (u == c_up[c]) return; if (u) { XSetWindowBackgroundPixmap(disp,deck_wins[c],get_face(c)); } else { get_back(backno); XSetWindowBackgroundPixmap(disp,deck_wins[c],deck_back[backno]); } XClearWindow(disp,deck_wins[c]); c_up[c] = u; } static void movecard(int c, int x, int y) { XMoveWindow(disp,deck_wins[c],x,y); c_x[c] = x; c_y[c] = y; } static void reset_pilecards(int p) { int i; int c; if (nup[p]+ndown[p] > 1) { Window wins[MAXWORKPILES+13]; int wincnt; wincnt = 0; for (i=nup[p]-1;i>=0;i--) wins[wincnt++] = deck_wins[uppiles[p][i]]; for (i=ndown[p]-1;i>=0;i--) wins[wincnt++] = deck_wins[downpiles[p][i]]; XRestackWindows(disp,&wins[0],wincnt); } for (i=nup[p]-1;i>=0;i--) { c = uppiles[p][i]; movecard(c,pile_x[p],pile_y[p]+(ndown[p]*CARD_DOWN_OVL_Y)+(i*CARD_UP_OVL_Y)); setfaceup(c,1); } for (i=ndown[p]-1;i>=0;i--) { c = downpiles[p][i]; setfaceup(c,0); movecard(c,pile_x[p],pile_y[p]+(i*CARD_DOWN_OVL_Y)); } } static void reset_undo_enable(void) { setbuttonenable(&undobutton,(ncarry<0)&&(undo_type!=U_NIL)); } static void setundo(int t) { undo_type = t; reset_undo_enable(); } static void setdeckflips(int nflips) { int old; XWindowChanges chg; old = ndeckflips; ndeckflips = nflips; switch (nflips) { case 0: XSetWindowBackgroundPixmap(disp,spacewins[DECKSPACE],outline_pm); chg.width = CARD_MARGIN_X; XConfigureWindow(disp,flipwin,CWWidth,&chg); XMapWindow(disp,flipwin); break; case 1: if (old == 1) return; chg.width = CARD_MARGIN_X / 2; XConfigureWindow(disp,flipwin,CWWidth,&chg); if (old > 1) { XSetWindowBackgroundPixmap(disp,spacewins[DECKSPACE],outline_pm); XMapWindow(disp,flipwin); } break; default: if (old > 1) return; XSetWindowBackgroundPixmap(disp,spacewins[DECKSPACE],gray_pm); XUnmapWindow(disp,flipwin); break; } } static void reset_pile_wins(void) { XWindowChanges chg; chg.width = (n_deck + n_up) * 2; if (chg.width == 0) { if (pileszp_mapped) { XUnmapWindow(disp,pileszp_deck); XUnmapWindow(disp,pileszp_up); } pileszp_mapped = 0; } else { XConfigureWindow(disp,pileszp_deck,CWWidth,&chg); XConfigureWindow(disp,pileszp_up,CWWidth,&chg); if (! pileszp_mapped) { XMapWindow(disp,pileszp_deck); XMapWindow(disp,pileszp_up); } pileszp_mapped = 1; } if (n_deck > 0) { chg.width = n_deck * 2; XConfigureWindow(disp,pilesize_deck,CWWidth,&chg); if (! piles_mapped_deck) XMapWindow(disp,pilesize_deck); piles_mapped_deck = 1; } else { if (piles_mapped_deck) XUnmapWindow(disp,pilesize_deck); piles_mapped_deck = 0; } if (n_up > 0) { chg.width = n_up * 2; XConfigureWindow(disp,pilesize_up,CWWidth,&chg); if (! piles_mapped_up) XMapWindow(disp,pilesize_up); piles_mapped_up = 1; } else { if (piles_mapped_up) XUnmapWindow(disp,pilesize_up); piles_mapped_up = 0; } } static void setup_game(void) { int i; int j; int x; int c; int sptr; char shuf[MAXDECKSIZE]; Window deckwin[MAXDECKSIZE]; x = desw - (suitpiles * (CARD_MARGIN_X+CARD_XSIZE)); for (i=0;i0;i--) { j = rnd() % (i+1); if (j != i) { c = shuf[i]; shuf[i] = shuf[j]; shuf[j] = c; } } sptr = decksize - 1; #define GETCARD() (shuf[sptr--]) #define PEEKCARD() (shuf[sptr]) for (i=0;i=0;i--) { c = GETCARD(); deck[i] = c; whereis[c].where = WHERE_DECK; whereis[c].cwp = i; deckwin[i] = deck_wins[c]; } XRestackWindows(disp,&deckwin[0],n_deck); for (i=0;ideals ++; score->score = - deal_cost; score->total -= deal_cost; scoredirty ++; gametouched = 0; gamedone = 0; setbuttonenable(&finishbutton,0); setundo(U_NIL); reset_pile_wins(); } static void setup_spaces(void) { int i; int x; int y; for (i=0;i 0) return(0); if (n_up > 1) return(0); for (i=0;i 0) return(0); return(1); } static void checkfinish(void) { setbuttonenable(&finishbutton,finishable()); } static void setup_finish(void) { int i; int j; Window stackwins[MAXDECKSIZE]; n_deck = 0; n_up = 1; upx[0] = CARD_MARGIN_X + CARD_XSIZE + CARD_MARGIN_X; for (i=0;i 2 #error Fix setup_finish for MAXDECKS #endif whereis[i].pile = (((i % 52) / 13) * decks) + (i >= 52); whereis[i].cwp = i % 13; } whereis[decksize-1].where = WHERE_UP; whereis[decksize-1].pile = 0; whereis[decksize-1].cwp = 0; for (i=0;i= desw) x = desw - 1; if (y < 0) y = 0; else if (y >= desh) y = desh - 1; if ( (abs(x-carrystartx) > drag_pixels) || (abs(y-carrystarty) > drag_pixels) ) { carryclick = 0; } XMoveWindow(disp,carrywin,x-carryoffx,y-carryoffy); } static void redraw_score(int clearfirst) { int w; int x; XCharStruct size; char sbuf[64]; sprintf(&sbuf[0],"Score: %s$%d/%d %s$%d/%d %s$%d", (score->total<0)?"-":"", abs(score->total), score->deals, (score->totaltotal-startscore.total), score->deals-startscore.deals, (score->score<0)?"-":"", abs(score->score)); XTextExtents(font,&sbuf[0],strlen(&sbuf[0]),XTE_JUNK,&size); x = (size.lbearing < 0) ? - size.lbearing : 0; w = x + size.rbearing; if (w < x+size.width) w = x + size.width; if (w > scorew) { scorex -= w - scorew; scorew = w; reset_scorewin(); /* let the resulting Expose trigger real redisplay (it will anyway) */ } else { if (clearfirst) XClearWindow(disp,scorewin); XDrawImageString(disp,scorewin,drawgc,x,font->ascent,&sbuf[0],strlen(&sbuf[0])); } } static void redrawbutton(BUTTON *b) { XClearArea(disp,b->win,0,0,b->winw,baselineskip,False); XDrawString(disp,(Drawable)b->win,drawgc,((b->winw-b->textw)/2)-b->textsize.lbearing,font->ascent,b->label,strlen(b->label)); if (b->pressed) XFillRectangle(disp,(Drawable)b->win,xorgc,0,0,b->winw,baselineskip); } static void doexpose(Window win, UNUSED_ARG(int x), UNUSED_ARG(int y), UNUSED_ARG(int w), UNUSED_ARG(int h), int count) { BUTTON *b; if (count != 0) return; if (win == scorewin) { redraw_score(0); return; } for (b=allbuttons;b;b=b->flink) { if (win == b->win) { redrawbutton(b); return; } } } static int xyincardat(int x, int y, int cx, int cy) { return((x >= cx) && (y >= cy) && (x < cx+CARD_XSIZE) && (y < cy+CARD_YSIZE)); } static int flipdeck(void) { int i; int c; if ((n_deck == 0) && (n_up == 0)) return(0); undo_n = 0; if (n_deck == 0) { setdeckflips(ndeckflips+1); for (i=0;i=0;i--) { c = up[i]; setfaceup(c,0); movecard(c,CARD_MARGIN_X,CARD_MARGIN_Y); whereis[c].where = WHERE_DECK; whereis[c].cwp = n_deck; deck[n_deck] = c; n_deck ++; } n_up = 0; } else { for (i=0;(i<3)&&(n_deck>0);i++) { undo_n ++; n_deck --; c = deck[n_deck]; raisecard(c); upx[n_up] = CARD_MARGIN_X + CARD_XSIZE + CARD_MARGIN_X + (i*CARD_UP_OVL_X); movecard(c,upx[n_up],CARD_MARGIN_Y); setfaceup(c,1); whereis[c].where = WHERE_UP; whereis[c].pile = i; whereis[c].cwp = n_up; up[n_up] = c; n_up ++; } checkfinish(); } gametouched = 1; setundo(U_FLIPDECK); reset_pile_wins(); return(1); } static void startcarry(int nc, int *cvp, int *cvnp, void (*after)(void), Time when, int x, int y) { int i; int c; ncarry = nc; carryvec = cvp; carryvecn = cvnp; carryafter = after; bcopy((char *)cvp,(char *)&carrycards[0],nc*sizeof(int)); carrystartt = when; carrystartx = x; carrystarty = y; c = *cvp; carryoffx = x - c_x[c]; carryoffy = y - c_y[c]; carryx = x; carryy = y; carryclick = 1; y = CARD_YSIZE + ((nc - 1) * CARD_UP_OVL_Y); XMoveResizeWindow(disp,carrywin,c_x[c],c_y[c],CARD_XSIZE,y); if (nc > carrybgn) { XFreePixmap(disp,carrybg); carrybg = XCreatePixmap(disp,carrywin,CARD_XSIZE,y,depth); } XCopyArea(disp,get_face(cvp[nc-1]),carrybg,drawgc,0,0,CARD_XSIZE,CARD_YSIZE,0,(nc-1)*CARD_UP_OVL_Y); for (i=nc-2;i>=0;i--) { XCopyArea(disp,get_face(cvp[i]),carrybg,drawgc,0,0,CARD_XSIZE,CARD_UP_OVL_Y,0,i*CARD_UP_OVL_Y); } XSetWindowBackgroundPixmap(disp,carrywin,carrybg); XMapRaised(disp,carrywin); for (i=0;iscore += n; score->total += n; scoredirty ++; } } static void endcarry(void) { int i; XUnmapWindow(disp,carrywin); for (i=0;i 0) { XEvent e; do { XNextEvent(disp,&e); handle_event(&e,1); if (gotbuttonpress) { XPutBackEvent(disp,&e); return(1); } } while (XEventsQueued(disp,QueuedAfterReading) > 0); } return(0); } static void checkfordone(void) { struct timeval nexttick; struct timeval now; int pileindex; int cardindex; double x; double y; double dx; double dy; int newcard; int nomove; int card; int xfd; if (ndeckflips >= 3) return; if (! donep()) return; redraw_score(1); gotbuttonpress = 0; xfd = XConnectionNumber(disp); XSetWindowBackgroundPixmap(disp,donewin,None); XMapRaised(disp,donewin); XSetWindowBackground(disp,donewin,bgcolor.pixel); gettimeofday(&nexttick,(struct timezone *)0); pileindex = suitpiles - 1; cardindex = 13; goto loopentry; while (1) { if (anim_event_loop()) { if (bouncegame && (buttonpress_win == iwin)) { XEvent e; XNextEvent(disp,&e); /* eat the buttonpress */ gotbuttonpress = 0; if ( (buttonpress_x >= x) && (buttonpress_x < x+CARD_XSIZE) && (buttonpress_y >= y) && (buttonpress_y < y+CARD_YSIZE) ) { int idx; int idy; idx = buttonpress_x - (x + (CARD_XSIZE/2)); idy = buttonpress_y - (y + (CARD_YSIZE/2)); dx -= 5 * (idx / (CARD_XSIZE/2.0)); dy -= 5 * (idy / (CARD_YSIZE/2.0)); } continue; } break; } gettimeofday(&now,(struct timezone *)0); if ( (now.tv_sec < nexttick.tv_sec) || ( (now.tv_sec == nexttick.tv_sec) && (now.tv_usec < nexttick.tv_usec) ) ) { struct timeval delta; fd_set fds; XFlush(disp); delta.tv_sec = nexttick.tv_sec - now.tv_sec; delta.tv_usec = nexttick.tv_usec - now.tv_usec; if (delta.tv_usec < 0) { delta.tv_usec += SECUSEC; delta.tv_sec --; } FD_ZERO(&fds); FD_SET(xfd,&fds); select(FD_SETSIZE,&fds,(fd_set *)0,(fd_set *)0,&delta); continue; } if (newcard) { loopentry:; pileindex ++; if (pileindex >= suitpiles) { pileindex = 0; cardindex --; if (cardindex < 0) break; } x = suit_x[pileindex]; y = suit_y[pileindex]; card = suitcards[pileindex][cardindex]; movecard(card,-CARD_XSIZE,-CARD_YSIZE); dx = (rnd() % 4) + 2; if (rnd() % 3) dx = - dx; dy = ((int)(rnd() % 31)) - 15; newcard = 0; nomove = 1; continue; } if (! nomove) { x += dx; if ((x > desw) || (x < -CARD_XSIZE)) { newcard = 1; continue; } y += dy; if (y+CARD_YSIZE >= gwheight) { y = (2*(gwheight-CARD_YSIZE)) - y; dy = - LOSS_MUL * dy; } dy += GRAVITY; } if (nomove > 0) nomove --; XCopyArea(disp,deck_face[deck_cards[card]],donewin,drawgc,0,0,CARD_XSIZE,CARD_YSIZE,roundint(x),roundint(y)); nexttick.tv_usec += BOUNCE_TICK; while (nexttick.tv_usec >= SECUSEC) { nexttick.tv_usec -= SECUSEC; nexttick.tv_sec ++; } } /* be careful; setup_game() may have been called by this point */ if (gametouched) /* ie, if no setup_game() */ { if (cardindex > 12) cardindex = 12; for (;cardindex>=0;cardindex--) { for (pileindex=0;pileindex= SECUSEC) { nexttick.tv_usec -= SECUSEC; nexttick.tv_sec ++; } } XMoveWindow(disp,win,x2,y2); return(gotbuttonpress); } static int natural_pile_for(int pileno, int suit) { return((pileno/decks)==suit); } static void push(int x, int y, Time when, int downp) { if (ncarry < 1) { int i; if (!downp) return; if (xyincardat(x,y,CARD_MARGIN_X,CARD_MARGIN_Y) && flipdeck()) { return; } if ((n_up > 0) && xyincardat(x,y,upx[n_up-1],CARD_MARGIN_Y)) { startcarry(1,&up[n_up-1],&n_up,reset_pile_wins,when,x,y); return; } for (i=0;i 0) && xyincardat(x,y,suit_x[i],suit_y[i])) { static int c; c = suitcards[i][suitheight[i]-1]; startcarry(1,&c,&suitheight[i],0,when,x,y); return; } } for (i=0;i=0;j--) { if (xyincardat(x,y,pile_x[i],pile_y[i]+(ndown[i]*CARD_DOWN_OVL_Y)+(j*CARD_UP_OVL_Y))) { startcarry(nup[i]-j,&uppiles[i][j],&nup[i],0,when,x,y); if ((ndown[i] == 0) && (j == 0)) carrying_basepile = 1; return; } } if ((nup[i] == 0) && (ndown[i] > 0) && xyincardat(x,y,pile_x[i],pile_y[i]+((ndown[i]-1)*CARD_DOWN_OVL_Y))) { flippile(i); return; } } } else { int dx; int dy; int d; int bestd; int i; int j; int ccard; int ox; int oy; WHEREIS where; int ccv; int ccs; ccard = carryvec[0]; ccv = CARD_VALUE(deck_cards[ccard]); ccs = CARD_SUIT(deck_cards[ccard]); if (! downp) { if ( (when-carrystartt < ignore_drag_time) || ( carryclick && (when-carrystartt < max_click_time) ) ) return; } x -= carryoffx; y -= carryoffy; ox = x; oy = y; if ( downp && (ncarry == 1) && (when-carrystartt < dbl_click_time) && (abs(x+carryoffx-carrystartx) < drag_pixels) && (abs(y+carryoffy-carrystarty) < drag_pixels) ) { j = 0; d = -1; if (ccv == CARD_A) { for (i=0;i CARD_XSIZE) || (abs(dy) > CARD_YSIZE)) continue; d = (dx * dx) + (dy * dy); if (d < bestd) { bestd = d; where.where = WHERE_SUIT; where.pile = i; } } } for (i=0;i CARD_XSIZE) || (abs(dy) > CARD_YSIZE)) continue; d = (dx * dx) + (dy * dy); if (d < bestd) { bestd = d; where.where = WHERE_UPILE; where.pile = i; } } i = where.pile; switch (where.where) { case WHERE_SUIT: if (ncarry != 1) panic("dropping multiple cards on suit pile"); anim_move(carrywin,ox,oy,suit_x[i],suit_y[i]); undo_score = score->score; undo_n = 1; undo_cards[0] = ccard; undo_where[0] = whereis[ccard]; undo_vecn = &suitheight[i]; gametouched = 1; (*carryvecn) --; suitsuit[i] = CARD_SUIT(deck_cards[ccard]); suitcards[i][suitheight[i]] = ccard; if (whereis[ccard].where != WHERE_SUIT) chgscore(card_win); whereis[ccard].where = WHERE_SUIT; whereis[ccard].pile = i; whereis[ccard].cwp = suitheight[i]++; raisecard(ccard); movecard(ccard,suit_x[i],suit_y[i]); setundo(U_MOVE); endcarry(); checkfinish(); checkfordone(); break; case WHERE_UPILE: anim_move(carrywin,ox,oy,pile_x[i],pile_y[i]+(ndown[i]*CARD_DOWN_OVL_Y)+(nup[i]*CARD_UP_OVL_Y)); undo_score = score->score; undo_n = ncarry; for (j=0;jdisabled) return; if (downp) { b->pressed = 1; redrawbutton(b); } else { if (b->pressed) { b->pressed = 0; (*b->clickfn)(b); redrawbutton(b); } } } static void buttonevent(Window win, int x, int y, int button, Time t, int downp) { BUTTON *b; if (win == iwin) { push(x,y,t,downp); return; } for (b=allbuttons;b;b=b->flink) { if (win == b->win) { buttonbutton(b,button,downp); return; } } } static void buttonleave(BUTTON *b) { if (b->pressed) { b->pressed = 0; redrawbutton(b); } } static void enterleave(Window win, UNUSED_ARG(int x), UNUSED_ARG(int y), int enterp) { BUTTON *b; if (enterp) return; for (b=allbuttons;b;b=b->flink) { if (win == b->win) { buttonleave(b); return; } } } static void handle_event(XEvent *e, int animating) { switch (e->type) { default: break; case ConfigureNotify: /* XConfigureEvent - xconfigure */ resize(e->xconfigure.width,e->xconfigure.height); break; case Expose: /* XExposeEvent - xexpose */ doexpose(e->xexpose.window,e->xexpose.x,e->xexpose.y,e->xexpose.width,e->xexpose.height,e->xexpose.count); break; case ButtonPress: /* XButtonPressedEvent - XButtonEvent - xbutton */ gotbuttonpress = 1; buttonpress_win = e->xbutton.window; buttonpress_x = e->xbutton.x; buttonpress_y = e->xbutton.y; if (animating) return; buttonevent(e->xbutton.window,e->xbutton.x,e->xbutton.y,e->xbutton.button,e->xbutton.time,1); break; case ButtonRelease: /* XButtonPressedEvent - XButtonEvent - xbutton */ if (animating) return; buttonevent(e->xbutton.window,e->xmotion.x,e->xmotion.y,e->xbutton.button,e->xbutton.time,0); break; case MotionNotify: /* XMotionEvent - xmotion */ gotmotion ++; break; case EnterNotify: /* XEnterWindowEvent - XCrossingEvent - xcrossing */ enterleave(e->xcrossing.window,e->xcrossing.x,e->xcrossing.y,1); break; case LeaveNotify: /* XLeaveWindowEvent - XCrossingEvent - xcrossing */ enterleave(e->xcrossing.window,e->xcrossing.x,e->xcrossing.y,0); break; } } static void run(void) flowsink; static void run(void) { XEvent e; gotmotion = 0; while (1) { if (XEventsQueued(disp,QueuedAfterReading) == 0) { if (gotmotion) { gotmotion = 0; move(); } if (scoredirty) { scoredirty = 0; redraw_score(1); scorefile_save(); } } XNextEvent(disp,&e); handle_event(&e,0); } } static int wherex(int where, int pile, UNUSED_ARG(int cwp)) { switch (where) { case WHERE_DECK: return(CARD_MARGIN_X); break; case WHERE_UP: return(CARD_MARGIN_X+CARD_XSIZE+CARD_MARGIN_X+(pile*CARD_UP_OVL_X)); break; case WHERE_SUIT: return(suit_x[pile]); break; case WHERE_DPILE: case WHERE_UPILE: return(pile_x[pile]); break; } panic("bad where %d to wherex",where); } static int wherey(int where, int pile, int cwp) { switch (where) { case WHERE_DECK: case WHERE_UP: return(CARD_MARGIN_Y); break; case WHERE_SUIT: return(suit_y[pile]); break; case WHERE_DPILE: return(pile_y[pile]+(cwp*CARD_DOWN_OVL_Y)); break; case WHERE_UPILE: return(pile_y[pile]+(ndown[pile]*CARD_DOWN_OVL_Y)+(cwp*CARD_UP_OVL_Y)); break; } panic("bad where %d to wherey",where); } static void newgame_click(UNUSED_ARG(BUTTON *b)) { setup_game(); } static void undo_click(UNUSED_ARG(BUTTON *b)) { int i; int j; int c; if (ncarry > 0) return; switch (undo_type) { default: break; case U_FLIPDECK: if (undo_n == 0) { if (n_up != 0) panic("undoing deck flip but cards up"); setdeckflips(ndeckflips-1); for (i=n_deck-1;i>=0;i--) { c = deck[i]; raisecard(c); upx[n_up] = CARD_MARGIN_X + CARD_XSIZE + CARD_MARGIN_X + ((n_up%3)*CARD_UP_OVL_X); movecard(c,upx[n_up],CARD_MARGIN_Y); setfaceup(c,1); whereis[c].where = WHERE_UP; whereis[c].pile = n_up % 3; whereis[c].cwp = n_up; up[n_up] = c; n_up ++; } n_deck = 0; } else { if (n_up < undo_n) panic("undoing card flip, but not enough cards"); for (i=0;itotal -= score->score - undo_score; score->score = undo_score; scoredirty ++; for (i=0;i decksize)) panic("sanitycheck: bad card\n"); if ( (whereis[card].where != where) || (whereis[card].pile != pile) || (whereis[card].cwp != cwp) ) panic("sanitycheck: whereis wrong"); } switch (n_up) { default: abort(); break; case 0: break; case 1: foo(up[0],WHERE_UP,0,0); break; } for (i=0;i 13)) panic("sanitycheck: bad suit height"); if (suitheight[i] == 0) continue; if ((suitsuit[i] < CARD_MINSUIT) || (suitsuit[i] > CARD_MAXSUIT)) { panic("bad suitsuit"); } for (j=0;j 13)) panic("sanitycheck: bad nup"); for (j=0;j= suitpiles) || (suitheight[p] != v-CARD_A) || ((v != CARD_A) && (suitsuit[p] != s)) ) panic("move_to_suit bad pile"); switch (whereis[c].where) { case WHERE_UPILE: nup[whereis[c].pile] --; break; case WHERE_UP: n_up --; break; default: return(-1); break; } chgscore(card_win); redraw_score(1); scorefile_save(); tx = wherex(WHERE_SUIT,p,v); ty = wherey(WHERE_SUIT,p,v); raisecard(c); amrv = anim_move(deck_wins[c],c_x[c],c_y[c],tx,ty); movecard(c,tx,ty); suitcards[p][suitheight[p]] = c; suitsuit[p] = s; whereis[c].where = WHERE_SUIT; whereis[c].pile = p; whereis[c].cwp = suitheight[p]++; return(amrv); } static void finish_click(UNUSED_ARG(BUTTON *b)) { static int finishing = 0; int i; int j; int k; int l; int d; int c; int c2; int s; int v; int amrv; int tx; int ty; int stackoff; if (donep() || !finishable() || finishing) return; finishing = 1; stackoff = workpiles - suitpiles; setundo(U_NIL); while <"finishing"> (1) { /* Move all aces up. (This is mostly to ensure all suitsuit[] values can be assumed to be valid, below.) */ for (i=0;i; continue <"finishing">; } } } for (j=0;j<4;j++) { for (d=0;d; continue <"finishing">; } } } } /* Stack a pile on another pile whenever possible. */ for (i=workpiles-1;i>=0;i--) { if (nup[i] < 1) continue; c = uppiles[i][0]; v = CARD_VALUE(deck_cards[c]); if (v == CARD_K) continue; for (j=CARD_MINSUIT;j<=CARD_MAXSUIT;j++) { for (d=0;d= 0) endcarry(); sanitycheck(); if (amrv) break <"finishing">; continue <"finishing">; } } } } /* Stack the last deck card onto whatever it goes on. */ if (n_up) { s = CARD_SUIT(deck_cards[up[0]]); v = CARD_VALUE(deck_cards[up[0]]); for (i=0;i; continue <"finishing">; } } for (i=0;i 0) && (CARD_VALUE(deck_cards[uppiles[i][nup[i]-1]]) == CARD_VALUE(deck_cards[up[0]])+1) && (color[uppiles[i][nup[i]-1]] != color[up[0]]) ) ) { tx = wherex(WHERE_UPILE,i,nup[i]); ty = wherey(WHERE_UPILE,i,nup[i]); raisecard(up[0]); amrv = anim_move(deck_wins[up[0]],c_x[up[0]],c_y[up[0]],tx,ty); movecard(up[0],tx,ty); uppiles[i][nup[i]] = up[0]; whereis[up[0]].where = WHERE_UPILE; whereis[up[0]].pile = i; whereis[up[0]].cwp = nup[i]++; n_up = 0; sanitycheck(); if (amrv) break <"finishing">; continue <"finishing">; } } } /* Try to get the kings in their natural columns. */ for (i=suitpiles-1;i>=0;i--) { for (d=0;d= stackoff) && natural_pile_for(whereis[c].pile-stackoff,suitsuit[i]) ) ) continue; /* Found a UPILE king that's not in a natural column. Look for an empty natural column for it. */ for (k=0;k=0;l--) { c = uppiles[j][l]; uppiles[k+stackoff][l] = c; whereis[c].pile = k + stackoff; } nup[j] = 0; reset_pilecards(k+stackoff); endcarry(); sanitycheck(); if (amrv) break <"finishing">; continue <"finishing">; } } } } /* If any of the kings are out of natural columns, and one of the other columns is blank, push a column not containing its king there. */ for (j=stackoff-1;j>=0;j--) { if (nup[j] == 0) { /* We have a blank column, so all kings are UPILE or SUIT. (If DECK or DPILE, we wouldn't have been finishable; if UP, it would have been moved into the blank column above.) Look for a UPILE king not in a natural column; if found, at least one of its natural columns must contain something other than a natural king. Find such a column and push its contents into the blank column. (We know the natural columns for the king aren't empty, or the king would have been moved above.) */ for (i=suitpiles-1;i>=0;i--) { c2 = CARD_MAKE(suitsuit[i],CARD_K); for (d=0;d= stackoff) && natural_pile_for(whereis[c].pile-stackoff,suitsuit[i]) ) ) continue; /* Found king. Look at its natural columns. */ for (k=0;k=0;l--) { c = uppiles[k][l]; uppiles[j][l] = c; whereis[c].pile = j; } nup[k] = 0; reset_pilecards(j); endcarry(); sanitycheck(); if (amrv) break <"finishing">; continue <"finishing">; } panic("can't find occupied column"); } } } } /* Nothing else to do - move a card to a suit pile. */ for (i=0;i<=CARD_MAXVALUE-CARD_MINVALUE;i++) { for (j=0;j i) continue; c2 = CARD_MAKE(suitsuit[j],i+CARD_A); k = -1; for (d=0;d; continue <"finishing">; } if ( (whereis[c].where == WHERE_UPILE) && (whereis[c].cwp == nup[whereis[c].pile]-1) ) { if ((k < 0) || (abs(whereis[c].pile-j) < abs(k-j))) { k = whereis[c].pile; l = c; } } } if (k < 0) panic("can't find card to move"); amrv = move_to_suit(l,j); if (amrv < 0) continue; sanitycheck(); if (amrv) break <"finishing">; continue <"finishing">; } } break; } XSync(disp,False); checkfordone(); scoredirty = 1; finishing = 0; } static void quit_click(UNUSED_ARG(BUTTON *b)) { if (! gametouched) { score->score = 0; score->total += deal_cost; score->deals --; scorefile_save(); } exit(0); } static Display *open_display(char *d) { Display *rv; rv = XOpenDisplay(d); if (rv == 0) { fprintf(stderr,"%s: can't open display %s\n",__progname,XDisplayName(d)); exit(1); } return(rv); } static int ioerr(Display *d) { return((*oldioerr)(d)); } static int err(Display *d, XErrorEvent *e) { return((*olderr)(d,e)); } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac,av); disp = open_display(displayname); if (runsync) XSynchronize(disp,True); oldioerr = XSetIOErrorHandler(ioerr); olderr = XSetErrorHandler(err); scr = XDefaultScreenOfDisplay(disp); width = XWidthOfScreen(scr); height = XHeightOfScreen(scr); depth = XDefaultDepthOfScreen(scr); rootwin = XRootWindowOfScreen(scr); defcmap = XDefaultColormapOfScreen(scr); visual = XDefaultVisualOfScreen(scr); setup_db(); maybeseti3(&decks,atoi(get_default_value("decks","Decks")?:"-1")); maybeset(&geometryspec,get_default_value("geometry","Geometry")); maybeset(&fontname,get_default_value("font","Font")); maybeset(&foreground,get_default_value("foreground","Foreground")); maybeset(&background,get_default_value("background","Background")); maybeset(&bordercstr,get_default_value("borderColor","BorderColor")); maybeset(&borderwstr,get_default_value("borderWidth","BorderWidth")); maybeset(&bordermstr,get_default_value("borderMargin","BorderMargin")); maybeset(&margincstr,get_default_value("marginColor","Background")); maybeset(&name,get_default_value("name","Name")); maybeset(&iconname,get_default_value("iconName","IconName")); maybeset(&gammastr,get_default_value("gamma","Gamma")); maybeset(&deckpath,get_default_value("deckPath","DeckPath")); maybeset(&backnostr,get_default_value("backNumber","BackNumber")); maybeset(&dragpixstr,get_default_value("dragPixels","DragPixels")); maybeset(&igndragstr,get_default_value("ignoreDragTime","IgnoreDragTime")); maybeset(&maxclickstr,get_default_value("maxClickTime","MaxClickTime")); maybeset(&dblclickstr,get_default_value("doubleClickIdle","DoubleClickIdle")); maybeset(&scorefilename,get_default_value("scoreMemoryFile","ScoreMemoryFile")); maybeseti3(&rememberscore,yn3(get_default_value("rememberScore","RememberScore"))); maybeseti3(&bouncegame,yn3(get_default_value("bounceGame","BounceGame"))); setup_score(); if (justscore) { if (rememberscore) { printf("%s$%d/%d\n",(score->total<0)?"-":"",abs(score->total),score->deals); } else { printf("No score remembered.\n"); } exit(0); } setup_deck(); setup_font(); setup_numbers(); setup_colors(); setup_buttons(); setup_windows(); setup_game(); setup_spaces(); if (quickfinish) setup_finish(); run(); return(0); }