#include "c_includ.h" /* * Cbzone - xbzone w/ improvements in C * * Version 1.0 in Fortran by Justin S. Revenaugh -- MIT (5/86) * C port and modifications by Todd W. Mummert -- CMU (12/90) * email bugs and comments concerning this game to: * mummert+@sam.cs.cmu.edu * * Copyright Notice: This program is freely distributable on a * nonprofit basis as long as this notice is maintained on all * copies. Inclusion of any program code or derivative thereof * for commercial purposes is expressly forbidden. * December 10, 1990. * * RCS Info * $Header: c_main.c,v 1.1 91/01/12 02:03:34 mummert Locked $ * * Bugs/Features: * Tanks still pass through each other. Missiles/copters don't * collide with one another unless below 80 ft. Consider these * features for now. * * Enhancement Ideas: * -Convert the graphics routines to general purpose routines... * The graphics are either multiline(segments) or polyline(connected). * Polyline lines can either be open or closed. This could all be * done in a single routine with the number of mlines and plines * passed in...maybe too much bookkeeping involved. * -Have the ability to have both tanks and missiles on the screen at * the same time. Never bothered to do it, for I initially thought * it would be unplayable. Not so sure now. * -Make this multiplayer...two basic approaches. The first is one * program controlling two displays. The second is running on * different machines, passing the necessary objects back/forth. Since * you wouldn't need to pass graphics information, this is not * a lot of information to update. Instead of a call to move * you would get back the changes made by your opponent. * * -If you make any interesting changes, please send them to me and I'll * incorporate them into the next release. */ Option option; Optionp opt = &option; /* * Just initialize the array prior to starting play. Associate * the salvos with the correct objects. Set the cosines of some * of the angles to 1. */ void initarray(o) Genericp o; { Genericp s; Genericp g; Genericp pl = o; int i, j; pl->type = IS_PLAYER; /* player is always the first */ pl->attr = START_LIVING; /* object. Same size as the */ pl->criticalx = 45.0; /* other tanks. */ pl->criticaly = 70.0; for (g=o; gmobjects; g++) { g->ca = 1.0; /* for those objects which */ for (j=0; j<5; j++) /* have rotating parts, set */ g->dc[j].ctp = g->dc[j].cta = 1.0; /* the cos to 1.0 */ } s = o+opt->sstart; /* player gets msalvos, each */ pl->salvo = s; /* enemy gets one. the salvos */ for (i=0; imsalvos; i++) { /* must know who their owner */ s->salvo = pl; /* is for a variety of reasons */ s->type = IS_SALVO; /* first the player */ s->lntype = LN_SALVO; s++; } for (g=o+opt->estart; glstart; g++) { g->salvo = s; /* now the enemies */ s->salvo = g; s->type = IS_SALVO; s->lntype = LN_SALVO; s++; } } /* * The main routine for cbzone. Probably more complicated than it * need be...but it takes care of all the interobject dependencies. * Therefore other routines work on a single object (in general). * placeobjects() will place however many objects it can, while * scanner() needs to know where all the enemies are. */ void cbzone_main(argc, argv) int argc; char* argv[]; { Genericp o; Genericp pl, g, g2, s; char key; float alpha, ddx2, ddx, ddy2, ddy, dif, diff, dx, dy, v, dist; float blocksize = 100.0; float blocksizesqrd = 10000.0; float check, testx, testy; float landerthreshold = 0.5; float threshold = 0.7; int deadcount, i, icheck, position[2]; int nummissile = 0; int missilecount = 0; int nextmissile = 1200; int numleft = 3; LONG score = 0; LONG scorebase = 0; Bool new_salvo_flag, new_sight_flag, event, tank_stranded; Bool aligned = False; Bool blocked_flag = False; Bool dead = False; Bool first = True; Bool firstmissile = True; Bool keylast = True; Bool lander = False; Bool missilerun = False; Bool salvo_flag = False; Bool sens = False; Bool sight_flag = False; // extern long time(); ***HACK HACK HACK EricFo removed this struct timeval tstart; struct timeval tend; long tdiff, limit; #ifdef DEVELOPER int passes = 0; struct timeval game_start; struct timeval game_end; #endif //DEVELOPER gprinqconfig(&argc, argv); limit = opt->delay * 1.2e4; /* now that we have parsed the options, we know how large to */ /* make the world. Use calloc here as most of the array */ /* should start off 0. */ pl = o = (Genericp) calloc(opt->mobjects,sizeof(Generic)); if (o == NULL) { printf("Malloc failed...trying to create too many objects?\n"); #ifdef WIN32 return; #else //X11 exit(1); #endif } initarray(o); /* prepare the main array */ srandom(time((long *) 0)); /* start things off randomly */ screeninit(); updatedisplay(missilerun, lander, score, numleft, sens, False); xhairs(aligned); gprinqcursor(position); event = gprcondeventwait(&key, position); joystick(position, sens, pl); /* place the objects out there to start the game. if the player is */ /* is_new, then the objects are placed at a random distance, else */ /* the objects get placed on the horizon. */ placeobjects(o, missilerun, score); pl->attr &= ~IS_NEW; /* now the objects can be */ /* placed at the horizon. */ /* now calculate ranges to all the objects and translate them */ /* into a player-centric coordinate system */ pl->ca = cos(pl->azm); pl->sa = sin(pl->azm); for (g=o+opt->estart; gmobjects; g++) if (g->attr & IS_ALIVE) { dx = g->x - pl->x; dy = g->y - pl->y; g->range = sqrt(dx*dx + dy*dy); g->proy = -dx * pl->sa + dy * pl->ca; g->prox = dx * pl->ca + dy * pl->sa; } scanner(o); drawhorizon(pl->azm); /* now the work really starts....we just iterate through */ /* the following loop until the player dies or quits */ #ifdef DEVELOPER gettimeofday(&game_start, 0); #endif while (1) { #ifdef WIN32 if (GetAsyncKeyState( VK_F1 ) < 0) { free(o); return; // exit(scores(score)); //HACK OUT by Eric Fogelin } #endif gettimeofday(&tstart, 0); gprinqcursor(position); event = gprcondeventwait(&key, position); if (event && key == 'Q') { free(o); return; // exit(scores(score)); //HACK OUT by Eric Fogelin } if (event && key == 'R') { clearentirescreen(); staticscreen(); updatedisplay(False, False, -1, 0, False, True); updatedisplay(missilerun, lander, score, numleft, sens, False); if (sight_flag) message(1, False); if (pl->attr & IS_BLOCKED) message(2, False); if (salvo_flag) message(3, False); scanner(o); xhairs(aligned); drawhorizon(pl->azm); } joystick(position, sens, pl); if (paused) continue; for (i=0; imsalvos; i++) { /* now find a shot we can use */ s = pl->salvo+i; if (!(s->attr & STILL_THERE)) break; s = NULL; } if (event && pl->attr & IS_ALIVE) if (keylast) { for (i=0; imsalvos; i++) { /* now find a shot we can use */ s = pl->salvo+i; if (!(s->attr & STILL_THERE)) break; s = NULL; } if (key == 'a' && s!=NULL) { /* fire up one shot */ s->attr = START_LIVING; s->ecount = 0; s->x = pl->x; s->y = pl->y; s->z = 0.0; s->prox = 0; s->proy = 0; s->azm = pl->azm; s->speed = 40.0; keylast = False; } else if (key == 'b') { /* center our joystick */ position[0] = 500; position[1] = 355; gprsetcursorposition(position); joystick(position, sens, pl); keylast = False; } else if (key == 'c') { /* toggle sensitivity */ sens = !sens; joystick(position, sens, pl); keylast = False; } } else if (key == 'A' || key == 'B' || key == 'C') keylast = True; /* button released */ /* if we can move, update our rotation, bearing (azimuth), and */ /* position. */ if (pl->attr & IS_ALIVE && !(pl->attr & IS_BLOCKED)) { pl->azm += pl->rotate; if (pl->azm > PI2) pl->azm -= PI2; if (pl->azm <= 0.0) pl->azm += PI2; pl->ca = cos(pl->azm); pl->sa = sin(pl->azm); } if (pl->attr & IS_ALIVE) { pl->x -= pl->sa * pl->speed; pl->y += pl->ca * pl->speed; } else pl->speed = 0.0; /* now call the move generation routines for the objects */ /* which require thought, speed and/or rotation may be */ /* affected. */ for (g=o+opt->estart; gsstart; g++) if (g->attr & IS_ALIVE) switch (g->type) { case IS_TANK: movetank(g, pl); break; case IS_SUPER: movesuper(g, pl); break; case IS_MISSILE: movemissile(g, pl, first); break; case IS_COPTER: movecopter(g, pl); break; case IS_LANDER: movelander(g, pl); break; default: printf("Help! Something's alive and I don't know what...\n"); #ifdef WIN32 return; #else //X11 exit(1); #endif } /* now update their bearing and position */ for (g=o+opt->estart; glstart; g++) { if (g->attr & IS_ALIVE && !(g->attr & IS_BLOCKED)) g->azm += g->rotate; g->ca = cos(g->azm); g->sa = sin(g->azm); g->x -= g->sa * g->speed; g->y += g->ca * g->speed; } for (g=o+opt->lstart; gbstart; g++) if (g->attr & IS_ALIVE) { g->ca = cos(g->azm); g->sa = sin(g->azm); g->x -= g->sa * g->speed; g->y += g->ca * g->speed; } /* now compute ranges from objects to the player */ for (g=o+opt->estart; gmobjects; g++) if (g->attr & STILL_THERE) g->range = sqrt(DIST(g, pl)); for (g=o; glstart; g++) /* assume all objects are */ g->attr &= ~IS_BLOCKED; /* unblocked */ /* now check to see if they really were unblocked. If not, then */ /* project them back along their path until they are. This */ /* section just checks for being blocked by blocks. */ for (g=o+opt->bstart; gmobjects; g++) { if (g->range < blocksize) { pl->attr |= BLOCKED_BY_BLOCK; dx = pl->x - g->x; dy = pl->y - g->y; diff = dy * pl->ca - dx * pl->sa; if (pl->speed > 0.0) v = diff + sqrt(diff*diff + blocksizesqrd - g->range*g->range); else if (pl->speed < 0.0) v = diff - sqrt(diff*diff + blocksizesqrd - g->range*g->range); pl->x += pl->sa * v; pl->y -= pl->ca * v; } for (g2=o+opt->estart; g2lstart; g2++) if (g2->attr & IS_ALIVE && (dist = DIST(g, g2)) < blocksizesqrd) { g2->attr |= BLOCKED_BY_BLOCK; if (!(g2->type & (IS_MISSILE | IS_COPTER))) { dx = g2->x - g->x; dy = g2->y - g->y; diff = dy * g2->ca - dx * g2->sa; if (g2->speed > 0.0) v = diff + sqrt(diff*diff + blocksizesqrd - dist); else if (g2->speed < 0.0) v = diff - sqrt(diff*diff + blocksizesqrd - dist); g2->x += g2->sa * v; g2->y -= g2->ca * v; } } } /* if the player moved, or if an enemy did, we need to recompute */ /* the range to that enemy. */ for (g=o+opt->estart; glstart; g++) if (g->attr & IS_ALIVE && (g->attr & IS_BLOCKED || pl->attr & IS_BLOCKED)) g->range = sqrt(DIST(g, pl)); /* now check to see if the player is blocked by any enemy. */ /* if so, project them back. */ for (g=o+opt->estart; glstart; g++) if (g->attr & IS_ALIVE && g->range < blocksize) if (!(g->type & (IS_MISSILE | IS_COPTER))) { if (g->attr & IS_BLOCKED) { pl->speed = 0.0; if (fabs(g->speed) < 0.001) g->speed = sign(0.001, g->speed); } pl->attr |= BLOCKED_BY_ENEMY; g->attr |= BLOCKED_BY_ENEMY; ddx = pl->speed * pl->sa - g->speed * g->sa; ddy = pl->speed * pl->ca - g->speed * g->ca; ddx2 = ddx*ddx; ddy2 = ddy*ddy; dx = pl->x - g->x; dy = pl->y - g->y; dif = ddy * dy - ddx * dx; alpha = (dif + sqrt(dif*dif + (blocksizesqrd - g->range*g->range) * (ddx2 + ddy2))) / (ddx2 + ddy2); pl->x += alpha * pl->speed * pl->sa; pl->y -= alpha * pl->speed * pl->ca; g->x += alpha * g->speed * g->sa; g->y -= alpha * g->speed * g->ca; } /* if we've moved, recompute distance to all the salvos */ if (pl->attr & IS_BLOCKED) for (g=o+opt->sstart; gbstart; g++) if (g->attr & IS_ALIVE) g->range = sqrt(DIST(g, pl)); /* enemies disappear if their range is greater than 2200. */ /* We check last[0] to see if they need to be erased. In */ /* most cases probably not, unless we really screw with */ /* their speed. */ tank_stranded = False; for (g=o+opt->estart; glstart; g++) { g->ecount++; if (g->attr & IS_ALIVE) if (g->range > 2200.0) if (g->dc[0].last) g->attr = ERASE; else g->attr = 0; else if (g->type & (IS_SUPER | IS_TANK) && g->ecount > TANK_STRAND_COUNT) tank_stranded = True; } /* landers are out of range at 2750 */ for (g=o+opt->lstart; gsstart; g++) { g->ecount++; if (g->attr & IS_ALIVE && g->range > 2750.0) if (g->dc[0].last) g->attr = ERASE; else g->attr = 0; } /* blocks also at 2200 */ for (g=o+opt->bstart; gmobjects; g++) if (g->range > 2200.0) if (g->dc[0].last) g->attr = ERASE; else g->attr = 0; /* salvos are never out of range, but their lifetime is limited */ for (g=o+opt->sstart; gbstart; g++) { g->ecount++; if (g->attr & IS_ALIVE && g->ecount > 50) if (g->dc[0].last) g->attr = ERASE; else g->attr = 0; } /* we never set the 'salvo fired' message in this routine. */ /* however, we do have to turn it off. if salvos are alive */ /* we assume the message is on, once no enemy salvos are */ /* alive we turn it off. */ new_salvo_flag = False; for (g=o+opt->sstart; gbstart; g++) if (g->attr & IS_ALIVE) { /* if salvo exist and */ if (g->salvo != pl) /* not owned by player */ new_salvo_flag = True; /* then the flag is set */ /* check to see if a salvo hits a block */ for (g2=o+opt->bstart; g2mobjects; g2++) if (fabs(g2->x - g->x) < g2->criticalx && fabs(g2->y - g->y) < g2->criticaly) { g->attr = START_EXPLODING; g->ecount = 0; } /* now check to see if the salvo kills a lander. */ /* If so, and the player fired the salvo, update */ /* the score. */ for (g2=o+opt->lstart; g2sstart; g2++) if (g2->attr & IS_ALIVE) if (DIST(g, g2) < g2->criticalx) { g->attr = START_EXPLODING; g2->attr = START_EXPLODING; g->ecount = 0; g2->ecount = 0; if (g->salvo == pl) { score += 10000; icheck = score / 100000; if (icheck > scorebase) { numleft++; if (numleft > 4) numleft = 4; scorebase = icheck; } } } /* now check to see if the salvo hit any enemy. The salvo */ /* cannot hit the one who fired it. This prevents range */ /* checking problems when first fired. */ for (g2=o+opt->estart; g2lstart; g2++) if (g2->attr & IS_ALIVE && g->salvo != g2) { dx = g->x - g2->x; dy = g->y - g2->y; testx = fabs( dx * g2->ca + dy * g2->sa); testy = fabs(-dx * g2->sa + dy * g2->ca); if (testx < g2->criticalx && testy < g2->criticaly && (!(g2->type & (IS_MISSILE | IS_COPTER)) || g2->z < 80.0)) { g->attr = START_EXPLODING; g2->attr = START_EXPLODING; g->ecount = 0; g2->ecount = 0; /* if the player fired, give him credit */ if (g->salvo == pl) { if (g2->type & IS_SUPER) score += 5000; else if (g2->type & (IS_MISSILE | IS_COPTER)) { score += 5000; nummissile--; } else score += 3000; icheck = score / 100000; if (icheck > scorebase) { numleft++; if (numleft > 4) numleft = 4; scorebase = icheck; } } } } } /* check to see if a missile or copter rams a lander */ for (g=o+opt->estart; glstart; g++) if (g->type & (IS_MISSILE | IS_COPTER) && g->attr & IS_ALIVE) for (g2=o+opt->lstart; g2sstart; g2++) if (g2->attr & IS_ALIVE && DIST(g, g2) < g2->criticalx && g->z < 80) { nummissile--; g->attr = START_EXPLODING; g2->attr = START_EXPLODING; g->ecount = 0; g2->ecount = 0; } /* now check if we need to draw the object. */ /* convert into player-centric coordinates */ /* and project a cone forward to see if the */ /* enemy is within it. Also align the gun */ /* gun sights if necessary. */ new_sight_flag = False; aligned = False; lander = False; for (g=o+opt->estart; gmobjects; g++) if (g->attr & (IS_ALIVE|IS_EXPLODING)) { g->dc[0].seen = False; if (g->range < 2000.0) { dx = g->x - pl->x; dy = g->y - pl->y; g->proy = -dx * pl->sa + dy * pl->ca; g->prox = dx * pl->ca + dy * pl->sa; check = g->proy / (fabs(g->prox) + 1.0); if (check > threshold) { g->dc[0].seen = True; if (g->type & IS_ENEMY && g->attr & IS_ALIVE) new_sight_flag = True; if (fabs(g->prox) < 50 && g->attr & IS_ALIVE && !(g->type & (IS_ABLOCK | IS_SALVO))) aligned = True; } if (g->type & IS_LANDER && g->attr & IS_ALIVE && check > landerthreshold) { lander = True; if (fabs(g->prox) < 60) aligned = True; } } } /* change the various messages, if necessary. Never use */ /* the bell, unless opt->loud is True. */ if (sight_flag && !new_sight_flag) { message(-1, False); sight_flag = False; } else if (!sight_flag && new_sight_flag) { message(1, False); sight_flag = True; } if (pl->attr & IS_BLOCKED && !blocked_flag) { message(2, True); blocked_flag = True; } else if (!(pl->attr & IS_BLOCKED) && blocked_flag) { message(-2, False); blocked_flag = False; } if (salvo_flag && !new_salvo_flag) { message(-3, False); salvo_flag = False; } else if (!salvo_flag && new_salvo_flag) salvo_flag = True; scanner(o); updatedisplay(missilerun, lander, score, numleft, sens, False); xhairs(aligned); drawhorizon(pl->azm); /* now draw all the objects */ for (g=o+opt->estart; gmobjects; g++) { if (g->attr & (IS_ALIVE | ERASE)) drawobject(g, pl); else if (g->attr & (IS_EXPLODING | EXERASE)) switch (g->type) { case IS_SALVO: explodesalvo(g, pl); break; case IS_COPTER: case IS_MISSILE: case IS_LANDER: case IS_TANK: case IS_SUPER: explodeobject(g, pl); break; default: printf("Help! Cannot explode what doesn't exist.\n"); #ifdef WIN32 return; #else //X11 exit(1); #endif } g->attr &= ~(ERASE | EXERASE); } /* now start checking for player death. if there is a missile, */ /* check to see if it rammed the player. */ if (missilerun) for (g=o+opt->estart; glstart; g++) if (g->attr & IS_ALIVE && g->type & (IS_MISSILE | IS_COPTER) && g->range < blocksize && g->z < 80) { g->attr = START_EXPLODING; drawcracks(); pl->attr &= ~IS_ALIVE; dead = True; deadcount = 0; } /* check to see if any salvos hit. */ for (g=o+opt->sstart; gbstart; g++) if (g->attr & IS_ALIVE && g->salvo != pl && g->range < 100.0 && fabs(g->prox) < pl->criticalx && fabs(g->proy) < pl->criticaly) { drawobject(g, pl); g->attr = 0; drawcracks(); pl->attr &= ~IS_ALIVE; dead = True; deadcount = 0; } /* if we are dead, redraw the cracks every five turns. after 50 */ /* turns, we can start playing again if we have any lives left. */ if (dead) { if (deadcount%5 == 0) drawcracks(); if (deadcount > 50) { dead = False; numleft--; if (numleft < 0) { #ifdef DEVELOPER gettimeofday(&game_end, 0); if (opt->output) printf("The game took an average %10.8f secs.\n", (game_end.tv_sec-game_start.tv_sec + (game_end.tv_usec-game_start.tv_usec)*1.0e-6)/passes); #endif //DEVELOPER free(o); #ifdef WIN32 return; #else //X11 exit(scores(score)); #endif } if (missilerun) { nummissile -= 2; if (nummissile <= 0 && opt->mtanks) { missilerun = False; nextmissile = 750 * frand() + 750; } } clearscreen(); pl->x = 0.0; /* reset all our attributes */ pl->y = 0.0; pl->speed = 0.0; pl->azm = 0.0; pl->ca = 1.0; pl->sa = 0.0; pl->attr = START_LIVING; message(-1, False); /* turn off all the messages */ sight_flag = False; message(-2, False); blocked_flag = False; message(-3, False); salvo_flag = False; for (g=o+opt->estart; gmobjects; g++) g->attr = 0; /* remove all objects */ } } /* Now schedule the missile runs. There will be a missile run if */ /* mtanks==0 or we are in copter practice or if a tank has been */ /* around too long or we haven't had a missile in a while. */ if (pl->attr & IS_ALIVE && opt->mmissiles && (!opt->mtanks || opt->copters || tank_stranded || missilecount > nextmissile)) if (!missilerun) { missilecount = 0; for (g=o+opt->estart; gbstart; g++) if (!(g->type & (IS_LANDER | IS_MISSILE | IS_COPTER))) if (g->attr & IS_ALIVE) g->attr = ERASE; else if (g->attr & IS_EXPLODING) g->attr = EXERASE; nummissile = frand() * 3 * opt->mmissiles + 1; if (firstmissile) nummissile = 1; first = firstmissile; firstmissile = False; missilerun = True; } /* once this run is over, schedule another one for a later date */ if (missilerun && opt->mtanks && nummissile <= 0) { missilerun = False; nextmissile = 750 * frand() + 750; } /* now place whatever objects need to be placed. */ if (!dead) placeobjects(o, missilerun, score); pl->attr &= ~IS_NEW; /* in case the player was new */ deadcount++; if (!missilerun) missilecount++; /* use timeclock here instead of gettimeofday to get a sync(d, 0) * just in case there are graphics we need to draw. */ timeclock(&tend); tdiff = limit - ((tend.tv_sec-tstart.tv_sec)*1e6+tend.tv_usec-tstart.tv_usec); if (tdiff > 0) { tend.tv_sec = 0; tend.tv_usec = tdiff; select(0, 0, 0, 0, &tend); } #ifdef DEVELOPER passes++; #endif //DEVELOPER } }