1 /* Copyright (c) 1999 Adam Miller adum@aya.yale.edu
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * penetrate simulates the arcade classic with the cities and the stuff
12 * shooting down from the sky and stuff. The computer plays against itself,
13 * desperately defending the forces of good against those thingies raining
14 * down. Bonus cities are awarded at ever-increasing intervals. Every five
15 * levels appears a bonus round. The computer player gets progressively
16 * more intelligent as the game progresses. Better aim, more economical with
17 * ammo, and better target selection. Points are in the bottom right, and
18 * high score is in the bottom left. Start with -smart to have the computer
19 * player skip the learning process.
22 -- fixed an AI bug that was keeping the computer player a tad weak
28 #include "screenhack.h"
30 #define kSleepTime 10000
32 #define font_height(font) (font->ascent + font->descent)
34 #define kCityPause 500000
36 #define SCORE_MISSILE 100
37 #define kFirstBonus 5000
39 #define kMaxRadius 100
56 int x, y, rad, oflaser;
74 float velx, vely, fposx, fposy;
80 #define kMaxMissiles 256
82 #define kMaxLasers 128
86 #define kLaserLength 12
88 #define kMissileSpeed 0.003
89 #define kLaserSpeed (kMissileSpeed * 6)
96 XFontStruct *font, *scoreFont;
97 GC draw_gc, erase_gc, level_gc;
98 unsigned int default_fg_pixel;
102 int lrate, startlrate;
104 long score, highscore;
117 Missile missile[kMaxMissiles];
118 Boom boom[kMaxBooms];
119 City city[kNumCities];
120 Laser laser[kMaxLasers];
121 int blive[kNumCities];
123 int level, levMissiles, levFreq;
125 int draw_xlim, draw_ylim;
130 static void Explode(struct state *st, int x, int y, int max, XColor color, int oflaser)
134 for (i=0;i<kMaxBooms;i++)
135 if (!st->boom[i].alive) {
146 if (max > kMaxRadius)
151 m->oflaser = oflaser;
154 static void launch (struct state *st, int xlim, int ylim, int src)
157 Missile *m = 0, *msrc;
158 for (i=0;i<kMaxMissiles;i++)
159 if (!st->missile[i].alive) {
167 m->startx = (random() % xlim);
171 m->jenis = random() % 360;
176 m->splits = random() % j;
177 if (m->splits < ylim * 0.08)
181 /* special if we're from another missile */
183 int dc = random() % (kNumCities - 1);
184 msrc = &st->missile[src];
185 if (dc == msrc->dcity)
190 if (m->starty > ylim * 0.4 || m->splits <= m->starty)
191 m->splits = 0; /* too far down already */
192 m->jenis = msrc->jenis;
195 m->dcity = random() % kNumCities;
196 m->endx = st->city[m->dcity].x + (random() % 20) - 10;
202 hsv_to_rgb (m->jenis, 1.0, 1.0,
203 &m->color.red, &m->color.green, &m->color.blue);
204 m->color.flags = DoRed | DoGreen | DoBlue;
205 if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
206 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
207 m->color.red = m->color.green = m->color.blue = 0xFFFF;
213 #define kSpeedDiff 3.5
214 #define kMaxToGround 0.75
215 static int fire(struct state *st, int xlim, int ylim)
223 int choosy = 0, economic = 0, careful = 0;
224 int suitor[kMaxMissiles];
226 int ytargetmin = ylim * 0.75;
230 choosy = (random() % 100) < st->choosypersen;
231 economic = (random() % 100) < st->econpersen;
232 careful = (random() % 100) < st->carefulpersen;
234 /* count our cities */
235 for (i=0;i<kNumCities;i++)
236 livecity += st->city[i].alive;
238 return 1; /* no guns */
240 for (i=0;i<kMaxLasers;i++)
241 if (!st->laser[i].alive) {
248 /* if no missiles on target, no need to be choosy */
251 for (j=0;j<kMaxMissiles;j++) {
252 mis = &st->missile[j];
253 if (!mis->alive || (mis->y > ytargetmin))
255 if (st->city[mis->dcity].alive)
262 for (j=0;j<kMaxMissiles;j++) {
263 mis = &st->missile[j];
265 if (!mis->alive || (mis->y > ytargetmin))
267 if (choosy && (st->city[mis->dcity].alive == 0))
269 ey = mis->starty + ((float) (mis->endy - mis->starty)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
270 if (ey > ylim * kMaxToGround)
271 continue; /* too far down */
276 /* count missiles that are on target and not being targeted */
277 if (choosy && economic)
278 for (j=0;j<kMaxMissiles;j++)
279 if (suitor[j] && st->missile[j].enemies == 0)
283 for (j=0;j<kMaxMissiles;j++) {
284 if (suitor[j] && cnt > 1)
285 if (st->missile[j].enemies > 0)
286 if (st->missile[j].enemies > 1 || untargeted == 0) {
290 /* who's closest? biggest threat */
291 if (suitor[j] && st->missile[j].y > deepest)
292 deepest = st->missile[j].y;
295 if (deepest > 0 && careful) {
296 /* only target deepest missile */
298 for (j=0;j<kMaxMissiles;j++)
299 if (suitor[j] && st->missile[j].y != deepest)
304 return 1; /* no targets available */
305 cnt = random() % cnt;
306 for (j=0;j<kMaxMissiles;j++)
309 mis = &st->missile[j];
315 return 1; /* shouldn't happen */
317 dcity = random() % livecity;
318 for (j=0;j<kNumCities;j++)
319 if (st->city[j].alive)
324 m->startx = st->city[dcity].x;
326 ex = mis->startx + ((float) (mis->endx - mis->startx)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
327 ey = mis->starty + ((float) (mis->endy - mis->starty)) * (mis->pos + kExpHelp + (1.0 - mis->pos) / kSpeedDiff);
328 m->endx = ex + random() % 16 - 8 + (random() % st->aim) - st->aim / 2;
329 m->endy = ey + random() % 16 - 8 + (random() % st->aim) - st->aim / 2;
330 if (ey > ylim * kMaxToGround)
331 return 0; /* too far down */
342 dx = (m->endx - m->x);
343 dy = (m->endy - m->y);
344 m->velx = dx / 100.0;
345 m->vely = dy / 100.0;
347 /* m->lenMul = (kLaserLength * kLaserLength) / (m->velx * m->velx + m->vely * m->vely); */
348 m->lenMul = -(kLaserLength / m->vely);
351 m->color.blue = 0x0000;
352 m->color.green = 0xFFFF;
353 m->color.red = 0xFFFF;
354 m->color.flags = DoRed | DoGreen | DoBlue;
355 if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
356 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
357 m->color.red = m->color.green = m->color.blue = 0xFFFF;
364 penetrate_init (Display *dpy, Window window)
366 struct state *st = (struct state *) calloc (1, sizeof(*st));
368 const char *levelfont = "-*-courier-*-r-*-*-*-380-*-*-*-*-*-*";
369 const char *scorefont = "-*-helvetica-*-r-*-*-*-180-*-*-*-*-*-*";
371 XWindowAttributes xgwa;
376 XGetWindowAttributes (st->dpy, st->window, &xgwa);
377 st->cmap = xgwa.colormap;
380 st->nextBonus = kFirstBonus;
383 st->smart = get_boolean_resource(st->dpy, "smart","Boolean");
384 st->bgrowth = get_integer_resource (st->dpy, "bgrowth", "Integer");
385 st->lrate = get_integer_resource (st->dpy, "lrate", "Integer");
386 if (st->bgrowth < 0) st->bgrowth = 2;
387 if (st->lrate < 0) st->lrate = 2;
388 st->startlrate = st->lrate;
390 st->font = XLoadQueryFont(st->dpy, levelfont);
392 fprintf (stderr, "%s: could not load font %s.\n", progname, levelfont);
393 st->font = XLoadQueryFont(st->dpy, scorefont);
395 st->font = XLoadQueryFont(st->dpy, "fixed");
396 if (! st->font) abort();
399 st->scoreFont = XLoadQueryFont(st->dpy, scorefont);
400 if (!st->scoreFont) {
401 fprintf (stderr, "%s: could not load font %s.\n", progname, scorefont);
402 st->scoreFont = XLoadQueryFont(st->dpy, levelfont);
404 st->scoreFont = XLoadQueryFont(st->dpy, "fixed");
405 if (! st->scoreFont) abort();
408 for (i = 0; i < kMaxMissiles; i++)
409 st->missile[i].alive = 0;
411 for (i = 0; i < kMaxLasers; i++)
412 st->laser[i].alive = 0;
414 for (i = 0; i < kMaxBooms; i++)
415 st->boom[i].alive = 0;
417 for (i = 0; i < kNumCities; i++) {
418 City *m = &st->city[i];
420 m->color.red = m->color.green = m->color.blue = 0xFFFF;
421 m->color.blue = 0x1111; m->color.green = 0x8888;
422 m->color.flags = DoRed | DoGreen | DoBlue;
423 if (!XAllocColor (st->dpy, st->cmap, &m->color)) {
424 m->color.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
425 m->color.red = m->color.green = m->color.blue = 0xFFFF;
429 gcv.foreground = st->default_fg_pixel =
430 get_pixel_resource(st->dpy, st->cmap, "foreground", "Foreground");
431 gcv.font = st->scoreFont->fid;
432 st->draw_gc = XCreateGC(st->dpy, st->window, GCForeground | GCFont, &gcv);
433 gcv.font = st->font->fid;
434 st->level_gc = XCreateGC(st->dpy, st->window, GCForeground | GCFont, &gcv);
435 XSetForeground (st->dpy, st->level_gc, st->city[0].color.pixel);
436 gcv.foreground = get_pixel_resource(st->dpy, st->cmap, "background", "Background");
437 st->erase_gc = XCreateGC(st->dpy, st->window, GCForeground, &gcv);
440 jwxyz_XSetAntiAliasing (st->dpy, st->erase_gc, False);
441 jwxyz_XSetAntiAliasing (st->dpy, st->draw_gc, False);
445 /* make a gray color for score */
447 st->scoreColor.red = st->scoreColor.green = st->scoreColor.blue = 0xAAAA;
448 st->scoreColor.flags = DoRed | DoGreen | DoBlue;
449 if (!XAllocColor (st->dpy, st->cmap, &st->scoreColor)) {
450 st->scoreColor.pixel = WhitePixel (st->dpy, DefaultScreen (st->dpy));
451 st->scoreColor.red = st->scoreColor.green = st->scoreColor.blue = 0xFFFF;
455 XClearWindow(st->dpy, st->window);
459 static void DrawScore(struct state *st, int xlim, int ylim)
463 sprintf(buf, "%ld", st->score);
464 width = XTextWidth(st->scoreFont, buf, strlen(buf));
465 height = font_height(st->scoreFont);
466 XSetForeground (st->dpy, st->draw_gc, st->scoreColor.pixel);
467 XFillRectangle(st->dpy, st->window, st->erase_gc,
468 xlim - width - 6, ylim - height - 2, width + 6, height + 2);
469 XDrawString(st->dpy, st->window, st->draw_gc, xlim - width - 2, ylim - 2,
472 sprintf(buf, "%ld", st->highscore);
473 width = XTextWidth(st->scoreFont, buf, strlen(buf));
474 XFillRectangle(st->dpy, st->window, st->erase_gc,
475 4, ylim - height - 2, width + 4, height + 2);
476 XDrawString(st->dpy, st->window, st->draw_gc, 4, ylim - 2,
480 static void AddScore(struct state *st, int xlim, int ylim, long dif)
483 for (i=0;i<kNumCities;i++)
484 sumlive += st->city[i].alive;
486 return; /* no cities, not possible to score */
489 if (st->score > st->highscore)
490 st->highscore = st->score;
491 DrawScore(st, xlim, ylim);
494 static void DrawCity(struct state *st, int x, int y, XColor col)
496 XSetForeground (st->dpy, st->draw_gc, col.pixel);
497 XFillRectangle(st->dpy, st->window, st->draw_gc,
498 x - 30, y - 40, 60, 40);
499 XFillRectangle(st->dpy, st->window, st->draw_gc,
500 x - 20, y - 50, 10, 10);
501 XFillRectangle(st->dpy, st->window, st->draw_gc,
502 x + 10, y - 50, 10, 10);
505 static void DrawCities(struct state *st, int xlim, int ylim)
508 for (i = 0; i < kNumCities; i++) {
509 City *m = &st->city[i];
512 x = (i + 1) * (xlim / (kNumCities + 1));
515 DrawCity(st, x, ylim, m->color);
519 static void LoopMissiles(struct state *st, int xlim, int ylim)
522 for (i = 0; i < kMaxMissiles; i++) {
524 Missile *m = &st->missile[i];
529 m->pos += kMissileSpeed;
530 m->x = m->startx + ((float) (m->endx - m->startx)) * m->pos;
531 m->y = m->starty + ((float) (m->endy - m->starty)) * m->pos;
535 XSetLineAttributes(st->dpy, st->draw_gc, 4, 0,0,0);
536 XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
537 XDrawLine(st->dpy, st->window, st->draw_gc,
538 old_x, old_y, m->x, m->y);
540 /* maybe split off a new missile? */
541 if (m->splits && (m->y > m->splits)) {
543 launch(st, xlim, ylim, i);
548 if (st->city[m->dcity].alive) {
549 st->city[m->dcity].alive = 0;
550 Explode(st, m->x, m->y, kBoomRad * 2, m->color, 0);
554 /* check hitting explosions */
555 for (j=0;j<kMaxBooms;j++) {
556 Boom *b = &st->boom[j];
560 int dx = abs(m->x - b->x);
561 int dy = abs(m->y - b->y);
563 if ((dx < r) && (dy < r))
564 if (dx * dx + dy * dy < r * r) {
566 max = b->max + st->bgrowth - kBoomRad;
567 AddScore(st, xlim, ylim, SCORE_MISSILE);
575 Explode(st, m->x, m->y, kBoomRad + max, m->color, 0);
576 XSetLineAttributes(st->dpy, st->erase_gc, 4, 0,0,0);
577 /* In a perfect world, we could simply erase a line from
578 (m->startx, m->starty) to (m->x, m->y). This is not a
582 my_pos = kMissileSpeed;
583 while (my_pos <= m->pos) {
584 m->x = m->startx + ((float) (m->endx - m->startx)) * my_pos;
585 m->y = m->starty + ((float) (m->endy - m->starty)) * my_pos;
586 XDrawLine(st->dpy, st->window, st->erase_gc, old_x, old_y, m->x, m->y);
589 my_pos += kMissileSpeed;
595 static void LoopLasers(struct state *st, int xlim, int ylim)
597 int i, j, miny = ylim * 0.8;
599 for (i = 0; i < kMaxLasers; i++) {
600 Laser *m = &st->laser[i];
605 XSetLineAttributes(st->dpy, st->erase_gc, 2, 0,0,0);
606 XDrawLine(st->dpy, st->window, st->erase_gc,
607 m->oldx2, m->oldy2, m->oldx, m->oldy);
615 x = m->fposx + (-m->velx * m->lenMul);
616 y = m->fposy + (-m->vely * m->lenMul);
621 XSetLineAttributes(st->dpy, st->draw_gc, 2, 0,0,0);
622 XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
623 XDrawLine(st->dpy, st->window, st->draw_gc,
631 if (m->y < m->endy) {
635 /* check hitting explosions */
637 for (j=0;j<kMaxBooms;j++) {
638 Boom *b = &st->boom[j];
642 int dx = abs(m->x - b->x);
643 int dy = abs(m->y - b->y);
647 if ((dx < r) && (dy < r))
648 if (dx * dx + dy * dy < r * r) {
650 /* one less enemy on this missile -- it probably didn't make it */
651 if (st->missile[m->target].alive)
652 st->missile[m->target].enemies--;
659 XDrawLine(st->dpy, st->window, st->erase_gc,
661 Explode(st, m->x, m->y, kBoomRad, m->color, 1);
666 static void LoopBooms(struct state *st, int xlim, int ylim)
669 for (i = 0; i < kMaxBooms; i++) {
670 Boom *m = &st->boom[i];
677 if (m->rad >= m->max)
679 XSetLineAttributes(st->dpy, st->draw_gc, 1, 0,0,0);
680 XSetForeground (st->dpy, st->draw_gc, m->color.pixel);
681 XDrawArc(st->dpy, st->window, st->draw_gc, m->x - m->rad, m->y - m->rad, m->rad * 2, m->rad * 2, 0, 360 * 64);
684 XSetLineAttributes(st->dpy, st->erase_gc, 1, 0,0,0);
685 XDrawArc(st->dpy, st->window, st->erase_gc, m->x - m->rad, m->y - m->rad, m->rad * 2, m->rad * 2, 0, 360 * 64);
695 /* after they die, let's change a few things */
696 static void Improve(struct state *st)
701 return; /* no need, really */
703 if (st->level <= 2) st->aim -= 8;
704 if (st->level <= 5) st->aim -= 6;
707 st->carefulpersen += 6;
708 st->choosypersen += 4;
709 if (st->level <= 5) st->choosypersen += 3;
712 if (st->startlrate < kMinRate) {
713 if (st->lrate < st->startlrate)
714 st->lrate = st->startlrate;
717 if (st->lrate < kMinRate)
718 st->lrate = kMinRate;
720 if (st->level <= 5) st->econpersen += 3;
721 if (st->aim < 1) st->aim = 1;
722 if (st->choosypersen > 100) st->choosypersen = 100;
723 if (st->carefulpersen > 100) st->carefulpersen = 100;
724 if (st->econpersen > 100) st->econpersen = 100;
727 static void NewLevel(struct state *st, int xlim, int ylim)
730 int width, i, sumlive = 0;
734 if (st->level == 0) {
739 /* check for a free city */
740 if (st->score >= st->nextBonus) {
742 st->nextBonus += kFirstBonus * st->numBonus;
746 for (i=0;i<kNumCities;i++) {
748 st->city[i].alive = st->blive[i];
749 liv[i] = st->city[i].alive;
752 st->city[i].alive = 0;
755 /* print out screen */
756 XFillRectangle(st->dpy, st->window, st->erase_gc,
759 sprintf(buf, "Bonus Round Over");
761 if (sumlive || freecity)
762 sprintf(buf, "Level %d Cleared", st->level);
764 sprintf(buf, "GAME OVER");
767 width = XTextWidth(st->font, buf, strlen(buf));
768 XDrawString(st->dpy, st->window, st->level_gc, xlim / 2 - width / 2, ylim / 2 - font_height(st->font) / 2,
770 XSync(st->dpy, False);
775 if (sumlive || freecity) {
777 /* draw live cities */
778 XFillRectangle(st->dpy, st->window, st->erase_gc,
779 0, ylim - 100, xlim, 100);
781 sprintf(buf, "X %ld", st->level * 100L);
782 /* how much they get */
783 sumwidth = XTextWidth(st->font, buf, strlen(buf));
784 /* add width of city */
788 DrawCity(st, xlim / 2 - sumwidth / 2 + 30, ylim * 0.70, st->city[0].color);
789 XDrawString(st->dpy, st->window, st->level_gc, xlim / 2 - sumwidth / 2 + 40 + 60, ylim * 0.7, buf, strlen(buf));
790 for (i=0;i<kNumCities;i++) {
792 st->city[i].alive = 1;
793 AddScore(st, xlim, ylim, 100 * st->level);
794 DrawCities(st, xlim, ylim);
795 XSync(st->dpy, False);
807 for (i=0;i<kNumCities;i++)
808 st->city[i].alive = 1;
812 st->nextBonus = kFirstBonus;
814 DrawCities(st, xlim, ylim);
818 /* do free city part */
819 if (freecity && sumlive < 5) {
820 int ncnt = random() % (5 - sumlive) + 1;
821 for (i=0;i<kNumCities;i++)
822 if (!st->city[i].alive)
824 st->city[i].alive = 1;
825 strcpy(buf, "Bonus City");
826 width = XTextWidth(st->font, buf, strlen(buf));
827 XDrawString(st->dpy, st->window, st->level_gc, xlim / 2 - width / 2, ylim / 4, buf, strlen(buf));
828 DrawCities(st, xlim, ylim);
829 XSync(st->dpy, False);
833 XFillRectangle(st->dpy, st->window, st->erase_gc,
834 0, 0, xlim, ylim - 100);
838 if (st->level == 1) {
839 st->nextBonus = kFirstBonus;
842 if (st->level > 3 && (st->level % 5 == 1)) {
845 DrawCities(st, xlim, ylim);
850 st->levMissiles = 20 + st->level * 10;
852 for (i=0;i<kNumCities;i++)
853 st->blive[i] = st->city[i].alive;
854 sprintf(buf, "Bonus Round");
855 width = XTextWidth(st->font, buf, strlen(buf));
856 XDrawString(st->dpy, st->window, st->level_gc, xlim / 2 - width / 2, ylim / 2 - font_height(st->font) / 2, buf, strlen(buf));
857 XSync(st->dpy, False);
859 XFillRectangle(st->dpy, st->window, st->erase_gc,
860 0, 0, xlim, ylim - 100);
867 st->levMissiles = 5 + st->level * 3;
869 st->levMissiles += st->level * 5;
870 /* levMissiles = 2; */
871 st->levFreq = 120 - st->level * 5;
872 if (st->levFreq < 30)
882 penetrate_draw (Display *dpy, Window window, void *closure)
884 struct state *st = (struct state *) closure;
885 XWindowAttributes xgwa;
890 DrawCities(st, st->draw_xlim, st->draw_ylim);
893 XGetWindowAttributes(st->dpy, st->window, &xgwa);
894 st->draw_xlim = xgwa.width;
895 st->draw_ylim = xgwa.height;
897 /* see if just started */
900 st->choosypersen = st->econpersen = st->carefulpersen = 100;
901 st->lrate = kMinRate; st->aim = 1;
903 NewLevel(st, st->draw_xlim, st->draw_ylim);
904 DrawScore(st, st->draw_xlim, st->draw_ylim);
909 if (st->levMissiles == 0) {
910 /* see if anything's still on the screen, to know when to end level */
912 for (i=0;i<kMaxMissiles;i++)
913 if (st->missile[i].alive)
915 for (i=0;i<kMaxBooms;i++)
916 if (st->boom[i].alive)
918 for (i=0;i<kMaxLasers;i++)
919 if (st->laser[i].alive)
921 /* okay, nothing's alive, start end of level countdown */
922 usleep(kLevelPause*1000000);
923 NewLevel(st, st->draw_xlim, st->draw_ylim);
927 else if ((random() % st->levFreq) == 0) {
928 launch(st, st->draw_xlim, st->draw_ylim, -1);
932 if (st->loop - st->lastLaser >= st->lrate) {
933 if (fire(st, st->draw_xlim, st->draw_ylim))
934 st->lastLaser = st->loop;
937 if ((st->loop & 7) == 0)
940 LoopMissiles(st, st->draw_xlim, st->draw_ylim);
941 LoopLasers(st, st->draw_xlim, st->draw_ylim);
942 LoopBooms(st, st->draw_xlim, st->draw_ylim);
949 penetrate_reshape (Display *dpy, Window window, void *closure,
950 unsigned int w, unsigned int h)
952 XClearWindow (dpy, window);
956 penetrate_event (Display *dpy, Window window, void *closure, XEvent *event)
962 penetrate_free (Display *dpy, Window window, void *closure)
964 struct state *st = (struct state *) closure;
969 static const char *penetrate_defaults [] = {
970 ".background: black",
971 ".foreground: white",
977 "*geometry: 800x500",
981 static XrmOptionDescRec penetrate_options [] = {
982 { "-bgrowth", ".bgrowth", XrmoptionSepArg, 0 },
983 { "-lrate", ".lrate", XrmoptionSepArg, 0 },
984 {"-smart", ".smart", XrmoptionNoArg, "True" },
988 XSCREENSAVER_MODULE ("Penetrate", penetrate)