1 /* Copyright (C) 1993, 1992 Nathan Sidwell */
2 /* RCS $Id: xmris.c,v 4.19 1995/12/14 13:53:27 nathan Exp $ */
3 /*{{{  the problem with recursive function definitions*/
4 /*
5  * In order to keep a single control loop, I use animate functions,
6  * which are called through a pointer. To chain these, the animate
7  * functions return a pointer to the animate function to call next time
8  * round. Uh oh, a function which returns a pointer to a function which
9  * returns a pointer to ..., you get the idea. To make it worse, in order
10  * to implement callback, the animate functions take a pointer to the
11  * animate function to call back, when they're done. This gives the two
12  * main loops (demo and game play), the ability to both call the same
13  * animate functions, and have them return correctly.
14  * We could wrap the function definition up in a structure, because we
15  * can have self referential structures, but that introduces another
16  * level of indirection 'cos passing structures is not K&R compatible.
17  * So what I do is define the animate function as taking a pointer to
18  * a void function returning void and returning a pointer to a void
19  * function returning a void. Then all I have to do is _carefully_ cast
20  * these things before using them.
21  * And remember kiddies, ANSI says that sizeof(void *) maybe _smaller_
22  * than sizeof(void (*)()), which is why I don't cast function pointers
23  * into data pointers, 'cos they might not fit.
24  * Some (supposidly ANSI) compilers are confused about the declaration
25  * and definition of such functions. They only accept K&R style
26  * declarations and definitions for them. (It is possible that I've got
27  * them slightly wrong, with a superflous set of () some where, but gcc
28  * takes it, and my depreciated cdecl, hacked out of K&R 2 agrees with
29  * what I've got.) For these platforms, you can give the define
30  * BROKEN_FPTRS, which will force K&R mode on just those cases where
31  * it all goes sadly wrong. A notable case is codecenter (alias saberc),
32  * which just doesn't want to know. (It also performs an addition level
33  * of string stripping on the make file, so that the high score file name
34  * has its quotes stripped off, but that's another story.)
35  * May be I should just have a big switch statement, which will
36  * be more robust, and easier on optimizers, but break the
37  * object orientedness of the whole thing.
38  */
39 /*}}}*/
40 #include "xmris.h"
41 #ifdef NDEBUG
42 #define game_assert()
43 #endif /* NDEBUG */
44 static unsigned map_start;
45 /*{{{  prototypes*/
46 static VOIDFUNC action_expose
47     PROTOARG((Widget, XEvent *, String *, Cardinal *));
48 static VOIDFUNC action_keypress
49     PROTOARG((Widget, XEvent *, String *, Cardinal *));
50 static VOIDFUNC action_keyrelease
51     PROTOARG((Widget, XEvent *, String *, Cardinal *));
52 static VOIDFUNC action_map
53     PROTOARG((Widget, XEvent *, String *, Cardinal *));
54 static VOIDFUNC action_pause
55     PROTOARG((Widget, XEvent *, String *, Cardinal *));
56 static VOIDFUNC action_unmap
57     PROTOARG((Widget, XEvent *, String *, Cardinal *));
58 static VOIDFUNC age_scores PROTOARG((VOIDARG));
59 static PROTOANIMATE(animate_death);
60 static PROTOANIMATE(animate_pause);
61 #ifndef NDEBUG
62 static VOIDFUNC game_assert PROTOARG((VOIDARG));
63 #endif /* NDEBUG */
64 static VOIDFUNC set_key_default PROTOARG((VOIDARG));
65 static VOIDFUNC set_key_single PROTOARG((VOIDARG));
66 static int xio_error PROTOARG((Display *));
67 /*}}}*/
68 /*{{{  static tables*/
69 /*{{{  static XtActionsRec actions[] =*/
70 static XtActionsRec actions[] =
71 {
72   {"keypress", action_keypress},
73   {"keyrelease", action_keyrelease},
74   {"pause", action_pause},
75   {"map", action_map},
76   {"unmap", action_unmap},
77   {"expose", action_expose},
78 };
79 /*}}}*/
80 /*{{{  static char CONST garden_translations[] =*/
81 static char CONST garden_translations[] =
82 "\
83 <KeyPress>:keypress()\n\
84 <KeyRelease>:keyrelease()\n\
85 <Expose>:expose()\n\
86 ";
87 /*}}}*/
88 /*{{{  static char CONST form_translations[] =*/
89 static char CONST form_translations[] =
90 "\
91 <FocusOut>:pause()\n\
92 ";
93 /*}}}*/
94 /*{{{  static char CONST toplevel_translations[] =*/
95 static char CONST toplevel_translations[] =
96 "\
97 <MapNotify>:map()\n\
98 <UnmapNotify>:unmap()\n\
99 ";
100 /*}}}*/
101 /*}}}*/
102 /*{{{  void action_expose(widget, event, params, num_params)*/
103 static VOIDFUNC action_expose
104 FUNCARG((widget, event, params, num_params),
105 	Widget    widget
106 ARGSEP  XEvent    *event
107 ARGSEP  String    *params
108 ARGSEP  Cardinal  *num_params
109 )
110 {
111 #ifdef DEBUGEVENTLOOP
112   fprintf(stderr, "action_expose(0x%lx)\n", XtWindow(widget));
113 #endif /* DEBUGEVENTLOOP */
114   if(display.mapped)
115     refresh_window();
116   return;
117 }
118 /*}}}*/
119 /*{{{  void action_keypress(widget, event, params, num_params)*/
120 static VOIDFUNC action_keypress
121 FUNCARG((widget, event, params, num_params),
122 	Widget    widget
123 ARGSEP  XEvent    *event
124 ARGSEP  String    *params
125 ARGSEP  Cardinal  *num_params
126 )
127 /*
128  * When a key is pressed, we check to see if it is
129  * a control key. If so, then we set the relevant pressed bit.
130  */
131 {
132   unsigned  ix;
133   KeySym    keysym;
134 
135   keysym = XKeycodeToKeysym(display.display, event->xkey.keycode, 0);
136 #ifdef DEBUGEVENTLOOP
137   fprintf(stderr, "action_keypress(0x%lx) keysym=%0xlx\n",
138       (long)XtWindow(widget), (long)keysym);
139 #endif /* DEBUGEVENTLOOP */
140   global.key = keysym;
141   if(global.state != MODE_KEY_DEF)
142     for(ix = KEYS; ix--;)
143       if(keysym == data.keysyms[ix])
144 	{
145 	  global.pressed |= 1 << ix;
146 	  if(ix == KEY_PAUSE)
147 	    {
148 	      timer_set((unsigned long)0, TIMING_PAUSE);
149 	      global.pause = 1;
150 	    }
151 	  break;
152 	}
153   return;
154 }
155 /*}}}*/
156 /*{{{  void action_keyrelease(widget, event, params, num_params)*/
157 static VOIDFUNC action_keyrelease
158 FUNCARG((widget, event, params, num_params),
159 	Widget    widget
160 ARGSEP  XEvent    *event
161 ARGSEP  String    *params
162 ARGSEP  Cardinal  *num_params
163 )
164 /*
165  * When a key is released, we check to see if it is
166  * a control key. If so, then we set the relevant pressed bit.
167  */
168 {
169   unsigned  ix;
170   KeySym    keysym;
171 
172   keysym = XKeycodeToKeysym(display.display, event->xkey.keycode, 0);
173 #ifdef DEBUGEVENTLOOP
174   fprintf(stderr, "action_keyrelease(0x%lx) keysym=%0xlx\n",
175       (long)XtWindow(widget), (long)keysym);
176 #endif /* DEBUGEVENTLOOP */
177   if(global.state != MODE_KEY_DEF)
178     for(ix = KEYS; ix--;)
179       if(keysym == data.keysyms[ix])
180 	{
181 	  global.pressed &= ~(1 << ix);
182 	  break;
183 	}
184   return;
185 }
186 /*}}}*/
187 /*{{{  void action_map(widget, event, params, num_params)*/
188 static VOIDFUNC action_map
189 FUNCARG((widget, event, params, num_params),
190 	Widget    widget
191 ARGSEP  XEvent    *event
192 ARGSEP  String    *params
193 ARGSEP  Cardinal  *num_params
194 )
195 {
196 #ifdef DEBUGEVENTLOOP
197   fprintf(stderr, "action_map(0x%lx)\n", (long)XtWindow(widget));
198 #endif /* DEBUGEVENTLOOP */
199   display.mapped = 1;
200   if(map_start)
201     timer_set((unsigned long)0, TIMING_ON);
202   map_start = 0;
203   return;
204 }
205 /*}}}*/
206 /*{{{  void action_pause(widget, event, params, num_params)*/
207 static VOIDFUNC action_pause
208 FUNCARG((widget, event, params, num_params),
209 	Widget    widget
210 ARGSEP  XEvent    *event
211 ARGSEP  String    *params
212 ARGSEP  Cardinal  *num_params
213 )
214 {
215 #ifdef DEBUGEVENTLOOP
216   fprintf(stderr, "action_pause(0x%lx)\n", (long)XtWindow(widget));
217 #endif /* DEBUGEVENTLOOP */
218   global.pause = global.state < 6;
219   return;
220 }
221 /*}}}*/
222 /*{{{  void action_unmap(widget, event, params, num_params)*/
223 static VOIDFUNC action_unmap
224 FUNCARG((widget, event, params, num_params),
225 	Widget    widget
226 ARGSEP  XEvent    *event
227 ARGSEP  String    *params
228 ARGSEP  Cardinal  *num_params
229 )
230 {
231 #ifdef DEBUGEVENTLOOP
232   fprintf(stderr, "action_unmap(0x%lx)\n", (long)XtWindow(widget));
233 #endif /* DEBUGEVENTLOOP */
234   display.mapped = 0;
235   global.pause = global.state < 6;
236   map_start = timer_set((unsigned long)0, TIMING_PAUSE) == TIMING_ON;
237   set_key_default();
238   return;
239 }
240 /*}}}*/
241 /*{{{  void add_score(increment, x, y)*/
242 extern VOIDFUNC add_score
243 FUNCARG((points, x, y),
244 	unsigned  points
245 ARGSEP  int       x
246 ARGSEP  int       y
247 )
248 /*
249  * adds the given score (which may be zero)
250  * and displays it at the top of the screen
251  * if the coordinate > 0 then add the score into the onboard list
252  */
253 {
254   points = (unsigned)(((unsigned long)points * global.scale / SCORE_SCALE) *
255 	SCORE_ROUND);
256   if(!global.pedestal)
257     global.score += points;
258   /*{{{  text score*/
259   {
260     size_t    length;
261     char      text[10];
262     int       x, y;
263 
264     length = itoa(text, global.score, 0);
265     x = PIXELX(4, -GAP_WIDTH) - (length + 2) * font.width;
266     y = PIXELY(-1, CELL_HEIGHT / 2) + font.center;
267     XDrawImageString(display.display, display.back, GCN(GC_TEXT),
268 	x, y, text, (int)length);
269     add_background(x, y - font.ascent,
270 	length * font.width, (unsigned)(font.ascent + font.descent));
271   }
272   /*}}}*/
273   /*{{{  board score?*/
274   if(y)
275     {
276       size_t    length;
277       char      text[10];
278       int       i;
279       SCORE     *sptr;
280       SPRITE    *dptr;
281 
282       dptr = &sprites[SPRITE_DIGITS];
283       length = itoa(text, (unsigned long)points, 0);
284       /*{{{  remove oldest score?*/
285       if(update.score.scores == BOARD_SCORES)
286 	{
287 	  add_background(update.score.list[0].place.x,
288 	      update.score.list[0].place.y,
289 	      DIGIT_WIDTH * 4, DIGIT_HEIGHT);
290 	  update.score.scores--;
291 	  for(sptr = update.score.list, i = update.score.scores; i--; sptr++)
292 	    memcpy(sptr, sptr + 1, sizeof(SCORE));
293 	}
294       /*}}}*/
295       sptr = &update.score.list[update.score.scores++];
296       sptr->count = SCORE_SHOW;
297       sptr->place.x = x - DIGIT_WIDTH * 2;
298       sptr->place.y = y - DIGIT_HEIGHT / 2;
299       /*{{{  centering*/
300       if(length != 4)
301 	{
302 	  x = (4 - length) * (DIGIT_WIDTH / 2);
303 	  XCopyArea(display.display, dptr->mask, sptr->mask, GCN(GC_COPY),
304 	      10 * DIGIT_WIDTH, 0, (unsigned)x, DIGIT_HEIGHT, 0, 0);
305 	  XCopyArea(display.display, dptr->mask, sptr->mask, GCN(GC_COPY),
306 	      10 * DIGIT_WIDTH, 0, (unsigned)x,
307 	      DIGIT_HEIGHT, 4 * DIGIT_WIDTH - x, 0);
308 	}
309       else
310 	x = 0;
311       /*}}}*/
312       for(i = 0; i < length; i++, x += DIGIT_WIDTH)
313 	{
314 	  XCopyArea(display.display, dptr->image, sptr->image, GCN(GC_COPY),
315 	      (text[i] - '0') * DIGIT_WIDTH, 0, DIGIT_WIDTH, DIGIT_HEIGHT,
316 	      x, 0);
317 	  XCopyArea(display.display, dptr->mask, sptr->mask, GCN(GC_COPY),
318 	      (text[i] - '0') * DIGIT_WIDTH, 0, DIGIT_WIDTH, DIGIT_HEIGHT,
319 	      x, 0);
320 	}
321       XFillRectangle(display.display, sptr->mask, GCN(GC_COPY),
322 	  (int)((4 - length) * (DIGIT_WIDTH / 2) + DIGIT_WIDTH / 2), 1,
323 	  (length - 1) * DIGIT_WIDTH, DIGIT_HEIGHT - 2);
324       XCopyArea(display.display, sptr->mask, sptr->image, GCN(GC_AND),
325 	  0, 0, DIGIT_WIDTH * 4, DIGIT_HEIGHT, 0, 0);
326     }
327   /*}}}*/
328   return;
329 }
330 /*}}}*/
331 /*{{{  void age_scores()*/
332 static VOIDFUNC age_scores FUNCARGVOID
333 /*
334  * ages the onboard scores, and removes the old ones
335  */
336 {
337   SCORE     *sptr;
338   int       i;
339 
340   for(sptr = update.score.list, i = update.score.scores; i--; sptr++)
341     if(!sptr->count--)
342       {
343 	Pixmap    image, mask;
344 	unsigned  ix;
345 	SCORE     *optr;
346 
347 	add_background(sptr->place.x, sptr->place.y,
348 	    DIGIT_WIDTH * 4, DIGIT_HEIGHT);
349 	mask = sptr->mask;
350 	image = sptr->image;
351 	for(ix = i, optr = sptr; ix--; optr++)
352 	  memcpy(optr, optr + 1, sizeof(SCORE));
353 	optr->mask = mask;
354 	optr->image = image;
355 	update.score.scores--;
356 	sptr--;
357      }
358   return;
359 }
360 /*}}}*/
361 /*{{{  ANIMATE animate_death(next)*/
FUNCANIMATE(animate_death,next)362 static FUNCANIMATE(animate_death, next)
363 /*
364  * spin the player around a bit, and then remove all the monsters and
365  * return back to whoever called me
366  */
367 {
368   static PROTOANIMATE((*rts));
369   PROTOVOID(*then);
370 
371   then = (PROTOVOID(*))animate_death;
372   if(next)
373     /*{{{  start*/
374     {
375       unsigned  i;
376       unsigned  base;
377 
378       rts = (PROTOANIMATE((*)))next;
379       player.ball.count = 8;
380       i = monster.list[0].face;
381       if(i >= 6)
382 	i = 2 + (i & 1);
383       for(base = 8; base--;)
384 	if(player_dies[base] == i)
385 	  break;
386       global.count = 0;
387       monster.list[0].shot = 0;
388       monster.list[0].count = 0;
389       monster.list[0].cycle = (base & 3) << 1;
390       monster.list[0].image = (base & 4) << 1;
391       monster.list[0].type = SPRITE_PLAYER_DEAD + (base << 1);
392       timer_set((unsigned long)0, TIMING_PAUSE);
393     }
394     /*}}}*/
395   if(global.count && global.count < 16)
396     /*{{{  skip?*/
397     if(global.throw == 1)
398       {
399 	global.throw = 2;
400 	global.count = 16;
401 	monster.list[0].count = DIE_DELAY;
402       }
403     /*}}}*/
404   if(monster.list[0].count++ < DIE_DELAY)
405     age_scores();
406   else if(global.count == 16)
407     /*{{{  finish*/
408     {
409       MONSTER   *mptr;
410       unsigned  ix;
411 
412       for(mptr = monster.list, ix = monster.monsters; ix--; mptr++)
413 	add_background(mptr->pixel.x, mptr->pixel.y,
414 	  CELL_WIDTH, CELL_HEIGHT);
415       monster.monsters = 0;
416       global.lives--;
417       if(global.lives)
418 	{
419 	  XFillRectangle(display.display, display.back, GCN(GC_CLEAR),
420 	      PIXELX((int)global.lives - 1, 0), PIXELY(CELLS_DOWN, 0),
421 	      CELL_WIDTH, CELL_HEIGHT);
422 	  add_background(PIXELX((int)global.lives - 1, 0),
423 	      PIXELY(CELLS_DOWN, 0), CELL_WIDTH, CELL_HEIGHT);
424 	}
425       if(extra.escape)
426 	{
427 	  extra.escape = 0;
428 	  draw_extra();
429 	}
430       global.count = 17;
431     }
432     /*}}}*/
433   else if(global.count == 17)
434     /*{{{  end*/
435     {
436       assert(rts != (PROTOANIMATE((*)))NULL);
437       global.count = 0;
438       then = (*rts)((PROTOVOID(*))NULL);
439     }
440     /*}}}*/
441   else
442     /*{{{  animate*/
443     {
444       age_scores();
445       monster.list[0].count = 0;
446       global.count++;
447       monster.list[0].type =
448 	  SPRITE_PLAYER_DEAD + monster.list[0].image +
449 	  ((monster.list[0].cycle + global.count) & 7);
450     }
451     /*}}}*/
452   return then;
453 }
454 /*}}}*/
455 /*{{{  ANIMATE animate_pause(next)*/
FUNCANIMATE(animate_pause,next)456 static FUNCANIMATE(animate_pause, next)
457 /*
458  * let everybody go to sleep until they're woken up.
459  * Don't forget to put a flying carpet under the player, so
460  * it doesn't have to float in mid air. (No one has yet observed
461  * yogic flying, and they're not going to start now.
462  */
463 {
464   static PROTOANIMATE((*rts));
465   static int face;
466   static MONSTER *seat;
467   PROTOVOID(*then);
468 
469   then = (PROTOVOID(*))animate_pause;
470   global.pause = 0;
471   if(next)
472     /*{{{  start*/
473     {
474       char      buffer[65];
475       size_t    length;
476       CELL      *cptr;
477       int       depth;
478 
479       timer_set((unsigned long)0, TIMING_PAUSE);
480       set_key_default();
481       rts = (PROTOANIMATE((*)))next;
482       sprintf(buffer, "%s to continue, %s to quit",
483 	  XKeysymToString(data.keysyms[KEY_THROW]),
484 	  XKeysymToString(data.keysyms[KEY_QUIT]));
485       length = strlen(buffer);
486       assert(length < sizeof(buffer));
487       XFillRectangle(display.display, display.copy, GCN(GC_CLEAR),
488 	  BORDER_LEFT + 1, PIXELY(CELLS_DOWN, 0),
489 	  BOARD_WIDTH - 2, CELL_HEIGHT);
490       XDrawImageString(display.display, display.copy, GCN(GC_TEXT),
491 	  CENTER_X - (int)(length * font.width / 2),
492 	  PIXELY(CELLS_DOWN, CELL_HEIGHT / 2) + font.center,
493 	  buffer, (int)length);
494       XCopyArea(display.display, display.copy, display.window, GCN(GC_COPY),
495 	  BORDER_LEFT + 1, PIXELY(CELLS_DOWN, 0),
496 	  BOARD_WIDTH - 2, CELL_HEIGHT,
497 	  BORDER_LEFT + 1, PIXELY(CELLS_DOWN, 0));
498       face = monster.list[0].face;
499       monster.list[0].cycle = MONSTER_CYCLES * 3;
500       cptr = BOARDCELL(monster.list[0].cell.x, monster.list[0].cell.y);
501       depth = cptr->depths[1];
502       if(monster.list[0].offset.x < 0)
503 	{
504 	  if(depth < cptr[-1].depths[1])
505 	    depth = cptr[-1].depths[1];
506 	}
507       else if(monster.list[0].offset.x > 0)
508 	{
509 	  if(depth < cptr[1].depths[1])
510 	    depth = cptr[1].depths[1];
511 	}
512       if(!monster.list[0].shot && monster.list[0].offset.y < depth)
513 	seat = spawn_monster(0, SPRITE_SEAT, 0, 0,
514 	    monster.list[0].cell.x, monster.list[0].cell.y,
515 	    monster.list[0].offset.x, monster.list[0].offset.y + CELL_HEIGHT);
516       else
517 	seat = NULL;
518     }
519     /*}}}*/
520   if(global.pressed & (1 << KEY_QUIT) || global.throw == 1)
521     /*{{{  end*/
522     {
523       MONSTER   *mptr;
524       unsigned  count;
525 
526       global.throw = 2;
527       if(global.pressed & (1 << KEY_QUIT))
528 	{
529 	  global.lives = 1;
530 	  global.count = 1;
531 	  monster.list[0].shot = 1;
532 	}
533       global.pressed &= ~(1 << KEY_QUIT);
534       for(mptr = monster.list, count = monster.monsters; count--; mptr++)
535 	mptr->cycle %= MONSTER_CYCLES;
536       if(seat)
537 	seat->type = 5;
538       monster.list[0].face = face;
539       add_background(BORDER_LEFT + 1, PIXELY(CELLS_DOWN, 0),
540 	  BOARD_WIDTH - 2, CELL_HEIGHT);
541       set_key_single();
542       timer_set((unsigned long)0, TIMING_ON);
543       assert(rts != (PROTOANIMATE((*)))NULL);
544       then = (*rts)((PROTOVOID(*))NULL);
545     }
546     /*}}}*/
547   else
548     /*{{{  animate*/
549     {
550       MONSTER   *mptr;
551       unsigned  count;
552 
553       age_scores();
554       if(player.ball.state == 2 || player.ball.state == 4)
555 	{
556 	  bounce_ball();
557 	  if(!player.ball.state)
558 	    player.ball.count = 8;
559 	}
560       for(mptr = monster.list, count = monster.monsters; count--; mptr++)
561 	if(mptr->face < 16)
562 	  {
563 	    if(!mptr->cycle)
564 	      {
565 		if(mptr == monster.list && mptr->face < 8)
566 		  {
567 		    mptr->face = 8 | ((mptr->face & 1) ^
568 		      (mptr->face == 1 || mptr->face == 4));
569 		    if(!player.ball.state)
570 		      player.ball.count = 8;
571 		  }
572 		mptr->cycle = MONSTER_CYCLES * 4;
573 		mptr->image++;
574 		if(mptr->image == (mptr->type != 6 ?
575 		    MONSTER_IMAGES : DIAMOND_IMAGES))
576 		  mptr->image = 0;
577 	      }
578 	    mptr->cycle--;
579 	  }
580     }
581     /*}}}*/
582   return then;
583 }
584 /*}}}*/
585 /*{{{  ANIMATE animate_game(next)*/
FUNCANIMATE(animate_game,next)586 extern FUNCANIMATE(animate_game, next)
587 /*
588  * this is one of the main control loops. It
589  * plays a game until the player dies.
590  */
591 {
592   static int cell_x = 0;
593   static int cell_y = 0;
594   static int state = 0;
595   static int den_x = 0;
596   static int den_y = 0;
597   PROTOVOID(*then);
598 
599   assert(!next);
600   if(state == 1 && global.pause)
601     {
602       global.pause = 0;
603       then = animate_pause((PROTOVOID(*))animate_game);
604     }
605   else
606     {
607       for(then = (PROTOVOID(*))NULL; then == (PROTOVOID(*))NULL;)
608 	switch(state)
609 	{
610 	  /*{{{  case 0:*/
611 	  case 0:
612 	  {
613 	    extra.got = 0;
614 	    extra.select = 0;
615 	    extra.escape = 0;
616 	    extra.score = 0;
617 	    extra.count = FRAMES_PER_SECOND - 1;
618 	    global.diamond = 0;
619 	    create_xtra_monster(0);
620 	    global.lives = START_LIVES;
621 	    global.score = 0;
622 	    global.screen = global.pedestal;
623 	    history.msec = 0;
624 	    state = 4;
625 	    set_key_single();
626 	    break;
627 	  }
628 	  /*}}}*/
629 	  /*{{{  case 1:*/
630 	  case 1:
631 	  {
632 	    if(global.count)
633 	      /*{{{  game frame*/
634 	      {
635 		then = (PROTOVOID(*))animate_game;
636 		age_scores();
637 		/*{{{  den?*/
638 		while(cell_y != CELLS_DOWN)
639 		  {
640 		    cell_x++;
641 		    if(cell_x == CELLS_ACROSS)
642 		      {
643 			cell_x = 0;
644 			cell_y++;
645 		      }
646 		    if(BOARDCELL(cell_x, cell_y)->den)
647 		      {
648 			set_back_sprite(global.state ? global.state != 1 ?
649 			    0 : (global.screen - 1) % SPRITE_PRIZES +
650 			    SPRITE_PRIZE_BASE : SPRITE_DEN, cell_x, cell_y);
651 			break;
652 		      }
653 		  }
654 		/*}}}*/
655 		if(global.state != 4)
656 		  {
657 		    if(move_player())
658 		      {
659 			cell_x = -1;
660 			cell_y = 0;
661 		      }
662 		  }
663 		else if(!monster.list[0].shot)
664 		  {
665 		    if(!monster.list[0].cycle)
666 		      {
667 			monster.list[0].cycle = MONSTER_CYCLES;
668 			monster.list[0].image++;
669 			if(monster.list[0].image == MONSTER_IMAGES)
670 			  monster.list[0].image = 0;
671 		      }
672 		    monster.list[0].cycle--;
673 		  }
674 		if(global.broken || global.stepped)
675 		  calc_distances();
676 		if(global.broken && extra.escape == 2)
677 		  calc_extra_home(0);
678 		global.broken = global.stepped = 0;
679 		game_assert();
680 		move_monsters();
681 		game_assert();
682 		bounce_ball();
683 		game_assert();
684 		move_apples();
685 		game_assert();
686 		if(!global.state)
687 		  /*{{{  monster escape?*/
688 		  {
689 		    if(!monster.delay && chaotic() < DEN_ESCAPE_PROB)
690 		      {
691 			unsigned  ix;
692 
693 			monster.delay =
694 			    DEN_ESCAPE_DELAY * DEN_ESCAPE_FLASH + 1;
695 			ix = chaotic() % global.dens;
696 			den_x = -1;
697 			den_y = 0;
698 			do
699 			  {
700 			    do
701 			      {
702 				den_x++;
703 				if(den_x == CELLS_ACROSS)
704 				  {
705 				    den_x = 0;
706 				    den_y++;
707 				  }
708 			      }
709 			    while(!BOARDCELL(den_x, den_y)->den);
710 			  }
711 			while(ix--);
712 		      }
713 		    if(monster.delay)
714 		      {
715 			monster.delay--;
716 			if(!(monster.delay % DEN_ESCAPE_FLASH))
717 			  set_back_sprite((unsigned)(monster.delay /
718 			      DEN_ESCAPE_FLASH & 1 ?
719 			      SPRITE_NORMAL + 6 : SPRITE_DEN), den_x, den_y);
720 			if(!monster.delay)
721 			  {
722 			    spawn_monster(0, 0, 3, 3, den_x, den_y, 0, 0);
723 			    monster.den--;
724 			    if(!monster.den)
725 			      {
726 				global.state = 1;
727 				cell_x = -1;
728 				cell_y = 0;
729 			      }
730 			  }
731 		      }
732 		  }
733 		  /*}}}*/
734 		else if(global.state == 2 &&
735 		    !monster.den && !monster.drones)
736 		  {
737 		    color_set(BACKGROUND_NORMAL);
738 		    global.state = 3;
739 		  }
740 		if(global.state != 4)
741 		  {
742 		    killed_player();
743 		    if(!global.cherries || !monster.normals ||
744 			monster.list[0].shot || extra.got == 0x1F ||
745 			global.diamond == 2)
746 		      {
747 			timer_set((unsigned long)0, TIMING_PAUSE);
748 			/*{{{  happy?*/
749 			if(!monster.list[0].shot)
750 			  {
751 			    int       depth;
752 			    CELL      *cptr;
753 
754 			    monster.list[0].face = 10;
755 			    cptr = BOARDCELL(monster.list[0].cell.x,
756 				monster.list[0].cell.y);
757 			    depth = cptr->depths[1];
758 			    if(monster.list[0].offset.x < 0)
759 			      {
760 				if(depth < cptr[-1].depths[1])
761 				  depth = cptr[-1].depths[1];
762 			      }
763 			    else if(monster.list[0].offset.x > 0)
764 			      {
765 				if(depth < cptr[1].depths[1])
766 				  depth = cptr[1].depths[1];
767 			      }
768 			    if(monster.list[0].offset.y < depth)
769 			      spawn_monster(0, SPRITE_SEAT, 0, 0,
770 				  monster.list[0].cell.x,
771 				  monster.list[0].cell.y,
772 				  monster.list[0].offset.x,
773 				  monster.list[0].offset.y + CELL_HEIGHT);
774 			  }
775 			/*}}}*/
776 			global.state = 4;
777 			color_set(BACKGROUND_NORMAL);
778 		      }
779 		  }
780 		else
781 		  /*{{{  ending*/
782 		  {
783 		    global.count--;
784 		    if(update.score.scores || apple.moving ||
785 			player.ball.state)
786 		      {
787 			if(!global.count)
788 			  global.count = 1;
789 		      }
790 		    else if(global.throw == 1)
791 		      global.count = 0;
792 		    if(player.ball.state == 1 &&
793 			(global.count < SCORE_SHOW / 2 || global.throw == 1))
794 		      {
795 			player.ball.state = 2;
796 			player.ball.count = 0;
797 		      }
798 		    else if(player.ball.state == 3)
799 		      player.ball.state = 4;
800 		  }
801 		  /*}}}*/
802 		/*{{{  extra stuff*/
803 		if(!extra.escape)
804 		  {
805 		    unsigned  temp;
806 
807 		    if(!extra.count--)
808 		      new_xtra();
809 		    temp = (unsigned)(global.score / 5000);
810 		    if(global.state != 4 && temp != extra.score)
811 		      {
812 			extra.score = temp;
813 			extra_escape();
814 			extra.count = XTRA_HOME_DELAY - 1;
815 		      }
816 		  }
817 		else
818 		  {
819 		    extra.score = (unsigned)(global.score / 5000);
820 		    if(global.state != 2 && extra.escape != 2 &&
821 			!extra.count--)
822 		      {
823 			extra.escape = 2;
824 			calc_extra_home(0);
825 		      }
826 		  }
827 		/*}}}*/
828 		game_assert();
829 	      }
830 	      /*}}}*/
831 	    else if(monster.list[0].shot)
832 	      {
833 		if(global.lives > 1 ||
834 		    (extra.got != 0x1F && global.diamond != 2))
835 		  state = 2;
836 		then = animate_death((PROTOVOID(*))animate_game);
837 	      }
838 	    else
839 	      {
840 		if(global.throw == 1)
841 		  global.throw = 2;
842 		timer_set((unsigned long)0, TIMING_OFF);
843 		history.msec += global.msec;
844 		history.times[0] = global.msec;
845 		global.msec = 0;
846 		if(global.diamond == 2)
847 		  {
848 		    global.diamond = 3;
849 		    history.ending |= 3;
850 		    state = global.lives ? 3 : extra.got == 0x1F ? 1 : 2;
851 		    then = animate_diamond((PROTOVOID(*))animate_game);
852 		  }
853 		else if(extra.got == 0x1F)
854 		  /*{{{  extra life*/
855 		  {
856 		    history.ending |= 2;
857 		    state = 3;
858 		    then = animate_extra_life((PROTOVOID(*))animate_game);
859 		  }
860 		  /*}}}*/
861 		else if(!monster.normals)
862 		  {
863 		    history.ending |= 1;
864 		    state = 3;
865 		  }
866 		else
867 		  state = 3;
868 	      }
869 	    break;
870 	  }
871 	  /*}}}*/
872 	  /*{{{  case 2:*/
873 	  case 2:
874 	  {
875 	    global.state = 0;
876 	    if(!global.lives)
877 	      {
878 		global.pedestal = 0;
879 		state = 0;
880 		timer_set((unsigned long)0, TIMING_OFF);
881 		history.msec += global.msec;
882 		high_score(global.score, global.screen, history.msec);
883 		set_key_default();
884 		then = animate_demo((PROTOVOID(*))NULL);
885 	      }
886 	    else
887 	      {
888 		state = 1;
889 		timer_set(FRAME_RATE, TIMING_ON);
890 		global.count = SCORE_SHOW;
891 		if(global.diamond < 3)
892 		  global.diamond = 0;
893 		/*{{{  initialize stuff*/
894 		{
895 		  cell_x = -1;
896 		  cell_y = 0;
897 		  monster.monsters = 0;
898 		  monster.delay = 0;
899 		  monster.den = monster.normals;
900 		  monster.drones = 0;
901 		  spawn_monster(0, 4, 3, 3, global.start.x, global.start.y,
902 		      0, 0);
903 		  monster.list[0].stop = 1;
904 		  monster.player = BOARDCELL(global.start.x, global.start.y);
905 		  player.old_ball.state = 0;
906 		  player.old_ball.count = 8;
907 		  player.ball.state = 0;
908 		  player.thrown = 0;
909 		  player.bashed = 0;
910 		  player.pressed = 0;
911 		  player.next = 0;
912 		  extra.count = FRAMES_PER_SECOND - 1;
913 		  bounce_ball();
914 		}
915 		/*}}}*/
916 		calc_distances();
917 	      }
918 	    break;
919 	  }
920 	  /*}}}*/
921 	  /*{{{  case 3:*/
922 	  case 3:
923 	  {
924 	    state = 4;
925 	    if(!(global.screen % HISTORY_SHOW))
926 	      then = animate_history((PROTOVOID(*))animate_game);
927 	    break;
928 	  }
929 	  /*}}}*/
930 	  /*{{{  case 4:*/
931 	  case 4:
932 	  {
933 	    unsigned  ix;
934 
935 	    global.state = 0;
936 	    state = 2;
937 	    global.screen++;
938 	    new_board();
939 	    global.difficulty = global.screen + DIFFICULTY_PEDESTAL;
940 	    monster.normals = global.screen >= 3 ?
941 		8 + global.screen / 10 * 2 : 6;
942 	    if(monster.normals > MONSTERS - 6)
943 	      monster.normals = MONSTERS - 6;
944 	    history.prize <<= 1;
945 	    history.ending <<= 2;
946 	    for(ix = CELLS_DOWN - 2; ix--;)
947 	      history.times[ix + 1] = history.times[ix];
948 	    then = animate_zoom((PROTOVOID(*))animate_game);
949 	    break;
950 	  }
951 	  /*}}}*/
952 	}
953     }
954   return then;
955 }
956 /*}}}*/
957 /*{{{  void calc_distances()*/
958 extern VOIDFUNC calc_distances FUNCARGVOID
959 /*
960  * sets the distances from each cell to the player
961  * this is so the monsters have non-local knowlegde
962  * increment the non-zero cells
963  * this proceeds as a sort of flood fill operation, starting
964  * from the player's cell and moving outwards
965  */
966 {
967   CELL    **aptr, **sptr;
968   CELL    *list[2][FLOOD_FILL];
969   CELL    *cptr;
970   int     toggle;
971   int     x, y;
972   int     count;
973   int     visited;
974 
975   for(y = CELLS_DOWN; y--;)
976     for(cptr = BOARDCELL(0, y), x = CELLS_ACROSS; x--; cptr++)
977       cptr->distance = cptr->visit ? 0 : 255;
978   toggle = 0;
979   cptr = BOARDCELL(monster.list[0].cell.x, monster.list[0].cell.y);
980   cptr->distance = count = 1;
981   visited = 0;
982   list[0][0] = cptr;
983   list[0][1] = NULL;
984   while(list[toggle][0])
985   {
986     sptr = list[toggle];
987     toggle = !toggle;
988     aptr = list[toggle];
989     count++;
990     visited++;
991     while((cptr = *sptr++) != NULL)
992       {
993 	CELL      *tptr;
994 
995 	/*{{{  go up?*/
996 	if(cptr->depths[0])
997 	  {
998 	    tptr = cptr - CELL_STRIDE;
999 	    if(!tptr->distance)
1000 	      {
1001 		tptr->distance = count;
1002 		*aptr++ = tptr;
1003 	      }
1004 	  }
1005 	/*}}}*/
1006 	/*{{{  go down?*/
1007 	if(cptr->depths[1])
1008 	  {
1009 	    tptr = cptr + CELL_STRIDE;
1010 	    if(!tptr->distance)
1011 	      {
1012 		tptr->distance = count;
1013 		*aptr++ = tptr;
1014 	      }
1015 	  }
1016 	/*}}}*/
1017 	/*{{{  go left?*/
1018 	if(cptr->depths[2])
1019 	  {
1020 	    tptr = cptr - 1;
1021 	    if(!tptr->distance)
1022 	      {
1023 		tptr->distance = count;
1024 		*aptr++ = tptr;
1025 	      }
1026 	  }
1027 	/*}}}*/
1028 	/*{{{  go right?*/
1029 	if(cptr->depths[3])
1030 	  {
1031 	    tptr = cptr + 1;
1032 	    if(!tptr->distance)
1033 	      {
1034 		tptr->distance = count;
1035 		*aptr++ = tptr;
1036 	      }
1037 	  }
1038 	/*}}}*/
1039 	assert(aptr - list[toggle] < FLOOD_FILL);
1040       }
1041     *aptr = NULL;
1042   }
1043   global.visited = visited;
1044   return;
1045 }
1046 /*}}}*/
1047 /*{{{  void calc_extra_home(start)*/
1048 extern VOIDFUNC calc_extra_home
1049 FUNCARG((start),
1050 	unsigned  start
1051 )
1052 /*
1053  * sets the distances from each cell to the extra home row
1054  * this is so the monsters have non-local knowlegde
1055  * increment the non-zero cells
1056  * this proceeds as a sort of flood fill operation, starting
1057  * from the home row and moving outwards
1058  */
1059 {
1060   CELL    **aptr, **sptr;
1061   CELL    *list[2][FLOOD_FILL];
1062   CELL    *cptr;
1063   int     toggle;
1064   int     x, y;
1065   int     count;
1066 
1067   for(y = CELLS_DOWN; y--;)
1068     for(cptr = BOARDCELL(0, y), x = CELLS_ACROSS; x--; cptr++)
1069       cptr->xtra = cptr->visit ? 0 : 255;
1070   toggle = 0;
1071   /*{{{  start positions*/
1072   if(start)
1073     {
1074       cptr = BOARDCELL(start, 0);
1075       cptr->xtra = 1;
1076       list[0][0] = cptr;
1077       list[0][1] = NULL;
1078     }
1079   else
1080     {
1081       cptr = BOARDCELL(4, 0);
1082       cptr[0].xtra = cptr[1].xtra = cptr[2].xtra = cptr[3].xtra = 1;
1083       list[0][0] = cptr;
1084       list[0][1] = cptr + 1;
1085       list[0][2] = cptr + 2;
1086       list[0][3] = cptr + 3;
1087       list[0][4] = NULL;
1088     }
1089   /*}}}*/
1090   count = 1;
1091   while(list[toggle][0])
1092   {
1093     sptr = list[toggle];
1094     toggle = !toggle;
1095     aptr = list[toggle];
1096     count++;
1097     while((cptr = *sptr++) != NULL)
1098       {
1099 	CELL      *tptr;
1100 
1101 	/*{{{  go up?*/
1102 	if(cptr->depths[0])
1103 	  {
1104 	    tptr = cptr - CELL_STRIDE;
1105 	    if(!tptr->xtra)
1106 	      {
1107 		tptr->xtra = count;
1108 		*aptr++ = tptr;
1109 	      }
1110 	  }
1111 	/*}}}*/
1112 	/*{{{  go down?*/
1113 	if(cptr->depths[1])
1114 	  {
1115 	    tptr = cptr + CELL_STRIDE;
1116 	    if(!tptr->xtra)
1117 	      {
1118 		tptr->xtra = count;
1119 		*aptr++ = tptr;
1120 	      }
1121 	  }
1122 	/*}}}*/
1123 	/*{{{  go left?*/
1124 	if(cptr->depths[2])
1125 	  {
1126 	    tptr = cptr - 1;
1127 	    if(!tptr->xtra)
1128 	      {
1129 		tptr->xtra = count;
1130 		*aptr++ = tptr;
1131 	      }
1132 	  }
1133 	/*}}}*/
1134 	/*{{{  go right?*/
1135 	if(cptr->depths[3])
1136 	  {
1137 	    tptr = cptr + 1;
1138 	    if(!tptr->xtra)
1139 	      {
1140 		tptr->xtra = count;
1141 		*aptr++ = tptr;
1142 	      }
1143 	  }
1144 	/*}}}*/
1145 	assert(aptr - list[toggle] < FLOOD_FILL);
1146       }
1147     *aptr = NULL;
1148   }
1149   return;
1150 }
1151 /*}}}*/
1152 /*{{{  unsigned chaotic()*/
1153 extern unsigned chaotic FUNCARGVOID
1154 /*
1155  * a simple pseudo random number generator
1156  * it generates 8 new bits of number at each call
1157  * using a 31 bit maximal length linear feedback shift register
1158  * the taps are bits 0 and 3
1159  */
1160 {
1161 static unsigned long seed;
1162   unsigned  bits;
1163 
1164   if(!seed)
1165     seed = time((time_t *)NULL);
1166   bits = (unsigned)(((seed >> 3) ^ seed) & 0xFF);
1167   seed = (seed >> 8) | ((unsigned long)bits << 23);
1168   return bits;
1169 }
1170 /*}}}*/
1171 #ifndef NDEBUG
1172 /*{{{  void game_assert()*/
1173 static VOIDFUNC game_assert FUNCARGVOID
1174 {
1175   unsigned  count;
1176   unsigned  ix;
1177 
1178   count = 0;
1179   for(ix = monster.monsters; --ix;)
1180     if(monster.list[ix].type < 2 && !monster.list[ix].squished)
1181       count++;
1182   if(global.state != 4)
1183     assert(count == monster.normals - (global.state ? 0 : monster.den));
1184   return;
1185 }
1186 /*}}}*/
1187 #endif /* NDEBUG */
1188 /*{{{  size_t itoa(text, n, width)*/
1189 extern size_t itoa
1190 FUNCARG((text, number, digits),
1191 	char      *text    /* output text (include 0) */
1192 ARGSEP  unsigned long number   /* number to convert */
1193 ARGSEP  unsigned  digits   /* field width to convert into */
1194 )
1195 /*
1196  * formats an integer to a string
1197  * in the specified number of digits
1198  * pads leading zeros to ' '
1199  * returns the number of characters used
1200  */
1201 {
1202   char      reverse[10];
1203   size_t    l, length;
1204 
1205   l = 0;
1206   do
1207     {
1208       reverse[l++] = (char)(number % 10 + '0');
1209       number /= 10;
1210     }
1211   while(number);
1212   if(!digits)
1213     length = 0;
1214   else if(l < digits)
1215     {
1216       length = digits - l;
1217       memset(text, ' ', length);
1218     }
1219   else
1220     {
1221       length = 0;
1222       l = digits;
1223     }
1224   while(l)
1225     text[length++] = reverse[--l];
1226   text[length] = 0;
1227   return length;
1228 }
1229 /*}}}*/
1230 /*{{{  int main(argc, argv)*/
1231 extern int main
1232 FUNCARG((argc, argv),
1233 	int     argc
1234 ARGSEP  char CONST **argv
1235 )
1236 {
1237   PROTOANIMATE((*animate));
1238 
1239   myname = *argv ? *argv : "Xmris";
1240 #ifndef TRANSPUTER
1241   real_uid = getuid();
1242   effective_uid = geteuid();
1243   if(real_uid != effective_uid)
1244     set_euid(real_uid);
1245   current_uid = real_uid;
1246 #endif /* TRANSPUTER */
1247   open_toolkit(argc, (String *)argv);
1248   init_scores();
1249   /*{{{  help?*/
1250   if(data.help)
1251     {
1252       char CONST *ptr;
1253 
1254       ptr = myname;
1255       for(ptr += strlen(ptr) - 1; ptr != *argv; ptr--)
1256 	if(ptr[-1] == '/')
1257 	  break;
1258       list_help(ptr);
1259       return 0;
1260     }
1261   /*}}}*/
1262   /*{{{  high scores?*/
1263   if(data.scores)
1264     {
1265       list_scores();
1266       return 0;
1267     }
1268   /*}}}*/
1269 #ifndef NDEBUG
1270   XSetErrorHandler(error_handler);
1271 #endif /* NDEBUG */
1272   XSetIOErrorHandler(xio_error);
1273   create_boards();
1274   create_widget();
1275   XtAppAddActions(display.context, actions, XtNumber(actions));
1276   XtOverrideTranslations(display.garden,
1277       XtParseTranslationTable(garden_translations));
1278   XtOverrideTranslations(display.form,
1279       XtParseTranslationTable(form_translations));
1280   XtOverrideTranslations(display.toplevel,
1281       XtParseTranslationTable(toplevel_translations));
1282   XtRealizeWidget(display.toplevel);
1283 #ifdef DEBUGEVENTLOOP
1284   printf("Toplevel is 0x%lx\n", (long)XtWindow(display.toplevel));
1285   printf("Garden is 0x%lx\n", (long)XtWindow(display.garden));
1286 #endif /* DEBUGEVENTLOOP */
1287   display.window = XtWindow(display.garden);
1288   timer_open();
1289   animate = animate_demo;
1290   while(!global.quit)
1291     {
1292       color_cycle();
1293       while(XtAppPending(display.context) || !display.mapped ||
1294 	  global.pressed & 1 << KEY_PAUSE ||
1295 	  (!global.key && global.state == MODE_KEY_DEF))
1296 	{
1297 #ifdef DEBUGEVENTLOOP
1298 	  XEvent event;
1299 
1300 	  XtAppNextEvent(display.context, &event);
1301 	  fprintf(stderr, "Event %lu, Window 0x%lx\n",
1302 	      (long)event.xany.type, (long)event.xany.window);
1303 	  XtDispatchEvent(&event);
1304 #else
1305 	  XtAppProcessEvent(display.context, XtIMAll);
1306 #endif /* DEBUGEVENTLOOP */
1307 	}
1308       if(global.pressed & (1 << KEY_THROW))
1309 	{
1310 	  if(global.throw == 0)
1311 	    global.throw = 1;
1312 	}
1313       else if(global.throw == 2)
1314 	global.throw = 0;
1315       if(global.pressed & 1 << KEY_ICONIZE)
1316 	{
1317 	  XIconifyWindow(display.display, XtWindow(display.toplevel),
1318 	    display.screen);
1319 	  global.pressed ^= 1 << KEY_ICONIZE;
1320 	}
1321       else
1322 	{
1323 	  assert(animate != (PROTOANIMATE((*)))NULL);
1324 	  animate = (PROTOANIMATE((*)))(*animate)((PROTOVOID(*))NULL);
1325 	  show_updates();
1326 	  timer_wait();
1327 	}
1328       assert(monster.normals < 20);
1329     }
1330   timer_close();
1331   return 0;
1332 }
1333 /*}}}*/
1334 /*{{{  void set_key_default()*/
1335 static VOIDFUNC set_key_default FUNCARGVOID
1336 {
1337   if(display.repeat != AutoRepeatModeOff)
1338     {
1339       XKeyboardControl control;
1340 
1341       control.auto_repeat_mode = display.repeat;
1342       XChangeKeyboardControl(display.display, KBAutoRepeatMode, &control);
1343     }
1344   return;
1345 }
1346 /*}}}*/
1347 /*{{{  void set_key_single()*/
1348 static VOIDFUNC set_key_single FUNCARGVOID
1349 {
1350   if(display.repeat != AutoRepeatModeOff)
1351     {
1352       XKeyboardControl control;
1353 
1354       control.auto_repeat_mode = AutoRepeatModeOff;
1355       XChangeKeyboardControl(display.display, KBAutoRepeatMode, &control);
1356     }
1357   return;
1358 }
1359 /*}}}*/
1360 /*{{{  int xio_error(dptr)*/
1361 static int xio_error
1362 /* ARGSUSED */
1363 FUNCARG((dptr),
1364 	Display *dptr
1365 )
1366 {
1367   exit(0);
1368   return 0;
1369 }
1370 /*}}}*/
1371