# include <stdio.h>
# include <stdlib.h>
# include <math.h>
# include <stddef.h>


/******** prototipi **********/
double sigmoid(double);

/********* Variabili globali  ************/

#define INPUT_NEURONS 4
#define HIDDEN_NEURONS 3
#define OUTPUT_NEURONS 4
/* Input to Hidden Weights (with Biases) */
double wih[INPUT_NEURONS+1][HIDDEN_NEURONS];
/* Hidden to Output Weights (with Biases) */
double who[HIDDEN_NEURONS+1][OUTPUT_NEURONS];

/* Activations */

double inputs[INPUT_NEURONS];
double hidden[HIDDEN_NEURONS];
double target[OUTPUT_NEURONS];
double actual[OUTPUT_NEURONS];
/* Unit Errors */
double erro[OUTPUT_NEURONS];
double errh[HIDDEN_NEURONS];


/*********** macro ***************/
#define LEARN_RATE 0.2 /* Rho */
#define RAND_WEIGHT ( ((float)rand() / (float)RAND_MAX) -0.5)
#define getSRand() ((float)rand() / (float)RAND_MAX)
#define getRand(x) (int)((x) * getSRand())
#define sqr(x) ((x) * (x))

/*** definizioni  ***/

typedef struct{
	double	health;
	double	knife;
	double	gun;
	double	enemy;
	double	out[OUTPUT_NEURONS];
} ELEMENT;


# define MAX_SAMPLES	18
ELEMENT samples[MAX_SAMPLES]={
{2.0,0.0,0.0,0.0,{0.0,0.0,1.0,0.0}},
{2.0,0.0,0.0,1.0,{0.0,0.0,1.0,0.0}},
{2.0,0.0,1.0,1.0,{1.0,0.0,0.0,0.0}},
{2.0,0.0,1.0,2.0,{1.0,0.0,0.0,1.0}},
{2.0,1.0,0.0,2.0,{0.0,0.0,0.0,1.0}},
{2.0,1.0,0.0,1.0,{1.0,0.0,0.0,0.0}},


{1.0,0.0,0.0,0.0,{0.0,0.0,1.0,0.0}},
{1.0,0.0,0.0,1.0,{0.0,0.0,0.0,1.0}},
{1.0,0.0,1.0,1.0,{1.0,0.0,0.0,0.0}},
{1.0,0.0,1.0,2.0,{0.0,0.0,0.0,1.0}},
{1.0,1.0,0.0,2.0,{0.0,0.0,0.0,1.0}},
{1.0,1.0,0.0,1.0,{0.0,0.0,0.0,1.0}},

{0.0,0.0,0.0,0.0,{0.0,0.0,1.0,0.0}},
{0.0,0.0,0.0,1.0,{0.0,0.0,0.0,1.0}},
{0.0,0.0,1.0,1.0,{0.0,0.0,0.0,1.0}},
{0.0,0.0,1.0,2.0,{0.0,1.0,0.0,0.0}},
{0.0,1.0,0.0,2.0,{0.0,1.0,0.0,0.0}},
{0.0,1.0,0.0,1.0,{0.0,0.0,0.0,1.0}}
};

/******* prototipi ********/





/******** Funzione Feed-Forward:calcolo dell'output con pesi dati ******/
void feedForward(void )
{
	int inp, hid, out;
	double sum;
	/* Calculate input to hidden layer */
	for (hid = 0 ; hid < HIDDEN_NEURONS ; hid++) {
		sum = 0.0;
		for (inp = 0 ; inp < INPUT_NEURONS ; inp++) {
			sum += inputs[inp] * wih[inp][hid];
		}
	/* Add in Bias */
		sum += wih[INPUT_NEURONS][hid];
		hidden[hid]= sigmoid(sum);
	}
	/* Calculate the hidden to output layer */
	for (out=0;out<OUTPUT_NEURONS;out++){
		sum=0.0;
		for (hid=0;hid<HIDDEN_NEURONS;hid++){
			sum+=hidden[hid]*who[hid][out];
		}
		/* add in bias */
		sum+=who[HIDDEN_NEURONS][out];
		actual[out]=sigmoid(sum);
	}
}

/**** assegnazione del valore iniziale ai pesi ****/
void assignRandomWeights( void )
{
	int hid, inp, out;
	for (inp = 0 ; inp < INPUT_NEURONS+1 ; inp++) {
		for (hid = 0 ; hid < HIDDEN_NEURONS ; hid++) {
			wih[inp][hid] = RAND_WEIGHT;
		}
	}
	for (hid = 0 ; hid < HIDDEN_NEURONS+1 ; hid++) {
		for (out = 0 ; out < OUTPUT_NEURONS ; out++) {
			who[hid][out] = RAND_WEIGHT;
		}
	}
}

/*** sigmoide ****/
double sigmoid( double val )
{
	return (1.0 / (1.0 + exp(-val)));
}

/*** derivata della sigmoide ***/
double sigmoidDerivative( double val )
{
	return ( val * (1.0 - val) );
}

/**** backpropagation ***/
void backPropagate(void )
{
	int inp, hid, out;
	/* Calculate the output layer error */
	for (out=0;out<OUTPUT_NEURONS;out++) {
		erro[out]=(target[out]-actual[out])*sigmoidDerivative(actual[out]);
	}
	/* Calculate the hidden layer error */
	for (hid=0;hid<HIDDEN_NEURONS;hid++){
		errh[hid]=0.0;
		for(out=0;out<OUTPUT_NEURONS;out++){
			errh[hid]+=erro[out]*who[hid][out];
		}
		errh[hid]*=sigmoidDerivative(hidden[hid]);
	}
		/* Update the weights for the output layer */
	for (out=0;out<OUTPUT_NEURONS; out++){
		for (hid=0;hid<HIDDEN_NEURONS;hid++){
			who[hid][out]+=(LEARN_RATE*erro[out]*hidden[hid]);
		}
	}

		/* Update bias */
		who[HIDDEN_NEURONS][out]+=(LEARN_RATE*erro[out]);

	/* Update the layer for the hidden layer */
	for (hid=0;hid<HIDDEN_NEURONS;hid++){
			for (inp=0;inp<INPUT_NEURONS;inp++){
				wih[inp][hid]+=(LEARN_RATE*errh[hid]*inputs[inp]);
			}
			/* Update bias */
			wih[INPUT_NEURONS][hid]+=(LEARN_RATE*errh[hid]);
		}

}

/************ main ****************/
int main()
{
	double err;
	int i, sample=0,iterations=0;
	FILE  *out;

	out=fopen("stats.txt","w");
	/*seed the random number generator */
	srand(time(NULL));
	assignRandomWeights();
	/** train the network */
	while(1){
		if(++sample==MAX_SAMPLES) sample=0;
		inputs[0]=samples[sample].health;
		inputs[1]=samples[sample].knife;
		inputs[2] = samples[sample].gun;
		inputs[3] = samples[sample].enemy;
		target[0] = samples[sample].out[0];
		target[1] = samples[sample].out[1];
		target[2] = samples[sample].out[2];
		target[3] = samples[sample].out[3];
		feedForward();
		err = 0.0;
		for (i = 0 ; i < OUTPUT_NEURONS ; i++) {
			err += sqr( (samples[sample].out[i] - actual[i]) );
		}
		err = 0.5 * err;
		fprintf(out, "%g\n", err);
		printf("mse = %g\n", err);
		if (iterations++ > 100000) break;
		backPropagate();
	}
	fclose(out);
	return 0;
}

