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