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