2 * Adam Miller adum@aya.yale.edu
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
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.
23 -- fixed an AI bug that was keeping the computer player a tad weak
29 #include "screenhack.h"
31 #define kSleepTime 10000
33 #define font_height(font) (font->ascent + font->descent)
34 #define FONT_NAME "-*-times-*-*-*-*-80-*-*-*-*-*-*-*"
36 #define kCityPause 500000
38 #define SCORE_MISSILE 100
39 #define kFirstBonus 5000
41 #define kMaxRadius 100
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;
49 int lrate = 80, startlrate;
51 long score = 0, highscore = 0;
52 long nextBonus = kFirstBonus;
60 int carefulpersen = 0;
78 int x, y, rad, oflaser;
96 float velx, vely, fposx, fposy;
102 #define kMaxMissiles 256
103 #define kMaxBooms 512
104 #define kMaxLasers 128
108 #define kLaserLength 12
110 #define kMissileSpeed 0.003
111 #define kLaserSpeed (kMissileSpeed * 6)
113 Missile missile[kMaxMissiles];
114 Boom boom[kMaxBooms];
115 City city[kNumCities];
116 Laser laser[kMaxLasers];
117 int blive[kNumCities];
119 static void Explode(int x, int y, int max, XColor color, int oflaser)
123 for (i=0;i<kMaxBooms;i++)
124 if (!boom[i].alive) {
135 if (max > kMaxRadius)
140 m->oflaser = oflaser;
143 static void launch (int xlim, int ylim,
144 Display *dpy, Colormap cmap, int src)
147 Missile *m = 0, *msrc;
148 for (i=0;i<kMaxMissiles;i++)
149 if (!missile[i].alive) {
157 m->startx = (random() % xlim);
161 m->jenis = random() % 360;
164 m->splits = random() % ((int) (ylim * 0.4));
165 if (m->splits < ylim * 0.08)
169 /* special if we're from another missile */
171 int dc = random() % (kNumCities - 1);
172 msrc = &missile[src];
173 if (dc == msrc->dcity)
178 if (m->starty > ylim * 0.4 || m->splits <= m->starty)
179 m->splits = 0; /* too far down already */
180 m->jenis = msrc->jenis;
183 m->dcity = random() % kNumCities;
184 m->endx = city[m->dcity].x + (random() % 20) - 10;
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;
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)
212 int choosy = 0, economic = 0, careful = 0;
213 int suitor[kMaxMissiles];
215 int ytargetmin = ylim * 0.75;
219 choosy = (random() % 100) < choosypersen;
220 economic = (random() % 100) < econpersen;
221 careful = (random() % 100) < carefulpersen;
223 /* count our cities */
224 for (i=0;i<kNumCities;i++)
225 livecity += city[i].alive;
227 return 1; /* no guns */
229 for (i=0;i<kMaxLasers;i++)
230 if (!laser[i].alive) {
237 /* if no missiles on target, no need to be choosy */
240 for (j=0;j<kMaxMissiles;j++) {
242 if (!mis->alive || (mis->y > ytargetmin))
244 if (city[mis->dcity].alive)
251 for (j=0;j<kMaxMissiles;j++) {
254 if (!mis->alive || (mis->y > ytargetmin))
256 if (choosy && (city[mis->dcity].alive == 0))
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 */
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)
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) {
279 /* who's closest? biggest threat */
280 if (suitor[j] && missile[j].y > deepest)
281 deepest = missile[j].y;
284 if (deepest > 0 && careful) {
285 /* only target deepest missile */
287 for (j=0;j<kMaxMissiles;j++)
288 if (suitor[j] && missile[j].y != deepest)
293 return 1; /* no targets available */
294 cnt = random() % cnt;
295 for (j=0;j<kMaxMissiles;j++)
304 return 1; /* shouldn't happen */
306 dcity = random() % livecity;
307 for (j=0;j<kNumCities;j++)
313 m->startx = city[dcity].x;
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 */
331 dx = (m->endx - m->x);
332 dy = (m->endy - m->y);
333 m->velx = dx / 100.0;
334 m->vely = dy / 100.0;
336 /* m->lenMul = (kLaserLength * kLaserLength) / (m->velx * m->velx + m->vely * m->vely); */
337 m->lenMul = -(kLaserLength / m->vely);
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;
353 init_penetrate(Display *dpy, Window window)
356 /*char *fontname = "-*-new century schoolbook-*-r-*-*-*-380-*-*-*-*-*-*"; */
357 char *fontname = "-*-courier-*-r-*-*-*-380-*-*-*-*-*-*";
362 XWindowAttributes xgwa;
363 XGetWindowAttributes (dpy, window, &xgwa);
364 cmap = xgwa.colormap;
366 if (get_string_resource("smart","String")!=NULL && get_string_resource("smart","String")[0]!=0)
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;
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])))
380 fprintf (stderr, "%s: Can't find a large font.", progname);
383 XFreeFontNames(list);
386 if (!(scoreFont = XLoadQueryFont(dpy, "-*-times-*-r-*-*-*-180-*-*-*-*-*-*")))
387 fprintf(stderr, "%s: Can't load Times font.", progname);
389 for (i = 0; i < kMaxMissiles; i++)
390 missile[i].alive = 0;
392 for (i = 0; i < kMaxLasers; i++)
395 for (i = 0; i < kMaxBooms; i++)
398 for (i = 0; i < kNumCities; i++) {
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;
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);
420 /* make a gray color for score */
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;
430 XClearWindow(dpy, window);
434 static void DrawScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
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,
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,
455 static void AddScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim, long dif)
458 for (i=0;i<kNumCities;i++)
459 sumlive += city[i].alive;
461 return; /* no cities, not possible to score */
464 if (score > highscore)
466 DrawScore(dpy, window, cmap, xlim, ylim);
469 static void DrawCity(Display *dpy, Window window, Colormap cmap, int x, int y, XColor col)
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);
480 static void DrawCities(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
483 for (i = 0; i < kNumCities; i++) {
487 x = (i + 1) * (xlim / (kNumCities + 1));
490 DrawCity(dpy, window, cmap, x, ylim, m->color);
494 static void LoopMissiles(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
497 for (i = 0; i < kMaxMissiles; i++) {
499 Missile *m = &missile[i];
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;
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);
515 /* maybe split off a new missile? */
516 if (m->splits && (m->y > m->splits)) {
518 launch(xlim, ylim, dpy, cmap, i);
523 if (city[m->dcity].alive) {
524 city[m->dcity].alive = 0;
525 Explode(m->x, m->y, kBoomRad * 2, m->color, 0);
529 /* check hitting explosions */
530 for (j=0;j<kMaxBooms;j++) {
535 int dx = abs(m->x - b->x);
536 int dy = abs(m->y - b->y);
538 if ((dx < r) && (dy < r))
539 if (dx * dx + dy * dy < r * r) {
541 max = b->max + bgrowth - kBoomRad;
542 AddScore(dpy, window, cmap, xlim, ylim, SCORE_MISSILE);
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
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);
565 my_pos += kMissileSpeed;
571 static void LoopLasers(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
573 int i, j, miny = ylim * 0.8;
575 for (i = 0; i < kMaxLasers; i++) {
576 Laser *m = &laser[i];
581 XSetLineAttributes(dpy, erase_gc, 2, 0,0,0);
582 XDrawLine(dpy, window, erase_gc,
583 m->oldx2, m->oldy2, m->oldx, m->oldy);
591 x = m->fposx + (-m->velx * m->lenMul);
592 y = m->fposy + (-m->vely * m->lenMul);
597 XSetLineAttributes(dpy, draw_gc, 2, 0,0,0);
598 XSetForeground (dpy, draw_gc, m->color.pixel);
599 XDrawLine(dpy, window, draw_gc,
607 if (m->y < m->endy) {
611 /* check hitting explosions */
613 for (j=0;j<kMaxBooms;j++) {
618 int dx = abs(m->x - b->x);
619 int dy = abs(m->y - b->y);
623 if ((dx < r) && (dy < r))
624 if (dx * dx + dy * dy < r * r) {
626 /* one less enemy on this missile -- it probably didn't make it */
627 if (missile[m->target].alive)
628 missile[m->target].enemies--;
635 XDrawLine(dpy, window, erase_gc,
637 Explode(m->x, m->y, kBoomRad, m->color, 1);
642 static void LoopBooms(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
645 for (i = 0; i < kMaxBooms; i++) {
653 if (m->rad >= m->max)
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);
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);
670 int level = 0, levMissiles, levFreq;
672 /* after they die, let's change a few things */
673 static void Improve(void)
678 return; /* no need, really */
680 if (level <= 2) aim -= 8;
681 if (level <= 5) aim -= 6;
686 if (level <= 5) choosypersen += 3;
689 if (startlrate < kMinRate) {
690 if (lrate < startlrate)
694 if (lrate < kMinRate)
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;
704 static void NewLevel(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
707 int width, i, sumlive = 0;
716 /* check for a free city */
717 if (score >= nextBonus) {
719 nextBonus += kFirstBonus * numBonus;
723 for (i=0;i<kNumCities;i++) {
725 city[i].alive = blive[i];
726 liv[i] = city[i].alive;
732 /* print out screen */
733 XFillRectangle(dpy, window, erase_gc,
736 sprintf(buf, "Bonus Round Over");
738 if (sumlive || freecity)
739 sprintf(buf, "Level %d Cleared", level);
741 sprintf(buf, "GAME OVER");
744 width = XTextWidth(font, buf, strlen(buf));
745 XDrawString(dpy, window, level_gc, xlim / 2 - width / 2, ylim / 2 - font_height(font) / 2,
748 screenhack_handle_events(dpy);
753 if (sumlive || freecity) {
755 /* draw live cities */
756 XFillRectangle(dpy, window, erase_gc,
757 0, ylim - 100, xlim, 100);
759 sprintf(buf, "X %ld", level * 100L);
760 /* how much they get */
761 sumwidth = XTextWidth(font, buf, strlen(buf));
762 /* add width of city */
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++) {
771 AddScore(dpy, window, cmap, xlim, ylim, 100 * level);
772 DrawCities(dpy, window, cmap, xlim, ylim);
774 screenhack_handle_events(dpy);
781 screenhack_handle_events(dpy);
783 screenhack_handle_events(dpy);
787 for (i=0;i<kNumCities;i++)
792 nextBonus = kFirstBonus;
794 DrawCities(dpy, window, cmap, xlim, ylim);
798 /* do free city part */
799 if (freecity && sumlive < 5) {
800 int ncnt = random() % (5 - sumlive) + 1;
801 for (i=0;i<kNumCities;i++)
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);
810 screenhack_handle_events(dpy);
814 XFillRectangle(dpy, window, erase_gc,
815 0, 0, xlim, ylim - 100);
820 nextBonus = kFirstBonus;
823 if (level > 3 && (level % 5 == 1)) {
826 DrawCities(dpy, window, cmap, xlim, ylim);
831 levMissiles = 20 + level * 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));
839 screenhack_handle_events(dpy);
841 XFillRectangle(dpy, window, erase_gc,
842 0, 0, xlim, ylim - 100);
849 levMissiles = 5 + level * 3;
851 levMissiles += level * 5;
852 /* levMissiles = 2; */
853 levFreq = 120 - level * 5;
862 static void penetrate(Display *dpy, Window window, Colormap cmap)
864 XWindowAttributes xgwa;
865 static int xlim, ylim;
867 XGetWindowAttributes(dpy, window, &xgwa);
871 /* see if just started */
874 choosypersen = econpersen = carefulpersen = 100;
875 lrate = kMinRate; aim = 1;
877 NewLevel(dpy, window, cmap, xlim, ylim);
878 DrawScore(dpy, window, cmap, xlim, ylim);
883 if (levMissiles == 0) {
884 /* see if anything's still on the screen, to know when to end level */
886 for (i=0;i<kMaxMissiles;i++)
887 if (missile[i].alive)
889 for (i=0;i<kMaxBooms;i++)
892 for (i=0;i<kMaxLasers;i++)
895 /* okay, nothing's alive, start end of level countdown */
896 screenhack_handle_events(dpy);
898 NewLevel(dpy, window, cmap, xlim, ylim);
902 else if ((random() % levFreq) == 0) {
903 launch(xlim, ylim, dpy, cmap, -1);
907 if (loop - lastLaser >= lrate) {
908 if (fire(xlim, ylim, dpy, window, cmap))
913 screenhack_handle_events(dpy);
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);
924 char *progclass = "Penetrate";
926 char *defaults [] = {
927 ".background: black",
928 ".foreground: white",
931 "*geometry: 800x500",
935 XrmOptionDescRec options [] = {
936 { "-bgrowth", ".bgrowth", XrmoptionSepArg, 0 },
937 { "-lrate", ".lrate", XrmoptionSepArg, 0 },
938 {"-smart", ".smart", XrmoptionIsArg,0},
943 screenhack (Display *dpy, Window window)
945 Colormap cmap = init_penetrate(dpy, window);
947 penetrate(dpy, window, cmap);