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