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;
95 float velx, vely, fposx, fposy;
101 #define kMaxMissiles 256
102 #define kMaxBooms 512
103 #define kMaxLasers 128
107 #define kLaserLength 12
109 #define kMissileSpeed 0.003
110 #define kLaserSpeed (kMissileSpeed * 6)
112 Missile missile[kMaxMissiles];
113 Boom boom[kMaxBooms];
114 City city[kNumCities];
115 Laser laser[kMaxLasers];
116 int blive[kNumCities];
118 static void Explode(int x, int y, int max, XColor color, int oflaser)
122 for (i=0;i<kMaxBooms;i++)
123 if (!boom[i].alive) {
134 if (max > kMaxRadius)
139 m->oflaser = oflaser;
142 static void launch (int xlim, int ylim,
143 Display *dpy, Colormap cmap, int src)
146 Missile *m = 0, *msrc;
147 for (i=0;i<kMaxMissiles;i++)
148 if (!missile[i].alive) {
156 m->startx = (random() % xlim);
160 m->jenis = random() % 360;
163 m->splits = random() % ((int) (ylim * 0.4));
164 if (m->splits < ylim * 0.08)
168 /* special if we're from another missile */
170 int dc = random() % (kNumCities - 1);
171 msrc = &missile[src];
172 if (dc == msrc->dcity)
177 if (m->starty > ylim * 0.4 || m->splits <= m->starty)
178 m->splits = 0; /* too far down already */
179 m->jenis = msrc->jenis;
182 m->dcity = random() % kNumCities;
183 m->endx = city[m->dcity].x + (random() % 20) - 10;
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;
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)
211 int choosy = 0, economic = 0, careful = 0;
212 int suitor[kMaxMissiles];
214 int ytargetmin = ylim * 0.75;
218 choosy = (random() % 100) < choosypersen;
219 economic = (random() % 100) < econpersen;
220 careful = (random() % 100) < carefulpersen;
222 /* count our cities */
223 for (i=0;i<kNumCities;i++)
224 livecity += city[i].alive;
226 return 1; /* no guns */
228 for (i=0;i<kMaxLasers;i++)
229 if (!laser[i].alive) {
236 /* if no missiles on target, no need to be choosy */
239 for (j=0;j<kMaxMissiles;j++) {
241 if (!mis->alive || (mis->y > ytargetmin))
243 if (city[mis->dcity].alive)
250 for (j=0;j<kMaxMissiles;j++) {
253 if (!mis->alive || (mis->y > ytargetmin))
255 if (choosy && (city[mis->dcity].alive == 0))
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 */
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)
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) {
278 /* who's closest? biggest threat */
279 if (suitor[j] && missile[j].y > deepest)
280 deepest = missile[j].y;
283 if (deepest > 0 && careful) {
284 /* only target deepest missile */
286 for (j=0;j<kMaxMissiles;j++)
287 if (suitor[j] && missile[j].y != deepest)
292 return 1; /* no targets available */
293 cnt = random() % cnt;
294 for (j=0;j<kMaxMissiles;j++)
303 return 1; /* shouldn't happen */
305 dcity = random() % livecity;
306 for (j=0;j<kNumCities;j++)
312 m->startx = city[dcity].x;
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 */
328 dx = (m->endx - m->x);
329 dy = (m->endy - m->y);
330 m->velx = dx / 100.0;
331 m->vely = dy / 100.0;
333 /* m->lenMul = (kLaserLength * kLaserLength) / (m->velx * m->velx + m->vely * m->vely); */
334 m->lenMul = -(kLaserLength / m->vely);
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;
350 init_penetrate(Display *dpy, Window window)
353 /*char *fontname = "-*-new century schoolbook-*-r-*-*-*-380-*-*-*-*-*-*"; */
354 char *fontname = "-*-courier-*-r-*-*-*-380-*-*-*-*-*-*";
359 XWindowAttributes xgwa;
360 XGetWindowAttributes (dpy, window, &xgwa);
361 cmap = xgwa.colormap;
363 if (get_string_resource("smart","String")!=NULL && get_string_resource("smart","String")[0]!=0)
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;
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])))
377 fprintf (stderr, "%s: Can't find a large font.", progname);
380 XFreeFontNames(list);
383 if (!(scoreFont = XLoadQueryFont(dpy, "-*-times-*-r-*-*-*-180-*-*-*-*-*-*")))
384 fprintf(stderr, "%s: Can't load Times font.", progname);
386 for (i = 0; i < kMaxMissiles; i++)
387 missile[i].alive = 0;
389 for (i = 0; i < kMaxLasers; i++)
392 for (i = 0; i < kMaxBooms; i++)
395 for (i = 0; i < kNumCities; i++) {
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;
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);
417 /* make a gray color for score */
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;
427 XClearWindow(dpy, window);
431 static void DrawScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
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,
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,
452 static void AddScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim, long dif)
455 for (i=0;i<kNumCities;i++)
456 sumlive += city[i].alive;
458 return; /* no cities, not possible to score */
461 if (score > highscore)
463 DrawScore(dpy, window, cmap, xlim, ylim);
466 static void DrawCity(Display *dpy, Window window, Colormap cmap, int x, int y, XColor col)
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);
477 static void DrawCities(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
480 for (i = 0; i < kNumCities; i++) {
484 x = (i + 1) * (xlim / (kNumCities + 1));
487 DrawCity(dpy, window, cmap, x, ylim, m->color);
491 static void LoopMissiles(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
494 for (i = 0; i < kMaxMissiles; i++) {
496 Missile *m = &missile[i];
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;
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);
512 /* maybe split off a new missile? */
513 if (m->splits && (m->y > m->splits)) {
515 launch(xlim, ylim, dpy, cmap, i);
520 if (city[m->dcity].alive) {
521 city[m->dcity].alive = 0;
522 Explode(m->x, m->y, kBoomRad * 2, m->color, 0);
526 /* check hitting explosions */
527 for (j=0;j<kMaxBooms;j++) {
532 int dx = abs(m->x - b->x);
533 int dy = abs(m->y - b->y);
535 if ((dx < r) && (dy < r))
536 if (dx * dx + dy * dy < r * r) {
538 max = b->max + bgrowth - kBoomRad;
539 AddScore(dpy, window, cmap, xlim, ylim, SCORE_MISSILE);
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);
554 static void LoopLasers(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
556 int i, j, miny = ylim * 0.8;
558 for (i = 0; i < kMaxLasers; i++) {
559 Laser *m = &laser[i];
567 x = m->fposx + (-m->velx * m->lenMul);
568 y = m->fposy + (-m->vely * m->lenMul);
570 XSetLineAttributes(dpy, erase_gc, 4, 0,0,0);
571 XDrawLine(dpy, window, erase_gc,
572 x, y, m->oldx, m->oldy);
576 XSetLineAttributes(dpy, draw_gc, 2, 0,0,0);
577 XSetForeground (dpy, draw_gc, m->color.pixel);
578 XDrawLine(dpy, window, draw_gc,
581 if (m->y < m->endy) {
585 /* check hitting explosions */
587 for (j=0;j<kMaxBooms;j++) {
592 int dx = abs(m->x - b->x);
593 int dy = abs(m->y - b->y);
597 if ((dx < r) && (dy < r))
598 if (dx * dx + dy * dy < r * r) {
600 /* one less enemy on this missile -- it probably didn't make it */
601 if (missile[m->target].alive)
602 missile[m->target].enemies--;
609 XDrawLine(dpy, window, erase_gc,
611 Explode(m->x, m->y, kBoomRad, m->color, 1);
616 static void LoopBooms(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
619 for (i = 0; i < kMaxBooms; i++) {
627 if (m->rad >= m->max)
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);
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);
643 int level = 0, levMissiles, levFreq;
645 /* after they die, let's change a few things */
646 static void Improve(void)
651 return; /* no need, really */
653 if (level <= 2) aim -= 8;
654 if (level <= 5) aim -= 6;
659 if (level <= 5) choosypersen += 3;
662 if (startlrate < kMinRate) {
663 if (lrate < startlrate)
667 if (lrate < kMinRate)
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;
677 static void NewLevel(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
680 int width, i, sumlive = 0;
689 /* check for a free city */
690 if (score >= nextBonus) {
692 nextBonus += kFirstBonus * numBonus;
696 for (i=0;i<kNumCities;i++) {
698 city[i].alive = blive[i];
699 liv[i] = city[i].alive;
705 /* print out screen */
706 XFillRectangle(dpy, window, erase_gc,
709 sprintf(buf, "Bonus Round Over");
711 if (sumlive || freecity)
712 sprintf(buf, "Level %d Cleared", level);
714 sprintf(buf, "GAME OVER");
717 width = XTextWidth(font, buf, strlen(buf));
718 XDrawString(dpy, window, level_gc, xlim / 2 - width / 2, ylim / 2 - font_height(font) / 2,
721 screenhack_handle_events(dpy);
726 if (sumlive || freecity) {
728 /* draw live cities */
729 XFillRectangle(dpy, window, erase_gc,
730 0, ylim - 100, xlim, 100);
732 sprintf(buf, "X %ld", level * 100L);
733 /* how much they get */
734 sumwidth = XTextWidth(font, buf, strlen(buf));
735 /* add width of city */
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++) {
744 AddScore(dpy, window, cmap, xlim, ylim, 100 * level);
745 DrawCities(dpy, window, cmap, xlim, ylim);
747 screenhack_handle_events(dpy);
754 screenhack_handle_events(dpy);
756 screenhack_handle_events(dpy);
760 for (i=0;i<kNumCities;i++)
765 nextBonus = kFirstBonus;
767 DrawCities(dpy, window, cmap, xlim, ylim);
771 /* do free city part */
772 if (freecity && sumlive < 5) {
773 int ncnt = random() % (5 - sumlive) + 1;
774 for (i=0;i<kNumCities;i++)
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);
783 screenhack_handle_events(dpy);
787 XFillRectangle(dpy, window, erase_gc,
788 0, 0, xlim, ylim - 100);
793 nextBonus = kFirstBonus;
796 if (level > 3 && (level % 5 == 1)) {
799 DrawCities(dpy, window, cmap, xlim, ylim);
804 levMissiles = 20 + level * 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));
812 screenhack_handle_events(dpy);
814 XFillRectangle(dpy, window, erase_gc,
815 0, 0, xlim, ylim - 100);
822 levMissiles = 5 + level * 3;
824 levMissiles += level * 5;
825 /* levMissiles = 2; */
826 levFreq = 120 - level * 5;
835 static void penetrate(Display *dpy, Window window, Colormap cmap)
837 XWindowAttributes xgwa;
838 static int xlim, ylim;
840 XGetWindowAttributes(dpy, window, &xgwa);
844 /* see if just started */
847 choosypersen = econpersen = carefulpersen = 100;
848 lrate = kMinRate; aim = 1;
850 NewLevel(dpy, window, cmap, xlim, ylim);
851 DrawScore(dpy, window, cmap, xlim, ylim);
856 if (levMissiles == 0) {
857 /* see if anything's still on the screen, to know when to end level */
859 for (i=0;i<kMaxMissiles;i++)
860 if (missile[i].alive)
862 for (i=0;i<kMaxBooms;i++)
865 for (i=0;i<kMaxLasers;i++)
868 /* okay, nothing's alive, start end of level countdown */
869 screenhack_handle_events(dpy);
871 NewLevel(dpy, window, cmap, xlim, ylim);
875 else if ((random() % levFreq) == 0) {
876 launch(xlim, ylim, dpy, cmap, -1);
880 if (loop - lastLaser >= lrate) {
881 if (fire(xlim, ylim, dpy, window, cmap))
886 screenhack_handle_events(dpy);
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);
897 char *progclass = "Penetrate";
899 char *defaults [] = {
900 ".background: black",
901 ".foreground: white",
904 "*geometry: 800x500",
908 XrmOptionDescRec options [] = {
909 { "-bgrowth", ".bgrowth", XrmoptionSepArg, 0 },
910 { "-lrate", ".lrate", XrmoptionSepArg, 0 },
911 {"-smart", ".smart", XrmoptionIsArg,0},
916 screenhack (Display *dpy, Window window)
918 Colormap cmap = init_penetrate(dpy, window);
920 penetrate(dpy, window, cmap);