]> git.hungrycats.org Git - xscreensaver/blob - hacks/penetrate.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / hacks / penetrate.c
1 /* Copyright (c) 1999 Adam Miller adum@aya.yale.edu
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10
11  * penetrate simulates the arcade classic with the cities and the stuff
12  * shooting down from the sky and stuff. The computer plays against itself,
13  * desperately defending the forces of good against those thingies raining
14  * down. Bonus cities are awarded at ever-increasing intervals. Every five
15  * levels appears a bonus round. The computer player gets progressively
16  * more intelligent as the game progresses. Better aim, more economical with
17  * ammo, and better target selection. Points are in the bottom right, and
18  * high score is in the bottom left. Start with -smart to have the computer
19  * player skip the learning process.
20
21  Version: 0.2
22  -- fixed an AI bug that was keeping the computer player a tad weak
23  Version: 0.1
24  -- first release
25
26  */
27
28 #include "screenhack.h"
29
30 #define kSleepTime 10000 
31
32 #define font_height(font)               (font->ascent + font->descent)
33
34 #define kCityPause 500000
35 #define kLevelPause 1
36 #define SCORE_MISSILE 100
37 #define kFirstBonus 5000
38 #define kMinRate 30
39 #define kMaxRadius 100
40
41 typedef struct {
42   int alive;
43   int x, y;
44   int startx, starty;
45   int endx, endy;
46   int dcity;
47   float pos;
48   int enemies;
49   int jenis;
50   int splits;
51   XColor color;
52 } Missile;
53
54 typedef struct {
55   int alive;
56   int x, y, rad, oflaser;
57   int max, outgoing;
58   XColor color;
59 } Boom;
60
61 typedef struct {
62   int alive;
63   int x;
64   XColor color;
65 } City;
66
67 typedef struct {
68   int alive;
69   int x, y;
70   int startx, starty;
71   int endx, endy;
72   int oldx, oldy;
73   int oldx2, oldy2;
74   float velx, vely, fposx, fposy;
75   float lenMul;
76   XColor color;
77   int target;
78 } Laser;
79
80 #define kMaxMissiles 256
81 #define kMaxBooms 512
82 #define kMaxLasers 128
83 #define kBoomRad 40
84 #define kNumCities 5
85
86 #define kLaserLength 12
87
88 #define kMissileSpeed 0.003
89 #define kLaserSpeed (kMissileSpeed * 6)
90
91
92 struct state {
93   Display *dpy;
94   Window window;
95
96    XftFont *font, *scoreFont;
97    XftColor xft_fg, xft_level_fg;
98    XftDraw *xftdraw;
99    GC draw_gc, erase_gc;
100    unsigned int default_fg_pixel;
101    XColor scoreColor;
102
103    int bgrowth;
104    int lrate, startlrate;
105    long loop;
106    long score, highscore;
107    long nextBonus;
108    int numBonus;
109    int bround;
110    long lastLaser;
111    int gamez;
112    int aim;
113    int econpersen;
114    int choosypersen;
115    int carefulpersen;
116    int smart;
117    Colormap cmap;
118
119    Missile missile[kMaxMissiles];
120    Boom boom[kMaxBooms];
121    City city[kNumCities];
122    Laser laser[kMaxLasers];
123    int blive[kNumCities];
124
125    int level, levMissiles, levFreq;
126
127    int draw_xlim, draw_ylim;
128    int draw_reset;
129    int pscale;
130 };
131
132
133 static void Explode(struct state *st, int x, int y, int max, XColor color, int oflaser)
134 {
135   int i;
136   Boom *m = 0;
137   for (i=0;i<kMaxBooms;i++)
138          if (!st->boom[i].alive) {
139                 m = &st->boom[i];
140                 break;
141          }
142   if (!m)
143          return;
144
145   m->alive = 1;
146   m->x = x;
147   m->y = y;
148   m->rad = 0;
149   if (max > kMaxRadius)
150          max = kMaxRadius;
151   m->max = max;
152   m->outgoing = 1;
153   m->color = color;
154   m->oflaser = oflaser;
155 }
156
157 static void launch (struct state *st, int xlim, int ylim, int src)
158 {
159   int i;
160   Missile *m = 0, *msrc;
161   for (i=0;i<kMaxMissiles;i++)
162          if (!st->missile[i].alive) {
163                 m = &st->missile[i];
164                 break;
165          }
166   if (!m)
167          return;
168
169   m->alive = 1;
170   m->startx = (random() % xlim);
171   m->starty = 0;
172   m->endy = ylim;
173   m->pos = 0.0;
174   m->jenis = random() % 360;
175   m->splits = 0;
176   if (m->jenis < 50) {
177     int j = ylim * 0.4;
178     if (j) {
179          m->splits = random() % j;
180          if (m->splits < ylim * 0.08)
181                 m->splits = 0;
182     }
183   }
184
185   /* special if we're from another missile */
186   if (src >= 0) {
187          int dc = random() % (kNumCities - 1);
188          msrc = &st->missile[src];
189          if (dc == msrc->dcity)
190                 dc++;
191          m->dcity = dc;
192          m->startx = msrc->x;
193          m->starty = msrc->y;
194          if (m->starty > ylim * 0.4 || m->splits <= m->starty)
195                 m->splits = 0;  /* too far down already */
196          m->jenis = msrc->jenis;
197   }
198   else
199          m->dcity = random() % kNumCities;
200   m->endx = st->city[m->dcity].x + (random() % 20) - 10;
201   m->x = m->startx;
202   m->y = m->starty;
203   m->enemies = 0;
204
205   if (!mono_p) {
206          hsv_to_rgb (m->jenis, 1.0, 1.0,
207                                          &m->color.red, &m->color.green, &m->color.blue);
208          m->color.flags = DoRed | DoGreen | DoBlue;
209          if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
210                 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
211                 m->color.red = m->color.green = m->color.blue = 0xFFFF;
212          }
213   }
214 }
215
216 #define kExpHelp 0.2
217 #define kSpeedDiff 3.5
218 #define kMaxToGround 0.75
219 static int fire(struct state *st, int xlim, int ylim)
220 {
221   int i, j, cnt = 0;
222   int dcity;
223   long dx, dy, ex, ey;
224   Missile *mis = 0;
225   Laser *m = 0;
226   int untargeted = 0;
227   int choosy = 0, economic = 0, careful = 0;
228   int suitor[kMaxMissiles];
229   int livecity = 0;
230   int ytargetmin = ylim * 0.75;
231   int deepest = 0;
232   int misnum = 0;
233
234   choosy = (random() % 100) < st->choosypersen;
235   economic = (random() % 100) < st->econpersen;
236   careful = (random() % 100) < st->carefulpersen;
237
238   /* count our cities */
239   for (i=0;i<kNumCities;i++)
240          livecity += st->city[i].alive;
241   if (livecity == 0)
242          return 1;  /* no guns */
243
244   for (i=0;i<kMaxLasers;i++)
245          if (!st->laser[i].alive) {
246                 m = &st->laser[i];
247                 break;
248          }
249   if (!m)
250          return 1;
251
252   /* if no missiles on target, no need to be choosy */
253   if (choosy) {
254          int choo = 0;
255          for (j=0;j<kMaxMissiles;j++) {
256                 mis = &st->missile[j];
257                 if (!mis->alive || (mis->y > ytargetmin))
258                   continue;
259                 if (st->city[mis->dcity].alive)
260                   choo++;
261          }
262          if (choo == 0)
263                 choosy = 0;
264   }
265
266   for (j=0;j<kMaxMissiles;j++) {
267          mis = &st->missile[j];
268          suitor[j] = 0;
269          if (!mis->alive || (mis->y > ytargetmin))
270                 continue;
271          if (choosy && (st->city[mis->dcity].alive == 0))
272                 continue;
273          ey = mis->starty + ((float) (mis->endy - mis->starty)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
274          if (ey > ylim * kMaxToGround)
275                 continue;  /* too far down */
276          cnt++;
277          suitor[j] = 1;
278   }
279
280   /* count missiles that are on target and not being targeted */
281   if (choosy && economic)
282          for (j=0;j<kMaxMissiles;j++)
283                 if (suitor[j] && st->missile[j].enemies == 0)
284                   untargeted++;
285
286   if (economic)
287          for (j=0;j<kMaxMissiles;j++) {
288                 if (suitor[j] && cnt > 1)
289                   if (st->missile[j].enemies > 0)
290                          if (st->missile[j].enemies > 1 || untargeted == 0) {
291                                 suitor[j] = 0;
292                                 cnt--;
293                          }
294                 /* who's closest? biggest threat */
295                 if (suitor[j] && st->missile[j].y > deepest)
296                   deepest = st->missile[j].y;
297          }
298
299   if (deepest > 0 && careful) {
300          /* only target deepest missile */
301          cnt = 1;
302          for (j=0;j<kMaxMissiles;j++)
303                 if (suitor[j] && st->missile[j].y != deepest)
304                   suitor[j] = 0;
305   }
306
307   if (cnt == 0)
308          return 1;  /* no targets available */
309   cnt = random() % cnt;
310   for (j=0;j<kMaxMissiles;j++)
311          if (suitor[j])
312                 if (cnt-- == 0) {
313                   mis = &st->missile[j];
314                   misnum = j;
315                   break;
316                 }
317
318   if (mis == 0)
319          return 1;  /* shouldn't happen */
320
321   dcity = random() % livecity;
322   for (j=0;j<kNumCities;j++)
323          if (st->city[j].alive)
324                 if (dcity-- == 0) {
325                   dcity = j;
326                   break;
327                 }
328   m->startx = st->city[dcity].x;
329   m->starty = ylim;
330   ex = mis->startx + ((float) (mis->endx - mis->startx)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
331   ey = mis->starty + ((float) (mis->endy - mis->starty)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
332   m->endx = ex + random() % 16 - 8 + (random() % st->aim) - st->aim / 2;
333   m->endy = ey + random() % 16 - 8 + (random() % st->aim) - st->aim / 2;
334   if (ey > ylim * kMaxToGround)
335          return 0;  /* too far down */
336   mis->enemies++;
337   m->target = misnum;
338   m->x = m->startx;
339   m->y = m->starty;
340   m->oldx = -1;
341   m->oldy = -1;
342   m->oldx2 = -1;
343   m->oldy2 = -1;
344   m->fposx = m->x;
345   m->fposy = m->y;
346   dx = (m->endx - m->x);
347   dy = (m->endy - m->y);
348   m->velx = dx / 100.0;
349   m->vely = dy / 100.0;
350   m->alive = 1;
351   /* m->lenMul = (kLaserLength * kLaserLength) / (m->velx * m->velx + m->vely * m->vely); */
352   m->lenMul = -(kLaserLength / m->vely);
353
354   if (!mono_p) {
355          m->color.blue = 0x0000;
356          m->color.green = 0xFFFF;
357          m->color.red = 0xFFFF;
358          m->color.flags = DoRed | DoGreen | DoBlue;
359          if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
360                 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
361                 m->color.red = m->color.green = m->color.blue = 0xFFFF;
362          }
363   }
364   return 1;
365 }
366
367 static void *
368 penetrate_init (Display *dpy, Window window)
369 {
370   struct state *st = (struct state *) calloc (1, sizeof(*st));
371   int i;
372   const char *levelfont = "monospace 38";
373   const char *scorefont = "sans-serif 18";
374   XGCValues gcv;
375   XWindowAttributes xgwa;
376   char *s;
377
378   st->dpy = dpy;
379   st->window = window;
380
381   XGetWindowAttributes (st->dpy, st->window, &xgwa);
382   st->cmap = xgwa.colormap;
383
384   st->pscale = 1;
385   if (xgwa.width > 2560 || xgwa.height > 2560)
386     st->pscale *= 3;  /* Retina displays */
387
388   st->lrate = 80;
389   st->nextBonus = kFirstBonus;
390   st->aim = 180;
391
392   st->smart = get_boolean_resource(st->dpy, "smart","Boolean");
393   st->bgrowth = get_integer_resource (st->dpy, "bgrowth", "Integer");
394   st->lrate = get_integer_resource (st->dpy, "lrate", "Integer");
395   if (st->bgrowth < 0) st->bgrowth = 2;
396   if (st->lrate < 0) st->lrate = 2;
397   st->startlrate = st->lrate;
398
399   st->font = load_xft_font_retry(st->dpy,  screen_number (xgwa.screen),
400                                  levelfont);
401   if (!st->font) abort();
402
403   st->scoreFont = load_xft_font_retry(st->dpy, screen_number (xgwa.screen),
404                                       scorefont);
405   if (!st->scoreFont) abort();
406
407   for (i = 0; i < kMaxMissiles; i++)
408     st->missile[i].alive = 0;
409
410   for (i = 0; i < kMaxLasers; i++)
411     st->laser[i].alive = 0;
412
413   for (i = 0; i < kMaxBooms; i++)
414     st->boom[i].alive = 0;
415
416   for (i = 0; i < kNumCities; i++) {
417          City *m = &st->city[i];
418     m->alive = 1;
419          m->color.red = m->color.green = m->color.blue = 0xFFFF;
420          m->color.blue = 0x1111; m->color.green = 0x8888;
421          m->color.flags = DoRed | DoGreen | DoBlue;
422          if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
423                 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
424                 m->color.red = m->color.green = m->color.blue = 0xFFFF;
425          }
426   }
427
428   gcv.foreground = st->default_fg_pixel =
429     get_pixel_resource(st->dpy, st->cmap, "foreground", "Foreground");
430   st->draw_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
431   gcv.foreground = get_pixel_resource(st->dpy, st->cmap, "background", "Background");
432   st->erase_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
433   s = get_string_resource (st->dpy, "foreground", "Foreground");
434   if (!s) s = strdup ("white");
435   XftColorAllocName (st->dpy, xgwa.visual, xgwa.colormap, s, &st->xft_fg);
436   free (s);
437
438   /* Level color was st->city[0].color which is hardcoded as: */
439   s = "#FF8811";
440   XftColorAllocName (st->dpy, xgwa.visual, xgwa.colormap, s,
441                      &st->xft_level_fg);
442   st->xftdraw = XftDrawCreate (st->dpy, window, xgwa.visual, xgwa.colormap);
443
444 # ifdef HAVE_JWXYZ
445   jwxyz_XSetAntiAliasing (st->dpy, st->erase_gc, False);
446   jwxyz_XSetAntiAliasing (st->dpy, st->draw_gc, False);
447 # endif
448
449
450   /* make a gray color for score */
451   if (!mono_p) {
452          st->scoreColor.red = st->scoreColor.green = st->scoreColor.blue = 0xAAAA;
453          st->scoreColor.flags = DoRed | DoGreen | DoBlue;
454          if (!XAllocColor (st->dpy, st->cmap, &st->scoreColor)) {
455                 st->scoreColor.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
456                 st->scoreColor.red = st->scoreColor.green = st->scoreColor.blue = 0xFFFF;
457          }
458   }
459
460   XClearWindow(st->dpy, st->window);
461   return st;
462 }
463
464 static void DrawScore(struct state *st, int xlim, int ylim)
465 {
466   char buf[16];
467   int width, height;
468   XGlyphInfo overall;
469   sprintf(buf, "%ld", st->score);
470   XftTextExtentsUtf8 (st->dpy, st->scoreFont, (FcChar8 *) buf, 
471                       strlen(buf), &overall);
472   width = overall.xOff;
473   height = font_height(st->scoreFont);
474   XSetForeground (st->dpy, st->draw_gc, st->scoreColor.pixel);
475   XFillRectangle(st->dpy, st->window, st->erase_gc,
476                  xlim - width - 6, ylim - height - 2, width + 6, height + 2);
477   XftDrawStringUtf8 (st->xftdraw, &st->xft_fg, st->scoreFont,
478                      xlim - width - 2, ylim - 2,
479                      (FcChar8 *) buf, strlen(buf));
480
481   sprintf(buf, "%ld", st->highscore);
482   XftTextExtentsUtf8 (st->dpy, st->scoreFont, (FcChar8 *) buf, 
483                       strlen(buf), &overall);
484   width = overall.xOff;
485   XFillRectangle(st->dpy, st->window, st->erase_gc,
486                                   4, ylim - height - 2, width + 4, height + 2);
487   XftDrawStringUtf8 (st->xftdraw, &st->xft_fg, st->scoreFont,
488                      4, ylim - 2,
489                      (FcChar8 *) buf, strlen(buf));
490 }
491
492 static void AddScore(struct state *st, int xlim, int ylim, long dif)
493 {
494   int i, sumlive = 0;
495   for (i=0;i<kNumCities;i++)
496          sumlive += st->city[i].alive;
497   if (sumlive == 0)
498          return;   /* no cities, not possible to score */
499
500   st->score += dif;
501   if (st->score > st->highscore)
502          st->highscore = st->score;
503   DrawScore(st, xlim, ylim);
504 }
505
506 static void DrawCity(struct state *st, int x, int y, XColor col)
507 {
508          XSetForeground (st->dpy, st->draw_gc, col.pixel);
509          XFillRectangle(st->dpy, st->window, st->draw_gc,
510                         x - 30 * st->pscale, y - 40 * st->pscale,
511                         60 * st->pscale, 40 * st->pscale);
512          XFillRectangle(st->dpy, st->window, st->draw_gc,
513                         x - 20 * st->pscale, y - 50 * st->pscale, 
514                         10 * st->pscale, 10 * st->pscale);
515          XFillRectangle(st->dpy, st->window, st->draw_gc,
516                         x + 10 * st->pscale, y - 50 * st->pscale,
517                         10 * st->pscale, 10 * st->pscale);
518 }
519
520 static void DrawCities(struct state *st, int xlim, int ylim)
521 {
522   int i, x;
523   for (i = 0; i < kNumCities; i++) {
524          City *m = &st->city[i];
525          if (!m->alive)
526                 continue;
527          x = (i + 1) * (xlim / (kNumCities + 1));
528          m->x = x;
529
530          DrawCity(st, x, ylim, m->color);
531   }
532 }
533
534 static void LoopMissiles(struct state *st, int xlim, int ylim)
535 {
536   int i, j, max = 0;
537   for (i = 0; i < kMaxMissiles; i++) {
538          int old_x, old_y;
539          Missile *m = &st->missile[i];
540          if (!m->alive)
541                 continue;
542          old_x = m->x;
543          old_y = m->y;
544          m->pos += kMissileSpeed;
545          m->x = m->startx + ((float) (m->endx - m->startx)) * m->pos;
546          m->y = m->starty + ((float) (m->endy - m->starty)) * m->pos;
547
548       /* erase old one */
549
550          XSetLineAttributes(st->dpy, st->draw_gc, 4*st->pscale, 0,0,0);
551     XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
552          XDrawLine(st->dpy, st->window, st->draw_gc,
553                                   old_x, old_y, m->x, m->y);
554
555          /* maybe split off a new missile? */
556          if (m->splits && (m->y > m->splits)) {
557                 m->splits = 0;
558                 launch(st, xlim, ylim, i);
559          }
560          
561          if (m->y >= ylim) {
562                 m->alive = 0;
563                 if (st->city[m->dcity].alive) {
564                   st->city[m->dcity].alive = 0;
565                   Explode(st, m->x, m->y, kBoomRad * 2, m->color, 0);
566                 }
567          }
568
569          /* check hitting explosions */
570          for (j=0;j<kMaxBooms;j++) {
571                 Boom *b = &st->boom[j];
572                 if (!b->alive)
573                   continue;
574                 else {
575                   int dx = abs(m->x - b->x);
576                   int dy = abs(m->y - b->y);
577                   int r = b->rad + 2 * st->pscale;
578                   if ((dx < r) && (dy < r))
579                          if (dx * dx + dy * dy < r * r) {
580                                 m->alive = 0;
581                                 max = b->max + st->bgrowth - kBoomRad;
582                                 AddScore(st, xlim, ylim, SCORE_MISSILE);
583                   }
584                 }
585          }
586
587          if (m->alive == 0) {
588                 float my_pos;
589                 /* we just died */
590                 Explode(st, m->x, m->y, kBoomRad + max, m->color, 0);
591                 XSetLineAttributes(st->dpy, st->erase_gc, 4*st->pscale, 0,0,0);
592                 /* In a perfect world, we could simply erase a line from
593                    (m->startx, m->starty) to (m->x, m->y). This is not a
594                    perfect world. */
595                 old_x = m->startx;
596                 old_y = m->starty;
597                 my_pos = kMissileSpeed;
598                 while (my_pos <= m->pos) {
599                         m->x = m->startx + ((float) (m->endx - m->startx)) * my_pos;
600                         m->y = m->starty + ((float) (m->endy - m->starty)) * my_pos;
601                         XDrawLine(st->dpy, st->window, st->erase_gc, old_x, old_y, m->x, m->y);
602                         old_x = m->x;
603                         old_y = m->y;
604                         my_pos += kMissileSpeed;
605                 }
606          }
607   }
608 }
609
610 static void LoopLasers(struct state *st, int xlim, int ylim)
611 {
612   int i, j, miny = ylim * 0.8;
613   int x, y;
614   for (i = 0; i < kMaxLasers; i++) {
615          Laser *m = &st->laser[i];
616          if (!m->alive)
617                 continue;
618
619          if (m->oldx != -1) {
620                  XSetLineAttributes(st->dpy, st->erase_gc, 2*st->pscale, 0,0,0);
621                  XDrawLine(st->dpy, st->window, st->erase_gc,
622                                   m->oldx2, m->oldy2, m->oldx, m->oldy);
623          }
624
625          m->fposx += m->velx;
626          m->fposy += m->vely;
627          m->x = m->fposx;
628          m->y = m->fposy;
629          
630          x = m->fposx + (-m->velx * m->lenMul);
631          y = m->fposy + (-m->vely * m->lenMul);
632
633          m->oldx = x;
634          m->oldy = y;
635
636          XSetLineAttributes(st->dpy, st->draw_gc, 2*st->pscale, 0,0,0);
637     XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
638          XDrawLine(st->dpy, st->window, st->draw_gc,
639                                   m->x, m->y, x, y);
640
641          m->oldx2 = m->x;
642          m->oldy2 = m->y;
643          m->oldx = x;
644          m->oldy = y;
645          
646          if (m->y < m->endy) {
647                 m->alive = 0;
648          }
649
650          /* check hitting explosions */
651          if (m->y < miny)
652                 for (j=0;j<kMaxBooms;j++) {
653                   Boom *b = &st->boom[j];
654                   if (!b->alive)
655                          continue;
656                   else {
657                          int dx = abs(m->x - b->x);
658                          int dy = abs(m->y - b->y);
659                          int r = b->rad + 2 * st->pscale;
660                          if (b->oflaser)
661                                 continue;
662                          if ((dx < r) && (dy < r))
663                                 if (dx * dx + dy * dy < r * r) {
664                                   m->alive = 0;
665                                   /* one less enemy on this missile -- it probably didn't make it */
666                                   if (st->missile[m->target].alive)
667                                          st->missile[m->target].enemies--;
668                                 }
669                   }
670                 }
671          
672          if (m->alive == 0) {
673                 /* we just died */
674                 XDrawLine(st->dpy, st->window, st->erase_gc,
675                                   m->x, m->y, x, y);
676                 Explode(st, m->x, m->y, kBoomRad, m->color, 1);
677          }
678   }
679 }
680
681 static void LoopBooms(struct state *st, int xlim, int ylim)
682 {
683   int i;
684   for (i = 0; i < kMaxBooms; i++) {
685          Boom *m = &st->boom[i];
686          if (!m->alive)
687                 continue;
688          
689          if (st->loop & 1) {
690                 if (m->outgoing) {
691                   m->rad++;
692                   if (m->rad >= m->max)
693                          m->outgoing = 0;
694                   XSetLineAttributes(st->dpy, st->draw_gc, 1*st->pscale, 0,0,0);
695                   XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
696                   XDrawArc(st->dpy, st->window, st->draw_gc,
697                            m->x - m->rad * st->pscale,
698                            m->y - m->rad * st->pscale,
699                            m->rad * 2 * st->pscale,
700                            m->rad * 2 * st->pscale,
701                            0, 360 * 64);
702                 }
703                 else {
704                   XSetLineAttributes(st->dpy, st->erase_gc, 1*st->pscale, 0,0,0);
705                   XDrawArc(st->dpy, st->window, st->erase_gc,
706                            m->x - m->rad * st->pscale,
707                            m->y - m->rad * st->pscale,
708                            m->rad * 2 * st->pscale,
709                            m->rad * 2 * st->pscale,
710                            0, 360 * 64);
711                   m->rad--;
712                   if (m->rad <= 0)
713                          m->alive = 0;
714                 }
715          }
716   }
717 }
718
719
720 /* after they die, let's change a few things */
721 static void Improve(struct state *st)
722 {
723   if (st->smart)
724          return;
725   if (st->level > 20)
726          return;  /* no need, really */
727   st->aim -= 4;
728   if (st->level <= 2) st->aim -= 8;
729   if (st->level <= 5) st->aim -= 6;
730   if (st->gamez < 3)
731          st->aim -= 10;
732   st->carefulpersen += 6;
733   st->choosypersen += 4;
734   if (st->level <= 5) st->choosypersen += 3;
735   st->econpersen += 4;
736   st->lrate -= 2;
737   if (st->startlrate < kMinRate) {
738          if (st->lrate < st->startlrate)
739                 st->lrate = st->startlrate;
740   }
741   else {
742          if (st->lrate < kMinRate)
743                 st->lrate = kMinRate;
744   }
745   if (st->level <= 5) st->econpersen += 3;
746   if (st->aim < 1) st->aim = 1;
747   if (st->choosypersen > 100) st->choosypersen = 100;
748   if (st->carefulpersen > 100) st->carefulpersen = 100;
749   if (st->econpersen > 100) st->econpersen = 100;
750 }
751
752 static void NewLevel(struct state *st, int xlim, int ylim)
753 {
754   char buf[32];
755   int width, i, sumlive = 0;
756   int liv[kNumCities];
757   int freecity = 0;
758   XGlyphInfo overall;
759
760   if (st->level == 0) {
761          st->level++;
762          goto END_LEVEL;
763   }
764
765   /* check for a free city */
766   if (st->score >= st->nextBonus) {
767          st->numBonus++;
768          st->nextBonus += kFirstBonus * st->numBonus;
769          freecity = 1;
770   }
771
772   for (i=0;i<kNumCities;i++) {
773          if (st->bround)
774                 st->city[i].alive = st->blive[i];
775          liv[i] = st->city[i].alive;
776          sumlive += liv[i];
777          if (!st->bround)
778                 st->city[i].alive = 0;
779   }
780
781   /* print out screen */
782   XFillRectangle(st->dpy, st->window, st->erase_gc,
783                                   0, 0, xlim, ylim);
784   if (st->bround)
785          sprintf(buf, "Bonus Round Over");
786   else {
787          if (sumlive || freecity)
788                 sprintf(buf, "Level %d Cleared", st->level);
789          else
790                 sprintf(buf, "GAME OVER");
791   }
792   if (st->level > 0) {
793          XftTextExtentsUtf8 (st->dpy, st->font, (FcChar8 *) buf, 
794                              strlen(buf), &overall);
795          width = overall.xOff;
796          XftDrawStringUtf8 (st->xftdraw, &st->xft_level_fg, st->font,
797                    xlim / 2 - width / 2, ylim / 2 - font_height(st->font) / 2,
798                             (FcChar8 *) buf, strlen(buf));
799          XSync(st->dpy, False);
800          usleep(1000000);
801   }
802
803   if (!st->bround) {
804          if (sumlive || freecity) {
805                 int sumwidth;
806                 /* draw live cities */
807                 XFillRectangle(st->dpy, st->window, st->erase_gc,
808                                0, ylim - 100 * st->pscale, xlim, 100 * st->pscale);
809
810                 sprintf(buf, "X %ld", st->level * 100L);
811                 /* how much they get */
812                 XftTextExtentsUtf8 (st->dpy, st->font, (FcChar8 *) buf, 
813                                     strlen(buf), &overall);
814                 sumwidth = overall.xOff;
815                 /* add width of city */
816                 sumwidth += 60;
817                 /* add spacer */
818                 sumwidth += 40;
819                 DrawCity(st, xlim / 2 - sumwidth / 2 + 30, ylim * 0.70, st->city[0].color);
820                 XftDrawStringUtf8 (st->xftdraw, &st->xft_level_fg, st->font,
821                                    xlim / 2 - sumwidth / 2 + 40 + 60,
822                                    ylim * 0.7, 
823                                    (FcChar8 *) buf, strlen(buf));
824                 for (i=0;i<kNumCities;i++) {
825                   if (liv[i]) {
826                          st->city[i].alive = 1;
827                          AddScore(st, xlim, ylim, 100 * st->level);
828                          DrawCities(st, xlim, ylim);
829                          XSync(st->dpy, False);
830                          usleep(kCityPause);
831                   }
832                 }
833          }
834          else {
835                 /* we're dead */
836                 usleep(3000000);
837
838                 /* start new */
839                 st->gamez++;
840                 Improve(st);
841                 for (i=0;i<kNumCities;i++)
842                   st->city[i].alive = 1;
843                 st->level = 0;
844                 st->loop = 1;
845                 st->score = 0;
846                 st->nextBonus = kFirstBonus;
847                 st->numBonus = 0;
848                 DrawCities(st, xlim, ylim);
849          }
850   }
851
852   /* do free city part */
853   if (freecity && sumlive < 5) {
854          int ncnt = random() % (5 - sumlive) + 1;
855          for (i=0;i<kNumCities;i++)
856                 if (!st->city[i].alive)
857                   if (!--ncnt)
858                          st->city[i].alive = 1;
859          strcpy(buf, "Bonus City");
860          XftTextExtentsUtf8 (st->dpy, st->font, (FcChar8 *) buf, 
861                              strlen(buf), &overall);
862          width = overall.xOff;
863          XftDrawStringUtf8 (st->xftdraw, &st->xft_level_fg, st->font,
864                             xlim / 2 - width / 2, ylim / 4,
865                             (FcChar8 *) buf, strlen(buf));
866          DrawCities(st, xlim, ylim);
867          XSync(st->dpy, False);
868          usleep(1000000);
869   }
870
871   XFillRectangle(st->dpy, st->window, st->erase_gc,
872                  0, 0, xlim, ylim - 100 * st->pscale);
873   
874   if (!st->bround)
875          st->level++;
876   if (st->level == 1) {
877          st->nextBonus = kFirstBonus;
878   }
879
880   if (st->level > 3 && (st->level % 5 == 1)) {
881          if (st->bround) {
882                 st->bround = 0;
883                 DrawCities(st, xlim, ylim);
884          }
885          else {
886                 /* bonus round */
887                 st->bround = 1;
888                 st->levMissiles = 20 + st->level * 10;
889                 st->levFreq = 10;
890                 for (i=0;i<kNumCities;i++)
891                   st->blive[i] = st->city[i].alive;
892                 sprintf(buf, "Bonus Round");
893                 XftTextExtentsUtf8 (st->dpy, st->font, (FcChar8 *) buf, 
894                                     strlen(buf), &overall);
895                 width = overall.xOff;
896                 XftDrawStringUtf8 (st->xftdraw, &st->xft_level_fg, st->font,
897                                    xlim / 2 - width / 2,
898                                    ylim / 2 - font_height(st->font) / 2,
899                                    (FcChar8 *) buf, strlen(buf));
900                 XSync(st->dpy, False);
901                 usleep(1000000);
902                 XFillRectangle(st->dpy, st->window, st->erase_gc,
903                                0, 0, xlim, ylim - 100 * st->pscale);
904          }
905   }
906
907  END_LEVEL: ;
908
909   if (!st->bround) {
910          st->levMissiles = 5 + st->level * 3;
911          if (st->level > 5)
912                 st->levMissiles += st->level * 5;
913          /*  levMissiles = 2; */
914          st->levFreq = 120 - st->level * 5;
915          if (st->levFreq < 30)
916                 st->levFreq = 30;
917   }
918
919   /* ready to fire */
920   st->lastLaser = 0;
921 }
922
923
924 static unsigned long
925 penetrate_draw (Display *dpy, Window window, void *closure)
926 {
927   struct state *st = (struct state *) closure;
928   XWindowAttributes xgwa;
929
930   if (st->draw_reset)
931     {
932       st->draw_reset = 0;
933       DrawCities(st, st->draw_xlim, st->draw_ylim);
934     }
935
936   XGetWindowAttributes(st->dpy, st->window, &xgwa);
937   st->draw_xlim = xgwa.width;
938   st->draw_ylim = xgwa.height;
939
940   /* see if just started */
941   if (st->loop == 0) {
942          if (st->smart) {
943                 st->choosypersen = st->econpersen = st->carefulpersen = 100;
944                 st->lrate = kMinRate; st->aim = 1;
945          }
946          NewLevel(st, st->draw_xlim, st->draw_ylim);
947          DrawScore(st, st->draw_xlim, st->draw_ylim);
948   }
949
950   st->loop++;
951
952   if (st->levMissiles == 0) {
953          /* see if anything's still on the screen, to know when to end level */
954          int i;
955          for (i=0;i<kMaxMissiles;i++)
956                 if (st->missile[i].alive)
957                   goto END_CHECK;
958          for (i=0;i<kMaxBooms;i++)
959                 if (st->boom[i].alive)
960                   goto END_CHECK;
961          for (i=0;i<kMaxLasers;i++)
962                 if (st->laser[i].alive)
963                   goto END_CHECK;
964          /* okay, nothing's alive, start end of level countdown */
965          usleep(kLevelPause*1000000);
966          NewLevel(st, st->draw_xlim, st->draw_ylim);
967          goto END;
968   END_CHECK: ;
969   }
970   else if ((random() % st->levFreq) == 0) {
971          launch(st, st->draw_xlim, st->draw_ylim, -1);
972          st->levMissiles--;
973   }
974
975   if (st->loop - st->lastLaser >= st->lrate) {
976          if (fire(st, st->draw_xlim, st->draw_ylim))
977                 st->lastLaser = st->loop;
978   }
979
980   if ((st->loop & 7) == 0)
981     st->draw_reset = 1;
982
983   LoopMissiles(st, st->draw_xlim, st->draw_ylim);
984   LoopLasers(st, st->draw_xlim, st->draw_ylim);
985   LoopBooms(st, st->draw_xlim, st->draw_ylim);
986
987  END:
988   return kSleepTime;
989 }
990
991 static void
992 penetrate_reshape (Display *dpy, Window window, void *closure, 
993                  unsigned int w, unsigned int h)
994 {
995   XClearWindow (dpy, window);
996 }
997
998 static Bool
999 penetrate_event (Display *dpy, Window window, void *closure, XEvent *event)
1000 {
1001   return False;
1002 }
1003
1004 static void
1005 penetrate_free (Display *dpy, Window window, void *closure)
1006 {
1007   struct state *st = (struct state *) closure;
1008   XFreeGC (dpy, st->draw_gc);
1009   XFreeGC (dpy, st->erase_gc);
1010   XftFontClose (st->dpy, st->font);
1011   XftFontClose (st->dpy, st->scoreFont);
1012   XftDrawDestroy (st->xftdraw);
1013   free (st);
1014 }
1015
1016
1017 static const char *penetrate_defaults [] = {
1018 /*  ".lowrez:     true", */
1019   ".background: black",
1020   ".foreground: white",
1021   "*fpsTop:     true",
1022   "*fpsSolid:   true",
1023   "*bgrowth:    5",
1024   "*lrate:      80",
1025   "*smart:      False",
1026   0
1027 };
1028
1029 static XrmOptionDescRec penetrate_options [] = {
1030   { "-bgrowth",         ".bgrowth",     XrmoptionSepArg, 0 },
1031   { "-lrate",           ".lrate",       XrmoptionSepArg, 0 },
1032   {"-smart",            ".smart",       XrmoptionNoArg, "True" },
1033   { 0, 0, 0, 0 }
1034 };
1035
1036 XSCREENSAVER_MODULE ("Penetrate", penetrate)