// CNTRLR.CPP - "driver" function for RARS - M. Timin, March, 1995
// adapted to ver. 0.4 3/12/95 by M. Timin
// for ver. 0.60

             /* Commented Robot Driver */
/*
This robot driver calculates a cornering speed for each corner based on its
radius.  He accelerates on each straightaway until a "redline" speed
is reached.  The "redline" value is calculated based on the cornering speed
for the coming corner, and the distance remaining in the straight.
Then he slows down, attempting to arrive at the corner with the proper
cornering speed.  In the corner he attempts to maintain the cornering speed
while attempting to stay near the inside.  His strategy for passing is to
choose right or left at random, and then throw the car into a sharp slide
toward that direction.
*/

#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "car.h"

// This structure will be built in the data area provided by the caller:
struct params {   
   double CORN_SPD_CON;   // determines how fast to take corners
   double STEER_GAIN;     // servo gain, for staying in "lane"
   double STEER_DAMP;     // servo damping, to prevent "weaving"
   double BRAK_ACCEL; // we accelerate until this fraction of length
   double END_CORNER;// used to decide when to start leaving the corer
   double SLIP_LIM;  // maximum wheel slip, ft/sec, in wheel_slip()
   double SLIP_CON;
   double BIAS;
   double NEAR_END;
   double SHARP_TURN; // change in alpha when attempting to pass
   int PASSING_TIME;  // time to stay in passing maneuver, counts
};

inline double ABS(double arg) { return arg < 0.0 ? -arg : arg; }

// The following function calculates the speed for a corner.
// The lateral force produced by cornering is proportional to the square
// of the speed, and is inversely proportional to the radius of the path.
// Therefore, the attainable cornering speed for differnt radii is
// proportional to the square root of the radius.  This function implements
// that rule.  The value to use for CORN_SPD_CON can be determined by
// trial and error.  Example result: if the car is following a path with
// radius of 100 ft, and if CORN_SPD_CON is 5.0, then corn_speed is 50 ft/sec.
double corn_speed(double radius, double param)
{
   if(radius < 0.0)         // change sign of negative radius
      radius = -radius;
   else if(radius == 0.0)   // This is just insurance, this funtion doesn't
      return(200.0);        // make sense when the radius is zero.
   return param * sqrt(radius+45.0);
}

// In order to set vc, if you know how fast you want to go (goal), and how
// fast you are going now (present), This function will compute a reasonable
// value for vc.  The value is never very far from the present speed, both
// to attempt to stay within the power limit, and to maintain steering control.
// You can adjust the resulting slip by changing "param".
double wheel_speed(double goal, double present, double param)
{
   double ws;

   if(present > goal + 2 * param)  // if too fast,
      ws = present - param;      // slow down.
   else if(present < goal - 2 * param)  // if too slow,
      ws = present + param;             // accelerate.
   else                           // if quite close,
      ws = (goal + present) / 2;      // approach desired speed gently.

   return ws;
}

/* These two structures from CAR.H are repeated here as comments, because
   the "driver" function receives situation as input and produces con_vec
   as output.
struct situation {       // a car's local situation as seen by the driver
   double cur_rad;       // radius of inner wall of curve (0 means straight)
   double cur_len;       // length of current track segment (angle if curve)
   double to_lft;        // distance to left wall
   double to_rgt;        // distance to right wall
   double to_end;        // how far to end of current track seg. (angle or feet)
   double v;             // the speed of the car, feet per second
   double vn;            // component of v perpendicular to track direction
   double nex_len;       // length of the next track segment (angle if curve)
   double nex_rad;       // radius of inner wall of next segment (or 0)
   double after_rad;     // radius of the segment after that one. (or 0)
   double power_req;     // ratio: power requested by driver to maximum power
   int dead_ahead;       // set when there is a car dead ahead, else 0
   int backward;         // set if cars motion is opposed to track direction
   rel_state* nearby;      // relative states of three cars in front of you
   void* data_ptr;         // pointer to driver's scratchpad RAM area
};

struct con_vec { double alpha, vc; };  // control vector, steering & throttle
*/

// The task of this function is to compute vc and alpha.  A high speed
// car on a track is a little like the keel of a boat; if you set the keel
// at a slight angle to the direction of the oncoming water, you get a large
// force to the side.  That is how we corner the car.  The driver sets the
// car at a slight angle with respect to its direction of motion, this
// cause a force to the side, causing the path of the car to curve.  The
// magnitude of the force is proportional to the angle (alpha) for very
// small alpha, and when there is not much wheel spin.  The wheel spin
// is controlled by vc, which is the rearward speed of the bottom of the
// tire.  When going down the straight at a constant, moderate velocity,
// then vc is equal to the speed of the car.  For acceleration, vc is
// made a little greater than the speed.  For braking, it is made a little
// less.  When accelerating, vc is limited by the power available.
con_vec cntrlR(situation &s)
{
   const char name[] = "Rudy";        // This is the robot driver's name!
   static int init_flag = 1;          // cleared by first call
   double speed;                      // target speed for cornering, ft/sec
   double speed_next;                 // target speed for next corner
   con_vec result;                    // This is what is returned.
   double width;                      // track width, feet
   double redline;             // speed at which to begin braking on straight
   double alpha, vc;           // components of result
   static double alpha_inc = 0.0;  // alpha increment during passing maneuver
   static int counting = 0;    // will be set and counting down when passing
   params* p_ptr;

   // This paragraph has nothing to do with car control; it is just
   // to identify the driver by copying its name to a global RAM area:
   // This happens only on the very first call to this function

   p_ptr = (params*)s.data_ptr;     // point to the data area

   if(init_flag == 1)  {  // first time only, copy name:
      my_name_is(name);        // copy the name string into the host program
      init_flag = 0;
      result.alpha = result.vc = 0;
      return result;
   }

   if(s.starting)  {  // one time only, set parameter values:
      // These parameters may be adjusted to get better performance:
      p_ptr->CORN_SPD_CON = 6.1; // determines how fast to take corners
      p_ptr->STEER_GAIN = 1.1;     // servo gain, for staying in "lane"
      p_ptr->STEER_DAMP = 0.8;     // servo damping, to prevent "weaving"
      p_ptr->BRAK_ACCEL = 33.0; // acceleration during braking
      p_ptr->END_CORNER = 2.3;// used to decide when to start leaving the corer
      p_ptr->SLIP_LIM = 5.0;  // maximum wheel slip, ft/sec, in wheel_slip()
      p_ptr->SLIP_CON = 500.0; //
      p_ptr->BIAS = .09;        // cornering estimated alpha
      p_ptr->NEAR_END = 2.2;   // widths from corner to start with BIAS
      p_ptr->SHARP_TURN = .07; // change in alpha when attempting to pass
      p_ptr->PASSING_TIME = 90;  // time to stay in passing maneuver, counts
      result.alpha = 0.0;   result.vc = s.v + 40; // accelerate, full power!
      return result;
   }

  if(stuck(s.backward, s.v,s.vn, s.to_lft,s.to_rgt, &result.alpha,&result.vc))
      return result;

   // Set alpha based on a servo-mechanism approach, trying to stay
   // in the middle of the track, i.e., s.to_left equal to .5 * width:
   width = s.to_lft + s.to_rgt;                        // find width of track
   if(s.cur_rad > 0)  {
      alpha = p_ptr->STEER_GAIN * (s.to_lft - .15 * width) / width;
      alpha += p_ptr->BIAS;
   }
   else if(s.cur_rad < 0)  {
      alpha = p_ptr->STEER_GAIN * (s.to_lft - .85 * width) / width;
      alpha -= p_ptr->BIAS;
   }
   else {      // on straightaway
     alpha = .2 * p_ptr->STEER_GAIN * (s.to_lft - .58 * width) / width;
     if(s.to_end < p_ptr->NEAR_END * width)
       if(s.nex_rad > 0)
          alpha += p_ptr->BIAS;
       else if(s.nex_rad < 0)
          alpha -= p_ptr->BIAS;
   }
   alpha -= p_ptr->STEER_DAMP * s.vn / s.v;  // This is damping, to prevent oscillation

   // calculate target speeds for current corner and the next:
   speed = corn_speed(s.cur_rad, p_ptr->CORN_SPD_CON);
   speed_next = corn_speed(s.nex_rad, p_ptr->CORN_SPD_CON);

   // now set the tire speed, vc:
   if(s.cur_rad == 0.0) {                  // If we are on a straightaway,
      redline = sqrt(speed_next*speed_next + p_ptr->BRAK_ACCEL * 2.0*s.to_end);
      if(s.v < redline)
         vc = s.v + p_ptr->SLIP_CON / s.v;  // keep accellerating near full power
      else                    // otherwise,
         if(s.v <= 1.1 * speed_next)
            vc = wheel_speed(speed_next, s.v, p_ptr->SLIP_LIM);
         else
            vc = 0.0;     // hard braking
   }
   else                              // If we're in the curve, maintain speed.
      if(s.to_end*ABS(s.cur_rad) > p_ptr->END_CORNER * width *
                                                       sqrt(s.v/speed_next)) {
                  // if we are far from the next corner, stay at "speed".
         vc = wheel_speed(speed, s.v, p_ptr->SLIP_LIM);
         if(s.vn < .95 * speed)
            alpha += s.cur_rad > 0 ? -p_ptr->BIAS : p_ptr->BIAS;  //remove bias
      }   
      else  {      // but when we near the next corner, adjust to "speed_next"
         vc = wheel_speed(speed_next, s.v, p_ptr->SLIP_LIM);
         alpha += s.cur_rad > 0 ? -p_ptr->BIAS : p_ptr->BIAS;     //remove bias
      }

   // The passing maneuver:
   if(s.dead_ahead & !counting)  {  // When first encountering the car ahead:
      counting = p_ptr->PASSING_TIME;         // setup the timer,
      if(rand() < RAND_MAX/2)        // choose a right or left maneuver:
         alpha_inc = p_ptr->SHARP_TURN;
      else
         alpha_inc = -p_ptr->SHARP_TURN;
   }
   if(counting)  {                // If we are still in the passing maneuver,
      alpha += alpha_inc;              // change alpha
      --counting;                      // count down to zero
   }

   result.vc = vc;   result.alpha = alpha;
   return result;
}
