1 /* $Id: board.c,v 1.5 2002/03/02 21:02:21 sverrehu Exp $ */
2 /**************************************************************************
3  *
4  *  FILE            board.c
5  *  MODULE OF       Card game.
6  *
7  *  WRITTEN BY      Sverre H. Huseby <shh@thathost.com>
8  *
9  **************************************************************************/
10 
11 #include <stdlib.h>
12 #include <string.h>
13 #include <time.h>
14 
15 #include <X11/Xlib.h>
16 #include <X11/Intrinsic.h>
17 #include <X11/StringDefs.h>
18 
19 #include <shhmsg.h>
20 #include <xalloc.h>
21 #include <shhcards.h>
22 
23 #include "card.h"
24 #include "pile.h"
25 #include "game.h"
26 #include "win.h"
27 #include "board.h"
28 
29 /**************************************************************************
30  *                                                                        *
31  *                       P R I V A T E    D A T A                         *
32  *                                                                        *
33  **************************************************************************/
34 
35 /* number of pixels the cursor must move for each repaint of the
36  * dragged card. only when save_under is not supported. */
37 #define DRAG_QUANT 15
38 
39 static int dragQuantSquared = 0;
40 static int multiClickTime = 200;
41 static Pile *popupPile = NULL;
42 static Pile *popupFromPile;
43 static int pointerX, pointerY;
44 static int lastPointerX, lastPointerY;
45 static int popupDeltaX, popupDeltaY;
46 static int popupWidth, popupHeight;
47 static XFontStruct *fontInfo = NULL;
48 static GC fontGC = 0;
49 static char *theText = NULL;
50 static Pixmap pileCache[NUM_PILES];
51 static GC  pileCacheGC;
52 
53 
54 
55 /**************************************************************************
56  *                                                                        *
57  *                        P U B L I C    D A T A                          *
58  *                                                                        *
59  **************************************************************************/
60 
61 Window  boardWin;
62 Window  boardPopup;
63 int     boardWidth = (CARD_WIDTH * 7 + CARD_SEP_X * 8);
64 int     boardHeight = (CARD_HEIGHT * 2 + CARD_SEP_Y * 3 + CARD_SHOW_Y * 12);
65 int     boardCardBackground = 0;
66 
67 
68 
69 /**************************************************************************
70  *                                                                        *
71  *                   P R I V A T E    F U N C T I O N S                   *
72  *                                                                        *
73  **************************************************************************/
74 
75 /* must be called after the pile characteristics are set up in game.c */
76 static void
boardInitPileCache(void)77 boardInitPileCache(void)
78 {
79     int               q;
80     XGCValues         gcv;
81     unsigned long     gcvMask = 0;
82     XColor            xc, xc2;
83     XWindowAttributes xa;
84     Pile              *p;
85 
86     XAllocNamedColor(winDisplay,
87 		     DefaultColormap(winDisplay, DefaultScreen(winDisplay)),
88 		     BOARD_BACKGROUND_COLOR, &xc, &xc2);
89     gcv.background = gcv.foreground = xc.pixel;
90     gcvMask |= GCBackground | GCForeground;
91     gcv.graphics_exposures = 0;
92     gcvMask |= GCGraphicsExposures;
93     pileCacheGC = XtGetGC(boardWidget, gcvMask, &gcv);
94     XGetWindowAttributes(winDisplay, boardWin, &xa);
95 
96     for (q = 0; q < NUM_PILES; q++) {
97 	p = pile[q];
98 	pileCache[q] = XCreatePixmap(winDisplay, boardWin,
99 				     p->maxWidth, p->maxHeight, xa.depth);
100     }
101 }
102 
103 static void
boardFinishPileCache(void)104 boardFinishPileCache(void)
105 {
106     XtReleaseGC(boardWidget, pileCacheGC);
107 }
108 
109 static void
boardDrawPileToPixmap(Pile * p,Pixmap pix)110 boardDrawPileToPixmap(Pile *p, Pixmap pix)
111 {
112     int  x, y, count;
113     Card *c;
114 
115     count = 0;
116     x = 0;
117     y = 0;
118     XFillRectangle(winDisplay, pix, pileCacheGC,
119 		   x, y, p->maxWidth, p->maxHeight);
120     if (p->numCards == 0) {
121 	XFillRectangle(winDisplay, pix, pileCacheGC,
122 		       x, y, CARD_WIDTH, CARD_HEIGHT);
123 	if (p->outline > -1)
124 	    cardDraw(pix, x, y, CARD_OUTLINE, p->outline);
125     } else {
126 	c = p->bottom;
127 	for (;;) {
128 	    if (c->frontUp)
129 		cardDraw(pix, x, y, c->suit, c->value);
130 	    else
131 		cardDraw(pix, x, y, CARD_BACKGROUND, boardCardBackground);
132 	    ++count;
133 	    if ((c = c->next) == NULL)
134 		break;
135 	    if (p->deltaEach > 0 && (count % p->deltaEach) == 0) {
136 		x += p->dx;
137 		y += p->dy;
138 	    } else if (p->deltaEach < 0
139 		       && count > p->numCards + p->deltaEach) {
140 		x += p->dx;
141 		y += p->dy;
142 	    }
143 	}
144     }
145 }
146 
147 static Card *
boardFindCard(int cx,int cy)148 boardFindCard(int cx, int cy)
149 {
150     int  q, x, y, count;
151     Pile *p;
152     Card *c, *found = NULL;
153 
154     for (q = 0; q < NUM_PILES; q++) {
155 	p = pile[q];
156 	if (!p || p->numCards == 0)
157 	    continue;
158 	count = 0;
159 	x = p->x;
160 	y = p->y;
161 	c = p->bottom;
162 	for (;;) {
163 	    if (cx >= x && cx <= x + CARD_WIDTH
164 		&& cy >= y && cy <= y + CARD_HEIGHT)
165 		found = c;
166 	    ++count;
167 	    if ((c = c->next) == NULL)
168 		break;
169 	    if (p->deltaEach > 0 && (count % p->deltaEach) == 0) {
170 		x += p->dx;
171 		y += p->dy;
172 	    } else if (p->deltaEach < 0
173 		       && count > p->numCards + p->deltaEach) {
174 		x += p->dx;
175 		y += p->dy;
176 	    }
177 	}
178 	if (found)
179 	    break;
180     }
181     return found;
182 }
183 
184 static void
boardGetCardPos(Card * card,int * x,int * y)185 boardGetCardPos(Card *card, int *x, int *y)
186 {
187     int           count;
188     register Pile *p;
189     register Card *c;
190 
191     if (!card) {
192 	*x = *y = 0;
193 	return;
194     }
195     p = card->pile;
196     count = 0;
197     *x = p->x;
198     *y = p->y;
199     c = p->bottom;
200     for (;;) {
201 	if (c == card)
202 	    break;
203 	++count;
204 	if ((c = c->next) == NULL)
205 	    break;
206 	if (p->deltaEach > 0 && (count % p->deltaEach) == 0) {
207 	    *x += p->dx;
208 	    *y += p->dy;
209 	} else if (p->deltaEach < 0
210 		   && count > p->numCards + p->deltaEach) {
211 	    *x += p->dx;
212 	    *y += p->dy;
213 	}
214     }
215 }
216 
217 static void
boardGetPileCoords(Pile * p,int * x0,int * y0,int * x1,int * y1)218 boardGetPileCoords(Pile *p, int *x0, int *y0, int *x1, int *y1)
219 {
220     int tmp;
221 
222     if (!p->numCards) {
223 	*x0 = p->x;
224 	*y0 = p->y;
225 	*x1 = *x0 + CARD_WIDTH;
226 	*y1 = *y0 + CARD_HEIGHT;
227 	return;
228     }
229     boardGetCardPos(p->bottom, x0, y0);
230     boardGetCardPos(p->top, x1, y1);
231     if (*x1 < *x0) {
232 	tmp = *x1;
233 	*x1 = *x0;
234 	*x0 = tmp;
235     }
236     if (*y1 < *y0) {
237 	tmp = *y1;
238 	*y1 = *y0;
239 	*y0 = tmp;
240     }
241     *x1 += CARD_WIDTH;
242     *y1 += CARD_HEIGHT;
243 }
244 
245 static void
boardCreatePopup(void)246 boardCreatePopup(void)
247 {
248     XSetWindowAttributes xwa;
249     unsigned long attr = 0;
250     Arg args[1];
251     Pixel bg;
252 
253     XtSetArg(args[0], XtNbackground, &bg);
254     XtGetValues(boardWidget, args, 1);
255     boardPopup = XCreateSimpleWindow(winDisplay, boardWin,
256 				     0, 0, 1, 1, /* x, y, w, h */
257 				     0, 0, bg);
258     if (XDoesSaveUnders(XtScreenOfObject(winMainWindow))) {
259 	xwa.save_under = 1;
260 	attr |= CWSaveUnder;
261     } else {
262 #if 0
263 	msgError("for some stupid reason, your X server doesn't "
264 		 "support save_under.\n"
265 		 "     card dragging will cause lots of flickering.\n");
266 #endif
267 	dragQuantSquared = DRAG_QUANT * DRAG_QUANT;
268     }
269     xwa.override_redirect = 1; /* no WM intervention */
270     attr |= CWOverrideRedirect;
271     XChangeWindowAttributes(winDisplay, boardPopup, attr, &xwa);
272     XSelectInput(winDisplay, boardPopup, ExposureMask);
273 }
274 
275 static int
boardRectIntersect(int r0x0,int r0y0,int r0x1,int r0y1,int r1x0,int r1y0,int r1x1,int r1y1)276 boardRectIntersect(int r0x0, int r0y0, int r0x1, int r0y1,
277 		   int r1x0, int r1y0, int r1x1, int r1y1)
278 {
279     if ((r0x0 < r1x0 && r0x1 < r1x0)           /* outside left */
280 	|| (r0x0 > r1x1 && r0x1 > r1x1)        /* outside right */
281 	|| (r0y0 < r1y0 && r0y1 < r1y0)        /* outside above */
282 	|| (r0y0 > r1y1 && r0y1 > r1y1))       /* outside below */
283 	return 0;
284     return 1;
285 }
286 
287 static void
boardGetIntersectingPiles(int xx0,int yy0,int xx1,int yy1,int intersect[],int * n)288 boardGetIntersectingPiles(int xx0, int yy0, int xx1, int yy1,
289 			  int intersect[], int *n)
290 {
291     int  q, x0, y0, x1, y1;
292     Pile *p;
293 
294     *n = 0;
295     for (q = 0; q < NUM_PILES; q++) {
296 	if ((p = pile[q]) == NULL)
297 	    continue;
298 	boardGetPileCoords(p, &x0, &y0, &x1, &y1);
299 	if (boardRectIntersect(x0, y0, x1, y1, xx0, yy0, xx1, yy1))
300 	    intersect[(*n)++] = q;
301     }
302 }
303 
304 static void
boardLoadFont(void)305 boardLoadFont(void)
306 {
307     char *fontName[] = {
308 	"-adobe-utopia-bold-i-*-*-60-*-*-*-*-*-*-*",
309 	"-bitstream-courier-bold-i-*-*-60-*-*-*-*-*-*-*",
310 	"-*-*-*-*-*-*-60-*-*-*-*-*-iso8859-1",
311 	"12x24",
312 	"fixed"
313     };
314     int q;
315     XGCValues gcv;
316     unsigned long gcvMask = 0;
317     XColor xc, xc2;
318 
319     if (fontInfo)
320 	return;
321     for (q = 0; q < sizeof(fontName) / sizeof(char *); q++)
322 	if ((fontInfo = XLoadQueryFont(winDisplay, fontName[q])) != NULL)
323 	    break;
324     if (q == sizeof(fontName) / sizeof(char *)) {
325 	msgError("can't load a font\n");
326 	return;
327     }
328     gcv.font = fontInfo->fid;
329     gcvMask |= GCFont;
330     XAllocNamedColor(winDisplay,
331 		     DefaultColormap(winDisplay, DefaultScreen(winDisplay)),
332 		     "red", &xc, &xc2);
333     gcv.foreground = xc.pixel;
334     gcvMask |= GCForeground;
335     fontGC = XCreateGC(winDisplay, boardWin, gcvMask, &gcv);
336 }
337 
338 static void
boardUpdateText(void)339 boardUpdateText(void)
340 {
341     int w, h, len;
342 
343     if (!theText)
344 	return;
345     len = strlen(theText);
346     w = XTextWidth(fontInfo, theText, len);
347     h = fontInfo->ascent + fontInfo->descent;
348     XDrawString(winDisplay, boardWin, fontGC,
349 		(boardWidth - w) / 2,
350 		(boardHeight - h) / 2 + fontInfo->ascent,
351 		theText, len);
352     winFlush();
353 }
354 
355 
356 
357 /**************************************************************************
358  *                                                                        *
359  *                    P U B L I C    F U N C T I O N S                    *
360  *                                                                        *
361  **************************************************************************/
362 
363 void
boardInit(void)364 boardInit(void)
365 {
366     XSelectInput(winDisplay, boardWin,
367 		 ExposureMask | ButtonPressMask | ButtonReleaseMask
368 		 | PointerMotionMask);
369     multiClickTime = XtGetMultiClickTime(winDisplay);
370     boardLoadFont();
371 }
372 
373 void
boardFinish(void)374 boardFinish(void)
375 {
376     boardFinishPileCache();
377     if (fontInfo) {
378 	XFreeFont(winDisplay, fontInfo);
379 	fontInfo = NULL;
380     }
381     if (fontGC)
382 	XFreeGC(winDisplay, fontGC);
383     if (theText) {
384 	free(theText);
385 	theText = NULL;
386     }
387     XDestroyWindow(winDisplay, boardPopup);
388 }
389 
390 void
boardInitGame(void)391 boardInitGame(void)
392 {
393     int q;
394 
395     boardShowText(NULL);
396     XClearArea(winDisplay, boardWin, 0, 0, 0, 0, 1);
397     for (q = 0; q < NUM_PILES; q++)
398 	boardUpdatePileCache(pile[q]);
399 }
400 
401 void
boardFinishGame(void)402 boardFinishGame(void)
403 {
404 }
405 
406 void
boardHandleEvent(XEvent * evt)407 boardHandleEvent(XEvent *evt)
408 {
409     switch (evt->type) {
410       case Expose:
411 	{
412 	    int q, intersect[NUM_PILES], n;
413 	    static int setupDone = 0;
414 
415 	    if (!setupDone) {
416 		setupDone = 1;
417 		boardShowText("Be patient...");
418 		cardInit(winDisplay, boardWin);
419 		boardShowText(NULL);
420 		boardInitPileCache();
421 		gameInitGame();
422 		boardCreatePopup();
423 	    }
424 	    boardGetIntersectingPiles(evt->xexpose.x, evt->xexpose.y,
425 				      evt->xexpose.x + evt->xexpose.width,
426 				      evt->xexpose.y + evt->xexpose.height,
427 				      intersect, &n);
428 	    for (q = 0; q < n; q++)
429 		boardDrawPile(pile[intersect[q]], 0);
430 	    boardUpdateText();
431 	}
432 	break;
433       case ButtonPress:
434 	  {
435 	      Card *c;
436 	      int x, y;
437 	      int doubleClick;
438 	      static Card *lastCard = NULL;
439 	      static Time lastClickTime = 0;
440 
441 	      if (!gameRunning || !XtIsSensitive(boardWidget) || popupPile)
442 		  break;
443 	      x = evt->xbutton.x;
444 	      y = evt->xbutton.y;
445 	      c = boardFindCard(x, y);
446 	      if (evt->xbutton.button == 1) {
447 		  pointerX = lastPointerX = x;
448 		  pointerY = lastPointerY = y;
449 		  if (c != NULL) {
450 		      doubleClick = (c == lastCard
451 				     && evt->xbutton.time
452 				     < lastClickTime + multiClickTime);
453 		      if (doubleClick) {
454 			  gameHandleDoubleClick(c);
455 		      } else {
456 			  gameHandleSingleClick(c);
457 			  lastCard = c;
458 			  lastClickTime = evt->xbutton.time;
459 		      }
460 		  } else if (x >= pile[0]->x
461 			     && x <= pile[0]->x + CARD_WIDTH
462 			     && y >= pile[0]->y
463 			     && y <= pile[0]->y + CARD_HEIGHT){
464 		      gameDrawNext();
465 		  }
466 	      } else if (evt->xbutton.button == 2
467 			 || evt->xbutton.button == 3) {
468 		  /* to be a little bit helpful, allow other buttons to work
469 		   * as doubleclicks. */
470 		  if (c != NULL)
471 		      gameHandleDoubleClick(c);
472 	      }
473 	  }
474 	break;
475       case ButtonRelease:
476 	if (!gameRunning || !XtIsSensitive(boardWidget))
477 	    break;
478 	if (evt->xbutton.button == 1)
479 	    boardStopDrag();
480 	break;
481       case MotionNotify:
482 	if (popupPile) {
483 	    int dx, dy;
484 
485 	    dx = evt->xmotion.x - lastPointerX;
486 	    dy = evt->xmotion.y - lastPointerY;
487 	    pointerX = evt->xmotion.x;
488 	    pointerY = evt->xmotion.y;
489 	    if (dx * dx + dy * dy >= dragQuantSquared) {
490 		lastPointerX = pointerX;
491 		lastPointerY = pointerY;
492 		XMoveWindow(winDisplay, boardPopup,
493 			    pointerX - popupDeltaX, pointerY - popupDeltaY);
494 	    }
495 	}
496 	break;
497     }
498 }
499 
500 void
boardHandlePopupEvent(XEvent * evt)501 boardHandlePopupEvent(XEvent *evt)
502 {
503     switch (evt->type) {
504       case Expose:
505 	if (evt->xexpose.count)
506 	    break;
507 	boardDrawPile(popupPile, 1);
508 	break;
509     }
510 }
511 
512 void
boardDrawPile(Pile * p,int popup)513 boardDrawPile(Pile *p, int popup)
514 {
515     int pileNum;
516 
517     if (!p)
518 	return;
519     if (popup)
520 	boardDrawPileToPixmap(p, boardPopup);
521     else {
522 	pileNum = gameFindPileNumOfPile(p);
523 	XCopyArea(winDisplay, pileCache[pileNum], boardWin, pileCacheGC,
524 		  0, 0, p->maxWidth, p->maxHeight, p->x, p->y);
525     }
526 }
527 
528 void
boardUpdatePileCache(Pile * p)529 boardUpdatePileCache(Pile *p)
530 {
531     if (!p)
532 	return;
533     boardDrawPileToPixmap(p, pileCache[gameFindPileNumOfPile(p)]);
534 }
535 
536 void
boardStartDragCardAndAbove(Card * c)537 boardStartDragCardAndAbove(Card *c)
538 {
539     int px, py, x0, y0, x1, y1;
540 
541     boardGetCardPos(c, &px, &py);
542     popupDeltaX = pointerX - px;
543     popupDeltaY = pointerY - py;
544     popupFromPile = c->pile;
545     popupPile = pileMoveToNewPileFromCard(popupFromPile, c);
546     boardUpdatePileCache(popupFromPile);
547     boardDrawPile(popupFromPile, 0);
548     boardGetPileCoords(popupPile, &x0, &y0, &x1, &y1);
549     popupWidth = x1 - x0;
550     popupHeight = y1 - y0;
551     XResizeWindow(winDisplay, boardPopup, popupWidth, popupHeight);
552     XMoveWindow(winDisplay, boardPopup, px, py);
553     XMapRaised(winDisplay, boardPopup);
554 }
555 
556 void
boardStopDrag(void)557 boardStopDrag(void)
558 {
559     int  intersect[NUM_PILES], n;
560     int  popx0, popy0, popx1, popy1;
561     Pile *p;
562 
563     if (!popupPile)
564 	return;
565     XUnmapWindow(winDisplay, boardPopup);
566 
567     popx0 = lastPointerX - popupDeltaX;
568     popy0 = lastPointerY - popupDeltaY;
569     popx1 = popx0 + popupWidth;
570     popy1 = popy0 + popupHeight;
571     boardGetIntersectingPiles(popx0, popy0, popx1, popy1, intersect, &n);
572     p = gameCardDropped(popupFromPile, popupPile, intersect, n);
573     pileAddPileTop(p, popupPile);
574     boardUpdatePileCache(p);
575     boardDrawPile(p, 0);
576     pileDelete(popupPile);
577     popupPile = NULL;
578     gameCheckIfSolution();
579 }
580 
581 void
boardShowText(const char * s)582 boardShowText(const char *s)
583 {
584     if (!fontInfo) {
585 	printf("%s\n", s);
586 	return;
587     }
588     if (theText)
589 	free(theText);
590     if (!s || *s == '\0') {
591 	theText = NULL;
592 	return;
593     }
594     theText = xstrdup(s);
595     boardUpdateText();
596 }
597