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