// CARZ.CPP - simulated robot car racing - begin development Dec. '94
// by Mitchell E. Timin, State College, PA
// see CAR.H & TRACK.H for class and structure declarations
// This version is for Borland C++, version 3.1, and is for DOS
// This is part of version 0.60 of RARS (Robot Auto Racing Simulation)
// ver. 0.1 release January 12, 1995
// ver. 0.2 1/23/95
// ver. 0.3 2/7/95
// ver. 0.39 3/6/95
// ver. 0.45 3/21/95
// ver. 0.50 4/5/95
// ver. 0.6b 5/8/95 b for beta

#include <math.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <iostream.h>
#include "car.h"
#include "track.h"
#include "os.h"
#include "misc.h"

static const double PM = 1e5;    // Power, Maximum, 100,000 ft. lb. per sec
static const double DRAG_CON = .0065;  // air drag, lb. per (ft/sec)^2
static const double M = 75;  // mass, slugs   (about 2400 lb.)
static const double SFC = .45e-6;  // Specific Fuel Consumption, lb. per ft. lb.
static const double FUEL_START = 150;           // inital fuel supply, lb.
static const double REFILL_SECONDS = 12.0;      // time to refill the tank
static const unsigned long MAX_DAMAGE = 30000;  // out of race with this damage
static const double STARTING_SPEED = 40.0;  // cars start at this speed, ft/sec
static const double REVERSE_GEAR_LIMIT = 20; // ft/sec max to allow reverse
static const int PRIV_DATA_SIZE = 4096;  // size of robot's private data area

// file scope global variables:
static char *glob_name;  // will hold name of each robot driver (one at a time)
static double time_count;  // elapsed time, seconds
static segment *trackout; // global variables that describe the track:
static segment *trackin;  // trackin and trackout are arrays of segments
static  int  NSEG;        // the number of segments of the track
static  double  width;    // track width, feet
static STAGE stage = BEFORE;  // see CAR.H
static int done_count;  // incremented by each car that finishes the race
static int out_count;   // incremented by each car that crashes
static int* order;                 // will point to array of positions
static int* position;              // re-arrangement of order array
static int* new_data;              // points to array of new data indicators
static colors car_colors[MAXCARS]; // There should be MAXCARS of these color pairs:

// global variables:
Car* pcar[MAXCARS];                // array of pointers to the various cars
int lap[MAXCARS];                  // set, then cleared as each lap completes
double length;       // total lenth of track (average of inner and outer rails)
// These are set by the command line arguments: (see get_args() in OS.CPP)
int lap_count;       // length of the race in laps
int car_count;       // how many cars in the race
int race_count;      // how many races?
int real_speed;      // if non-zero, PC clock will control speed of race
int no_display;      // if non-zero, race will be invisible
int keep_order;      // if this is 0, then starting order will be re-arranged
int surface;         // surface type, 0 is looser, 1 is harder. (see friction())
static int designated = -1; // index of a keyboard-selected car; -1 means no selection

// These are the control or "driver" programs which compete in the race:
con_vec cntrl0(situation &s);  // See drivers[] below, and CAR.H
con_vec cntrl5(situation &s);
con_vec cntrlR(situation &s);
con_vec dynamic(situation &s);
con_vec Arelys(situation &s);
con_vec TutMan3(situation &s);
con_vec Valerie(situation &s);
con_vec Bingo1(situation &s);
con_vec Ramdu(situation &s);
con_vec Rusty(situation &s);
con_vec Heath2(situation &s);
con_vec Blender(situation &s);
con_vec Burns(situation &s);
con_vec Indretti(situation &s);
con_vec OscCar2(situation &s);
con_vec WappuCar(situation &s);

// This is the permanent array of available drivers.
// Their order here determines their car colors.
car_ID drivers[] = {
   { cntrlR,  {car_clrs[0].nose, car_clrs[0].tail},  (char *)0 },
   { Burns,  {car_clrs[1].nose, car_clrs[1].tail},  (char *)0 },
   { Bingo1,  {car_clrs[2].nose, car_clrs[2].tail},  (char *)0 },
   { Blender, {car_clrs[3].nose, car_clrs[3].tail},  (char *)0 },
   { Indretti, {car_clrs[4].nose, car_clrs[4].tail},  (char *)0 },
   { Arelys,  {car_clrs[5].nose, car_clrs[5].tail},  (char *)0 },
   { Heath2,  {car_clrs[6].nose, car_clrs[6].tail},  (char *)0 },
   { Ramdu,  {car_clrs[7].nose, car_clrs[7].tail},  (char *)0 },
   { cntrl0,  {car_clrs[8].nose, car_clrs[8].tail},  (char *)0 },
   { OscCar2,  {car_clrs[9].nose, car_clrs[9].tail},  (char *)0 },
   { cntrl5,  {car_clrs[10].nose, car_clrs[10].tail}, (char *)0 },
   { Rusty,    {car_clrs[11].nose, car_clrs[11].tail}, (char *)0 },
   { TutMan3, {car_clrs[12].nose, car_clrs[12].tail}, (char *)0 },
   { Valerie, {car_clrs[13].nose, car_clrs[13].tail}, (char *)0 },
   { dynamic, {car_clrs[14].nose, car_clrs[14].tail}, (char *)0 },
   { WappuCar, {car_clrs[15].nose, car_clrs[15].tail}, (char *)0 }  };

void report_overall(int, int, car_ID*, long);          // in REPORT.CPP
void report_results(int, int*, car_ID*, Car**, int*);  // in REPORT.CPP
void RAM_report(void);                                 // in REPORT.CPP

// these things are defined in DRAW.CPP:
void instruments(int);            // updates the instrument panel
void designate(int i);            // marks car i on scoreboard
extern void drawcar(double, double, double, int, int);
extern void lapper(int which, int lap);  // shows lap count on scoreboard,
extern void resume_normal_display(void);
extern void leaders(int, int*);
extern void scoreboard(STAGE);
extern void graph_setup(void);
extern void refresh_finish_line(void);
extern double drawpath(double, double, double, segment*);

//----------------------------------------------------------
// Random Variable Generation:
#define MULTIPLIER      0x015a4e35L
#define INCREMENT       1
const int MAXRAND = 0x7FFF;
static  long   Seed = 1;
static  long   Seed2 = 1;
static  long   Seed_to_report = 1;

inline int raand(void)   // This one is for use within this file.
{
        Seed = MULTIPLIER * Seed + INCREMENT;
        return((int)(Seed >> 16) & 0x7FFF);
}

// Command line processing in OS.CPP can either call this with input of 0,
// or a user's entry, or not call it.  0 input means really randomize.
// If not called at all, the default seed of 1 is used.
void Randomize(long input)
{
   if(input)              // This is when the user gives the seed
      Seed = input;
   else                   // this is when the user wants to randomize
      Seed = pick_random();    // get a random no. from the op. sys.
   Seed_to_report = Seed;   // this won't change again.
   Seed2 = (long)raand();
}

int rand(void)    // robots and other functions can use this one:
{
        Seed2 = MULTIPLIER * Seed2 + INCREMENT;
        return((int)(Seed2 >> 16) & 0x7FFF);
}
//---------------------------------------------------------------------

void my_name_is(const char* name)  // for a robot driver to identify itself
{
      strcpy(glob_name, name);
}

inline double vec_mag(double x, double y)  // sqrt of sum of squares
{                                          // (used for vector magnitude)
   return sqrt(x * x + y * y);
}

inline int incseg(int seg)       // returns the next segment of the track
{                                // (i.e. 0 yields 1, 1 yields 2, but
   if(++seg == NSEG)             // NSEG-1 yields 0)
      seg = 0;
   return seg;
}

// This is the model of the track friction force on the tire.  This force
// provides propulsion, cornering, and braking.  Force is assumed to depend
// only on the slip speed, rising very rapidly with small slip speed,
// and then asymtotically approaching an upper limit.  (This is similar to
// tires on unpaved surfaces.)  There are two models here, the global
// variable "surface" chooses between them.
const double MYU_MAX0 = 1.0;  // maximum coeficient of friction, tire vs. track:
const double MYU_MAX1 = 1.05; // maximum coeficient of friction, tire vs. track:
const double SLIPPING = 2.0;
// SLIPPING is the slip speed at which:
//    for type 0, half maximum force is reached.
//    for type 1, .632 of maximum force is reached.
inline double friction(double slip)     // returns the coef. of friction,
{                                       // given the slip speed, ft. per sec.
   if(surface)
      return MYU_MAX1 * (1.0 - exp(-slip/SLIPPING));   // (harder surface)
   else
      return (MYU_MAX0 * slip)/(SLIPPING + slip);      // (very loose surface)
}

// A version of the type 0 model with randomness in the "slipping" variable.
// The effect is small at high slip rates, large at small slip rates.
inline double rfriction(double slip)     // returns the coef. of friction,
{                                       // given the slip speed, ft. per sec.
   double slipping;     // slip speed at which half maximum force is reached

   slipping = 1.5 + (double)raand()/MAXRAND;    // range is 1.5 to 2.5 fps
   if(surface)
      return MYU_MAX1 * (1.0 - exp(-slip/slipping));   // (harder surface)
   else
      return (MYU_MAX0 * slip)/(slipping + slip);      // (very loose surface)
}

// Comparison routine for determining who's ahead: (used when sorting)
// Returns 0 iff car0 has gone farther than car1, 1 otherwise.
int farther(Car* car0, Car* car1)
{
   int blok0, blok1;  /* A block is the same as a track segment except for
                         segment 0.  For segment 0, the block is 0 when the
                         car is past the finish line.  When the car is heading
                         toward the finish line the block is NSEG.  */
   blok0 = car0->seg_id;
   if(!blok0)
      if(car0->to_end > from_start_to_seg1)
         blok0 = NSEG;
   blok1 = car1->seg_id;
   if(!blok1)
      if(car1->to_end > from_start_to_seg1)
         blok1 = NSEG;
   if(car0->laps > car1->laps)
      return 0;
   else if(car0->laps < car1->laps)
      return 1;                              // else laps are equal
   else if(blok0 > blok1)
      return 0;
   else if(blok1 > blok0)
      return 1;                            // else they are in the same blok:
   else if(car0->to_end <= car1->to_end)
      return 0;
   else return 1;        // returns 1 in case of total equality
}

// a sorting routine that's fast when things are already in order:
// This routine updates the order[] array which has the position of each
// car.  order[0] is the ID of the leader, order[1] is in second place, etc.
// Also, the new_data[] array is set to show which positions have changed.
void sortem(int num_disp)
{
   int i, temp;
   int old_order[MAXCARS];

   for(i=0; i<num_disp; i++)     // copy first part of order[] array
      old_order[i] = order[i];

   for(i=0; i<car_count-1; i++) // the while loop below does not usually repeat:
      while(farther(pcar[order[i]], pcar[order[i+1]]))  {
         temp = order[i];     // When a car passes another, we swap positions
         order[i] = order[i+1];
         order[i+1] = temp;
         if(i > 0)              // now we have to see if it passed another car
            --i;
      }

   for(i=0; i<num_disp; i++)     // see what has changed and flag it:
      new_data[i] = (old_order[i] != order[i]);
}

// returns laps and passes seg_id and distance to next segment via pointers:
inline int Car::where_is_it(int* seg_id_addr, double* to_end_addr)
{
   *seg_id_addr = seg_id;
   *to_end_addr = to_end;
   return laps;
}

// the purpose of control() is to call the car's driver function, and
// then to return the steering and throttle settings produced by
// that routine.
void Car::control(situation& s)  // calls the control function via the pointer
{                               // that was installed in the car object by
   con_vec output;              // by the constructor.

   if(out)
      output.alpha = output.vc = 0.0;  // no action if out of race
   else {
      s.data_ptr = data_ptr;  // the robot's data area in RAM
      s.starting = starting;  // startup signal for the robot
      starting = 0;
      output = (*cntrl)(s);   // call the robot driver to set alpha and vc
   }
   alpha = output.alpha;   vc = output.vc;  // set private vars in car object
}

// This function is used only by zbrent().  It calculates power delivered
// to the tires using the same formulas as in move_car().  The value
// returned to the caller is that power minus 99.75% of maximum power.
// The target power used by zbrent() is therefore .9975 * PM.
// We use this instead of full power because zbrent() can err slightly
// on either side of the target, and we should not exceed PM.
double power_excess(double vc, double sine, double cosine, double v, double mass)
{
   double Ln, Lt;      // normal and tangential components of slip vector
   double l;           // magnitude of slip vector, ft. per sec.
   double F;           // force on car from track, lb.
   double Fn, Ft;      // tangential and normal (to car's path) force components

   Ln = -vc * sine;   Lt = v - vc * cosine; // vector sum to compute slip vector
   l = vec_mag(Lt, Ln);                     // compute slip speed
   F = mass * g * friction(l);                 // compute friction force from track
   if(l < .0001)              // to prevent possible division by zero
      Fn = Ft = 0.0;
   else  {
      Fn = -F * Ln/l;      // compute components of force vector
      Ft = -F * Lt/l;
   }
   // compute power excess over target value of .9975*PM:
   return (vc < 0.0 ? -vc : vc) * (Ft * cosine + Fn * sine) - .9975*PM;
}

inline double SIGN(double a, double b)   // something needed by zbrent()
{
   return b >= 0.0 ? fabs(a) : -fabs(a);
}

// This routine was adapted from the book "Numerical Recipes in C" by
// Press, et. al.  It searches for a value of vc which causes the
// power to be very close to the maximum available.
// (This is Brent's method of root finding.)  x1 and x2 are values of
// vc which bracket the root.  b represents the variable vc.
// (See power_excess(), above.)
double zbrent(double sine, double cosine, double v, double x1, double x2, double mass, double tol)
{
   const int ITMAX = 20;
   const double EPS = 1.0e-8;
	int iter;
	double a=x1, b=x2, c=x2, d,e,min1,min2;
	double fa=power_excess(a, sine, cosine, v, mass);
	double fb=power_excess(b, sine, cosine, v, mass);
   double fc,p,q,r,s,tol1,xm;
   double Ln, Lt;      // normal and tangential components of slip vector
   double l;           // magnitude of slip vector, ft. per sec.
   double F;           // force on car from track, lb.
   double Fn, Ft;      // tangential and normal (to car's path) force components

	if ((fa > 0.0 && fb > 0.0) || (fa < 0.0 && fb < 0.0))
		return b;              // This should never happen.
	fc=fb;
	for (iter=1;iter<=ITMAX;iter++) {
		if ((fb > 0.0 && fc > 0.0) || (fb < 0.0 && fc < 0.0)) {
			c=a;
			fc=fa;
			e=d=b-a;
		}
		if (fabs(fc) < fabs(fb)) {
			a=b;
			b=c;
			c=a;
			fa=fb;
			fb=fc;
			fc=fa;
		}
		tol1=2.0*EPS*fabs(b)+0.5*tol;
		xm=0.5*(c-b);
		if (fabs(xm) <= tol1 || fb == 0.0)
         return b;
		if (fabs(e) >= tol1 && fabs(fa) > fabs(fb)) {
			s=fb/fa;
			if (a == c) {
				p=2.0*xm*s;
				q=1.0-s;
			} else {
				q=fa/fc;
				r=fb/fc;
				p=s*(2.0*xm*q*(q-r)-(b-a)*(r-1.0));
				q=(q-1.0)*(r-1.0)*(s-1.0);
			}
			if (p > 0.0) q = -q;
			p=fabs(p);
			min1=3.0*xm*q-fabs(tol1*q);
			min2=fabs(e*q);
			if (2.0*p < (min1 < min2 ? min1 : min2)) {
				e=d;
				d=p/q;
			} else {
				d=xm;
				e=d;
			}
		} else {
			d=xm;
			e=d;
		}
		a=b;
		fa=fb;
		if (fabs(d) > tol1)
			b += d;
		else
			b += SIGN(tol1,xm);
      Ln = -b * sine;   Lt = v - b * cosine; // vector sum to compute slip vector
      l = vec_mag(Lt, Ln);                     // compute slip speed
      F = mass * g * friction(l);                 // compute friction force from track
      if(l < .0001)              // to prevent possible division by zero
         Fn = Ft = 0.0;
      else  {
         Fn = -F * Ln/l;      // compute components of force vector
         Ft = -F * Lt/l;
      }
      // compute power delivered:
      fb = (b < 0.0 ? -b : b) * (Ft * cosine + Fn * sine) - .9975*PM;
	}
	return b;
}

// Simulates the physics, moves the car by changing the state variables.
// The angle "alpha" is the angle between the car's orientation angle and
// its velocity vector.  (like angle of attack of an aircraft)
// Cornering force depends on alpha.  (It is also thought of as a slip angle.)
// "Slip" refers to wheel vs. track motion.
// "vc" is the speed of the bottom of the wheel relative to the car.
// This model is like a four-wheel drive car, since the forces are not
// computed separately for front and rear wheels.
void Car::move_car()
{
   double D;        // force on car from air, lb.
   double Fn, Ft;      // normal & tangential components of track force vector
   double P;           // power delivered to track, ft. lb. per sec.
   double v;           // car's speed
   double Ln, Lt;      // normal and tangential components of slip vector
   double l;           // magnitude of slip vector, ft. per sec.
   double F;           // force on car from track, lb.
   double x_a, y_a;    // accelleration components in x & y directions
   double sine, cosine, separation, dx, dy;
   double pvec_x, pvec_y;   // pointing vector of car (velocity vec + alpha)
   int i, other_seg;
   double dot, mag_prod, temp;
   double rel_xdot, rel_ydot;   // velocity relative to other car being hit
   double mass;                 // current mass of car

   v = vec_mag(xdot, ydot);                     // the car's speed, feet/sec
   if(v > speed_max)                            // keep track of max speed
        speed_max = v;
   mass = M + fuel/g;                             // current mass of car + fuel
   // limit how fast alpha can change, and its maximum value
   alpha = alpha_limit(prev_alpha, alpha);
   prev_alpha = alpha;
   sine = sin(alpha);   cosine = cos(alpha);    // alpha is angle of attack
   // don't allow reverse gear
   if(vc < 0.0)    // This would be a reverse gear request
      if(v > REVERSE_GEAR_LIMIT)   // if going too fast
         vc = 0.0;                      // make it maximum braking

   // check accumulated damage and fuel, take car out of race if necessary:
   if((stage != PRACTICE && damage > MAX_DAMAGE) || fuel <= 0.0) {
         if(offroad) {
            if(damage > MAX_DAMAGE)  {
               if(!out)
                  ++out_count;    // Put it out of race if too much damage.
               xdot = ydot = 0.0;   // stop it dead.
               out = 1;
               return;
            }
            if(full_time == 0.0) {    // sign that car just ran out of gas
               out = 1;
               xdot = ydot = 0.0;
               full_time = time_count + REFILL_SECONDS;
            }
            else if(full_time < time_count) {
               out = 0;
               full_time = 0.0;
               fuel = FUEL_START;
            }
         }
         else {
            sine = alpha = -.005;   // veer off to the right
            cosine = .99999;
            vc = 88.0;       // approach 60 mph.
         }
      }
   if(out)            // This gets set if the car is stuck and off the track
      return;

   dead_ahead = 0;   // will be set if there is a car more-or-less dead ahead
   for(i=0; i<car_count; i++) {     // check for cars nearby or bumping
      if(i == which) continue;         // ignore oneself
      other_seg = pcar[i]->seg_id;     // only consider present and next segment
      if(!(seg_id == other_seg || incseg(seg_id) == other_seg))
          continue;
      dx = pcar[i]->x - x;      // components of vector to other car
      dy = pcar[i]->y - y;
      separation = vec_mag(dx, dy);      // distance to the other car
      if(separation > 2.5 * CARLEN)      // ignore cars farther away than this
         continue;
      // Is there a car dead ahead?  "dead ahead" means within 2.5 car lengths
      // AND within a few degrees of pointing vector  (arcos(.94) == 20 deg.)
      // First compute pointing vector by rotating velocity vector by alpha:
      pvec_x = xdot * cosine - ydot * sine;  // components of pointing vector:
      pvec_y = xdot * sine + ydot * cosine;
      // compute dot product, rel. position & pointing vectors:
      dot = pvec_x * dx + pvec_y * dy;
      mag_prod = separation * v;    // p_vec and v vectors are same length
      if(dot > .94 * mag_prod)      // test based on properties of dot product
         dead_ahead = 1;            // there is a car more-or-less dead ahead
      if(separation > CARLEN)    // If separation is greater than length,
           continue;             // cars cannot be bumping.
      // cars might be bumping, test for that.  Simplified test, assumes
      // cars are parallel.  Also, only test for a car in front, as the
      // other car will test also, and find this car in front.
      if(v < .01)              // to prevent division error just below
         continue;
      temp = dot / v;          // dot/v is longitudinal component of separation
      if(temp > CARLEN || dot < 0.0)
         continue;
      if(separation - temp/separation > CARWID)  // test for lateral separation
         continue;
      // The cars are bumping, reduce speed of rear car, and damage both;
      // The damage is proportional the the square of the relative velocity.
      rel_xdot = xdot - pcar[i]->xdot;
      rel_ydot = ydot - pcar[i]->ydot;
      temp = rel_xdot * rel_xdot + rel_ydot * rel_ydot;  //impact energy
      damage += .62 * temp;
      pcar[i]->damage += .35 * temp;   // less damage to car in front
      xdot *= .8;
      ydot *= .8;
   }

   int it = 0;  // This is a loop counter.
   VC:    // maybe loop to control power (we don't permit P > PM)

   Ln = -vc * sine;   Lt = v - vc * cosine; // vector sum to compute slip vector
   l = vec_mag(Lt, Ln);                     // compute slip speed
   F = mass * g * friction(l);          // compute friction force from track
   D = DRAG_CON * v * v;                    // air drag force
   if(offroad)  {                       // if the car is off the track,
      D = (0.6 + .008 * v) * mass * g;     // add a lot more resistance
      if(veryoffroad)
         D += 1.7 * mass * g;
   }
   if(l < .0001)              // to prevent possible division by zero
      Fn = Ft = 0.0;
   else  {
      Fn = -F * Ln/l;      // compute components of force vector
      Ft = -F * Lt/l;
   }
   // compute power delivered:
   P =  (vc < 0.0 ? -vc : vc) * (Ft * cosine + Fn * sine);

   if(!it)  {         // If this is the first time through here, then:
      power_req = P/PM;  // Tell the driver how much power it requested.
      if(P > PM) {       // If the request was too high, reduce it to 100% pwr.
          ++it;
          vc = zbrent(sine, cosine, v, v * cosine, vc, mass, .006);
          goto VC;
      }
   }
   power = P/PM;   // store this value in the car object

   // put some randomness in the magnitude of the traction force, F:
   // (Ft might be set to 0.0 above, in which case F will be zero.)
   if(Ft != 0.0 && randomotion)
      temp = rfriction(l) * mass * g / F;  // ratio of new to original force
   else
      temp = 1.0;

   // compute centripetal and tangential acceleration components:
   cen_a = Fn * temp /  mass;
   tan_a = (Ft * temp - D) / mass;

   if(P > 0.0)
      fuel -= P * SFC * delta_time;

   if(offroad)  {    // If off the track the car accumulates damage.
      // damage is proportional to square of acceleration:
      damage += int(tan_a * tan_a + cen_a * cen_a) / 10;
   }

   if(v < .0001)                         // prevent division by zero
      adot = sine = cosine = 0.0;
   else  {
     adot = cen_a / v;                         // angular velocity
     sine = ydot/v;   cosine = xdot/v;         // direction of motion
   }
   x_a = tan_a * cosine - cen_a * sine;      // x & y components of acceleration
   y_a = cen_a * cosine + tan_a * sine;

   // Advance the state using the Adam's predictor formula:
   x += (1.5 * xdot - .5 * pre_xdot) * delta_time;
   y += (1.5 * ydot - .5 * pre_ydot) * delta_time;
   pre_xdot = xdot;  pre_ydot = ydot;
   xdot += (1.5 * x_a - .5 * pre_x_a) * delta_time;
   ydot += (1.5 * y_a - .5 * pre_y_a) * delta_time;
   pre_x_a = x_a;   pre_y_a = y_a;
   if(v >= .0001)
      ang = atan2(ydot,xdot);                   // new orientation angle
}


void Car::draw_car(void)  // Calls drawcar() twice, once to erase, once to draw
{
   if(out)       // Don't redraw a car that is out of the race.
      return;
   drawcar(prex, prey, prang, TRACK_COLOR, TRACK_COLOR);     // erase old one
   drawcar(x, y, prang = ang+alpha, nose_color, tail_color); // draw new one
   prex = x;   prey = y;     // save these to erase car next time
}

// The Car constructor:
Car::Car(int i)
{
   which = i;                    // Each car has it own index into pcar[]
   cntrl = drivers[i].rob_ptr;      // Each car has a pointer to its driver.
   nose_color = drivers[i].paint_job.nose;
   tail_color = drivers[i].paint_job.tail;
   prex = trackout[0].beg_x;            // These 3 are initialized so that
   prey = trackout[0].beg_y + width/2;  // draw_car() will work OK the
   prang = 0.0;                         // first time it is called.
   power_req = .9;      // meaningless, but will be soon replaced by move_car()
   data_ptr = (void*)new char[PRIV_DATA_SIZE]; // give the robot some RAM to use
}

void Car::put_car(double x_pos, double y_pos, double alf_ang)
{
   x = x_pos;
   y = y_pos;
   ang = alf_ang;
   to_end = trackin[0].length;   // not exact, but corrected by observe()
   alpha = ydot = ang = prev_alpha = full_time = 0.0;
   lap_time = speed_avg = speed_max = 0;
   laps = -1;    // to become 0 crossing the finish line at start of race
   starting = 1;
   out = lap_flag = seg_id = dead_ahead = 0;
   init_flag = 0;
   damage = 0;
   xdot = vc = STARTING_SPEED;
   fuel = FUEL_START;
   pre_x_a = pre_y_a = pre_xdot = pre_ydot = 0.0;
   if(!no_display)
      draw_car();    // draw the new car on the screen
}

// This function uses the current state of the car, and the current
// track segment data, to compute the car's local situation as seen by
// the driver.  (see struct situation)
void Car::observe(rel_state* rel_vec_ptr, situation& s)
{
   double rad;                // current radius
   double dx, dy, xp, yp;
   double sine, cosine, dot;  // dot used for dot product of two vectors
   double temp;
   int nex_seg;         // segment ID of the next segment
   double separation, dxdot, dydot;
   int i, k, m, other_seg;
   // These two arrays describe the three closest cars:
   // element 0 is for the closest, element 1 next, element two after that.
   int closest[] = { 999, 999, 999 };       // their ID's, initially too big
   double how_close[] = { 1e5, 1e5, 1e5 };  // initially very large distances
   int flag = 1;        // controls possible repetition of calculations due to
                        // completion of a lap.
   if(out)            // This gets set if the car is stuck and off the track
      return;

   s.nearby = rel_vec_ptr;            // the robot's pointer to his RAM area
   s.v      = vec_mag(xdot, ydot);    // the actual speed
   s.dead_ahead = dead_ahead;         // copy the value set by move_car()
   s.power_req = power_req;           // copy the value set by move_car()
   s.power  = power;                  // copy the value set by move_car()
   s.fuel   = fuel;                   // copy the value set by move_car()
   s.damage = damage;                 // copy the value set by move_car()
   s.cen_a  = cen_a;                  // copy the value set by move_car()
   s.tan_a  = tan_a;                  // copy the value set by move_car()
   s.alpha  = alpha;                  // copy the value set by move_car()
   s.vc     = vc;
   s.start_time = start_time;
   s.lap_flag = lap_flag;
   s.time_count = time_count;         // copy the global value
   s.stage = stage;                   // copy the global value
   s.position = position[which];
   s.my_ID = which;

   // Computations below are based on the data in trackin[] and
   // trackout[].  Radii are based on the inside rail in each case.

   while(flag)  {  // This loop repeats only when a segment boundary is crossed,
                   //   in which case it repeats once:

   // compute sine and cosine of track direction:
   sine = sin(temp = trackout[seg_id].beg_ang);
   cosine = cos(temp);

   if((nex_seg = seg_id + 1) == NSEG)               // which segment is next
      nex_seg = 0;
   s.nex_len = trackout[nex_seg].length;            // length and radius
   s.nex_rad = trackin[nex_seg].radius;             // of next segment
   if(s.nex_rad < 0.0)                              // always use smaller radius
      s.nex_rad = trackout[nex_seg].radius;

   if(++nex_seg == NSEG)
      nex_seg = 0;
   s.after_len = trackout[nex_seg].length;            // length and radius
   s.after_rad = trackin[nex_seg].radius;         // and the one after that
   if(s.after_rad < 0.0)                          // always use smaller radius
      s.after_rad = trackout[nex_seg].radius;

   if(++nex_seg == NSEG)
      nex_seg = 0;
   s.aftaft_len = trackout[nex_seg].length;            // length and radius
   s.aftaft_rad = trackin[nex_seg].radius;         // and the one after that
   if(s.aftaft_rad < 0.0)                          // always use smaller radius
      s.aftaft_rad = trackout[nex_seg].radius;

   s.cur_len = trackout[seg_id].length;           // copy these two fields:
   s.cur_rad = rad = trackin[seg_id].radius;  // rt. turn will use trackout
   if(seg_id != 0)      // This is used to tell when the finish line is 
      lap_flag = 0;     // crossed, thus completing each lap.     

   if(rad == 0) {   // if current segment is straight,
      // xp and yp locate the car with respect to the beginning of the right
      // hand wall of the straight segment.  calculate them:
      dx = x - trackout[seg_id].beg_x;
      dy = y - trackout[seg_id].beg_y;
      xp = dx * cosine + dy * sine;
      yp = dy * cosine - dx * sine;
      s.to_rgt = yp;                   // fill in to_rgt and to_end:
      s.to_end = s.cur_len - xp;

      if(seg_id == 0)
         if(xp > FINISH * s.cur_len && !lap_flag) {
            lap_flag = 1;        // This means the line crossing was noted.
            if(++laps == (stage == PRACTICE ? practice : lap_count))
               ++done_count;
            if(!no_display)
               lapper(which, laps);  // display the new lap count
            lap[which] = 1;
            if(laps == 0)       // record when the starting line is crossed:
                start_time = last_crossing = time_count;
            else      // update overall average speed:
                speed_avg = length * laps / (time_count-start_time);
                lap_time = time_count - last_crossing;
                last_crossing = time_count;
         }

      // here we make sure we are still in the same segment:
      if(s.to_end <= 0.0)  {           // see if a lap has been completed,
         if(++seg_id == NSEG)
            seg_id = 0;
         continue;           // repeat the loop in context of next segment
      }
      s.to_lft = width - yp;           // fill in to_lft & cur_rad:
      s.cur_rad = 0.0;
      s.vn      = ydot * cosine - xdot * sine;  // compute cross-track speed
      s.backward = (xdot * cosine + ydot * sine < 0.0);
   }
   else if(rad > 0.0) {      // when current segment is a left turn:
      dx = x - trackout[seg_id].cen_x; // compute position relative to center
      dy = y - trackout[seg_id].cen_y;
      temp = atan2(dy, dx);           // this is the current angular position
      s.to_end = trackout[seg_id].end_ang - temp - PI/2.0;  // this is an angle
      if(s.to_end > 1.5 * PI)
         s.to_end -= 2.0 * PI;
      else if(s.to_end < -.5 * PI)
         s.to_end += 2.0 * PI;
      if(s.to_end <= 0.0)  {           // Handle segment crossing:
         if(++seg_id == NSEG)
            seg_id = 0;                // going from last segment to 1st
         continue;
      }
      s.to_lft = vec_mag(dx, dy) - rad;
      s.to_rgt = width - s.to_lft;
      s.vn = (-xdot * dx - ydot * dy)/vec_mag(dx, dy);   // a trig thing
      s.backward = (ydot * dx - xdot * dy < 0.0);
   }
   else {
      s.cur_rad = rad = trackout[seg_id].radius;  // rt. turn needs trackout
      dx = x - trackout[seg_id].cen_x; // compute position relative to center
      dy = y - trackout[seg_id].cen_y;
      temp = atan2(dy, dx);           // this is the current angular position
      s.to_end = -trackout[seg_id].end_ang + temp - PI/2.0;  // this is an angle
      if(s.to_end < -.5 * PI)
         s.to_end += 2.0 * PI;
      else if(s.to_end >= 1.5 * PI)
         s.to_end -= 2.0 * PI;
      if(s.to_end <= 0.0)  {           // Handle segment transistion:
         if(++seg_id == NSEG)
            seg_id = 0;
         continue;
      }
      s.to_rgt = vec_mag(dx, dy) + rad;
      s.to_lft = width - s.to_rgt;
      s.vn = (xdot * dx + ydot * dy)/vec_mag(dx, dy);   // a trig thing
      s.backward = (xdot * dy - ydot * dx < 0.0);
   }

   // If we get this far, we do not repeat the big loop.
   flag = 0;
   }  // end while(flag) loop

   s.seg_ID = seg_id;
   s.lap_time = lap_time;
   s.laps_to_go = lap_count - laps;
   if(s.laps_to_go > lap_count)
      s.laps_to_go = lap_count;
   offroad = (s.to_lft < 0.0 || s.to_rgt < 0.0);   // maybe set offroad flag
   veryoffroad = (s.to_lft < -width || s.to_rgt < -width); // maybe veryoffroad
   to_end = s.to_end;                       // fill in this field in Car object

   if(s.backward)
      return;         // If going backwards, skip looking for nearby cars.

   // find three closest cars in front:
   // "front", here, is direction of velocity vector, not pointing vector.
   for(i=0; i<car_count; i++) {     // check for cars nearby
      if(i == which) continue;         // ignore oneself
      other_seg = pcar[i]->seg_id;     // only consider present and next segment
      if(!(seg_id == other_seg || incseg(seg_id) == other_seg))
          continue;
      dx = pcar[i]->x - x;      // components of vector to other car
      dy = pcar[i]->y - y;
      separation = vec_mag(dx, dy);      // distance to the other car
      // compute dot product, rel. position & velocity vectors:
      dot = xdot * dx + ydot * dy;
      if(dot < 0.0)
         continue;              // Ignore cars behind you.
      for(k=0; k<3; k++)  {
         if(separation < how_close[k])  {
            for(m = 2; m > k; m--) {
               closest[m] = closest[m-1];
               how_close[m] = how_close[m-1];
            }
            closest[k] = i;
            how_close[k] = separation;
            break;
         }
      }
   }
     // now compute local relative state vector for those three cars:
   for(k=0; k<3; k++)  {
      if(closest[k] > car_count) {  // This is when there are less than 3
         for(; k<3; k++)
            s.nearby[k].who = 999;
         break;
      }
      i = closest[k];
      dx = pcar[i]->x - x;      // components of relative position vector
      dy = pcar[i]->y - y;
      dxdot = pcar[i]->xdot - xdot; // components of relative velocity
      dydot = pcar[i]->ydot - ydot;
      // The relative vectors must be found wrt to velocity vector frame.
      // we need sine and cosine of rotation of axes:
      if(s.v > 1e-7)  {        // prevent division by 0
         sine = - xdot / s.v;                 // direction of velocity
         cosine = ydot / s.v;                 // wrt the x axis.
      }
      else  {
         sine = -1.0;
         cosine = 0.0;
      }
      s.nearby[k].who = i;
      s.nearby[k].rel_x = cosine * dx + sine * dy;
      s.nearby[k].rel_y = cosine * dy - sine * dx;
      s.nearby[k].rel_xdot = cosine * dxdot + sine * dydot;
      s.nearby[k].rel_ydot = cosine * dydot - sine * dxdot;
   }
}

// Get the names from the robot drivers and store them in RAM.
// Fill in drivers[i].rob_name, which is an array of pointers to the names.
// some of these pointers may point to the same place; others may be 0;
void get_names(void)
{
   int i, j;
   situation s;                    // to pass data from observe() to control()
   rel_state rel_state_vec[3];     // needed by fill_situation() function

   for(i=0; i<MAXCARS; i++) {
      glob_name[0] = 0;        // to check later to see if driver filled it
      // fill in s to avoid div. by zero in driver.
      s = fill_situation(rel_state_vec);
      // The first call to a robot driver may put its name in glob_name.
      (void)drivers[i].rob_ptr(s);     // Call the robot "driver" function.
      if(glob_name[0] != 0)  {   // add new name to drivers[] array:
         drivers[i].rob_name = new char[strlen(glob_name) + 1];
         strcpy(drivers[i].rob_name, glob_name);
         if(strlen(glob_name) > 9)     // chop any names greater than 8 chars.
            *(drivers[i].rob_name + 8) = '\0'; 
      }
      else                       // if no name was given, see if there is
         for(j=i-1; j>=0; j--)   // already a name for that driver:
            if(drivers[i].rob_ptr == drivers[j].rob_ptr) {
               drivers[i].rob_name = drivers[j].rob_name;
               break;
            }
   }
}

static int racers[MAXCARS];    // the starting arrangement goes here

// fills racers{} with random permutation of 0, 1, 2..., car_count-1
void pick_random_order(void)
{
     int selected[MAXCARS];  // marks the points as they are picked
     int i, j, k, count;

     for(i=0; i<car_count; i++) {     // initialize selected[] to all zeroes:
	     selected[i] = 0;    // (meaning no points are picked)
     }

     // for each element of rptr, j is no. of alternates
     for(j=car_count; j>0; j--) {  // remaining.  here we pick one of them:
        k = j>1 ? (raand()/128)%j : 0;       // k chooses among the remaining pts.
	     for(i=0, count=0; i<car_count; i++)
   	     if(!selected[i])            // if not already picked,
	           if(count++ == k)  {
		           selected[i] = 1;     // marks this point as taken
		           racers[j-1] = i;       // puts it into the target
	           }
     }
}

// Reverse the starting order of the cars, by reversing the racers[] array:
void reverse_order(void)
{
   int temp;

   for(int i=0; i<car_count/2; i++) {
        temp = racers[i];
        racers[i] = racers[car_count - 1 - i];
        racers[car_count - 1 - i] = temp;
   }
}

main(int argc, char* argv[])   // args are interpreted by get_args() in OS.CPP
{
   int i, j, ml;
   double x, y, dx, dy;            // used only for initial positions of cars
   double ip_update_time;          // ip is for instument panel
   situation s;                    // to pass data from observe() to control()
   int num_disp;   // how many cars to display in leaders area of scoreboard
   int starting;        // indicates the race is just starting
   int c;               // for a keyboard character code
   rel_state rel_state_vec[3];    // contains up to 3 relative state vectors

   glob_name = new char[33];      // temporary storage for a driver's name
   strcpy(glob_name, "Not yet filled in, 32 characters");

   get_names();  // call all the robot drivers to fill in the drivers[] array.

   // Set the track, car_count, lap_count, and various options:
   get_args(argc, argv);
   if(no_display)
      real_speed = 0;

   // decide how many leaders to display on leader board:
   num_disp = (car_count < 5) ? car_count : 5;  // show up to 5

   // setup the graphics display, also compute track data:
   graph_setup();

   // get track description into this file's variables
   NSEG = get_track_description().NSEG;
   width = get_track_description().width;
   trackin = get_track_description().trackin;
   trackout = get_track_description().trackout;

   randomizer();   // maybe randomly initilize the random variable generator
   report_overall(car_count, lap_count, drivers, Seed_to_report);

   for(i=0; i<car_count; i++) {
      pcar[i] = new Car(i);    // create the car objects
   }

   // Main loop - Once through here for every complete race or practice session
 for(ml=0; ml<race_count; ++ml)  {
   done_count = 0;  // incremented by each car that finishes the race
   out_count = 0;   // incremented by each car that crashes
   time_count = ip_update_time = 0.0;  // elapsed time, seconds
   starting = 1;

   if(keep_order)          // decide on the starting positions
      for(i=0; i<car_count; i++)
         racers[i] = i;
   else
      if(!(ml%2))
         pick_random_order();  // arrange the starting positions
      else
         reverse_order();      // reverse positions for 2nd race

   // arrange the cars on the track:
   dy = width/4;          // the dy and dx stuff is for arranging
   dx = 2.5 * CARLEN;     // the cars in their starting positions
   x = trackout[0].beg_x - dx;                // We assume that segment 0
   for(i=0; i<car_count; i++) {               // is straight, and
      if(!(i%4)) {                            // parallel to the x axis.
         y = trackout[0].beg_y + dy/2;        // Cars start on seg. 0.
         x += dx;
      }
      else
         y += dy;
      pcar[racers[i]]->put_car(x, y, 0);   // This is the key part.
   }

   // draw track boundaries:
   if(ml && !no_display)  {
      set_color(RAIL_COLOR);
      (void)drawpath(TRK_STRT_X, TRK_STRT_Y, 0, trackout);
      (void)drawpath(TRK_STRT_X, TRK_STRT_Y+width, 0, trackin);
   }

   if(!car_count) {  //When zero cars are requested, we only show the track.
      if(no_display)  {
         cout << "Zero cars were requested." << endl;
         exit(0);
      }
      get_ch();         // this executes only when 0 cars are requested
      resume_normal_display();
      exit(0);
   }

   if(practice && stage == BEFORE) {
      stage = PRACTICE;
      --ml;
   }
   else
      stage = RACING;

   // Put up the scoreboard and leader board:
   if(!no_display)
      scoreboard(stage);

   done_count = 0;
   one_tick(1);
   order = new int[car_count];    //setup the order[] array:
   position = new int[car_count];    //setup the position[] array:
   new_data = new int[num_disp];  //setup the new_data[] array:
   for(i=0, j=car_count-1; i<car_count; i++, j--) { // initialize order[] array
      order[i] = j;            // must correspond with initial track positions
      position[j] = i;
      lap[i] = 1;
   }
   if(!no_display)
      // Don't actually start the race until someone hits a key.
      if(get_ch() == ESC)  // Any key except ESC begins the race.
         break;
   // BEGIN THE RACE!
   // Note that the lap count on the scoreboard is updated by the
   // observe() function, which call lapper() to do that.
   if(no_display)
      cout << "Beginning race " << (ml+1) << endl;
   else   {
      designated = -1;        // the instrument panel is initially inactive
      designate(designated);
   }

   while(1) {                             // main loop of the race:
      for(i=0; i<car_count; i++) {            // for each car:
         pcar[i]->observe(rel_state_vec, s); // compute its local situation
         pcar[i]->control(s);                 // compute a control vector
      }
      for(i=0; i<car_count; i++) {        // for each car:
         pcar[i]->move_car();                 // update state of car
      }
      if(!no_display)
         for(i=0; i<car_count; i++)       // for each car:
           pcar[i]->draw_car();               // update screen image of car
      time_count += delta_time;           // Advance the simulated time.
      if(!no_display && designated >= 0)  // maybe update the instrument panel
         if(time_count - ip_update_time > .25) {   // every .25 seconds
            instruments(designated);
            ip_update_time = time_count;
         }

      // This section is for the leader board:
      sortem(num_disp);      // maintains the order[] and new_data[] arrays
      // For any line flagged by new_data[], update that line in "LEADERS" area:
      int pos_done = 0;
      for(i=0; i<num_disp; i++)
         if(starting || new_data[i] || lap[order[i]]) {
            lap[order[i]] = 0;
            if(!no_display)
               leaders(i, order);
            if(!pos_done)  {          // compute position[] only when needed:
               for(int kk = 0; kk < car_count; kk++)
                  position[order[kk]] = kk;
               pos_done = 1;
            }
         }

      if(do_kb())   // check the keyboard rarely if trying to go fast
         if(kb_hit())    // respond to the keyboard if its hit
            if((c = get_ch()) == ESC)     // ESC key will end the race.
               break;
            else if(!no_display)          // F and S keys will speed up/slow down.
               if(c == UP)  {             // up and down arrow designate next car.
                  if(--designated < 0)  {
                     designated = - 1;
                     instruments(designated);
                  }
                  designate(designated);
               }
               else if(c == DOWN)  {
                  if(++designated >= car_count)
                     designated = car_count - 1;
                  designate(designated);
               }
               else if(real_speed && (c == 'f' || c == 'F'))
                  real_speed = 0;
               else if(!real_speed && (c == 's' || c == 'S'))
                  real_speed = 1;
               else if(c == 'p' || c == 'P')      // P is the Pause key
                  get_ch();
             
      if(!no_display)
         refresh_finish_line();
      // check for race over:
      if(done_count >= 2 || done_count >= car_count - out_count)
         break;
      // This is where (maybe) we slow the race down to realistic speed:
      if(real_speed)
         one_tick(0);     // wait here till the next clock tick
      starting = 0;       // the race has begun!
   }
   if(no_display)
      cout << "End of race " << (ml+1) << endl;

   if(stage == RACING)
      report_results(ml+1, order, drivers, pcar, racers);

   stage = RACING;

   if(!no_display)
      if(get_ch() == ESC)  // Continue showing end of race until a key is hit.
          break;

 } // End of Main Loop.

   /* clean up, end graphics, back to normal */
   resume_normal_display();
   RAM_report();
   return 0;  // all done with execution, return to DOS.
}

