/* c_jovian_ai.cc 1.14 95/12/28 00:41:30 */


// xspacewarp by Greg Walker (gow@math.orst.edu)

// This is free software. Non-profit redistribution and/or modification
// is allowed and welcome.


// These are the "artificial intelligence" routines for jovians.  Any
// upgrades of the Jovian AI should only require modifying the
// functions in this file.
// Probabilities are represented by floats in [0,1].
//
// pick_action() just decides the general kind of action for the jovian to make
// (MOVE, SHOOT, LEAP, NONE). After calling pick_action(), either
// pick_direction(), pick_target(), or pick_sector() depending if pick_action()
// returned MOVE, SHOOT, or LEAP respectively.
// pick_direction(), pick_target(), and pick_sector() provide details on
// what direction to move in, who to shoot, and what sector to go to.
// After pick_direction(), pick_target() or pick_sector() are called,
// the Decision struct gets filled in and is then ready to be passed to the
// act() method.

#include "c_jovian.hh"
#include "c_jovian_ai.hh"	// params and inlines used in these ai routines
#include "c_sector.hh"
#include <iostream.h>
#include <stdlib.h>		// rand()
#include "common.hh"
#include "params.hh"
#include "globals.hh"		// access to universe[][]
#include "space_objects.hh"


// pick_action() fills in these values:

static Point target;		// the nearest target or {-1, -1} if no target
static long sdist;		// square of distance to nearest target or -1


// returns probability of shooting target

static float pshoot(void);


// initialize the non-static AI specific data

void Jovian::init_ai_flags()
{
  mustmove = false;
  retreat_flag = false;
  forcedvert = false;
  forcedhoriz = false;
  israiding = false;
}


// initialize the static AI specific data

void Jovian::init_ai()
{
  raidflag = false;
}


// returns a general description of what the jovian should do
// (MOVE, SHOOT, LEAP, NONE)

Action Jovian::pick_action()
{
  Point base_pos, endv_pos, jov_pos;	// Positions of base, endever and this
					// jovian.
  long base_sqdist, endv_sqdist; // squares of distances to base and endever

  float fight_speed, raid_speed, leap_speed; // Rates of jovian actions based on
					   // skill level & c_jovian_ai.hh.
					   // fight_speed refers to when either
					   // the endever or a base being
					   // raided is in same sector as the
					   // jovian. raid_speed refers to
					   // when only a base is around.
					   // leap_speed refers to when no
					   // base/endever is around.

  // fight_speed, raid_speed and leap_speed are determined as linear
  // functions of skill level.

  fight_speed = evallinear(gamestate.skill, MINSKILL, SLOWFIGHT,
			   MAXSKILL, FASTFIGHT);
  raid_speed = evallinear(gamestate.skill, MINSKILL, SLOWRAID,
			  MAXSKILL, FASTRAID);
  leap_speed = evallinear(gamestate.skill, MINSKILL, SLOWLEAP,
			  MAXSKILL, FASTLEAP);

  // Query sector as to whether a base or the endever are around.

  base_pos = universe[urow-1][ucol-1].getbase();
  endv_pos = universe[urow-1][ucol-1].getendever();
  jov_pos = center();

  // Determine what to do (MOVE, SHOOT, LEAP, NONE). decision is based
  // on whether no targets are around or if a base is around or if the
  // endever is around or if both a base and the endever are around.
  
  if (endv_pos.x >= 0 && base_pos.x >= 0) // both endever and base are around
  {
        if (israiding)		// endever has arrived, raid over.
	{
	  raidflag = false;
	  israiding = false;
	}
        
	// stuff for deciding to make retreats

	if (retreat_flag)	   // once jov decides to retreat, stick to it
	   if (on_edge() &&
	       !shot.inprogress && // leaping now would leave an abandoned shot
	       f_rand() <= time_to_prob(RETREAT_SPEED))
	      return (LEAP);
	   else if (f_rand() <= time_to_prob(RETREAT_SPEED))
	      return (MOVE);
	   else
	      return (NONE);
	else if (getthrusters() < RETREAT_THRUST &&
		 getwarpdrive() < RETREAT_WARP &&
		 getfasers() < RETREAT_FASER &&
		 getshields() < RETREAT_SHIELD &&
		 getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp
		 (1 + rand()%100) <= RETREAT_PROB)
	{
	  retreat_flag = true;  // run away
	  forcedvert = false;	// start with a blank slate when retreating
	  forcedhoriz = false;	// ditto
	  mustmove = false;	// ditto
	  if (on_edge() &&
	      !shot.inprogress && // leaping now would leave an abandoned shot
	      f_rand() <= time_to_prob(RETREAT_SPEED))
	     return (LEAP);
	  else if (f_rand() <= time_to_prob(RETREAT_SPEED))
	     return (MOVE);
	  else
	     return (NONE);
	}

	// stuff for deciding whether to attack

	// put location and distance of nearest target in static storage
	base_sqdist = sqdist(base_pos, jov_pos);
	endv_sqdist = sqdist(endv_pos, jov_pos);

	if (endv_sqdist <= base_sqdist) // choose endever as target
	{
	  target = endv_pos;
	  sdist = endv_sqdist;
	}
	else			// choose base as target
	{
	  target = base_pos;
	  sdist = base_sqdist;
	}

	// make sure target selected before returning a forced decision

	if (mustmove)
	{
	  if (f_rand() <= time_to_prob(fight_speed))
	     return (MOVE);	// forced move
	  else
	     return (NONE);	// to slow things down
	}

	if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move
	{
	  if (f_rand() <= pshoot())
	     return (SHOOT);
	  else
	     return (MOVE);
	}
	else
	   return (NONE);
  }
  else if (endv_pos.x >= 0 && base_pos.x < 0) // only endever is around
  {
        if (israiding)	      // endever has arrived & base gone, so raid over.
	{
	  raidflag = false;
	  israiding = false;
	}

	// stuff for deciding to make retreats (copied from above)

	if (retreat_flag)	   // once jov decides to retreat, stick to it
	   if (on_edge() &&
	       !shot.inprogress && // leaping now would leave an abandoned shot
	       f_rand() <= time_to_prob(RETREAT_SPEED))
	      return (LEAP);
	   else if (f_rand() <= time_to_prob(RETREAT_SPEED))
	      return (MOVE);
	   else
	      return (NONE);
	else if (getthrusters() < RETREAT_THRUST &&
		 getwarpdrive() < RETREAT_WARP &&
		 getfasers() < RETREAT_FASER &&
		 getshields() < RETREAT_SHIELD &&
		 getwarpdrive() >= JOVMINWARPERG && // no retreat if no warp
		 (1 + rand()%100) <= RETREAT_PROB)
	{
	  retreat_flag = true;  // run away
	  forcedvert = false;	// start with a blank slate when retreating
	  forcedhoriz = false;	// ditto
	  mustmove = false;	// ditto
	  if (on_edge() &&
	      !shot.inprogress && // leaping now would leave an abandoned shot
	      f_rand() <= time_to_prob(RETREAT_SPEED))
	     return (LEAP);
	  else if (f_rand() <= time_to_prob(RETREAT_SPEED))
	     return (MOVE);
	  else
	     return (NONE);
	}

	// stuff for deciding whether to attack

	// put in static storage
	target = endv_pos;
	sdist = sqdist(endv_pos, jov_pos);

	// make sure target selected before returning a forced decision

	if (mustmove)
	{
	  if (f_rand() <= time_to_prob(fight_speed))
	     return (MOVE);	// forced move
	  else
	     return (NONE);	// to slow things down
	}

	if (f_rand() <= time_to_prob(fight_speed)) // then either shoot or move
	{
	  if (f_rand() <= pshoot())
	     return (SHOOT);
	  else
	     return (MOVE);
	}
	else
	   return (NONE);
  }
  else if (endv_pos.x < 0 && base_pos.x >= 0) // only base is around
  {
	if (retreat_flag)		// jov out of danger now
	{
	  retreat_flag = false;
	  forcedvert = false;	// start with a blank slate now jov is safe
	  forcedhoriz = false;	// ditto
	  mustmove = false;	// ditto
	}

	// put in static storage
	target = base_pos;
	sdist = sqdist(base_pos, jov_pos);

	// make sure target selected before returning a forced decision

	if (mustmove)
	{
	  if (f_rand() <= time_to_prob(fight_speed))
	     return (MOVE);	// forced move
	  else
	     return (NONE);	// to slow things down
	}

	// If raidflag is already set, then skip this.
	// divide the probability by Jovian::raidable since we are allowing
	// at most one jovian to mount a raid on a given call on jovian_to().

	if (!raidflag && (f_rand() <= time_to_prob(raid_speed)/raidable))
	{
	  raidflag = true;
	  israiding = true;
	}

	if (israiding)		// this jov is attacking a base
	{
	  if (f_rand() <= time_to_prob(fight_speed)) // either shoot or move
	  {
	    if (f_rand() <= pshoot())
	       return (SHOOT);
	    else
	       return (MOVE);
	  }
	  else
	     return (NONE);
	}
	else
	   return (NONE);
  }
  else				// no targets around
  {
        if (israiding)		// base is gone, raid over.
	{
	  raidflag = false;
	  israiding = false;
	}

	if (retreat_flag)		// jov out of danger now
	{
	  retreat_flag = false;
	  forcedvert = false;	// start with a blank slate now jov is safe
	  forcedhoriz = false;	// ditto
	  mustmove = false;	// ditto
	}

	if (leapflag)		// a jovian already leaped on this call to
	   return (NONE);	// jovian_to(); so this jovian cannot leap.

	// assign values to static storage indicating no targets around
	target.x = -1;
	target.y = -1;
	sdist = -1;

	// divide the probability by Jovian::leapable since we are allowing
	// at most one jovian to leap on a given call on jovian_to().

	if (f_rand() <= time_to_prob(leap_speed)/leapable)
	{
	  leapflag = true;
	  return (LEAP);
	}
	else
	   return (NONE);
  }
}


Direction Jovian::pick_direction()
{
  Direction horiz, vert;
  Point jov_pos = center();
  
  static int horizcount;	// must move FORCEDHORIZ steps horizontally
				// to clear an obstacle
  static Direction horizdir;	// move in this consistent horiz direction
				// until the obstacle is cleared.

  static int vertcount;		// Similar to horizcount, but FORCEDVERT
  static Direction vertdir;	// Similar to horizdir


  if (retreat_flag)
     return (retreat_direction());

  // determine which horizontal direction to prefer

  switch (sgn(target.x - jov_pos.x))
  {
  case -1:			// target is to left
    horiz = LEFT;
    break;
  case 0:
    if (mustmove)		// must move horizontal to clear obstacle
    {
      forcedhoriz = true;
      horizcount = FORCEDHORIZ;
      horizdir = (rand()%2)? LEFT:RIGHT; // randomly select a horiz preference
      return (horizdir);
    }
    else
       horiz = STAY;
    break;
  case 1:			// target is to right
    horiz = RIGHT;
    break;
  }

  // determine which vertical direction to prefer

  switch (sgn(target.y - jov_pos.y))
  {
  case -1:			// target is to left
    vert = UP;
    break;
  case 0:
    if (mustmove)		// must move vertical to clear obstacle
    {
      forcedvert = true;
      vertcount = FORCEDVERT;
      vertdir = (rand()%2)? UP:DOWN; // randomly select a vert preference
      return (vertdir);
    }
    else
       vert = STAY;
    break;
  case 1:			// target is to right
    vert = DOWN;
    break;
  }

  // how to respond when jovian is blocked by a star or other jovian

  if (forcedhoriz)
  {
    if (--horizcount == 1)	// keep moving horizontally in some consistent
       forcedhoriz = false;	// direction until the obstacle is passed.
    return (horizdir);
  }
  else if (forcedvert)
  {
    if (--vertcount == 1)	// keep moving vertically in some consistent
       forcedvert = false;	// direction until the obstacle is passed.
    return (vertdir);
  }

  // decide whether to go vertical or horizontal when not blocked by obstacle

  if (horiz == STAY)		// not random
     return (vert);
  else if (vert == STAY)	// not random
     return (horiz);
  else
     return ((rand()%2)? horiz:vert);
}


// returns a sector for the jovian to leap to or {-1, -1} if no available
// sectors exist.

Ucoors Jovian::pick_sector()
{
  Ucoors dests[UROWS*UCOLS];	// Destinations:
				// The set of all available sectors to leap to.
				// A sector is "available" if it contains a
				// base or contains the endever. Also, to be
				// "available", a sector must contain less
				// than MAXJOVSECT jovians.

  Ucoors nowhere = {-1, -1};	// Return this if no available sectors.
  int ndests = 0;			// The number of available destinations.
  int i, j;

  // look for available sectors

  if (retreat_flag)		// find sectors not containing the endever
  {
    for (i = 0; i < UROWS; i++)
    {
      for (j = 0; j < UCOLS; j++)
      {
	if (universe[i][j].getendever().x < 0 &&
	    universe[i][j].getjovpop() < MAXJOVSECT) // no endever & few jovs
	{
	  dests[ndests].urow = i+1;
	  dests[ndests].ucol = j+1;
	  ndests++;
	}
      }
    }
  }
  else				// find sectors having bases and/or the endever
  {
    for (i = 0; i < UROWS; i++)
    {
      for (j = 0; j < UCOLS; j++)
      {
	if ((universe[i][j].getbase().x < 0) &&
	    (universe[i][j].getendever().x < 0)) // no base and no endever
	{
	  continue;
	}
	else if (universe[i][j].getjovpop() < MAXJOVSECT)
	{
	  dests[ndests].urow = i+1;	// i,j is an available sector
	  dests[ndests].ucol = j+1;
	  ndests++;
	}
      }
    }
  }

  if (ndests == 0)
     return (nowhere);
  else
     return (dests[rand() % ndests]);
}


// This function actually does nothing. pick_action() computes the nearest
// target and stores its location staticly in "target".

Point Jovian::pick_target()
{
  return (target);
}


// Decide what direction to move in when jovian is in retreat mode.

Direction Jovian::retreat_direction()
{
  Direction horiz, vert;      // directions to closest horiz/vert sector edges
  int horiz_dist, vert_dist;	// shortest horiz/vert distances to sector edge
  static Direction dir;		// for saving the previous movement direction
  static int horizcount;	// must move icon_len steps horizontally
				// to clear an obstacle

  // find direction giving shortest path to the edge of the sector

  horiz = RIGHT;
  horiz_dist = SECTCOLS - 2 - col; // number steps needed to RIGHT to escape
  if (col - 1 < horiz_dist)
  {
    horiz = LEFT;
    horiz_dist = col - 1;
  }

  vert = DOWN;
  vert_dist = SECTROWS - 1 - row; // number steps needed DOWN to escape
  if (row < vert_dist)
  {
    vert = UP;
    vert_dist = row;
  }

  // decide what direction to take

  if (mustmove)			// last retreat movement got blocked
  {
    forcedhoriz = false;
    if (dir == LEFT || dir == RIGHT) // last movement was horizontal
    {
      dir = (rand()%2)? UP:DOWN; // randomly pick up or down
    }
    else  // last move was vert. move horiz icon_len steps to clear obstacle.
    {
      forcedhoriz = true;     // flag to ensure consistent direction next time
      horizcount = 1;		// count icon_len steps
      dir = (rand()%2)? LEFT:RIGHT; // randomly pick horiz direction
    }
  }
  else if (forcedhoriz)	 // move horiz some consistent direction for 3 steps
  {
    horizcount++;
    if (horizcount > icon_len)	// should have cleared obstacle by now
    {
      forcedhoriz = false;
      dir = (vert_dist < horiz_dist)? vert:horiz;
    }
  }
  else			// no obstacles. move toward nearest sector edge.
  {
    dir = (vert_dist < horiz_dist)? vert:horiz;
  }

  return (dir);
}
  
  


// Return a float in [0,1] representing the probability that the
// jovian will shoot at the target determined in pick_action()
// and stored staticly in "target". This probability is modeled
// as a cubic polynomial function of the squared distance
// between the jovian and its target. A cubic Lagrange
// polynomial is fit through four points in the plane whose y
// coors represent the probability of shooting (0 to 1) and
// whose x coors are the squared euclidean distances from
// the jovian to the potential target (x ranges from 0 to
// SECTDIAGSQ).

static float pshoot(void)
{
  // the four Lagrange interpolation points

  FPoint p0 = {P0_X, P0_Y};
  FPoint p1 = {P1_X, P1_Y};
  FPoint p2 = {P2_X, P2_Y};
  FPoint p3 = {P3_X, P3_Y};

  float d = (float)sdist;	// square of distance between jovian and target
  float prob;			// the return value

  // evaluate Lagrange interpolating polynomial at d
  
  prob = p0.y*((d - p1.x)/(p0.x - p1.x))*((d - p2.x)/(p0.x - p2.x))*
              ((d - p3.x)/(p0.x - p3.x)) +
	 p1.y*((d - p0.x)/(p1.x - p0.x))*((d - p2.x)/(p1.x - p2.x))*
	      ((d - p3.x)/(p1.x - p3.x)) +
	 p2.y*((d - p0.x)/(p2.x - p0.x))*((d - p1.x)/(p2.x - p1.x))*
	      ((d - p3.x)/(p2.x - p3.x)) +
	 p3.y*((d - p0.x)/(p3.x - p0.x))*((d - p1.x)/(p3.x - p1.x))*
              ((d - p2.x)/(p3.x - p2.x));

  return (prob);
}

// end
