
/* Program to do baseball career projections by Bill James's Brock2 system
   Copyright 1992, David Grabiner.  This program may be used, copied, and
   modified freely, provided that this copyright notice is included in all
   copies.  The Brock2 algorithm itself was placed in the public domain by
   Bill James.
*/

#include <stdio.h>

/* Column numbers in the array of statistics */
#define GAMES 0
#define AB 1
#define RUNS 2
#define HITS 3
#define DOUBLES 4
#define TRIPLES 5
#define HOMERS 6
#define RBI 7
#define WALKS 8
#define NUMSTATS 9 /* number of stats to read from file */
#define AVERAGE 9
#define TOTALBASES 10
#define RC 11
#define RC25 12
#define SUSTENANCE 13
#define OKREGULAR 14
#define OKBENCH 15
#define PLAYFACTOR 16
#define LASTCOLUMN 16

#define MINAGE 20 /* youngest and lowest ages */
#define MAXAGE 41 /* to which method applies */

#define TOTALS (MAXAGE-MINAGE+1) /* row for keeping career totals */

#define EPSILON .000001 /* used as denominator to avoid division by zero */

double stats[TOTALS+1][LASTCOLUMN+1];
int year, cumage, firstage, currage;
double sustlevel;
int age;
int row, col;
double a, tempavg;

/* Round to nearest integer */
double round(value)
double value;
{
  int tempval;
  tempval = (int)(value + .5);
  return((double)tempval);
}

/* Read a line of stats from the input */
void readstatline(age)
int age;
{
  int row, col;
  {
    row = (age ? (age-MINAGE) : TOTALS);
    if (scanf ("%lf %lf %lf %lf %lf %lf %lf %lf %lf", 
	       &stats[row][0], &stats[row][1], &stats[row][2],
	       &stats[row][3], &stats[row][4], &stats[row][5],
	       &stats[row][6], &stats[row][7], &stats[row][8]) != 9)
      {
	(void)printf ("Invalid stat line format \n");
	exit(1);
      }
    if (age > cumage) /* Must add to cumulative stats */
      for (col = 0; col < NUMSTATS; col++)
	stats[TOTALS][col] += stats[row][col];
    stats[row][AVERAGE] = stats[row][HITS] / (stats[row][AB] + EPSILON);
  }
  return;
}

/* Calculate auxilliary stats (runs created, etc.) */
void calcauxstats(row)
int row;
{
  stats[row][TOTALBASES] = stats[row][HITS] + stats[row][DOUBLES]
    + 2*stats[row][TRIPLES] + 3*stats[row][HOMERS];
  /* protect against division by zero */
  stats[row][RC] = stats[row][TOTALBASES]
    * (stats[row][HITS] + stats[row][WALKS])
      / (stats[row][AB] + stats[row][WALKS] + EPSILON);
  stats[row][RC25] = stats[row][RC] * 25
    / (stats[row][AB] - stats[row][HITS] + EPSILON);
  if (row >= (firstage-MINAGE))
    {
      stats[row][OKREGULAR] = (stats[row][RC25] > stats[row][SUSTENANCE]);
      stats[row][OKBENCH] = (stats[row][RC25] > stats[row][SUSTENANCE]
			     - (row > 12 ? .6 : 1));
    }
  else /* Assume player was good enough to play before rookie year */
    stats[row][OKREGULAR] = stats[row][OKBENCH] = 1;
  if (row < 1)
    stats[row][PLAYFACTOR] = 0;
  else if (row < 5)
    stats[row][PLAYFACTOR] = (stats[row][OKREGULAR] +
			      stats[row-1][OKBENCH] +
			      stats[row][OKBENCH]) / 3;
  else if (row < 11)
    stats[row][PLAYFACTOR] = (stats[row-1][OKREGULAR] +
			      stats[row][OKREGULAR] +
			      stats[row-1][OKBENCH] +
			      stats[row][OKBENCH]) / 4;
  else
    stats[row][PLAYFACTOR] = (stats[row-2][OKREGULAR] +
			      stats[row-1][OKREGULAR] +
			      stats[row][OKREGULAR] +
			      stats[row-1][OKBENCH] +
			      stats[row][OKBENCH]) / 5;
}
     

main()
{
  if (scanf("%d %d %d %d", &year, &cumage, &firstage, &currage) != 4)
    {
      (void)printf("Invalid age line format\n");
      exit(1);
    }
  if ((cumage < firstage-1) || (cumage > currage)
      || (firstage > currage) || (firstage < MINAGE)
      || (currage > MAXAGE) || (currage <= MINAGE))
    {
      (void)printf("Invalid ages on age line\n");
      exit(1);
    }
  if (scanf("%lf", &sustlevel) != 1)
    {
      (void)printf("Sustenance level must be on second line\n");
      exit(1);
    }
  /* Ignore line with stat column headings */
  (void)getchar(); /* first ignore newline */
  while (getchar() != '\n');
  readstatline(0); /* Cumulative stats */
  for (age = firstage; age <= currage; age++)
    readstatline(age);

  /* Calculate sustenance level for all ages first */
  stats[0][SUSTENANCE] = sustlevel;
  for (row = 1; row < TOTALS; row++)
    {
      switch(row)
	{
	case 1: 
	  a = -.1;
	  break;
	case 2: case 3: case 4: case 5:
	  a = 0;
	  break;
	case 6: case 7:
	  a = .015;
	  break;
	case 8: case 9: case 10:
	  a = .035;
	  break;
	case 11:
	  a = .045;
	  break;
	case 12:
	  a = .055;
	  break;
	case 13:
	  a = .07;
	  break;
	case 14: case 15: case 16:
	  a = .075;
	  break;
	case 17:
	  a = .05;
	  break;
	case 18:
	  a = .075;
	  break;
	case 19: case 20: case 21:
	  a = .05;
	  break;
	}
      stats[row][SUSTENANCE] = stats[row-1][SUSTENANCE] + a;
    }
  
  /* Need to know whether the player was good enough to be a regular
     for the last few years */
  for (row = ((currage < 31) ? currage-MINAGE-1 : currage-MINAGE-2);
       row <= currage-MINAGE; row++)
    calcauxstats(row);

  /* Now project the career */
  for (row = currage-MINAGE+1; row < TOTALS; row++)
    {
      switch(row)
	{
	case 2: case 3: case 4: case 6:
	  /* Young regulars assumed to play almost every day */
	  stats[row][GAMES] = ((149 + stats[row-1][RC25])
			       * stats[row-1][PLAYFACTOR]);
	  break;
	case 5: /* cosmetic inconsistency */
	  stats[row][GAMES] = ((144 + stats[row-1][RC25])
			       * stats[row-1][PLAYFACTOR]);
	  break;
	case 7: case 8: case 10: case 11: case 12: case 13:
	  stats[row][GAMES] = stats[row-1][PLAYFACTOR] *
	    (stats[row-2][GAMES] + stats[row-1][GAMES]
	     + 154 * stats[row-1][OKREGULAR]) / 3;
	  break;
	case 9:
	  stats[row][GAMES] = stats[row-1][PLAYFACTOR] *
	    (stats[row-2][GAMES] + stats[row-1][GAMES]
	     + 154 * stats[row-1][OKREGULAR]) / 3 + 4;
	  break;
	default: /* Older players play fewer games */
	  stats[row][GAMES] = stats[row-1][PLAYFACTOR] *
	    (stats[row-2][GAMES] + stats[row-1][GAMES]
	     + 150 * stats[row-1][OKREGULAR]) / 3 ;
	  break;
	}
      if (stats[row][GAMES] < .5) /* Retire player */
	break;

      switch(row)
	{
	case 2: case 3: case 4: case 5: case 6: case 7:
	  stats[row][AB] = .99 * stats[row][GAMES] *
	    (stats[row-2][AB] + stats[row-1][AB] + 58)
	      / (stats[row-2][GAMES] + stats[row-1][GAMES] + 15);
	  break;
	case 8: case 9:
	  stats[row][AB] = .99 * stats[row][GAMES] *
	    (stats[row-2][AB] + stats[row-1][AB] + 51)
	      / (stats[row-2][GAMES] + stats[row-1][GAMES] + 15);
	  break;
	case 10:
	  stats[row][AB] = .99 * stats[row][GAMES] *
	    (stats[row-2][AB] + stats[row-1][AB] + 79)
	      / (stats[row-2][GAMES] + stats[row-1][GAMES] + 15);
	  break;
	case 11:
	  /* As older players decline, AB/G decreases rapidly */
	  stats[row][AB] = stats[row][GAMES] *
	    ((stats[row-2][AB] + stats[row-1][AB] - 5)
	     / (stats[row-2][GAMES] + stats[row-2][GAMES]))
	      * ((stats[row-1][PLAYFACTOR] + 1.46) / 2.5);
	  break;
	default:
	  stats[row][AB] = stats[row][GAMES] *
	    ((stats[row-2][AB] + stats[row-1][AB])
	     / (stats[row-2][GAMES] + stats[row-2][GAMES]))
	      * ((stats[row-1][PLAYFACTOR] + 1.46) / 2.5);
	  break;
	}

      /* Runs scored is next column, but will be calculated later */
      if (row == 2)
	tempavg = ((stats[row-2][HITS] + stats[row-1][HITS] + 13)
		   / (stats[row-2][AB] + stats[row-1][AB] + 50));
      else if (row < 10)
	tempavg = ((stats[row-3][HITS] + stats[row-2][HITS]
		    + (stats[row-1][HITS]/2) + 13)
		   / (stats[row-3][AB] + stats[row-2][AB]
		      + (stats[row-1][AB]/2) + 50));
      else
	tempavg = ((stats[row-4][HITS] + stats[row-3][HITS]
		    + stats[row-2][HITS] + 12)
		   / (stats[row-4][AB] + stats[row-3][AB]
		      + stats[row-2][AB] + 50));
      switch(row)
	{
	case 2:
	  tempavg += .016;
	  break;
	case 3:
	  tempavg += .009;
	  break;
	case 4:
	  tempavg += .012;
	  break;
	case 5:
	  tempavg += .009;
	  break;
	case 6:
	  tempavg += .006;
	  break;
	case 7:
	  tempavg += .020;
	  break;
	case 8:
	  tempavg -= .007;
	  break;
	case 9:
	  tempavg -= .013;
	  break;
	case 10:
	  tempavg -= .007;
	  break;
	case 11:
	  tempavg -= .008;
	  break;
	case 12:
	  tempavg -= .012;
	  break;
	case 13:
	  tempavg -= .015;
	  break;
	case 14:
	  tempavg -= .011;
	  break;
	case 15:
	  tempavg -= .014;
	  break;
	case 16:
	  tempavg -= .014;
	  break;
	case 17:
	  tempavg -= .025;
	  break;
	case 18:
	  tempavg += .004;
	  break;
	case 19:
	  tempavg -= .030;
	  break;
	case 20:
	  tempavg -= .002;
	  break;
	case 21:
	  tempavg -= .010;
	  break;
	}
      /* tempavg would be the player's batting average, except that */
      /* hits will be rounded off when displayed		    */
      stats[row][HITS] = stats[row][AB] * tempavg;

      if (row == 15)
	stats[row][DOUBLES] = stats[row][HITS] *
	  ((stats[row-2][DOUBLES] + stats[row-1][DOUBLES])
	   / (stats[row-2][HITS] + stats[row-1][HITS] + 7));
      else
	stats[row][DOUBLES] = stats[row][HITS] *
	  ((stats[row-2][DOUBLES] + stats[row-1][DOUBLES] + 2)
	   / (stats[row-2][HITS] + stats[row-1][HITS] + 13));

      if (row < 6)
	stats[row][TRIPLES] = stats[row][HITS] *
	  ((stats[row-2][TRIPLES] + stats[row-1][TRIPLES])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));
      else /* Triples decrease rapidly with age */
	stats[row][TRIPLES] = .89 * stats[row][HITS] *
	  ((stats[row-2][TRIPLES] + stats[row-1][TRIPLES])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));

      if (row < 6)
	stats[row][HOMERS] = 1.05 * stats[row][HITS] *
	  ((stats[row-2][HOMERS] + stats[row-1][HOMERS])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));
      else if (row < 8)
	stats[row][HOMERS] = 1.15 * stats[row][HITS] *
	  ((stats[row-2][HOMERS] + stats[row-1][HOMERS])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));
      else if (row < 10)
	stats[row][HOMERS] = .95 * stats[row][HITS] *
	  ((stats[row-2][HOMERS] + stats[row-1][HOMERS])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));
      else if (row < 17) /* True power hitters lose power more slowly */
	stats[row][HOMERS] = stats[row][HITS] *
	  (.86 + (stats[row-2][HOMERS] + stats[row-1][HOMERS]) / 1000) *
	  ((stats[row-2][HOMERS] + stats[row-1][HOMERS])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));
      else
	stats[row][HOMERS] = stats[row][HITS] *
	  (.84 + (stats[row-2][HOMERS] + stats[row-1][HOMERS]) / 1000) *
	  ((stats[row-2][HOMERS] + stats[row-1][HOMERS])
	   / (stats[row-2][HITS] + stats[row-1][HITS]));

      /* RBI is next column, but will be calculated later */
      switch(row)
	{
	case 2: case 3: case 4: case 5:
	  stats[row][WALKS] = 1.07 * stats[row][AB] *
	    ((stats[row-2][WALKS] + stats[row-1][WALKS])
	     / (stats[row-2][AB] + stats[row-1][AB]));
	  break;
	case 8:
	  stats[row][WALKS] = stats[row][AB] *
	    ((stats[row-2][WALKS] + stats[row-1][WALKS])
	     / (stats[row-2][AB] + stats[row-1][AB] - 100));
	  if (stats[row][WALKS] >= 0)
	    break;
	  /* If negative denominator above, fall through and
	     use another formula to increase walks */
	case 7: case 13:
	  stats[row][WALKS] = stats[row][AB] *
	    ((stats[row-2][WALKS] + stats[row-1][WALKS] + 20)
	     / (stats[row-2][AB] + stats[row-1][AB] + 100));
	  break;
	case 14:
	  stats[row][WALKS] = stats[row][AB] *
	    ((stats[row-2][WALKS] + stats[row-1][WALKS])
	     / (stats[row-2][AB] + stats[row-1][AB] + 100));
	  break;
	default:
	  stats[row][WALKS] = stats[row][AB] *
	    ((stats[row-2][WALKS] + stats[row-1][WALKS] + 10)
	     / (stats[row-2][AB] + stats[row-1][AB] + 100));
	  break;
	}

      stats[row][AVERAGE] = round(stats[row][HITS]) /
	(EPSILON + round(stats[row][AB]));
      calcauxstats(row);
      /* Now we can calculate runs and RBI */
      stats[row][RUNS] = stats[row-1][RUNS] *
	(stats[row][RC]/stats[row-1][RC]);
      stats[row][RBI] = stats[row][HOMERS] + .235 * stats[row][TOTALBASES];
      for (col = 0; col < NUMSTATS; col++)
	stats[TOTALS][col] += round(stats[row][col]);
    }
  stats[TOTALS][AVERAGE] = stats[TOTALS][HITS] / stats[TOTALS][AB];

  /* And now print out the career, including past years if we have data */
  (void)printf("  YEAR     G    AB     R     H    2B    3B    HR   RBI    BB   AVG\n");
  for (row = 0; row < TOTALS; row++)
    if (stats[row][GAMES] != 0)
      (void)printf("%6d %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.3f \n",
	     year-cumage+MINAGE+row, stats[row][0], stats[row][1],
	     stats[row][2], stats[row][3], stats[row][4], stats[row][5],
	     stats[row][6], stats[row][7], stats[row][8], stats[row][9]);
  (void)printf("TOTALS %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.0f %5.3f \n",
	 stats[TOTALS][0], stats[TOTALS][1], stats[TOTALS][2], 
	 stats[TOTALS][3], stats[TOTALS][4], stats[TOTALS][5], 
	 stats[TOTALS][6], stats[TOTALS][7], stats[TOTALS][8], 
	 stats[TOTALS][9]);
  exit(0);
}

