/*

simulator written in C (requires GNU Scientific Library)
March 2006
Dennis Lee
http://www.falsecognate.org

This code is the beginnings of a possible baseball lineup simulator.

Currently this code will not account for steals, sac fly, sac bunt, or
speed.

*/


/* includes */
#include <stdio.h>
#include <string.h>
// #include <stdlib.h> // uncomment this line if you want to switch back to using rand()
#include <stdint.h>
#include <math.h>
#include <gsl/gsl_rng.h>

/* describe a player */
typedef struct {
	char name[64];
	int plate_app;
	int walks;
	int hits;
	int doubles;
	int triples;
	int hr;
	int strike_outs;
	int gidp;
	int hbp;
	} player;

/* global variables */
player init_lineup[9]; // lineup array
uint_fast32_t outcome_table[9][9]; // outcome table based off of initial lineup.
uint_fast32_t permute_table[9][9];
int i;
int j;
char line[100];
int diamond_status[5]; // game runs, 1st base, 2nd base, 3rd base, inning outs.
gsl_rng *r;

// reordering the algorithm for better speed. Outs, SOs, Hits, BBs, 2Bs, HRs, 3Bs, GDP, HBP
int at_bat(int batter) {
	int instance = gsl_rng_get(r);
	if (instance <= permute_table[batter][0]) // assign an out
		return 1;
	else if (instance <= permute_table[batter][1]) // assign a strikeout
		return 2;
	else if (instance <= permute_table[batter][2]) // assign a single
		return 3;
	else if (instance <= permute_table[batter][3]) // assign a walk
		return 4;
	else if (instance <= permute_table[batter][4]) // assign a double
		return 5;
	else if (instance <= permute_table[batter][5]) // assign a homer
		return 6;
	else if (instance <= permute_table[batter][6]) // assign a triple
		return 7;
	else if (instance <= permute_table[batter][7]) // assign possible GDP
		return 1;
	else if (instance <= permute_table[batter][8]) // assign HBP
		return 4;
	else // catch all. Shouldn't happen, but because of rounding errors it probably will. Assign an out.
		return 1;
	}

int scoring (int result) { // returns number of outs to be scored.
	switch (result) {
		case 1: // add an out. Behaves the same as a strikeout, currently. Could add additional hooks - rewrite GIDP here?. sac fly/bunt?
			if (diamond_status[4] == 2) { // Are there two outs already? If so, end the inning.
				diamond_status[1] = 0; // clear 1st
				diamond_status[2] = 0; // clear 2nd
				diamond_status[3] = 0; // clear 3rd
				diamond_status[4] = 0; // reset inning outs
				return 1;
				break;
				}
			else diamond_status[4] = diamond_status[4] + 1;
			return 1;
			break;
		case 2: // strikeout. Behaves as case 1 currently.
			if (diamond_status[4] == 2) { // Are there two outs already? If so, end the inning.
				diamond_status[1] = 0; // clear 1st
				diamond_status[2] = 0; // clear 2nd
				diamond_status[3] = 0; // clear 3rd
				diamond_status[4] = 0; // reset inning outs
				return 1;
				break;
				}
			else diamond_status[4] = diamond_status[4] + 1;
			return 1;
			break;
		case 3: // Single. Advance runners. Can take 1 extra base if two out situation.
			if (diamond_status[4] == 2) {
				diamond_status[0] == diamond_status[0] + diamond_status[2] + diamond_status[3];
				diamond_status[2] = 0;
				diamond_status[3] = diamond_status[1];
				diamond_status[1] = 1;
				return 0;
				break;
				}
			else {
				diamond_status[0] = diamond_status[0] + diamond_status[3];
				diamond_status[3] = diamond_status[2];
				diamond_status[2] = diamond_status[1];
				diamond_status[1] = 1;
				return 0;
				break;
				}
		case 4: // Walk or Hit By Pitch. Advance runners if necessary.
			if (diamond_status[1] == 1) {
				if (diamond_status[2] == 1) {
					if (diamond_status[3] == 1) {
						diamond_status[0] = diamond_status[0] + 1;
						return 0;
						break;
						}
					else diamond_status[3] = 1;
					return 0;
					break;
					}
				else diamond_status[2] = 1;
				return 0;
				break;
				}
			else diamond_status[1] = 1;
			return 0;
			break;
		case 5: // Double. Advance runners two bases. Take 1 extra base if two out situation.
			if (diamond_status[4] == 2) {
				diamond_status[0] = diamond_status[0] + diamond_status[1] + diamond_status[2] + diamond_status[3];
				diamond_status[3] = 0;
				diamond_status[1] = 0;
				diamond_status[2] = 1;
				return 0;
				break;
				}
			else {
				diamond_status[0] = diamond_status[0] + diamond_status[2] + diamond_status[3]; // add runners on 2nd and 3rd to score
				diamond_status[3] = diamond_status[1];
				diamond_status[2] = 1;
				diamond_status[1] = 0;
				return 0;
				break;
				}
		case 6: // Homer. Clear the bases!
			diamond_status[0] = diamond_status[0] + diamond_status[1] + diamond_status[2] + diamond_status[3] + 1; // add runners and batter to score
			diamond_status[1] = 0; // clear 1st
			diamond_status[2] = 0; // clear 2nd
			diamond_status[3] = 0; // clear 3rd
			return 0;
			break;
		case 7: // Triple. Advance the runners three bases.
			diamond_status[0] = diamond_status[0] + diamond_status[1] + diamond_status[2] + diamond_status[3]; // add runners to score
			diamond_status[1] = 0; // clear 1st
			diamond_status[2] = 0; // clear 2nd
			diamond_status[3] = 1; // batter goes to 3rd.
			return 0;
			break;
		case 8: // Possible GIDP. Add an out; check to see if should be a double play. Check # of outs, clear bases if necessary.
			if (diamond_status[4] == 2) { // Are there two outs already? If so, end the inning.
				diamond_status[1] = 0; // clear 1st
				diamond_status[2] = 0; // clear 2nd
				diamond_status[3] = 0; // clear 3rd
				diamond_status[4] = 0; // reset inning outs
				return 1;
				break;
				}
			else if (diamond_status[1] == 1) { // Is there a man on first? Sets up the force situation.
				if (diamond_status[4] == 1) { // Is there one out? If so, end the inning.
					diamond_status[1] = 0; // clear 1st
					diamond_status[2] = 0; // clear 2nd
					diamond_status[3] = 0; // clear 3rd
					diamond_status[4] = 0; // reset inning outs
					return 2;
					break;
					}
				else if (diamond_status[2] == 1) { // No outs; is there a man on second? Sets up force at all positions.
					if (diamond_status[3] == 1) { // Bases loaded? Probably score a run, advance a runner to 3rd. This could be debated.
						diamond_status[0] = diamond_status[0] + 1;
						diamond_status[2] = 0;
						diamond_status[1] = 0;
						diamond_status[4] = diamond_status[4] + 2;
						return 2;
						break;
						}
					diamond_status[3] = 1; // 1st and 2nd. Advance a runner to 3rd, mark the DP at 2nd and 1st.
					diamond_status[2] = 0;
					diamond_status[1] = 0;
					diamond_status[4] = diamond_status[4] + 2;
					return 2;
					break;
					}
				else if (diamond_status[3] == 1) { // 1st and 3rd. Score the runner, clear the bags, mark the DP.
					diamond_status[3] = 0;
					diamond_status[0] = diamond_status[0] + 1;
					diamond_status[1] = 0;
					diamond_status[4] = diamond_status[4] + 2;
					return 2;
					break;
					}
				}
			diamond_status[4] = diamond_status[4] + 1;
			return 1;
			break;
		default:
			printf("Something is wrong! The scoring function was not fed a proper input, or you programmed it wrong!");
			return 0;
			break;
		}
	}

double st_dev(int run_results[]) {
	int k;
	int pop = 0;
	int total = 0;
	double mean;
	for (k = 0; k < 100; k++) {
		pop = pop + run_results[k];
		total = total + (k * run_results[k]);
		}
	mean = (double) total / (double) pop;
	double variance = 0;
	for (k = 0; k < 100; k++) {
		variance = variance + (run_results[k] * (k - mean) * (k - mean));
		}
	return (double) (sqrt(variance / pop));
	}

int main () {
	const gsl_rng_type *T;
	T = gsl_rng_default; // Mersenne Twister 19937
	r = gsl_rng_alloc(T);
	int s = time(0);
	gsl_rng_set(r, s);
	uint_fast32_t rand_max = gsl_rng_max(r);
	printf("seed = %lu\nmax value = %lu\ngenerator type: %s\n", s, rand_max, gsl_rng_name(r));
	printf("int = %lu\nlong int = %lu\n", sizeof(int), sizeof(long int));
	
	/* Obtain player info. This section should also check for sane numbers. */
	char input_filename[100]; // = "astros.txt";
	char output_filename[100];
	FILE *in_file;
	FILE *out_file;
	char *work_line_ptr;
	char input_line[100];
	
	printf("Enter the name of the tab-delimited .txt file with the player stats: ");
	fgets(input_filename, sizeof(input_filename), stdin);
	input_filename[strlen(input_filename)-1] = '\0';

	strcpy(output_filename, input_filename);
	output_filename[strlen(input_filename)-4] = '\0';
	strcat(output_filename, "-out.txt");
	
	in_file = fopen(input_filename, "r");
	out_file = fopen(output_filename, "w");
	
	/*------------------------
	Player statistics should be placed in a tab-delimited file with in the following
	manner, one player per line:
	
	name, PAs, walks, hits, doubles, triples, homers, strike outs, GIDP, HBP 
	------------------------*/
	for (i = 0; i < 9; i++) {
		work_line_ptr = fgets(input_line, sizeof(input_line), in_file);
		sscanf(input_line, "%s\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", &init_lineup[i].name, &init_lineup[i].plate_app, &init_lineup[i].walks, &init_lineup[i].hits, &init_lineup[i].doubles, &init_lineup[i].triples, &init_lineup[i].hr, &init_lineup[i].strike_outs, &init_lineup[i].gidp, &init_lineup[i].hbp);
		
		// check for sane statistics
		while (init_lineup[i].plate_app < (init_lineup[i].walks + init_lineup[i].hits + init_lineup[i].doubles + init_lineup[i].triples + init_lineup[i].hr + init_lineup[i].strike_outs + init_lineup[i].gidp + init_lineup[i].hbp)) {
			printf("Plate appearances must be greater than or equal to all other stats combined!\n");
			return 23;
			}
		}

	// reordering the algorithm for better speed. Outs, SOs, Hits, BBs, 2Bs, HRs, 3Bs, GDP, HBP
	for (j = 0; j < 9; j++) {
		outcome_table[j][0] = rand_max - (uint_fast32_t) (((long double) (init_lineup[j].walks + init_lineup[j].hits + init_lineup[j].strike_outs + init_lineup[j].gidp + init_lineup[j].hbp) / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][1] = outcome_table[j][0] + (((long double) init_lineup[j].strike_outs / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][2] = outcome_table[j][1] + (uint_fast32_t) (((long double) (init_lineup[j].hits - (init_lineup[j].doubles + init_lineup[j].triples + init_lineup[j].hr)) / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][3] = outcome_table[j][2] + (uint_fast32_t) (((long double) init_lineup[j].walks / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][4] = outcome_table[j][3] + (uint_fast32_t) (((long double) init_lineup[j].doubles / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][5] = outcome_table[j][4] + (uint_fast32_t) (((long double) init_lineup[j].hr / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][6] = outcome_table[j][5] + (uint_fast32_t) (((long double) init_lineup[j].triples / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][7] = outcome_table[j][6] + (uint_fast32_t) (((long double) init_lineup[j].gidp / (long double) init_lineup[j].plate_app) * rand_max);
		outcome_table[j][8] = outcome_table[j][7] + (uint_fast32_t) (((long double) init_lineup[j].hbp / (long double) init_lineup[j].plate_app) * rand_max);
		}

	for (i = 0; i < 9; i++) {
		for (j = 0; j < 9; j++) {
			printf("%lu ", outcome_table[i][j]);
			}
		printf("\n");
		}

	int pos_1 = 0;
	int pos_2 = 0;
	int pos_3 = 0;
	int pos_4 = 0;
	int pos_5 = 0;
	int pos_6 = 0;
	int pos_7 = 0;
	int pos_8 = 0;
	int pos_9 = 0;

	int total_runs;
	int games;
	double mean_runs;
	int run_results[100];
	double sigma;
	double averages[100];
	double mean_mean_runs;
	double total_mean_runs;
	double variance_mean;
	double dev_mean;
	double season_runs;

	for (pos_1 = 0; pos_1 < 9; pos_1++) {
		for (i = 0; i < 9; i++) {
			permute_table[0][i] = outcome_table[pos_1][i];
			}
		for (pos_2 = 0; pos_2 < 9; pos_2++) {
			if (pos_2 == pos_1)
				continue;
			for (i = 0; i < 9; i++) {
				permute_table[1][i] = outcome_table[pos_2][i];
				}
			for (pos_3 = 0; pos_3 < 9; pos_3++) {
				if (pos_3 == pos_1 || pos_3 == pos_2)
					continue;
				for (i = 0; i < 9; i++) {
					permute_table[2][i] = outcome_table[pos_3][i];
					}
				for (pos_4 = 0; pos_4 < 9; pos_4++) {
					if (pos_4 == pos_3 || pos_4 == pos_2 || pos_4 == pos_1)
						continue;
					for (i = 0; i < 9; i++) {
						permute_table[3][i] = outcome_table[pos_4][i];
						}
					for (pos_5 = 0; pos_5 < 9; pos_5++) {
						if (pos_5 == pos_4 || pos_5 == pos_3 || pos_5 == pos_2 || pos_5 == pos_1)
							continue;
						for (i = 0; i < 9; i++) {
							permute_table[4][i] = outcome_table[pos_5][i];
							}
						for (pos_6 = 0; pos_6 < 9; pos_6++) {
							if (pos_6 == pos_5 || pos_6 == pos_4 || pos_6 == pos_3 || pos_6 == pos_2 || pos_6 == pos_1)
								continue;
							for (i = 0; i < 9; i++) {
								permute_table[5][i] = outcome_table[pos_6][i];
								}
							for (pos_7 = 0; pos_7 < 9; pos_7++) {
								if (pos_7 == pos_6 || pos_7 == pos_5 || pos_7 == pos_4 || pos_7 == pos_3 || pos_7 == pos_2 || pos_7 == pos_1)
									continue;
								for (i = 0; i < 9; i++) {
									permute_table[6][i] = outcome_table[pos_7][i];
									}
								for (pos_8 = 0; pos_8 < 9; pos_8++) {
									if (pos_8 == pos_7 || pos_8 == pos_6 || pos_8 == pos_5 || pos_8 == pos_4 || pos_8 == pos_3 || pos_8 == pos_2 || pos_8 == pos_1)
										continue;
									for (i = 0; i < 9; i++) {
										permute_table[7][i] = outcome_table[pos_8][i];
										}
									for (pos_9 = 0; pos_9 < 9; pos_9++) {
										if (pos_9 == pos_8 || pos_9 == pos_7 || pos_9 == pos_6 || pos_9 == pos_5 || pos_9 == pos_4 || pos_9 == pos_3 || pos_9 == pos_2 || pos_9 == pos_1)
											continue;
										for (i = 0; i < 9; i++) {
											permute_table[8][i] = outcome_table[pos_9][i];
											}

	/* Initialize the run for this particular lineup. */
	total_runs = 0;
	games = 0;
	mean_runs = 0.0;
	sigma = 0.0;
	mean_mean_runs = 0.0;
	total_mean_runs = 0.0;
	variance_mean = 0.0;
	dev_mean = 0.0;
	season_runs = 0.0;
	
	for (i = 0; i < 100; i++) {
		run_results[i] = 0;
		averages[i] = 0.0;
		}
	j = 0;

	while ((games < 64000) || ((3 * dev_mean) > 0.006)) {
		int total_outs = 0; // initialize total outs for the game.
		int batter = 0; // set first batter
		int add_outs;
		int result;
		for (i = 0; i < 5; i++) { // this loop initializes the diamond
			diamond_status[i] = 0;
			}
		
		while (total_outs < 27) { // this loop is the actual game
			result = (at_bat(batter)); // determine the outcome of the plate appearance
			add_outs = scoring(result); // how does it affect the diamond - return number of outs
			total_outs = total_outs + add_outs; // keep track of depth in game
			if (batter < 8) // next batter
				batter++;
			else batter = 0;
			}
		
		games++;
		total_runs = total_runs + diamond_status[0];
		run_results[diamond_status[0]] = run_results[diamond_status[0]] + 1;
		
		if ( games % 1000 == 1) { // every 1000 games, start the statistics run to see if we can quit for this lineup
			int k = (j % 100);
			mean_runs = (double) total_runs / (double) games;
			averages[k] = mean_runs;
			total_mean_runs = 0;
			for (i = 0; i < 100; i++) {
				total_mean_runs = total_mean_runs + averages[i];
				}
			mean_mean_runs = total_mean_runs / 100;
			variance_mean = 0;
			for (i = 0; i < 100; i++) {
				variance_mean = variance_mean + ((averages[i] - mean_mean_runs) * (averages[i] - mean_mean_runs));
				}
			dev_mean = sqrt(variance_mean / 100);
			j++;
			// printf("\n%d runs, %d games\n%f average, %f st. dev, %f st. dev mean\n", total_runs, games, mean_runs, sigma, dev_mean);
			}
		}
	
	season_runs = mean_runs * 162;	
	sigma = st_dev(run_results);
	fprintf(out_file, "%.3f\t%.3f\t%.3f\t%.3f\t%d\t%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", season_runs, mean_runs, sigma, dev_mean, total_runs, games, init_lineup[pos_1].name, init_lineup[pos_2].name, init_lineup[pos_3].name, init_lineup[pos_4].name, init_lineup[pos_5].name, init_lineup[pos_6].name, init_lineup[pos_7].name, init_lineup[pos_8].name, init_lineup[pos_9].name);

										}
									}
								}
							}
						}
					}
				}
			}
		}

	return 0;
	}
