/*
* Neural Tanks is a game created by Eddie O'Hagan that leverages Neural Networking
* to use for AI. This was created in Unreal Engine 4 which is developed by Epic Games.
* (Copyright Epic Games, Inc. All Rights Reserved.)
*
* Smart Tanks was originally developed by Mat Buckland in 2002 as an example
* on how Neural Networking can be used to train a set of minesweepers
* to collect mines. I (Eddie O'Hagan) updated this project in 2024 to use more
* Object Oriented Programming and modern practices. To see the original tutorial;
* visit <see href="http://www.ai-junkie.com/ann/evolved/nnt1.html">Neural Networking Tutorial</see>
* and <see href="http://www.ai-junkie.com/ga/intro/gat1.html">Genetic Algorithm Tutorial</see>
*/
#include "NeuralNetwork.h"

/// <summary>
/// Default Constructor. Initializes the Neural Network by creating the input, hidden layer(s), 
/// and output layers. 
/// </summary>
UNeuralNetwork::UNeuralNetwork()
{
	UNeuronLayer* currLayer;

	myNumberOfInputs = 4;
	myNumberOfOutputs = 2;
	myNumberOfHiddenLayers = 1;
	myNumberOfNeuronsPerHiddenLayer = 6;
	myTotalNumberOfNeuronInputs = 0U;

	if (myNumberOfHiddenLayers > 0U)
	{
		//Create first hidden layer
		currLayer = NewObject<UNeuronLayer>();
		currLayer->Initialize(myNumberOfNeuronsPerHiddenLayer, myNumberOfInputs);
		myNeuronLayers.Add(currLayer);
		 
		for (int count = 1; count <= myNumberOfHiddenLayers; count++)
		{
			currLayer = NewObject<UNeuronLayer>();
			currLayer->Initialize(myNumberOfNeuronsPerHiddenLayer, myNumberOfNeuronsPerHiddenLayer);
			myNeuronLayers.Add(currLayer);
		}

		//The output layer needs the same number of inputs as the last hidden layer's outputs.
		currLayer = NewObject<UNeuronLayer>();
		currLayer->Initialize(myNumberOfOutputs, myNumberOfNeuronsPerHiddenLayer);
		myNeuronLayers.Add(currLayer);
	}
	else
	{
		currLayer = NewObject<UNeuronLayer>();
		currLayer->Initialize(myNumberOfOutputs, myNumberOfInputs);
		myNeuronLayers.Add(currLayer);
	}

	for (int layerIndex = 0U; layerIndex < GetNumberOfLayers(); layerIndex++)
	{
		myTotalNumberOfNeuronInputs += myNeuronLayers[layerIndex]->GetTotalNumberOfNeuronInputs();
	}
}

/// <summary>
/// This is the "Meat and Potatoes" of the Neural Network. This takes the provided input values
/// (in the example of smart tanks: the x,y look at vector of the Tank and the x,y position
/// of the closest TankAmmunition, so 4 inputs in total).
/// </summary>
/// <param name="inputValues">The collection of inputs for the Neural Network to process.</param>
/// <returns>The calculated outputs from the Neural Network.</returns>
TArray<float> UNeuralNetwork::CalculateOutput(TArray<float>& inputValues)
{
	for (int layerIndex = 0U; layerIndex < myNumberOfHiddenLayers + 1; layerIndex++)
	{
		if (layerIndex == 0U)
		{
			//If it's the first time, we are passing in the initial input values to the input layer.
			myNeuronLayers[layerIndex]->SetInputs(inputValues);
		}
		else
		{
			//For every other time, we are dealing with the hidden layer(s) (or the output layer if it's the last layer).
			myNeuronLayers[layerIndex]->SetInputs(myOutputs);
		}

		myOutputs = myNeuronLayers[layerIndex]->CalculateOutputs();
	}

	return myOutputs;
}

/// <summary>
/// Returns the number of inputs values this Neural Network reads in.
/// </summary>
/// <returns>The number of inputs values this Neural Network reads in.</returns>
int UNeuralNetwork::GetNumberOfInputs() const
{
	return myNumberOfInputs;
}

/// <summary>
/// Returns the number of output values this Neural Network generates. 
/// </summary>
/// <returns>The number of output values this Neural Network generates.</returns>
int UNeuralNetwork::GetNumberOfOutputs() const
{
	return myNumberOfOutputs;
}

/// <summary>
/// Returns the total number of layers in the Neural Network.
/// </summary>
/// <returns>The total number of layers in the neural network.</returns>
int UNeuralNetwork::GetNumberOfLayers() const
{
	return myNeuronLayers.Num();
}

/// <summary>
/// Returns the total number of Neuron inputs in the entire Neural Network.
/// </summary>
/// <returns>The total number of Neuron inputs in the entire Neural Network</returns>
int UNeuralNetwork::GetTotalNumberOfNeuronInputs() const
{
	return myTotalNumberOfNeuronInputs;
}

/// <summary>
/// Assigns the provided weights to the Neuron Layers in the Neural Network.
/// </summary>
/// <param name="weights">The weights to assign to the neural network.</param>
void UNeuralNetwork::SetWeights(const TArray<float>& weights)
{
	int weightIndex = 0;

	//for each layer
	for (int layerIndex = 0; layerIndex < myNumberOfHiddenLayers + 1; layerIndex++)
	{
		myNeuronLayers[layerIndex]->SetWeights(weightIndex, weights);
	}
}
