43ca2cd4bb7c34c435c36bc6c31a6781764a24ca
[xscreensaver] / hacks / pacman.c
1 /* -*- Mode: C; tab-width: 4 -*- */
2 /* pacman --- Mr. Pacman and his ghost friends */
3
4 #if !defined( lint ) && !defined( SABER )
5 static const char sccsid[] = "@(#)pacman.c      5.00 2000/11/01 xlockmore";
6
7 #endif
8
9 /*-
10  * Copyright (c) 2002 by Edwin de Jong <mauddib@gmx.net>.
11  *
12  * Permission to use, copy, modify, and distribute this software and its
13  * documentation for any purpose and without fee is hereby granted,
14  * provided that the above copyright notice appear in all copies and that
15  * both that copyright notice and this permission notice appear in
16  * supporting documentation.
17  *
18  * This file is provided AS IS with no warranties of any kind.  The author
19  * shall have no liability with respect to the infringement of copyrights,
20  * trade secrets or any patents by this file or any part thereof.  In no
21  * event will the author be liable for any lost revenue or profits or
22  * other special, indirect and consequential damages.
23  *
24  * Revision History:
25  * 13-May-2002: Added -trackmouse feature thanks to code from 'maze.c'.  
26  *              splitted up code into several files.  Retouched AI code, cleaned
27  *              up code.
28  *  3-May-2002: Added AI to pacman and ghosts, slowed down ghosts.
29  * 26-Nov-2001: Random level generator added
30  * 01-Nov-2000: Allocation checks
31  * 04-Jun-1997: Compatible with xscreensaver
32  *
33  */
34
35 /* TODO:
36    1. add "bonus" dots
37    2. make better ghost sprites (eyes, waving dress)
38    3. make a bit better pacman sprite (mouth should be larger)
39    4. think of a better level generation algorithm
40 */
41
42 #ifdef STANDALONE
43 #       define MODE_pacman
44 #       define PROGCLASS "Pacman"
45 #       define HACK_INIT init_pacman
46 #       define HACK_DRAW draw_pacman
47 #       define pacman_opts xlockmore_opts
48 #       define DEFAULTS "*delay: 10000 \n" \
49                                         "*size: 0 \n" \
50                                         "*ncolors: 6 \n" \
51                                         "*trackmouse: False \n"
52 #       define UNIFORM_COLORS
53 #       define BRIGHT_COLORS
54 #       include "xlockmore.h"           /* in xscreensaver distribution */
55 #else /* STANDALONE */
56 #       include "xlock.h"               /* in xlockmore distribution */
57 #endif /* STANDALONE */
58
59 #ifdef MODE_pacman
60
61 #include "pacman.h"
62 #include "pacman_ai.h"
63 #include "pacman_level.h"
64
65 #ifdef DISABLE_INTERACTIVE
66 ModeSpecOpt pacman_opts = {
67         0, 
68         (XrmOptionDescRec *) NULL, 
69         0, 
70         (argtype *) NULL, 
71         (OptionStruct *) NULL
72 };
73 #else
74 static XrmOptionDescRec opts[] =
75 {
76         {(char *) "-trackmouse", (char *) ".pacman.trackmouse", XrmoptionNoArg,
77                 (caddr_t) "on"},
78         {(char *) "+trackmouse", (char *) ".pacman.trackmouse", XrmoptionNoArg,
79                 (caddr_t) "off"}
80 };
81
82 static argtype vars[] =
83 {
84         {(caddr_t *) & trackmouse, (char *) "trackmouse", (char *) "TrackMouse",
85                 (char *) DEF_TRACKMOUSE, t_Bool}
86 };
87
88 static OptionStruct desc[] =
89 {
90         {(char *) "-/+trackmouse", (char *) "turn on/off the tracking of the "
91                 "mouse"}
92 };
93
94 ModeSpecOpt pacman_opts =
95 {sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};
96 #endif
97
98 #ifdef USE_MODULES
99 ModStruct   pacman_description = { 
100         "pacman",               /* *cmdline_arg; */
101         "init_pacman",          /* *init_name; */
102         "draw_pacman",          /* *callback_name; */
103         "release_pacman",       /* *release_name; */
104         "refresh_pacman",       /* *refresh_name; */
105         "change_pacman",        /* *change_name; */
106         (char *) NULL,          /* *unused_name; */
107         &pacman_opts,           /* *msopts */
108         10000, 4, 1, 0, 64, 1.0, "", "Shows Pacman(tm)", 0, NULL
109 };
110
111 #endif
112 static void
113 free_pacman(Display *display, pacmangamestruct *pp)
114 {
115         int         dir, mouth;
116
117         if (pp->ghosts != NULL) {
118                 free(pp->ghosts);
119                 pp->ghosts = (ghoststruct *) NULL;
120         }
121         if (pp->stippledGC != None) {
122                 XFreeGC(display, pp->stippledGC);
123                 pp->stippledGC = None;
124         }
125         if (pp->ghostPixmap != None) {
126                 XFreePixmap(display, pp->ghostPixmap);
127                 pp->ghostPixmap = None;
128         }
129         for (dir = 0; dir < 4; dir++)
130                 for (mouth = 0; mouth < MAXMOUTH; mouth++)
131                         if (pp->pacmanPixmap[dir][mouth] != None) {
132                                 XFreePixmap(display, 
133                                         pp->pacmanPixmap[dir][mouth]);
134                                 pp->pacmanPixmap[dir][mouth] = None;
135                         }
136 }
137
138 /* Checks for death of any ghosts/pacman and updates.  It also makes a new
139    level if all ghosts are dead or all dots are eaten. */
140 static void
141 check_death(ModeInfo * mi, pacmangamestruct *pp)
142 {
143         Display *display =      MI_DISPLAY(mi);
144         Window   window  =      MI_WINDOW(mi);
145         unsigned int    ghost;
146         int     alldead;
147
148         alldead = 1;
149         for (ghost = 0; ghost < pp->nghosts; ghost++) {
150                 if (pp->ghosts[ghost].dead == True)
151                         continue;
152
153                 if ((pp->ghosts[ghost].nextrow == NOWHERE &&
154                      pp->ghosts[ghost].nextcol == NOWHERE)) {
155                         alldead = 0;
156                         continue;
157                 }
158
159                 if (((pp->ghosts[ghost].nextrow == pp->pacman.nextrow) &&
160                 (pp->ghosts[ghost].nextcol == pp->pacman.nextcol)) ||
161                     ((pp->ghosts[ghost].nextrow == pp->pacman.row) &&
162                      (pp->ghosts[ghost].nextcol == pp->pacman.col) &&
163                      (pp->ghosts[ghost].row == pp->pacman.nextrow) &&
164                      (pp->ghosts[ghost].col == pp->pacman.nextcol))) {
165                         pp->ghosts[ghost].dead = 1;
166                         XSetForeground(display, 
167                                              pp->stippledGC, 
168                                              MI_BLACK_PIXEL(mi));
169                         XFillRectangle(display, window, 
170                                             pp->stippledGC,
171                                             pp->ghosts[ghost].cf, 
172                                             pp->ghosts[ghost].rf, 
173                                             pp->spritexs, 
174                                             pp->spriteys);
175
176                 } else
177                         alldead = 0;
178         }
179
180         if (alldead == 1 || pp->dotsleft == 0)
181                 repopulate(mi);
182 }
183
184 /* Resets state of ghosts + pacman.  Creates a new level, draws that level. */
185 static void
186 repopulate(ModeInfo * mi)
187 {
188         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
189         unsigned int ghost;
190         int i = createnewlevel(mi);
191
192         MI_CLEARWINDOW(mi);
193         drawlevel(mi);
194
195         pp->gamestate = GHOST_DANGER;
196
197         pp->pacman.row = (LEVHEIGHT + JAILHEIGHT)/2 - i;
198         pp->pacman.col = (LEVWIDTH/2);
199         pp->pacman.nextrow = NOWHERE; 
200         pp->pacman.nextcol = NOWHERE;
201         pp->pacman.cf = NOWHERE;
202         pp->pacman.rf = NOWHERE;
203         pp->pacman.oldcf = NOWHERE;
204         pp->pacman.oldrf = NOWHERE;
205         pp->pacman.oldlx = NOWHERE;
206         pp->pacman.oldly = NOWHERE;
207         pp->pacman.aistate = ps_eating;
208         pp->pacman.cur_trace = 0;
209         pp->pacman.roundscore = 0;
210         pp->pacman.speed = 4;
211         pp->pacman.lastturn = 0;
212         pp->pacman.delta.x = 0;
213         pp->pacman.delta.y = 0;
214         pac_clear_trace(&(pp->pacman));
215
216         for (ghost = 0; ghost < pp->nghosts; ghost++) {
217                 pp->ghosts[ghost].col = (LEVWIDTH/2);
218                 pp->ghosts[ghost].row = (LEVHEIGHT/2);
219                 pp->ghosts[ghost].nextcol = NOWHERE;
220                 pp->ghosts[ghost].nextrow = NOWHERE;
221                 pp->ghosts[ghost].dead = 0;
222                 pp->ghosts[ghost].lastbox = START;
223                 pp->ghosts[ghost].cf = NOWHERE;
224                 pp->ghosts[ghost].rf = NOWHERE;
225                 pp->ghosts[ghost].oldcf = NOWHERE;
226                 pp->ghosts[ghost].oldrf = NOWHERE;
227                 pp->ghosts[ghost].aistate = inbox;
228                 pp->ghosts[ghost].timeleft = ghost * 50;
229                 pp->ghosts[ghost].speed = 3;
230                 pp->ghosts[ghost].delta.x = 0;
231                 pp->ghosts[ghost].delta.y = 0;
232
233                 ghost_update(pp, &(pp->ghosts[ghost]));
234         }
235         check_death(mi, pp);
236         pac_update(mi, pp, &(pp->pacman));
237 }
238
239 /* Sets the color to the color of a wall. */
240 static void 
241 setwallcolor(ModeInfo * mi) 
242 {
243         Display    *display = MI_DISPLAY(mi);
244         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
245
246         if (MI_NPIXELS(mi) > 2)
247                 XSetForeground(display, pp->stippledGC, 
248                                 MI_PIXEL(mi, BLUE));
249         else
250                 XSetForeground(display, pp->stippledGC, 
251                                 MI_WHITE_PIXEL(mi));
252 }
253
254 /* Sets the color to the color of a dot. */
255 static void 
256 setdotcolor(ModeInfo * mi) 
257 {
258         Display    *display = MI_DISPLAY(mi);
259         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
260
261         XSetForeground(display, pp->stippledGC, 
262                         MI_WHITE_PIXEL(mi));
263 }
264
265 /* Draws a block in the level at the specified x and y locations. */
266 static void
267 drawlevelblock(ModeInfo * mi, pacmangamestruct *pp, 
268                 const unsigned x, const unsigned y)
269 {
270         Display    *display = MI_DISPLAY(mi);
271         Window      window = MI_WINDOW(mi);
272         int dx = 0, dy = 0;
273
274         if (pp->xs % 2 == 1) dx = -1;
275         if (pp->ys % 2 == 1) dy = -1;
276
277         XSetFillStyle(display, pp->stippledGC, FillSolid);
278         XSetLineAttributes(display, pp->stippledGC, pp->wallwidth,
279                         LineSolid, CapRound, JoinMiter);
280
281         if (pp->xs < 2 || pp->ys < 2) {
282                 switch(pp->level[y*LEVWIDTH + x]) {
283                         case ' ':
284                         case '=':
285                                 break;
286                         case '.':
287                                 setdotcolor(mi);
288                                 (void)XDrawPoint(display, window,
289                                                  pp->stippledGC,
290                                                  x * pp->xs + pp->xb, 
291                                                  y * pp->ys + pp->yb);
292                                 break;
293                         default:
294                                 setwallcolor(mi);
295                                 (void)XDrawPoint(display, window,
296                                                  pp->stippledGC,
297                                                  x * pp->xs + pp->xb, 
298                                                  y * pp->ys + pp->yb);
299                 }
300
301                 return;
302         }
303
304         switch (pp->level[y*LEVWIDTH + x]) {
305                 case ' ':
306                 case '=':
307                         break;
308
309                 case '.':
310                         setdotcolor(mi);
311                         if (pp->xs < 8 || pp->ys < 8) {
312                                 (void)XDrawPoint(display, window,
313                                                  pp->stippledGC,
314                                                  x * pp->xs + pp->xb +
315                                                  pp->xs/2, 
316                                                  y * pp->ys + pp->yb +
317                                                  pp->ys/2);
318                                 break;
319                         }
320
321                         (void)XDrawArc(display, window, pp->stippledGC,
322                                 (pp->xs * x) + 
323                                         (pp->xs / 2) - 
324                                         (pp->xs > 32 ? (pp->xs / 16) : 1) +
325                                         pp->xb,
326                                 (pp->ys * y) + 
327                                         (pp->ys / 2) -
328                                         (pp->ys > 32 ? (pp->ys / 16) : 1) +
329                                         pp->yb,
330                                 (pp->xs > 32 ? (pp->xs / 32) : 1),
331                                 (pp->ys > 32 ? (pp->ys / 32) : 1),
332                                 0, 23040);
333                         break;
334
335                 case '-':
336                         setwallcolor(mi);
337                         (void)XDrawLine(display, window, pp->stippledGC,
338                                 (pp->xs * x) + pp->xb,
339                                 (pp->ys * y) + (pp->ys / 2) + pp->yb,
340                                 (pp->xs * (x + 1)) + pp->xb,
341                                 (pp->ys * y) + (pp->ys / 2) + pp->yb);
342                         break;
343
344                 case '|':
345                         setwallcolor(mi);
346                         (void)XDrawLine(display, window, pp->stippledGC,
347                                 (pp->xs * x) + (pp->xs / 2) + pp->xb,
348                                 (pp->ys * y) + pp->yb,
349                                 (pp->xs * x) + (pp->xs / 2) + pp->xb,
350                                 (pp->ys * (y + 1)) + pp->yb);
351                         break;
352
353                 case '_':
354                         setwallcolor(mi);
355                         (void)XDrawArc(display, window, pp->stippledGC,
356                                 (pp->xs * x) - (pp->ys / 2) + pp->xb + dx,
357                                 (pp->ys * y) + (pp->ys / 2) + pp->yb,
358                                 pp->xs, pp->ys,
359                                 0*64, 90*64);
360                         break;
361
362                 case ',':
363                         setwallcolor(mi);
364                         (void)XDrawArc(display, window, pp->stippledGC,
365                                 (pp->xs * x) + (pp->ys / 2) + pp->xb,
366                                 (pp->ys * y) + (pp->ys / 2) + pp->yb,
367                                 pp->xs, pp->ys,
368                                 90*64, 90*64);
369                         break;
370
371                 case '`':
372                         setwallcolor(mi);
373                         (void)XDrawArc(display, window, pp->stippledGC,
374                                 (pp->xs * x) + (pp->ys / 2) + pp->xb,
375                                 (pp->ys * y) - (pp->ys / 2) + pp->yb + dy,
376                                 pp->xs, pp->ys,
377                                 180*64, 90*64);
378                         break;
379
380                 case '\'':
381                         setwallcolor(mi);
382                         (void)XDrawArc(display, window, pp->stippledGC,
383                                 (pp->xs * x) - (pp->ys / 2) + pp->xb + dx,
384                                 (pp->ys * y) - (pp->ys / 2) + pp->yb + dy,
385                                 pp->xs, pp->ys,
386                                 270*64, 90*64);
387                         break;
388
389         }
390 }
391
392 /* Draws a complete level. */
393 static void
394 drawlevel(ModeInfo * mi)
395
396         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
397         unsigned int x, y;
398
399         for (y = 0; y < LEVHEIGHT; y++)
400                 for (x = 0; x < LEVWIDTH; x++)
401                         drawlevelblock(mi, pp, x, y);
402 }
403
404 /* There is some overlap so it can be made more efficient */
405 #define ERASE_IMAGE(d,w,g,x,y,xl,yl,xs,ys) \
406  if (yl<y) \
407   (y<(yl+ys))?XFillRectangle(d,w,g,xl,yl,xs,y-(yl)): \
408   XFillRectangle(d,w,g,xl,yl,xs,ys); \
409  else if (yl>y) \
410   (y>(yl-(ys)))?XFillRectangle(d,w,g,xl,y+ys,xs,yl-(y)): \
411   XFillRectangle(d,w,g,xl,yl,xs,ys); \
412  if (xl<x) \
413   (x<(xl+xs))?XFillRectangle(d,w,g,xl,yl,x-(xl),ys): \
414   XFillRectangle(d,w,g,xl,yl,xs,ys); \
415  else if (xl>x) \
416   (x>(xl-(xs)))?XFillRectangle(d,w,g,x+xs,yl,xl-(x),ys): \
417   XFillRectangle(d,w,g,xl,yl,xs,ys)
418
419
420 /* Draws the pacman sprite, removing the previous location. */
421 static void
422 draw_pacman_sprite(ModeInfo * mi)
423 {
424         Display *display = MI_DISPLAY(mi);
425         Window window  = MI_WINDOW(mi);
426         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
427         unsigned int dir;
428
429         pp->pacman.cf = pp->pacman.col * pp->xs + pp->pacman.delta.x * 
430                 pp->pacman.cfactor + pp->xb + pp->spritedx;
431         pp->pacman.rf = pp->pacman.row * pp->ys + pp->pacman.delta.y * 
432                 pp->pacman.rfactor + pp->yb + pp->spritedy;
433
434         dir = (ABS(pp->pacman.cfactor) * (2 - pp->pacman.cfactor) +
435                ABS(pp->pacman.rfactor) * (1 + pp->pacman.rfactor)) % 4;
436
437         XSetForeground(display, pp->stippledGC, 
438                              MI_BLACK_PIXEL(mi));
439         if (pp->pacman.oldcf != NOWHERE && pp->pacman.oldrf != NOWHERE) {
440 #ifdef FLASH
441                 XFillRectangle(display, window, pp->stippledGC,
442                              pp->pacman.oldcf, pp->pacman.oldrf, 
443                              pp->spritexs, pp->spriteys);
444 #else
445                 ERASE_IMAGE(display, window, pp->stippledGC, 
446                             pp->pacman.cf,    pp->pacman.rf,
447                             pp->pacman.oldcf, pp->pacman.oldrf, 
448                             pp->spritexs, pp->spriteys);
449 #endif
450         }
451
452         XSetTSOrigin(display, pp->stippledGC, 
453                            pp->pacman.cf, pp->pacman.rf);
454         if (MI_NPIXELS(mi) > 2)
455                 XSetForeground(display, pp->stippledGC, 
456                                      MI_PIXEL(mi, YELLOW));
457         else
458                 XSetForeground(display, pp->stippledGC, 
459                                      MI_WHITE_PIXEL(mi));
460
461         XSetStipple(display, pp->stippledGC,
462                     pp->pacmanPixmap[dir][pp->pacman.mouthstage]);
463 #ifdef FLASH
464         XSetFillStyle(display, pp->stippledGC, FillStippled);
465 #else
466         XSetFillStyle(display, pp->stippledGC, 
467                             FillOpaqueStippled);
468 #endif
469         if (pp->xs < 2 || pp->ys < 2)
470                 XDrawPoint(display, window, pp->stippledGC,
471                                 pp->pacman.cf, pp->pacman.rf);
472         else
473                 XFillRectangle(display, window, pp->stippledGC,
474                                pp->pacman.cf, pp->pacman.rf, 
475                                pp->spritexs, pp->spriteys);
476         pp->pacman.mouthstage += pp->pacman.mouthdirection;
477         if ((pp->pacman.mouthstage >= MAXMOUTH) ||
478             (pp->pacman.mouthstage < 0)) {
479                 pp->pacman.mouthdirection *= -1;
480                 pp->pacman.mouthstage += pp->pacman.mouthdirection * 2;
481         }
482         pp->pacman.oldcf = pp->pacman.cf;
483         pp->pacman.oldrf = pp->pacman.rf;
484 }
485
486 /* Draws a ghost sprite, removing the previous sprite and restores the level. */
487 static void
488 draw_ghost_sprite(ModeInfo * mi, const unsigned ghost) {
489         Display    *display = MI_DISPLAY(mi);
490         Window      window = MI_WINDOW(mi);
491         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
492
493         pp->ghosts[ghost].cf = 
494                 pp->ghosts[ghost].col * pp->xs + pp->ghosts[ghost].delta.x * 
495                 pp->ghosts[ghost].cfactor + pp->xb + pp->spritedx;
496         pp->ghosts[ghost].rf = 
497                 pp->ghosts[ghost].row * pp->ys + pp->ghosts[ghost].delta.y * 
498                 pp->ghosts[ghost].rfactor + pp->yb + pp->spritedy;
499         
500         XSetForeground(display, 
501                              pp->stippledGC, 
502                              MI_BLACK_PIXEL(mi));
503
504         if (pp->ghosts[ghost].oldcf != NOWHERE ||
505                 pp->ghosts[ghost].oldrf != NOWHERE) {
506 #ifdef FLASH
507                 XFillRectangle(display, window, 
508                                     pp->stippledGC, pp->ghosts[ghost].oldcf, 
509                                     pp->ghosts[ghost].oldrf, 
510                                     pp->spritexs, pp->spriteys);
511 #else
512                 ERASE_IMAGE(display, window, pp->stippledGC,
513                         pp->ghosts[ghost].cf, pp->ghosts[ghost].rf,
514                         pp->ghosts[ghost].oldcf, pp->ghosts[ghost].oldrf, 
515                         pp->spritexs, pp->spriteys);
516 #endif
517         }
518
519         drawlevelblock(mi, pp, 
520                 (unsigned int)pp->ghosts[ghost].col, 
521                 (unsigned int)pp->ghosts[ghost].row);
522
523         XSetTSOrigin(display, pp->stippledGC,
524                 pp->ghosts[ghost].cf, pp->ghosts[ghost].rf);
525
526         if (MI_NPIXELS(mi) > 2)
527                 XSetForeground(display, 
528                      pp->stippledGC, 
529                      MI_PIXEL(mi, GREEN));
530         else
531                 XSetForeground(display, 
532                      pp->stippledGC, 
533                      MI_WHITE_PIXEL(mi));
534
535         XSetStipple(display, pp->stippledGC, 
536                           pp->ghostPixmap);
537
538 #ifdef FLASH
539         XSetFillStyle(display, 
540                             pp->stippledGC, 
541                             FillStippled);
542 #else
543         XSetFillStyle(display, 
544                             pp->stippledGC, 
545                             FillOpaqueStippled);
546 #endif
547         if (pp->xs < 2 || pp->ys < 2)
548                 XDrawPoint(display, window, pp->stippledGC,
549                                 pp->ghosts[ghost].cf,
550                                 pp->ghosts[ghost].rf);
551         else
552                 XFillRectangle(display, 
553                                      window, 
554                                      pp->stippledGC,
555                                      pp->ghosts[ghost].cf, 
556                                      pp->ghosts[ghost].rf, 
557                                      pp->spritexs, pp->spriteys);
558
559         pp->ghosts[ghost].oldcf = pp->ghosts[ghost].cf;
560         pp->ghosts[ghost].oldrf = pp->ghosts[ghost].rf;
561 }
562
563 /* Does all drawing of moving sprites in the level. */
564 static void
565 pacman_tick(ModeInfo * mi)
566 {
567         Display    *display = MI_DISPLAY(mi);
568         pacmangamestruct *pp = &pacmangames[MI_SCREEN(mi)];
569         unsigned int ghost;
570
571         for (ghost = 0; ghost < pp->nghosts; ghost++) {
572                 if (pp->ghosts[ghost].dead == True)
573                         continue;
574                 draw_ghost_sprite(mi, ghost);
575         }
576
577         draw_pacman_sprite(mi);
578
579         (void)XFlush(display);
580 }
581
582 /* Hook function, sets state to initial position. */
583 void
584 init_pacman(ModeInfo * mi)
585 {
586         Display    *display = MI_DISPLAY(mi);
587         Window      window = MI_WINDOW(mi);
588         int         size = MI_SIZE(mi);
589         pacmangamestruct *pp;
590         XGCValues   gcv;
591         int         dir, mouth;
592         GC          fg_gc, bg_gc;
593         XPoint      points[9];
594
595         if (pacmangames == NULL) {
596                 if ((pacmangames = (pacmangamestruct *) 
597                                         calloc((size_t)MI_NUM_SCREENS(mi),
598                                          sizeof (pacmangamestruct))) == NULL)
599                         return;
600         }
601         pp = &pacmangames[MI_SCREEN(mi)];
602
603         pp->width = (unsigned short)MI_WIDTH(mi);
604         pp->height = (unsigned short)MI_HEIGHT(mi);
605         if (pp->ghostPixmap != None) {
606                 XFreePixmap(display, pp->ghostPixmap);
607                 pp->ghostPixmap = None;
608                 pp->graphics_format = 0 /*IS_NONE*/;
609         }
610
611         if (size == 0 ||
612                 MINGRIDSIZE * size > (int)pp->width ||
613                 MINGRIDSIZE * size > (int)pp->height) {
614
615                 pp->ys = pp->xs = MAX(MIN(pp->width/LEVWIDTH, 
616                                 pp->height/LEVHEIGHT), 1);
617         } else {
618                 if (size < -MINSIZE)
619                         pp->ys = (short)(NRAND( MIN( -size, MAX( MINSIZE, 
620                                    MIN( pp->width, pp->height) / MINGRIDSIZE)) 
621                                         - MINSIZE + 1) + MINSIZE);
622                 else if (size < MINSIZE)
623                         pp->ys = MINSIZE;
624                 else
625                         pp->ys = (short)(MIN(size, 
626                                      MAX(MINSIZE, MIN(pp->width, pp->height) /
627                                           MINGRIDSIZE)));
628                 pp->xs = pp->ys;
629         }
630
631         pp->wallwidth = (unsigned int)(pp->xs + pp->ys) >> 4;
632         if (pp->wallwidth < 1) pp->wallwidth = 1;
633         pp->incx = (pp->xs >> 3) + 1;
634         pp->incy = (pp->ys >> 3) + 1;
635         pp->ncols = (unsigned short)MAX(LEVWIDTH, 2);
636         pp->nrows = (unsigned short)MAX(LEVHEIGHT, 2);
637         pp->xb = (pp->width - pp->ncols * pp->xs) >> 1;
638         pp->yb = (pp->height - pp->nrows * pp->ys) >> 1;
639         pp->spritexs = MAX(pp->xs + (pp->xs >> 1) - 1, 1);
640         pp->spriteys = MAX(pp->ys + (pp->ys >> 1) - 1, 1);
641         pp->spritedx = (pp->xs - pp->spritexs) >> 1;
642         pp->spritedy = (pp->ys - pp->spriteys) >> 1;
643
644         if ((pp->ghostPixmap = XCreatePixmap(display, window,
645                 pp->spritexs, pp->spriteys, 1)) == None) {
646                 free_pacman(display, pp);
647                 return;
648         }
649
650         gcv.foreground = 0;
651         gcv.background = 1;
652         if ((bg_gc = XCreateGC(display, pp->ghostPixmap,
653                         GCForeground | GCBackground, &gcv)) == None) {
654                 free_pacman(display, pp);
655                 return;
656         }
657
658         gcv.foreground = 1;
659         gcv.background = 0;
660         if ((fg_gc = XCreateGC(display, pp->ghostPixmap,
661                         GCForeground | GCBackground, &gcv)) == None) {
662                 XFreeGC(display, bg_gc);
663                 free_pacman(display, pp);
664                 return;
665         }
666
667 #define SETPOINT(p, xp, yp) p.x = xp; p.y = yp
668
669         /* draw the triangles on the bottom (scalable) */
670         SETPOINT(points[0], 1,                          pp->spriteys * 5 / 6);
671         SETPOINT(points[1], pp->spritexs / 6,           pp->spriteys);
672         SETPOINT(points[2], pp->spritexs / 3,           pp->spriteys * 5 / 6);
673         SETPOINT(points[3], pp->spritexs / 2,           pp->spriteys);
674         SETPOINT(points[4], pp->spritexs * 2 / 3,       pp->spriteys * 5 / 6);
675         SETPOINT(points[5], pp->spritexs * 5 / 6,       pp->spriteys);
676         SETPOINT(points[6], pp->spritexs,               pp->spriteys * 5 / 6);
677         SETPOINT(points[7], pp->spritexs,               pp->spriteys / 2);
678         SETPOINT(points[8], 1,                          pp->spriteys / 2);
679
680         XFillRectangle(display, pp->ghostPixmap, bg_gc,
681                 0, 0, pp->spritexs, pp->spriteys);
682         XFillArc(display, pp->ghostPixmap, fg_gc,
683                 0, 0, pp->spritexs, pp->spriteys, 0, 11520);
684         XFillPolygon(display, pp->ghostPixmap, fg_gc,
685                 points, 9, Nonconvex, CoordModeOrigin);
686         XFreeGC(display, bg_gc);
687         XFreeGC(display, fg_gc);
688
689         if (!pp->stippledGC) {
690                 gcv.foreground = MI_BLACK_PIXEL(mi);
691                 gcv.background = MI_BLACK_PIXEL(mi);
692                 if ((pp->stippledGC = XCreateGC(display, window,
693                                 GCForeground | GCBackground, &gcv)) == None) {
694                         free_pacman(display, pp);
695                         return;
696                 }
697         }
698         if (pp->pacmanPixmap[0][0] != None)
699                 for (dir = 0; dir < 4; dir++)
700                         for (mouth = 0; mouth < MAXMOUTH; mouth++)
701                                 XFreePixmap(display, 
702                                                   pp->pacmanPixmap[dir]
703                                                         [mouth]);
704
705         for (dir = 0; dir < 4; dir++)
706                 for (mouth = 0; mouth < MAXMOUTH; mouth++) {
707                         if ((pp->pacmanPixmap[dir][mouth] = XCreatePixmap(
708                                         display, MI_WINDOW(mi), 
709                                         pp->spritexs, pp->spriteys, 1)) == 
710                                         None) {
711                                 free_pacman(display, pp);
712                                 return;
713                         }
714                         gcv.foreground = 1;
715                         gcv.background = 0;
716                         if ((fg_gc = XCreateGC(display, pp->pacmanPixmap[dir][mouth],
717                                         GCForeground | GCBackground, &gcv)) == None) {
718                                 free_pacman(display, pp);
719                                 return;
720                         }
721                         gcv.foreground = 0;
722                         gcv.background = 0;
723                         if ((bg_gc = XCreateGC(display, 
724                                                 pp->pacmanPixmap[dir][mouth],
725                                                 GCForeground | 
726                                                 GCBackground, &gcv)) == 
727                                         None) {
728                                 XFreeGC(display, fg_gc);
729                                 free_pacman(display, pp);
730                                 return;
731                         }
732                         XFillRectangle(display, 
733                                         pp->pacmanPixmap[dir][mouth], bg_gc,
734                                         0, 0, pp->spritexs, pp->spriteys);
735                         if (pp->spritexs == 1 && pp->spriteys == 1)
736                                 XFillRectangle(display, 
737                                              pp->pacmanPixmap[dir][mouth], 
738                                              fg_gc, 0, 0, pp->spritexs, 
739                                              pp->spriteys);
740                         else
741                                 XFillArc(display, 
742                                                pp->pacmanPixmap[dir][mouth],
743                                                fg_gc,
744                                         0, 0, pp->spritexs, pp->spriteys,
745                                         ((90 - dir * 90) + mouth * 5) * 64,
746                                         (360 + (-2 * mouth * 5)) * 64);
747                         XFreeGC(display, fg_gc);
748                         XFreeGC(display, bg_gc);
749                 }
750
751         pp->pacman.lastbox = START;
752         pp->pacman.mouthdirection = 1;
753         pp->pacman.nextcol = NOWHERE;
754         pp->pacman.nextrow = NOWHERE;
755
756         if (pp->ghosts != NULL) {
757                         free(pp->ghosts);
758                         pp->ghosts = (ghoststruct *) NULL;
759         }
760         pp->nghosts = GHOSTS;
761
762         if (!pp->ghosts)
763                 if ((pp->ghosts = (ghoststruct *) calloc((size_t)pp->nghosts,
764                                 sizeof (ghoststruct))) == NULL) {
765                         free_pacman(display, pp);
766                         return;
767                 }
768
769         pp->pacman.mouthstage = MAXMOUTH - 1;
770
771         MI_CLEARWINDOW(mi);
772         repopulate(mi);
773 }
774
775 /* Callback function called for each tick.  This is the complete machinery of
776    everything that moves. */
777 void
778 draw_pacman(ModeInfo * mi)
779 {
780         unsigned int g;
781         pacmangamestruct *pp;
782
783         if (pacmangames == NULL)
784                 return;
785         pp = &pacmangames[MI_SCREEN(mi)];
786         if (pp->ghosts == NULL)
787                 return;
788
789         pp->pacman.err.x = (pp->pacman.err.x + 1) % pp->pacman.speed;
790         pp->pacman.err.y = (pp->pacman.err.y + 1) % pp->pacman.speed;
791         pp->pacman.delta.x += pp->pacman.err.x != 0 ? pp->incx : 0;
792         pp->pacman.delta.y += pp->pacman.err.y != 0 ? pp->incy : 0;
793
794         if (pp->pacman.delta.x >= pp->xs && pp->pacman.delta.y >= pp->ys) {
795                 pac_update(mi, pp, &(pp->pacman));
796                 check_death(mi, pp);
797                 pp->pacman.delta.x = pp->incx;
798                 pp->pacman.delta.y = pp->incy;
799         }
800
801         if (pp->pacman.delta.x > pp->xs + pp->incx)
802                 pp->pacman.delta.x = pp->xs + pp->incx;
803         if (pp->pacman.delta.y > pp->ys + pp->incy)
804                 pp->pacman.delta.y = pp->ys + pp->incy;
805
806         for (g = 0; g < pp->nghosts; g++) {
807                 if (pp->ghosts[g].dead == True) continue;
808
809                 pp->ghosts[g].err.x = (pp->ghosts[g].err.x + 1) % 
810                         pp->ghosts[g].speed;
811                 pp->ghosts[g].err.y = (pp->ghosts[g].err.y + 1) % 
812                         pp->ghosts[g].speed;
813                 pp->ghosts[g].delta.x += pp->ghosts[g].err.x != 0 ? 
814                         pp->incx : 0;
815                 pp->ghosts[g].delta.y += pp->ghosts[g].err.y != 0 ? 
816                         pp->incy : 0;
817
818                 if (pp->ghosts[g].delta.x >= pp->xs && 
819                     pp->ghosts[g].delta.y >= pp->ys) {
820                         ghost_update(pp, &(pp->ghosts[g]));
821                         pp->ghosts[g].delta.x = pp->incx;
822                         pp->ghosts[g].delta.y = pp->incy;
823                 }
824
825                 if (pp->ghosts[g].delta.x > pp->xs + pp->incx)
826                         pp->ghosts[g].delta.x = pp->xs + pp->incx;
827                 if (pp->ghosts[g].delta.y > pp->ys + pp->incy)
828                         pp->ghosts[g].delta.y = pp->ys + pp->incy;
829         }
830
831         pacman_tick(mi);
832 }
833
834 /* Releases resources. */
835 void
836 release_pacman(ModeInfo * mi)
837 {
838         if (pacmangames != NULL) {
839                 int         screen;
840
841                 for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
842                         free_pacman(MI_DISPLAY(mi), &pacmangames[screen]);
843                 free(pacmangames);
844                 pacmangames = (pacmangamestruct *) NULL;
845         }
846 }
847
848 /* Refresh current level. */
849 void
850 refresh_pacman(ModeInfo * mi)
851 {
852         drawlevel(mi);
853         pacman_tick(mi);
854 }
855
856 /* Callback to change level. */
857 void
858 change_pacman(ModeInfo * mi)
859 {
860         MI_CLEARWINDOW(mi);
861         repopulate(mi);
862 }
863
864 #endif /* MODE_pacman */