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