/* $Id: board.c,v 1.5 2002/03/02 21:02:21 sverrehu Exp $ */ /************************************************************************** * * FILE board.c * MODULE OF Card game. * * WRITTEN BY Sverre H. Huseby * **************************************************************************/ #include #include #include #include #include #include #include #include #include #include "card.h" #include "pile.h" #include "game.h" #include "win.h" #include "board.h" /************************************************************************** * * * P R I V A T E D A T A * * * **************************************************************************/ /* number of pixels the cursor must move for each repaint of the * dragged card. only when save_under is not supported. */ #define DRAG_QUANT 15 static int dragQuantSquared = 0; static int multiClickTime = 200; static Pile *popupPile = NULL; static Pile *popupFromPile; static int pointerX, pointerY; static int lastPointerX, lastPointerY; static int popupDeltaX, popupDeltaY; static int popupWidth, popupHeight; static XFontStruct *fontInfo = NULL; static GC fontGC = 0; static char *theText = NULL; static Pixmap pileCache[NUM_PILES]; static GC pileCacheGC; /************************************************************************** * * * P U B L I C D A T A * * * **************************************************************************/ Window boardWin; Window boardPopup; int boardWidth = (CARD_WIDTH * 7 + CARD_SEP_X * 8); int boardHeight = (CARD_HEIGHT * 2 + CARD_SEP_Y * 3 + CARD_SHOW_Y * 12); int boardCardBackground = 0; /************************************************************************** * * * P R I V A T E F U N C T I O N S * * * **************************************************************************/ /* must be called after the pile characteristics are set up in game.c */ static void boardInitPileCache(void) { int q; XGCValues gcv; unsigned long gcvMask = 0; XColor xc, xc2; XWindowAttributes xa; Pile *p; XAllocNamedColor(winDisplay, DefaultColormap(winDisplay, DefaultScreen(winDisplay)), BOARD_BACKGROUND_COLOR, &xc, &xc2); gcv.background = gcv.foreground = xc.pixel; gcvMask |= GCBackground | GCForeground; gcv.graphics_exposures = 0; gcvMask |= GCGraphicsExposures; pileCacheGC = XtGetGC(boardWidget, gcvMask, &gcv); XGetWindowAttributes(winDisplay, boardWin, &xa); for (q = 0; q < NUM_PILES; q++) { p = pile[q]; pileCache[q] = XCreatePixmap(winDisplay, boardWin, p->maxWidth, p->maxHeight, xa.depth); } } static void boardFinishPileCache(void) { XtReleaseGC(boardWidget, pileCacheGC); } static void boardDrawPileToPixmap(Pile *p, Pixmap pix) { int x, y, count; Card *c; count = 0; x = 0; y = 0; XFillRectangle(winDisplay, pix, pileCacheGC, x, y, p->maxWidth, p->maxHeight); if (p->numCards == 0) { XFillRectangle(winDisplay, pix, pileCacheGC, x, y, CARD_WIDTH, CARD_HEIGHT); if (p->outline > -1) cardDraw(pix, x, y, CARD_OUTLINE, p->outline); } else { c = p->bottom; for (;;) { if (c->frontUp) cardDraw(pix, x, y, c->suit, c->value); else cardDraw(pix, x, y, CARD_BACKGROUND, boardCardBackground); ++count; if ((c = c->next) == NULL) break; if (p->deltaEach > 0 && (count % p->deltaEach) == 0) { x += p->dx; y += p->dy; } else if (p->deltaEach < 0 && count > p->numCards + p->deltaEach) { x += p->dx; y += p->dy; } } } } static Card * boardFindCard(int cx, int cy) { int q, x, y, count; Pile *p; Card *c, *found = NULL; for (q = 0; q < NUM_PILES; q++) { p = pile[q]; if (!p || p->numCards == 0) continue; count = 0; x = p->x; y = p->y; c = p->bottom; for (;;) { if (cx >= x && cx <= x + CARD_WIDTH && cy >= y && cy <= y + CARD_HEIGHT) found = c; ++count; if ((c = c->next) == NULL) break; if (p->deltaEach > 0 && (count % p->deltaEach) == 0) { x += p->dx; y += p->dy; } else if (p->deltaEach < 0 && count > p->numCards + p->deltaEach) { x += p->dx; y += p->dy; } } if (found) break; } return found; } static void boardGetCardPos(Card *card, int *x, int *y) { int count; register Pile *p; register Card *c; if (!card) { *x = *y = 0; return; } p = card->pile; count = 0; *x = p->x; *y = p->y; c = p->bottom; for (;;) { if (c == card) break; ++count; if ((c = c->next) == NULL) break; if (p->deltaEach > 0 && (count % p->deltaEach) == 0) { *x += p->dx; *y += p->dy; } else if (p->deltaEach < 0 && count > p->numCards + p->deltaEach) { *x += p->dx; *y += p->dy; } } } static void boardGetPileCoords(Pile *p, int *x0, int *y0, int *x1, int *y1) { int tmp; if (!p->numCards) { *x0 = p->x; *y0 = p->y; *x1 = *x0 + CARD_WIDTH; *y1 = *y0 + CARD_HEIGHT; return; } boardGetCardPos(p->bottom, x0, y0); boardGetCardPos(p->top, x1, y1); if (*x1 < *x0) { tmp = *x1; *x1 = *x0; *x0 = tmp; } if (*y1 < *y0) { tmp = *y1; *y1 = *y0; *y0 = tmp; } *x1 += CARD_WIDTH; *y1 += CARD_HEIGHT; } static void boardCreatePopup(void) { XSetWindowAttributes xwa; unsigned long attr = 0; Arg args[1]; Pixel bg; XtSetArg(args[0], XtNbackground, &bg); XtGetValues(boardWidget, args, 1); boardPopup = XCreateSimpleWindow(winDisplay, boardWin, 0, 0, 1, 1, /* x, y, w, h */ 0, 0, bg); if (XDoesSaveUnders(XtScreenOfObject(winMainWindow))) { xwa.save_under = 1; attr |= CWSaveUnder; } else { #if 0 msgError("for some stupid reason, your X server doesn't " "support save_under.\n" " card dragging will cause lots of flickering.\n"); #endif dragQuantSquared = DRAG_QUANT * DRAG_QUANT; } xwa.override_redirect = 1; /* no WM intervention */ attr |= CWOverrideRedirect; XChangeWindowAttributes(winDisplay, boardPopup, attr, &xwa); XSelectInput(winDisplay, boardPopup, ExposureMask); } static int boardRectIntersect(int r0x0, int r0y0, int r0x1, int r0y1, int r1x0, int r1y0, int r1x1, int r1y1) { if ((r0x0 < r1x0 && r0x1 < r1x0) /* outside left */ || (r0x0 > r1x1 && r0x1 > r1x1) /* outside right */ || (r0y0 < r1y0 && r0y1 < r1y0) /* outside above */ || (r0y0 > r1y1 && r0y1 > r1y1)) /* outside below */ return 0; return 1; } static void boardGetIntersectingPiles(int xx0, int yy0, int xx1, int yy1, int intersect[], int *n) { int q, x0, y0, x1, y1; Pile *p; *n = 0; for (q = 0; q < NUM_PILES; q++) { if ((p = pile[q]) == NULL) continue; boardGetPileCoords(p, &x0, &y0, &x1, &y1); if (boardRectIntersect(x0, y0, x1, y1, xx0, yy0, xx1, yy1)) intersect[(*n)++] = q; } } static void boardLoadFont(void) { char *fontName[] = { "-adobe-utopia-bold-i-*-*-60-*-*-*-*-*-*-*", "-bitstream-courier-bold-i-*-*-60-*-*-*-*-*-*-*", "-*-*-*-*-*-*-60-*-*-*-*-*-iso8859-1", "12x24", "fixed" }; int q; XGCValues gcv; unsigned long gcvMask = 0; XColor xc, xc2; if (fontInfo) return; for (q = 0; q < sizeof(fontName) / sizeof(char *); q++) if ((fontInfo = XLoadQueryFont(winDisplay, fontName[q])) != NULL) break; if (q == sizeof(fontName) / sizeof(char *)) { msgError("can't load a font\n"); return; } gcv.font = fontInfo->fid; gcvMask |= GCFont; XAllocNamedColor(winDisplay, DefaultColormap(winDisplay, DefaultScreen(winDisplay)), "red", &xc, &xc2); gcv.foreground = xc.pixel; gcvMask |= GCForeground; fontGC = XCreateGC(winDisplay, boardWin, gcvMask, &gcv); } static void boardUpdateText(void) { int w, h, len; if (!theText) return; len = strlen(theText); w = XTextWidth(fontInfo, theText, len); h = fontInfo->ascent + fontInfo->descent; XDrawString(winDisplay, boardWin, fontGC, (boardWidth - w) / 2, (boardHeight - h) / 2 + fontInfo->ascent, theText, len); winFlush(); } /************************************************************************** * * * P U B L I C F U N C T I O N S * * * **************************************************************************/ void boardInit(void) { XSelectInput(winDisplay, boardWin, ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask); multiClickTime = XtGetMultiClickTime(winDisplay); boardLoadFont(); } void boardFinish(void) { boardFinishPileCache(); if (fontInfo) { XFreeFont(winDisplay, fontInfo); fontInfo = NULL; } if (fontGC) XFreeGC(winDisplay, fontGC); if (theText) { free(theText); theText = NULL; } XDestroyWindow(winDisplay, boardPopup); } void boardInitGame(void) { int q; boardShowText(NULL); XClearArea(winDisplay, boardWin, 0, 0, 0, 0, 1); for (q = 0; q < NUM_PILES; q++) boardUpdatePileCache(pile[q]); } void boardFinishGame(void) { } void boardHandleEvent(XEvent *evt) { switch (evt->type) { case Expose: { int q, intersect[NUM_PILES], n; static int setupDone = 0; if (!setupDone) { setupDone = 1; boardShowText("Be patient..."); cardInit(winDisplay, boardWin); boardShowText(NULL); boardInitPileCache(); gameInitGame(); boardCreatePopup(); } boardGetIntersectingPiles(evt->xexpose.x, evt->xexpose.y, evt->xexpose.x + evt->xexpose.width, evt->xexpose.y + evt->xexpose.height, intersect, &n); for (q = 0; q < n; q++) boardDrawPile(pile[intersect[q]], 0); boardUpdateText(); } break; case ButtonPress: { Card *c; int x, y; int doubleClick; static Card *lastCard = NULL; static Time lastClickTime = 0; if (!gameRunning || !XtIsSensitive(boardWidget) || popupPile) break; x = evt->xbutton.x; y = evt->xbutton.y; c = boardFindCard(x, y); if (evt->xbutton.button == 1) { pointerX = lastPointerX = x; pointerY = lastPointerY = y; if (c != NULL) { doubleClick = (c == lastCard && evt->xbutton.time < lastClickTime + multiClickTime); if (doubleClick) { gameHandleDoubleClick(c); } else { gameHandleSingleClick(c); lastCard = c; lastClickTime = evt->xbutton.time; } } else if (x >= pile[0]->x && x <= pile[0]->x + CARD_WIDTH && y >= pile[0]->y && y <= pile[0]->y + CARD_HEIGHT){ gameDrawNext(); } } else if (evt->xbutton.button == 2 || evt->xbutton.button == 3) { /* to be a little bit helpful, allow other buttons to work * as doubleclicks. */ if (c != NULL) gameHandleDoubleClick(c); } } break; case ButtonRelease: if (!gameRunning || !XtIsSensitive(boardWidget)) break; if (evt->xbutton.button == 1) boardStopDrag(); break; case MotionNotify: if (popupPile) { int dx, dy; dx = evt->xmotion.x - lastPointerX; dy = evt->xmotion.y - lastPointerY; pointerX = evt->xmotion.x; pointerY = evt->xmotion.y; if (dx * dx + dy * dy >= dragQuantSquared) { lastPointerX = pointerX; lastPointerY = pointerY; XMoveWindow(winDisplay, boardPopup, pointerX - popupDeltaX, pointerY - popupDeltaY); } } break; } } void boardHandlePopupEvent(XEvent *evt) { switch (evt->type) { case Expose: if (evt->xexpose.count) break; boardDrawPile(popupPile, 1); break; } } void boardDrawPile(Pile *p, int popup) { int pileNum; if (!p) return; if (popup) boardDrawPileToPixmap(p, boardPopup); else { pileNum = gameFindPileNumOfPile(p); XCopyArea(winDisplay, pileCache[pileNum], boardWin, pileCacheGC, 0, 0, p->maxWidth, p->maxHeight, p->x, p->y); } } void boardUpdatePileCache(Pile *p) { if (!p) return; boardDrawPileToPixmap(p, pileCache[gameFindPileNumOfPile(p)]); } void boardStartDragCardAndAbove(Card *c) { int px, py, x0, y0, x1, y1; boardGetCardPos(c, &px, &py); popupDeltaX = pointerX - px; popupDeltaY = pointerY - py; popupFromPile = c->pile; popupPile = pileMoveToNewPileFromCard(popupFromPile, c); boardUpdatePileCache(popupFromPile); boardDrawPile(popupFromPile, 0); boardGetPileCoords(popupPile, &x0, &y0, &x1, &y1); popupWidth = x1 - x0; popupHeight = y1 - y0; XResizeWindow(winDisplay, boardPopup, popupWidth, popupHeight); XMoveWindow(winDisplay, boardPopup, px, py); XMapRaised(winDisplay, boardPopup); } void boardStopDrag(void) { int intersect[NUM_PILES], n; int popx0, popy0, popx1, popy1; Pile *p; if (!popupPile) return; XUnmapWindow(winDisplay, boardPopup); popx0 = lastPointerX - popupDeltaX; popy0 = lastPointerY - popupDeltaY; popx1 = popx0 + popupWidth; popy1 = popy0 + popupHeight; boardGetIntersectingPiles(popx0, popy0, popx1, popy1, intersect, &n); p = gameCardDropped(popupFromPile, popupPile, intersect, n); pileAddPileTop(p, popupPile); boardUpdatePileCache(p); boardDrawPile(p, 0); pileDelete(popupPile); popupPile = NULL; gameCheckIfSolution(); } void boardShowText(const char *s) { if (!fontInfo) { printf("%s\n", s); return; } if (theText) free(theText); if (!s || *s == '\0') { theText = NULL; return; } theText = xstrdup(s); boardUpdateText(); }