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