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.
24 #include "screenhack.h"
26 #define kSleepTime 10000
28 #define font_height(font) (font->ascent + font->descent)
29 #define FONT_NAME "-*-times-*-*-*-*-80-*-*-*-*-*-*-*"
31 #define kCityPause 500000
33 #define SCORE_MISSILE 100
34 #define kFirstBonus 5000
36 #define kMaxRadius 100
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;
44 int lrate = 80, startlrate;
46 long score = 0, highscore = 0;
47 long nextBonus = kFirstBonus;
55 int carefulpersen = 0;
73 int x, y, rad, oflaser;
90 float velx, vely, fposx, fposy;
96 #define kMaxMissiles 256
98 #define kMaxLasers 128
102 #define kLaserLength 12
104 #define kMissileSpeed 0.003
105 #define kLaserSpeed (kMissileSpeed * 6)
107 Missile missile[kMaxMissiles];
108 Boom boom[kMaxBooms];
109 City city[kNumCities];
110 Laser laser[kMaxLasers];
111 int blive[kNumCities];
113 static void Explode(int x, int y, int max, XColor color, int oflaser)
117 for (i=0;i<kMaxBooms;i++)
118 if (!boom[i].alive) {
129 if (max > kMaxRadius)
134 m->oflaser = oflaser;
137 static void launch (int xlim, int ylim,
138 Display *dpy, Colormap cmap, int src)
141 Missile *m = 0, *msrc;
142 for (i=0;i<kMaxMissiles;i++)
143 if (!missile[i].alive) {
151 m->startx = (random() % xlim);
155 m->jenis = random() % 360;
158 m->splits = random() % ((int) (ylim * 0.4));
159 if (m->splits < ylim * 0.08)
163 /* special if we're from another missile */
165 int dc = random() % (kNumCities - 1);
166 msrc = &missile[src];
167 if (dc == msrc->dcity)
172 if (m->starty > ylim * 0.4 || m->splits <= m->starty)
173 m->splits = 0; /* too far down already */
174 m->jenis = msrc->jenis;
177 m->dcity = random() % kNumCities;
178 m->endx = city[m->dcity].x + (random() % 20) - 10;
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;
194 static int fire(int xlim, int ylim,
195 Display *dpy, Window window, Colormap cmap)
203 int choosy = 0, economic = 0, careful = 0;
204 int suitor[kMaxMissiles];
206 int ytargetmin = ylim * 0.75;
210 choosy = (random() % 100) < choosypersen;
211 economic = (random() % 100) < econpersen;
212 careful = (random() % 100) < carefulpersen;
214 /* count our cities */
215 for (i=0;i<kNumCities;i++)
216 livecity += city[i].alive;
218 return 1; /* no guns */
220 for (i=0;i<kMaxLasers;i++)
221 if (!laser[i].alive) {
228 /* if no missiles on target, no need to be choosy */
231 for (j=0;j<kMaxMissiles;j++) {
233 if (!mis->alive || (mis->y > ytargetmin))
235 if (city[mis->dcity].alive)
242 for (j=0;j<kMaxMissiles;j++) {
245 if (!mis->alive || (mis->y > ytargetmin))
247 if (choosy && (city[mis->dcity].alive == 0))
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)
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) {
267 /* who's closest? biggest threat */
268 if (suitor[j] && missile[j].y > deepest)
269 deepest = missile[j].y;
272 if (deepest > 0 && careful) {
273 /* only target deepest missile */
275 for (j=0;j<kMaxMissiles;j++)
276 if (suitor[j] && missile[j].y != deepest)
281 return 1; /* no targets available */
282 cnt = random() % cnt;
283 for (j=0;j<kMaxMissiles;j++)
292 return 1; /* shouldn't happen */
294 dcity = random() % livecity;
295 for (j=0;j<kNumCities;j++)
301 m->startx = city[dcity].x;
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 */
319 dx = (m->endx - m->x);
320 dy = (m->endy - m->y);
321 m->velx = dx / 100.0;
322 m->vely = dy / 100.0;
324 /* m->lenMul = (kLaserLength * kLaserLength) / (m->velx * m->velx + m->vely * m->vely); */
325 m->lenMul = -(kLaserLength / m->vely);
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;
341 init_penetrate(Display *dpy, Window window)
344 /*char *fontname = "-*-new century schoolbook-*-r-*-*-*-380-*-*-*-*-*-*"; */
345 char *fontname = "-*-courier-*-r-*-*-*-380-*-*-*-*-*-*";
350 XWindowAttributes xgwa;
351 XGetWindowAttributes (dpy, window, &xgwa);
352 cmap = xgwa.colormap;
354 if (get_string_resource("smart","String")!=NULL && get_string_resource("smart","String")[0]!=0)
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;
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])))
368 fprintf (stderr, "%s: Can't find a large font.", progname);
371 XFreeFontNames(list);
374 if (!(scoreFont = XLoadQueryFont(dpy, "-*-times-*-r-*-*-*-180-*-*-*-*-*-*")))
375 fprintf(stderr, "%s: Can't load Times font.", progname);
377 for (i = 0; i < kMaxMissiles; i++)
378 missile[i].alive = 0;
380 for (i = 0; i < kMaxLasers; i++)
383 for (i = 0; i < kMaxBooms; i++)
386 for (i = 0; i < kNumCities; i++) {
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;
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);
408 /* make a gray color for score */
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;
418 XClearWindow(dpy, window);
422 static void DrawScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
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,
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,
443 static void AddScore(Display *dpy, Window window, Colormap cmap, int xlim, int ylim, long dif)
446 for (i=0;i<kNumCities;i++)
447 sumlive += city[i].alive;
449 return; /* no cities, not possible to score */
452 if (score > highscore)
454 DrawScore(dpy, window, cmap, xlim, ylim);
457 static void DrawCity(Display *dpy, Window window, Colormap cmap, int x, int y, XColor col)
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);
468 static void DrawCities(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
471 for (i = 0; i < kNumCities; i++) {
475 x = (i + 1) * (xlim / (kNumCities + 1));
478 DrawCity(dpy, window, cmap, x, ylim, m->color);
482 static void LoopMissiles(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
485 for (i = 0; i < kMaxMissiles; i++) {
487 Missile *m = &missile[i];
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;
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);
503 /* maybe split off a new missile? */
504 if (m->splits && (m->y > m->splits)) {
506 launch(xlim, ylim, dpy, cmap, i);
511 if (city[m->dcity].alive) {
512 city[m->dcity].alive = 0;
513 Explode(m->x, m->y, kBoomRad * 2, m->color, 0);
517 /* check hitting explosions */
518 for (j=0;j<kMaxBooms;j++) {
523 int dx = abs(m->x - b->x);
524 int dy = abs(m->y - b->y);
526 if ((dx < r) && (dy < r))
527 if (dx * dx + dy * dy < r * r) {
529 max = b->max + bgrowth - kBoomRad;
530 AddScore(dpy, window, cmap, xlim, ylim, SCORE_MISSILE);
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);
545 static void LoopLasers(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
547 int i, j, miny = ylim * 0.8;
549 for (i = 0; i < kMaxLasers; i++) {
550 Laser *m = &laser[i];
558 x = m->fposx + (-m->velx * m->lenMul);
559 y = m->fposy + (-m->vely * m->lenMul);
561 XSetLineAttributes(dpy, erase_gc, 4, 0,0,0);
562 XDrawLine(dpy, window, erase_gc,
563 x, y, m->oldx, m->oldy);
567 XSetLineAttributes(dpy, draw_gc, 2, 0,0,0);
568 XSetForeground (dpy, draw_gc, m->color.pixel);
569 XDrawLine(dpy, window, draw_gc,
572 if (m->y < m->endy) {
576 /* check hitting explosions */
578 for (j=0;j<kMaxBooms;j++) {
583 int dx = abs(m->x - b->x);
584 int dy = abs(m->y - b->y);
588 if ((dx < r) && (dy < r))
589 if (dx * dx + dy * dy < r * r) {
591 /* one less enemy on this missile -- it probably didn't make it */
592 if (missile[m->target].alive)
593 missile[m->target].enemies--;
600 XDrawLine(dpy, window, erase_gc,
602 Explode(m->x, m->y, kBoomRad, m->color, 1);
607 static void LoopBooms(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
610 for (i = 0; i < kMaxBooms; i++) {
618 if (m->rad >= m->max)
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);
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);
634 int level = 0, levMissiles, levFreq;
636 /* after they die, let's change a few things */
637 static void Improve(void)
642 return; /* no need, really */
644 if (level <= 2) aim -= 8;
645 if (level <= 5) aim -= 6;
650 if (level <= 5) choosypersen += 3;
653 if (startlrate < kMinRate) {
654 if (lrate < startlrate)
658 if (lrate < kMinRate)
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;
668 static void NewLevel(Display *dpy, Window window, Colormap cmap, int xlim, int ylim)
671 int width, i, sumlive = 0;
680 /* check for a free city */
681 if (score >= nextBonus) {
683 nextBonus += kFirstBonus * numBonus;
687 for (i=0;i<kNumCities;i++) {
689 city[i].alive = blive[i];
690 liv[i] = city[i].alive;
696 /* print out screen */
697 XFillRectangle(dpy, window, erase_gc,
700 sprintf(buf, "Bonus Round Over");
702 if (sumlive || freecity)
703 sprintf(buf, "Level %d Cleared", level);
705 sprintf(buf, "GAME OVER");
708 width = XTextWidth(font, buf, strlen(buf));
709 XDrawString(dpy, window, level_gc, xlim / 2 - width / 2, ylim / 2 - font_height(font) / 2,
712 screenhack_handle_events(dpy);
717 if (sumlive || freecity) {
719 /* draw live cities */
720 XFillRectangle(dpy, window, erase_gc,
721 0, ylim - 100, xlim, 100);
723 sprintf(buf, "X %ld", level * 100L);
724 /* how much they get */
725 sumwidth = XTextWidth(font, buf, strlen(buf));
726 /* add width of city */
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++) {
735 AddScore(dpy, window, cmap, xlim, ylim, 100 * level);
736 DrawCities(dpy, window, cmap, xlim, ylim);
738 screenhack_handle_events(dpy);
745 screenhack_handle_events(dpy);
747 screenhack_handle_events(dpy);
751 for (i=0;i<kNumCities;i++)
756 nextBonus = kFirstBonus;
758 DrawCities(dpy, window, cmap, xlim, ylim);
762 /* do free city part */
763 if (freecity && sumlive < 5) {
764 int ncnt = random() % (5 - sumlive) + 1;
765 for (i=0;i<kNumCities;i++)
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);
774 screenhack_handle_events(dpy);
778 XFillRectangle(dpy, window, erase_gc,
779 0, 0, xlim, ylim - 100);
784 nextBonus = kFirstBonus;
787 if (level > 3 && (level % 5 == 1)) {
790 DrawCities(dpy, window, cmap, xlim, ylim);
795 levMissiles = 20 + level * 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));
803 screenhack_handle_events(dpy);
805 XFillRectangle(dpy, window, erase_gc,
806 0, 0, xlim, ylim - 100);
813 levMissiles = 5 + level * 3;
815 levMissiles += level * 5;
816 /* levMissiles = 2; */
817 levFreq = 120 - level * 5;
826 static void penetrate(Display *dpy, Window window, Colormap cmap)
828 XWindowAttributes xgwa;
829 static int xlim, ylim;
831 XGetWindowAttributes(dpy, window, &xgwa);
835 /* see if just started */
838 choosypersen = econpersen = carefulpersen = 100;
839 lrate = kMinRate; aim = 1;
841 NewLevel(dpy, window, cmap, xlim, ylim);
842 DrawScore(dpy, window, cmap, xlim, ylim);
847 if (levMissiles == 0) {
848 /* see if anything's still on the screen, to know when to end level */
850 for (i=0;i<kMaxMissiles;i++)
851 if (missile[i].alive)
853 for (i=0;i<kMaxBooms;i++)
856 for (i=0;i<kMaxLasers;i++)
859 /* okay, nothing's alive, start end of level countdown */
860 screenhack_handle_events(dpy);
862 NewLevel(dpy, window, cmap, xlim, ylim);
866 else if ((random() % levFreq) == 0) {
867 launch(xlim, ylim, dpy, cmap, -1);
871 if (loop - lastLaser >= lrate) {
872 if (fire(xlim, ylim, dpy, window, cmap))
877 screenhack_handle_events(dpy);
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);
888 char *progclass = "Penetrate";
890 char *defaults [] = {
891 ".background: black",
892 ".foreground: white",
895 "*geometry: 800x500",
899 XrmOptionDescRec options [] = {
900 { "-bgrowth", ".bgrowth", XrmoptionSepArg, 0 },
901 { "-lrate", ".lrate", XrmoptionSepArg, 0 },
902 {"-smart", ".smart", XrmoptionIsArg,0},
907 screenhack (Display *dpy, Window window)
909 Colormap cmap = init_penetrate(dpy, window);
911 penetrate(dpy, window, cmap);