// DRAW.CPP - the system-independent graphics portion of RARS 0.39
// (was GRAPHICS.CPP) - by Mitchell E. Timin, State College, PA
// See GI.H, 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.39 of RARS (Robot Auto Racing Simulation).
// GI.CPP is the system-dependent graphics portion of RARS.
// 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 

/*
 * Modifications made:
 *
 * - l. 143 : Symbol 'errorcode' was not used and has been removed.
 */

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

static double finish_x, finish_y;    // These four variables are used to locate
static double finish_y_in, finish_y_out;    // the finish line on the screen.
static double spacing;             // see leaders()

extern Car* pcar[];      // array of pointers to the various cars
extern int done_count;   // how many cars have finished the race
extern int lap_count;    // how many laps is this race?
extern int car_count;    // how many cars in the race
extern double length;    // total lenth of track (average of inner and outer rails)
extern char* namptr[];   // array of pointers to their name strings
extern int* order;       // will point to array of positions
extern double CHR_HGT;          // height in feet of row of text
extern int no_display;

int round(double given)  // convert double to int by rounding
{
   if(given > 0.0)
      return int(given + .5);
   else
      return int(given - .5);
}

// Convert input into an ASCII string with two decimal digits.
void make_dec_string(char* out,         // pointer to destination string
                     double input)      // value to be converted
{
   long int value;
   char intpart[5], decpart[4];

   value = (long int)(100.0 * input + .5);
   itoa(int(value / 100), intpart, 10);
   itoa(int(value % 100), decpart, 10);
   if(decpart[1] == '\0') { // we might have to stick in a leading zero:
      decpart[2] = '\0';
      decpart[1] = decpart[0];
      decpart[0] = '0';
   }
   strcpy(out, intpart);
   strcat(out, ".");
   strcat(out, decpart);
}

// Assemble a string for the average speed of car i.  (goes to char* out)
void get_avg_spd(int i, char* out)
{
    make_dec_string(out, pcar[i]->speed_avg * MPH_FPS);
}

// Assemble a string for the maximum speed of car i.  (goes to char* out)
void get_max_spd(int i, char* out)
{
    make_dec_string(out, pcar[i]->speed_max * MPH_FPS);
}

/* new routine: draw_arc() routine that uses draw_line() to draw the arc */
#define LINESEG_LENGTH 10		/* this constant can be used to tweak the precision */
void draw_arc(double radius, double center_x, double center_y, double start_angle, double length)
{
	double a;
	double stepsize;
	double x1, y1, x2, y2;

	/* convert a right turn so it is consistent with the left turn */
	if (radius < 0.0)
	{
		radius = -radius;

		start_angle = start_angle - length - PI;
		while (start_angle < 0.0)
		{
			start_angle += (2 * PI);
		}
	}

	/* calculate the starting point */
	x1 = center_x + radius * sin(start_angle);
	y1 = center_y - radius * cos(start_angle);

	/* determine the step size */
	stepsize = LINESEG_LENGTH * (1.0 / (radius * SCALE));

	/* draw lines over the length from there, adapting the number of steps to the length */
	for (a = stepsize; a < length; a += stepsize)
	{
		/* calculate the end point of this line */
		x2 = center_x + radius * sin(start_angle + a);
		y2 = center_y - radius * cos(start_angle + a);

		/* draw the line */
		draw_line(x1, y1, x2, y2);

		/* make the end point the new starting point for the next line */
		x1 = x2;
		y1 = y2;
	}

	/* calculate the end point of the arc */
	x2 = center_x + radius * sin(start_angle + length);
	y2 = center_y - radius * cos(start_angle + length);

	/* draw the last line */
	draw_line(x1, y1, x2, y2);
}

// Draws the path specified by the segment array and starting
// conditions which are given as parameters.  Also, fills in the
// un-initialized portions of the segment array.  Returns the length.
double drawpath(double xstart,      // coordinates of starting point
                double ystart,
                double alfstart,    // starting tangent angle
                segment *track)     // pointer to structure that defines path
{
   double length = 0;          // to accumulate total length of path
   double cenx, ceny;          // center of circle arc
   double radius;              // radius of circle arc (negative == rt. turn)
   double x, y, alf;           // position and direction of start of segment
   double newx, newy, newalf;  // and the one after that  (alf in radians)
   int i;

   x = xstart;  y = ystart;   // store starting point & direction
   alf = alfstart;

   for(i=0; i < NSEG; i++) {                 // for each segment:
      radius = track[i].radius;
      if(radius == 0.0) {                   // is this a straightaway?
         length += track[i].length;
         newx = x + track[i].length * cos(alf);      // find end coordinates
         newy = y + track[i].length * sin(alf);
         track[i].end_x = newx;   track[i].end_y = newy;    // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;       // empty slots in
         track[i].beg_ang = track[i].end_ang = alf;         // the track array
         newalf = alf;                                // direction won't change
         if(!no_display)
            draw_line(x, y, newx, newy);                // draw the straight line
         if(i == 0)      {      // find pixel locations of start/finish line:
              finish_y = newy;   // assume straightaway parallel to x-axis
              finish_x = x + FINISH * length;
         }
      }
      else if(radius > 0.0) {
         length += radius * track[i].length;
         cenx = x - radius * sin(alf);  // compute center location:
         ceny = y + radius * cos(alf);
         track[i].cen_x = cenx;   track[i].cen_y = ceny;  // fill empty slots
         track[i].beg_ang = alf;
         newalf = alf + track[i].length;           // compute new direction
         track[i].end_ang = newalf;                // fill this empty slot
         newx = cenx + radius * sin(newalf);   // location of end
         newy = ceny - radius * cos(newalf);
         track[i].end_x = newx;   track[i].end_y = newy;  // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;     // empty slots
         if(!no_display)
            draw_arc(radius, cenx, ceny, alf, track[i].length);   // draw the arc
      }
      else {
         length -= radius * track[i].length;
         cenx = x - radius * sin(alf);  // compute center location:
         ceny = y + radius * cos(alf);
         track[i].cen_x = cenx;   track[i].cen_y = ceny;  // fill empty slots
         track[i].beg_ang = alf;
         newalf = alf - track[i].length;           // compute new direction
         track[i].end_ang = newalf;                // fill this empty slot
         newx = cenx + radius * sin(newalf);   // location of end
         newy = ceny - radius * cos(newalf);
         track[i].end_x = newx;   track[i].end_y = newy;  // fill in these
         track[i].beg_x = x;      track[i].beg_y = y;     // empty slots
         if(!no_display)
            draw_arc(radius, cenx, ceny, alf, track[i].length);   // draw the arc
      }
      x = newx;                     // repeat with new position and direction:
      y = newy;
      alf = newalf;
   }
   /* to close the circuit, we draw a line from the last point back to the first */
   /* this usually is not necessary, but it prevents flood fill leaking */
   if (!no_display)
      draw_line(x, y, xstart, ystart);

   return length;      // return the length of the path
}

// Draw a little car on the screen, at given position and orientation,
// and with the given colors.  (to erase the car, call it with track_color)
// returns a graphics error code, usually grOk, which is 0.
void drawcar(double x,         // coordinates of center of car
            double y,
            double ang,       // orientation angle of car, wrt x-axis, radians
            int nose,  // color of front portion
            int tail)  // color of rear portion
{
   double xx, yy, endx, endy;
   double sine, cosine, dx, dy;
   int i;

   sine = sin(ang);    cosine = cos(ang);
   xx = x + cosine * CARLEN/2 - sine * CARWID/2;    // left front corner coords
   yy = y + cosine * CARWID/2 + sine * CARLEN/2;
   x = xx;  y = yy;                                 // save the above values
   dx = 0.3333 * CARWID * sine;
   dy = -.3333 * CARWID * cosine;
   // below we draw three parallel lines to form the body of the car:
   set_color(tail);
   for(i=0; i<=3; i++) {
      endx = xx - CARLEN * cosine;
      endy = yy - CARLEN * sine;
      draw_line(xx, yy, endx, endy);
      if(i == 3) break;
      xx += dx;
      yy += dy;
   }
   // now three short lines of the nose color to decorate the front:
   set_color(nose);
   xx = x;  yy = y;             // restore x and y to left front of car
   for(i=0; i<=3; i++) {
      endx = xx + CARWID * sine;
      endy = yy - CARWID * cosine;
      draw_line(xx, yy, endx, endy);
      if(i == 3) break;
      xx += dy;
      yy -= dx;
   }
}

void lapper(int which, int lap)  // shows lap count on scoreboard,
{                               // also returns lap+1 to advance the lap count
   char string[] = "    ";      // and also increments done_count

   if(lap < 0)
      lap = 0;

   set_fill_color(FIELD_COLOR);   // the green part is off the track
   // This bar erases the previous lap count:
   rectangle(SCORE_BOARD_X + 10*CHR_WID,
             SCORE_BOARD_Y - which*CHR_HGT,
             SCORE_BOARD_X + 13*CHR_WID,
             SCORE_BOARD_Y - (which+.9)*CHR_HGT);

   set_color(TEXT_COLOR);          // now print text in black:
   itoa(lap, string, 10);
   text_output(SCORE_BOARD_X + 10 * CHR_WID,
               SCORE_BOARD_Y - which * CHR_HGT, string);
}

void border(void)
{
   set_color(RAIL_COLOR);
   draw_line(0.0, 0.0, X_MAX, 0.0);
   draw_line(X_MAX, 0.0, X_MAX, Y_MAX);
   draw_line(X_MAX, Y_MAX, 0.0, Y_MAX);
   draw_line(0.0, Y_MAX, 0.0, 0.0);
}

// Initializes graphics system, draws track, fills in colored regions:
void graph_setup(void)
{
   double alt_len;         // used in deciding the length of the track

   build_track();          // read track data and fill in trackou[], trackin[]
   if(!no_display)  {
      initialize_graphics();
      /* instead of flood filling everything, we draw a rectangle */
      set_fill_color(FIELD_COLOR);
      rectangle(0.0, Y_MAX, X_MAX, 0.0);
      border();               // draw border at screen boundary
      // paint the whole screen green:
      /*set_fill_color(FIELD_COLOR);*/
      /*flood_fill(20.0, 20.0);*/
   }
   // draw outer track boundary:
   length = drawpath(TRK_STRT_X, TRK_STRT_Y, 0, trackout);
   finish_y_out = finish_y;                   // locate one end of finish line
   // draw inner track boundary:
   alt_len = drawpath(TRK_STRT_X, TRK_STRT_Y+width, 0, trackin);
   if(alt_len < length)     // take length of shorter rail as track length
      length = alt_len;
   finish_y_in = finish_y;        // locate other end of finish line
   if(no_display)
      return;
   // pave the track:
   set_fill_color(TRACK_COLOR);
   flood_fill(TRK_STRT_X, TRK_STRT_Y + width/2);     // the track
}

void refresh_finish_line()
{
   set_color(TEXT_COLOR);
   draw_line(finish_x, finish_y_out, finish_x, finish_y_in);
}

// Put up the scoreboard:
void scoreboard()
{
   int i;

   double XS = SCORE_BOARD_X;
   double YS = SCORE_BOARD_Y;
   char string[] = "12345678";
   int kount;

   spacing = 1.15 * CHR_HGT;   // for the leader board only
   // these rectangles are for the leader board car pictures:
   kount = car_count < 5 ? car_count : 5;
   // first erase the old board, if any:
   set_fill_color(FIELD_COLOR);
  rectangle(LDR_BRD_X - 1.9 * CARLEN, LDR_BRD_Y - CHR_HGT,
             LDR_BRD_X + 19*CHR_WID, LDR_BRD_Y - (kount + .7) * spacing);
    set_fill_color(TRACK_COLOR);   // rectangular background for car pictures:
   rectangle(LDR_BRD_X - 1.9 * CARLEN, LDR_BRD_Y - CHR_HGT,
             LDR_BRD_X -.5 * CARLEN, LDR_BRD_Y - (kount + .7) * spacing);
   // these rectangles are for the scoreboard car pictures:
   // first erase the old board, if any:
   set_fill_color(FIELD_COLOR);
   rectangle(XS-.7*CARLEN, YS+CHR_HGT/2, XS+13*CHR_WID,
                               YS - car_count * CHR_HGT);
   set_fill_color(TRACK_COLOR);
   rectangle(XS-.7*CARLEN, YS+CHR_HGT/2, XS+.5*CARLEN,
                               YS - car_count * CHR_HGT);
   // draw the cars on the scoreboard:
   for(i=0; i<car_count; i++) {
      drawcar(XS-.1*CARLEN, YS - i * CHR_HGT - CARWID/2, 0,
                              car_colors[i].nose, car_colors[i].tail);
      set_color(TEXT_COLOR);
      itoa(lap_count, string, 10);
      text_output(XS+2*CHR_WID, YS - i * CHR_HGT, namptr[i]);
      (void)lapper(i,-1);
   }
   text_output(XS - 13 * CHR_WID, SCORE_BOARD_Y + 1.5 * CHR_HGT,
                                        "Race Length  Car  Driver  Laps");
   text_output(LOTIX, LOTIY, "Length of track is      ft.");
   itoa(int(length+.5), string, 10);
   text_output(LOTIX + 16.5 * CHR_WID, LOTIY, string);
   itoa(lap_count, string, 10);
   text_output(SCORE_BOARD_X-10*CHR_WID, SCORE_BOARD_Y, string);
   text_output(SCORE_BOARD_X-7*CHR_WID, SCORE_BOARD_Y, "laps");
   text_output(LDR_BRD_X - CHR_WID, LDR_BRD_Y, "LEADERS:   max   avg");
   text_output(LDR_BRD_X+11.5*CHR_WID, LDR_BRD_Y+CHR_HGT, "mph");
}

// update the leader board when necessary:
void leaders(int i)
{
   char string[] = "12345678";
   double Y;

   Y = LDR_BRD_Y - spacing * (i + 1);

   // Erase old text:
   set_fill_color(FIELD_COLOR);             // The infield color
   rectangle(LDR_BRD_X, Y, LDR_BRD_X+19*CHR_WID, Y - CHR_HGT);

   set_color(TEXT_COLOR);
   text_output(LDR_BRD_X, Y, namptr[order[i]]);
   get_max_spd(order[i], string);                    // the maximum speed:
   text_output(LDR_BRD_X+7.5*CHR_WID, Y, string);
   get_avg_spd(order[i], string);                    // the average speed:
   text_output(LDR_BRD_X+13.5*CHR_WID, Y, string);
   drawcar(LDR_BRD_X-1.2*CARLEN, Y - CARWID/2, 0.0,
                       car_colors[order[i]].nose, car_colors[order[i]].tail);
}
