/* game.cpp: Class that implements the main game level and menu structure

   Copyright (C) 2008  Stuart William Coulson

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include <sps2lib.h>
#include <sps2tags.h>
#include <sps2util.h>
#include <sps2regstructs.h>
#include <signal.h>
#include <iostream>
#include <list>
#include <math.h>
#include "PS2Defines.h"
#include "sps2wrap.h"
#include "dma.h"
#include "pad.h"
#include "vumanager.h"
#include "pipeline.h"
#include "game.h"
#include "timer.h"
#include "maze.h"
#include "beast.h"

#define dwarfScale 0.8					// Values used to scale the meshes to the correct size
#define ladderScale 2.0
#define chestScale 0.58
#define beastScale 0.35

extern "C" uint64 VU_vu1code_start;		// External pointers to the VCL code
extern "C" uint64 VU_vu1code_end;

bool CGame::m_bRunning;

float dwarfMenuRotate = 0.0f;			// Global variable used to spin the dwarf mesh during the menus

CGame::CGame(void)
{
	m_bRunning = true;
	GameState = SPLASH;
	difficulty = 1;						// Difficulty initialised to 1 or normal
}

CGame::~CGame(void)
{
	// Nothing
}

bool CGame::Start(void)
{		
	//set up our manager and the memory
	SPS2Manager.Initialise(4096);	// 4096 * 4K Pages = 16MB Total
	VIFStaticDMA.Initialise(3056);	// 3056 * 4K Pages = 12MB Static DMA
	VIFDynamicDMA.Initialise(256);	// 256 * 4K Pages * 2 Buffers =
									// 2MB Dynamic DMA
									
	// Capture all signals
	for(int sig=0; sig<128; sig++) 
		signal(sig, CGame::SignalHandler);
	
	CVU1MicroProgram VU1MicroCode(&VU_vu1code_start, &VU_vu1code_end);
	VU1MicroCode.Upload();
	
	Pipeline.Initialise();			// Initialise the pipeline class
									
	// Initialise control pad 0
	if(!pad_init(PAD_0, PAD_INIT_LOCK | PAD_INIT_ANALOGUE | PAD_INIT_PRESSURE))
	{
		printf("Failed to initialise control pad\n");
		pad_cleanup(PAD_0);
		return false;
	}
	
	// Set up the DMA packet to clear the screen. We want to clear to black.
	SPS2Manager.InitScreenClear(0x00, 0x00, 0x00);
	
	// Load the font bitmap and data
	if(!Font.Load("font.dat", true))
	{
		printf("Can't load font.dat\n");
		return false;
	}
	if(!FontTex.LoadBitmap("font.bmp", false, true))
	{
		printf("Can't load font.bmp\n");
		return false;
	}
	// Upload Font texture to VRAM page 496
	FontTex.Upload(TEXBUF496);
	
	if(!dwarf.loadModelData("dwarf.ms3d"))//, false, true))
	{
		printf("Error loading dwarf model\n");
		return false;
	}
		
	return true;
}

void CGame::GameLoop(void)
{
	pad_update(PAD_0);
	
	// Check for immediate exit condition
	// Use this for debug purposes
	if((pad[0].buttons & PAD_START)&&(pad[0].buttons & PAD_SELECT)) 
		SetRunningStatus(false);
	
	switch (GameState)			// Work out the current game state and execute the correct code
	{
		case SPLASH:
			GameSplash();
			break;
			
		case MENU:
			GameMenu();
			break;
			
		case LEVEL1:
			GameLevel1();
			break;
			
		case WIN:
			GameWin();
			break;
		
		case LOSE:
			GameLose();
			break;
			
		case QUIT:
			GameQuit();
			break;
			
		default:
			printf("Undefined Game State\n");
			SetRunningStatus(false);
			break;
	}
}

void CGame::GameSplash(void)
{
	static int alphaValue = 127;			// Variables used to cause the flashing of the "Press Start" wording
	static bool alphaDirection = true;
	
	// Initialise Lighting
	// Three lights and Ambient light
	//							Direction vector				  Colour	
	Pipeline.SetLight1(Vector4( 1.0f, 0.2f, 0.0f, 0.0f), Vector4(128.0f,128.0f,128.0f,0.0f));
	Pipeline.SetLight2(Vector4(-1.0f,-0.2f, 0.0f, 0.0f), Vector4(128.0f,128.0f,128.0f,0.0f));
	Pipeline.SetLight3(Vector4( 0.0f, 0.0f,-1.0f, 0.0f), Vector4(128.0f,128.0f,128.0f,0.0f));
	//                            Colour
	Pipeline.SetAmbient(Vector4(40.0f,40.0f,40.0f,0.0f));
	
	Pipeline.PositionCamera(Vector4(0.0f, 30.0f, 80.0f, 1.0f), 0.0f, 0.0f);
	
	Pipeline.Update(0.0f, 0.0f, 0.0f, 0.0f, 0.0f);		//Called with all parameters set to 0 so camera is updated but not moved
	
	Matrix4x4 dwarfWorld, dwarfTranslate, dwarfRotation;		// Create matrices to hold the dwarf transformations and world matrix
	dwarfTranslate.Translation(0.0f, 0.0f, 0.0f);				// Dwarf is always positioned at the origin
	
	while ((GameState == SPLASH) && GetRunningStatus()) {		// Enter a loop 
		
		pad_update(PAD_0);
		
		dwarfMenuRotate += 0.015f;								//  Update the dwarfMenuRotate variable so dwarf rotates slightly more
		dwarfRotation.RotationY(dwarfMenuRotate);				// Create a rotation matrix for the dwarf mesh
		
		dwarfWorld = dwarfRotation * dwarfTranslate;			// Create a world matrix using the rotation and translation matrices
		
		VIFDynamicDMA.Fire();									// Start uploading the current data in dynamic memory buffer to the VIF1
		
		SPS2Manager.BeginScene();								// Start creating a new scene in the other dynamic memory buffer
		
		dwarf.SetWorldMatrix(dwarfWorld);						// Set the dwarfs new world matrix, upload to the texture buffer his textures and render him
		dwarf.UploadTextures();
		dwarf.Render();
		
		FontTex.Upload(TEXBUF496);								// As the dwarf has two textures and uses both buffers when rendering, the Font texture has to be reloaded for every scene
		FontTex.Select();
		
		Font.printfC(  0, -240, 127, 127, 127, 127, "Quest for the Dungeon Treasure");
		Font.printfC(  0, -210, 127, 127, 127, 127, "A PS2 Game by Stuart Coulson - 0700037");
		
		if (alphaDirection) alphaValue -= 1; else alphaValue += 1;		// Check whether Press Start text is either getting more transparent or more opaque and update the alpha value accordingly
		if (alphaValue == 0) alphaDirection = false;					// Swap the direction of the transparency
		else if (alphaValue == 127) alphaDirection = true;
		
		Font.printfC(  0, 200, 127, 0, 0, alphaValue, "Press Start");
		
		SPS2Manager.EndScene();
		
		if(pad[0].pressed & PAD_START) GameState = MENU;				// Pressing start will move to the Menu state
	}
}

void CGame::GameMenu(void)
{
	static int menuItem = difficulty;			// Variable used to create the movement in the menu 
	
	
	// Dim the lights for the menu
	//							Direction vector				  Colour	
	Pipeline.SetLight1(Vector4( 1.0f, 0.2f, 0.0f, 0.0f), Vector4(60.0f,60.0f,60.0f,0.0f));
	Pipeline.SetLight2(Vector4(-1.0f,-0.2f, 0.0f, 0.0f), Vector4(60.0f,60.0f,60.0f,0.0f));
	Pipeline.SetLight3(Vector4( 0.0f, 0.0f,-1.0f, 0.0f), Vector4(60.0f,60.0f,60.0f,0.0f));
	//                            Colour
	Pipeline.SetAmbient(Vector4(20.0f,20.0f,20.0f,0.0f));
	
	Pipeline.PositionCamera(Vector4(0.0f, 30.0f, 80.0f, 1.0f), 0.0f, 0.0f);
	
	Matrix4x4 dwarfWorld, dwarfTranslate, dwarfRotation;
	dwarfTranslate.Translation(0, 0.0f, 0.0f);
	
	while ((GameState == MENU) && GetRunningStatus()) {
		
		pad_update(PAD_0);
		
		dwarfMenuRotate += 0.015f;
		dwarfRotation.RotationY(dwarfMenuRotate);
		
		dwarfWorld = dwarfRotation * dwarfTranslate;
		
		VIFDynamicDMA.Fire();
		
		SPS2Manager.BeginScene();
		
		dwarf.SetWorldMatrix(dwarfWorld);
		dwarf.UploadTextures();
		dwarf.Render();
		
		FontTex.Upload(TEXBUF496);
		FontTex.Select();
		
		Font.printfC(  0, -240, 127, 127, 127, 127, "Quest for the Dungeon Treasure");
		
		Font.printfC(  0, -100, 127, 127, 127, 127, "Please select difficulty:");
		
		// Change the colour of menu items depending on which difficulty is selected and which menu item is selected
		
		if (difficulty == 0) {
			if (menuItem == 0) Font.printfC(  0, -60, 127, 0, 0, 127, "Easy"); else Font.printfC(  0, -60, 127, 127, 0, 127, "Easy");
		}
		else {
			if (menuItem == 0) Font.printfC(  0, -60, 127, 0, 0, 127, "Easy"); else Font.printfC(  0, -60, 127, 127, 0, 60, "Easy");
		}
		
		if (difficulty == 1) {
			if (menuItem == 1) Font.printfC(  0, -20, 127, 0, 0, 127, "Medium"); else Font.printfC(  0, -20, 127, 127, 0, 127, "Medium");
		}
		else {
			if (menuItem == 1) Font.printfC(  0, -20, 127, 0, 0, 127, "Medium"); else Font.printfC(  0, -20, 127, 127, 0, 60, "Medium");
		}
		
		if (difficulty == 2) {
			if (menuItem == 2) Font.printfC(  0, 20, 127, 0, 0, 127, "Hard"); else Font.printfC(  0, 20, 127, 127, 0, 127, "Hard");
		}
		else {
			if (menuItem == 2) Font.printfC(  0, 20, 127, 0, 0, 127, "Hard"); else Font.printfC(  0, 20, 127, 127, 0, 60, "Hard");
		}
		if (menuItem == 3) Font.printfC(  0, 90, 127, 0, 0, 127, "Start Dungeon!"); else Font.printfC(  0, 90, 127, 127, 127, 127, "Start Dungeon!");
		
		Font.printfC(  -37, 160, 72, 88, 108, 127, "X");
		Font.printfC(  5, 160, 127, 127, 127, 127, ": Select");
		Font.printfC(  220, 230, 127, 127, 127, 70, "Stuart Coulson - 0700037");
		
		SPS2Manager.EndScene();	
		
		if(pad[0].pressed & PAD_DOWN) {					// Use up and down buttons to move through items in the menu and Cross to select. If the Start Dungeon is selected then move to the game level state
			if (menuItem < 3) menuItem++;
		}
		if(pad[0].pressed & PAD_UP) {
			if (menuItem > 0) menuItem--;
		}
		if(pad[0].pressed & PAD_CROSS) {
			if (menuItem == 3) GameState = LEVEL1;
			else difficulty = menuItem;
		}				
	}
}

void CGame::GameLevel1(void)
{
	// Variables to control the various behaviours needed in game
	int beastAttackCounter = 0, numberLives, numberAttacks, collectedTreasures = 0, totalTreasures, totalBeasts, dwarfUpdateCounter = 0, dwarfAttackEffectCount = 0, dwarfDamageEffectCount = 0, invincibleCounter = 0, invincibleFlashCount = 0, currBeastAISlot = 0, prevBeastAISlot = 0, timeDiff = 0;

	// A pointer to a maze object that is yet to be assigned
	maze* gameMaze;
	
	// Two list objects that hold objects relating to chests and beasts
	list<mazePosition> chestPosList;
	list<beastState> beastPosList;
	
	// Objects used to hold the positions of the dwarf, the ladder and an object used during calculations
	mazePosition currPos, dwarfPos, ladderPos;
	
	// Flags for various effects and events that occur in game
	bool dwarfAttack = false, dwarfRender = true, dwarfInvincible = true, dwarfAttackEffect = false, dwarfDamageEffect = false;
	
	// A timer object to obtain the FPS and time update event.
	CTimer Timer;
	
	// Create model objects for use within game and upload their Milkshape3D models
	MilkshapeModel ladder;
	if(!ladder.loadModelData("ladder.ms3d"))//, false, true))
	{
		printf("Error loading ladder model\n");
		SetRunningStatus(false);
	}

	MilkshapeModel beast;
	if(!beast.loadModelData("beast.ms3d"))//, false, true))
	{
		printf("Error loading beast model\n");
		SetRunningStatus(false);
	}

	MilkshapeModel chest;
	if(!chest.loadModelData("chest.ms3d"))//, false, true))
	{
		printf("Error loading chest model\n");
		SetRunningStatus(false);
	}
	
	// Check selected difficulty level and initialise variables as required
	if (difficulty == 0) {				// Easy
		gameMaze = new maze(10, 10);	// Create a maze object of the correct dimensions and set our pointer to it
		numberLives = 3;				// Set the number of lives
		numberAttacks = 3;				// Set the initial number of attacks				
		totalTreasures = 1;				// Set the number of treasures in the level
		totalBeasts = 1;				// Set the number of beasts in the level
	}
	else if (difficulty == 1) {			// Medium
		gameMaze = new maze(20, 20);
		numberLives = 3;
		numberAttacks = 8;
		totalTreasures = 3;
		totalBeasts = 3;
	}
	else if (difficulty == 2) {			// High
		gameMaze = new maze(30, 30);
		numberLives = 3;
		numberAttacks = 15;
		totalTreasures = 5;
		totalBeasts = 5;
	}
	if (!gameMaze->uploadTextures("dirt_floor.bmp", "stone_wall.bmp")) SetRunningStatus(false);			// Now we have the maze we can upload the textures for the maze floor and walls. If this fails the game is stopped
	
	for (int x = 0; x < totalTreasures; x++) {				// For each treasure in the level we create a random postion then fill a mazePosition object with the random position and then place this of the chest list
		currPos.x = (rand() % gameMaze->getWidth());
		currPos.y = (rand() % gameMaze->getHeight());
		chestPosList.push_back(currPos);
	}
	static float aiProcessGap = 3500 / totalBeasts;			// Work out the difference between AI process slots by taking the 0.2s and dividing by the number of beasts. This distributes the AI processing of all the beasts over multiple frames rather than take a large performance hit on one frame
	float currAIProcessSlot = 0;							// Start at 0 and will increment with aiProcessGap to assign slots
	for (int x = 0; x < totalBeasts; x++) {														// Cycle through all the beasts to be created
		beastState currBeast(rand() % gameMaze->getWidth(), rand() % gameMaze->getHeight());	// Create a beastState object for each beast and assign a random position
		currBeast.previousMove = none;															// Because they have not moved yet, assign a previous move value of none
		currAIProcessSlot = currAIProcessSlot + aiProcessGap;									// Assign the next AI slot to the beast
		currBeast.aiProcessSlot = currAIProcessSlot;
		beastPosList.push_back(currBeast);														// Push the beastState onto the list
	}
	
	float dwarfAttackTimer = 0.0f;																// Timer to control the time the attack effect is in action
	
	bool dwarfPosSearch = true;																	
	
	list<mazePosition>::iterator chestItr;														// Create two iterators, one for the chest list and one for the beast list
	list<beastState>::iterator beastItr;
	
	while (dwarfPosSearch) {																	// Start a loop that gets random positions and checks if they are equal to any chests or any beasts. If they are then create a new random position
		currPos.x = (rand() % gameMaze->getWidth());											// otherwise assign this as the dwarf and ladder starting position. This ensures that the dwarf does not start on a chest or beast
		currPos.y = (rand() % gameMaze->getHeight());
		
		for (chestItr = chestPosList.begin(); chestItr != chestPosList.end(); chestItr++) {
			if (currPos == *chestItr) {
				dwarfPosSearch = false;
			}
		}
		for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {
			if (currPos == beastItr->beastPos) {
				dwarfPosSearch = false;
			}
		}
		if (dwarfPosSearch) {
			dwarfPos = currPos;
			ladderPos = currPos;
			
			dwarfPosSearch = false;
		}
		else dwarfPosSearch = true;
	}
	
	// Initialise Lighting
	// Three lights and Ambient light
	//							Direction vector				  Colour	
	Pipeline.SetLight1(Vector4( 1.0f, -1.0f, 1.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
	Pipeline.SetLight2(Vector4( 0.0f, 1.0f, 0.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
	Pipeline.SetLight3(Vector4( 0.0f, 0.0f,-1.0f, 0.0f), Vector4(0.0f,0.0f,0.0f,0.0f));
	//                            Colour
	Pipeline.SetAmbient(Vector4(50.0f,50.0f,50.0f,0.0f));
	
	float offsetX, offsetY, fps = 0.0f, topLeftX = gameMaze->getTopLeftCornerX(), topLeftY = gameMaze->getTopLeftCornerY();
	
	Matrix4x4 objectWorld, objectTranslate, objectScale, objectRotation, ladderWorld, dwarfWorld;
	
	// Calculate ladder world matrix. This only needs to be done once as the ladder matrix never changes
	offsetX = (ladderPos.x * tileSize) + (tileSize / 2);
	offsetY = (ladderPos.y * tileSize) + (tileSize / 2);
	objectScale.Scaling(ladderScale);
	objectTranslate.Translation((topLeftX + offsetX), 0, (topLeftY + offsetY));
	ladderWorld = objectScale * objectTranslate;
		
	mazeDirection dwarfDirection = down;		// Variable to store the starting direction of the dwarf
	
	// Calculate initial dwarf world matrix
	offsetX = (dwarfPos.x * tileSize) + (tileSize / 2);
	offsetY = (dwarfPos.y * tileSize) + (tileSize / 2);
	objectTranslate.Translation((topLeftX + offsetX), 0, (topLeftY + offsetY));
	objectRotation.RotationY(M_PI);				//Initially set to face towards the player
	objectScale.Scaling(dwarfScale);
	dwarfWorld = objectScale * objectRotation * objectTranslate;
	
	// Position Camera
	Pipeline.PositionCamera(Vector4(((dwarfPos.x * tileSize) + (tileSize / 2) + topLeftX), 300.0f, ((dwarfPos.y * tileSize) + 250.0f  + topLeftY), 1.0f), -0.9f, 0.0f);

	Timer.PrimeTimer();

	// Game loop	
	while ((GameState == LEVEL1) && GetRunningStatus()) {
	
		Pipeline.Update(0.0f, 0.0f, 0.0f, 0.0f, 0.0f);			// Called to update the pipeline class as the camera moves about
		
		pad_update(PAD_0);
		
		VIFDynamicDMA.Fire();
		
		SPS2Manager.BeginScene();
		
		gameMaze->render(Matrix4x4::IDENTITY);					// Render the entire maze mesh
		
		ladder.UploadTextures();								// Upload the ladder texture, set the world matrix and render the ladder
		ladder.SetWorldMatrix(ladderWorld);
		ladder.Render();
		
		chest.UploadTextures();																		// Upload the chest texture
		for (chestItr = chestPosList.begin(); chestItr != chestPosList.end(); chestItr++) {			// Iterate through each chest in the list
			if (*chestItr == dwarfPos) {															// If the dwarf is on the same square as a chest, increment the treasure collected variable and erase this chest from the list
				collectedTreasures++;
				chestItr = chestPosList.erase(chestItr);
				continue;
			}
			offsetX = (((*chestItr).x) * tileSize) + (tileSize / 2);								// Build the scale and translation matrices, create world matrix, apply this and render the chest for each item in the list
			offsetY = (((*chestItr).y) * tileSize) + (tileSize / 2);
			objectScale.Scaling(chestScale);
			objectTranslate.Translation((topLeftX + offsetX), 0, (topLeftY + offsetY));
			objectWorld = objectScale * objectTranslate;
			chest.SetWorldMatrix(objectWorld);
			chest.Render();
		}

		prevBeastAISlot = currBeastAISlot;
		currBeastAISlot += timeDiff;
		
		beast.UploadTextures();																							// Upload the beast texture
		for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {								// Iterate through each beast in the list
			
			bool moveLoop = true, dwarfInView = false, beingAttacked = false, canAttack = false;						// Flags that control the beasts behaviour
			int wallCount = 0, moveDirection = rand() % 4;
			
			if (((*beastItr).aiProcessSlot > prevBeastAISlot) && ((*beastItr).aiProcessSlot <= currBeastAISlot)) {		// Check which AI slot we are currently in and only update those beasts with slot values within the current range. The AI cycle repeats every 15 frames rate
			
				// Check to see if the dwarf is in sight of the beast, i.e. directly ahead of the beast with no walls obstructing
				if (!dwarfInvincible) {
					switch ((*beastItr).previousMove) {																	// Check which way the beast is facing
						case up:																						// If up
							if (((*beastItr).beastPos.x == dwarfPos.x) && (dwarfPos.y < (*beastItr).beastPos.y)) {		// Check if the dwarf is in any squares directly in front of the beast
								int temp = (*beastItr).beastPos.y;
								while (gameMaze->moveDirection((*beastItr).beastPos.x, temp, up)) {						// Loop through each square in front of the beast until we hit a wall
									temp--;
									if (temp == dwarfPos.y) {															// If the dwarf is in any of these squares then there is a direct line of sight so flag set
										dwarfInView = true;
										break;
									}
								}
							}
							break;
						case down:																						// If down do the same thing as above but in the down direction
							if (((*beastItr).beastPos.x == dwarfPos.x) && (dwarfPos.y > (*beastItr).beastPos.y)) {
								int temp = (*beastItr).beastPos.y;
								while (gameMaze->moveDirection((*beastItr).beastPos.x, temp, down)) {
									temp++;
									if (temp == dwarfPos.y) {
										dwarfInView = true;
										break;
									}
								}
							}
							break;
						case left:																						// If left do the same thing as above but in the left direction
							if ((dwarfPos.x < (*beastItr).beastPos.x) && ((*beastItr).beastPos.y == dwarfPos.y)) {
								int temp = (*beastItr).beastPos.x;
								while (gameMaze->moveDirection(temp, (*beastItr).beastPos.y, left)) {
									temp--;
									if (temp == dwarfPos.x) {
										dwarfInView = true;
										break;
									}
								}
							}
							break;
						case right:																						// If right do the same thing as above but in the right direction
							if ((dwarfPos.x > (*beastItr).beastPos.x) && ((*beastItr).beastPos.y == dwarfPos.y)) {
								int temp = (*beastItr).beastPos.x;
								while (gameMaze->moveDirection(temp, (*beastItr).beastPos.y, right)) {
									temp++;
									if (temp == dwarfPos.x) {
										dwarfInView = true;
										break;
									}
								}
							}
							break;
					}
				}
			
				// Check to see how many walls surround beast.
				if (!gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up)) wallCount++;
				if (!gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left)) wallCount++;
				if (!gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right)) wallCount++;
				if (!gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down)) wallCount++;
				
				//Check to see if dwarf is attacking the beast by seeing if the dwarf is attacking and if dwarf is in any of squares round the beast without any walls in the way
				beingAttacked = (dwarfAttack &&
								(((((*beastItr).beastPos.x) == dwarfPos.x) && (((*beastItr).beastPos.y - 1) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up)) ||
								((((*beastItr).beastPos.x - 1) == dwarfPos.x) && (((*beastItr).beastPos.y) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left)) ||
								((((*beastItr).beastPos.x + 1) == dwarfPos.x) && (((*beastItr).beastPos.y) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right)) ||
								((((*beastItr).beastPos.x) == dwarfPos.x) && (((*beastItr).beastPos.y + 1) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down))));
				
				//Check to see if possible to attack dwarf by checking if dwarf is invincible and if dwarf is in front of the beast and no walls are in the way
				canAttack = (!dwarfInvincible &&
							((((*beastItr).previousMove == up) && ((*beastItr).beastPos.x == dwarfPos.x) && (((*beastItr).beastPos.y - 1) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up)) ||
							(((*beastItr).previousMove == down) && ((*beastItr).beastPos.x == dwarfPos.x) && (((*beastItr).beastPos.y + 1) == dwarfPos.y) && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down)) ||
							(((*beastItr).previousMove == left) && (((*beastItr).beastPos.x - 1) == dwarfPos.x) && ((*beastItr).beastPos.y == dwarfPos.y && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left))) ||
							(((*beastItr).previousMove == right) && (((*beastItr).beastPos.x + 1) == dwarfPos.x) && ((*beastItr).beastPos.y == dwarfPos.y && gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right)))));
				
				// If beast can't attack then its attackCount is reset
				if (!canAttack) (*beastItr).attackCount = 0;
				
				// If beast is being attack
				if (beingAttacked) {
					if (wallCount == 3) (*beastItr).attackCount = 0;		// If trapped in a corner the beast has nowhere to go so its counter will be reset
					else 
					{
						switch ((*beastItr).previousMove) {					// The beast will try and move back in the direction it has just come, otherwise in the opposite of the 2nd previous direction
							case up:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down)) (*beastItr).moveDown();
								else {
									switch ((*beastItr).previousMove2) {
										case up:
											(*beastItr).moveDown();
											break;
										case down:
											(*beastItr).moveUp();
											break;
										case left:
											(*beastItr).moveRight();
											break;
										case right:
											(*beastItr).moveLeft();
											break;
									}
								}
								break;
							case down:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up)) (*beastItr).moveUp();
								else {
									switch ((*beastItr).previousMove2) {
										case up:
											(*beastItr).moveDown();
											break;
										case down:
											(*beastItr).moveUp();
											break;
										case left:
											(*beastItr).moveRight();
											break;
										case right:
											(*beastItr).moveLeft();
											break;
									}
								}
								break;
							case left:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right)) (*beastItr).moveRight();
								else {
									switch ((*beastItr).previousMove2) {
										case up:
											(*beastItr).moveDown();
											break;
										case down:
											(*beastItr).moveUp();
											break;
										case left:
											(*beastItr).moveRight();
											break;
										case right:
											(*beastItr).moveLeft();
											break;
									}
								}
								break;
							case right:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left)) (*beastItr).moveLeft();
								else {
									switch ((*beastItr).previousMove2) {
										case up:
											(*beastItr).moveDown();
											break;
										case down:
											(*beastItr).moveUp();
											break;
										case left:
											(*beastItr).moveRight();
											break;
										case right:
											(*beastItr).moveLeft();
											break;
									}
								}
								break;
						}
					}
				}
				else if (canAttack) {																				// If the beast can attack
					(*beastItr).attackCount++;																		// Update the beast attack count
					if ((*beastItr).attackCount > 3) {																// If the attack count is greater than 3 then the attack is successful
						(*beastItr).attackCount = 0;																// Reset attack count
						numberLives--;																				// Reduce the number of lives by one
						dwarfDamageEffect = true;																	// Set the damage effect (a red light flash)
						Pipeline.SetLight1(Vector4( 1.0f, -1.0f, 1.0f, 0.0f), Vector4(128.0f,0.0f,0.0f,0.0f));		// Set lights to red
						Pipeline.SetLight2(Vector4( 0.0f, 1.0f, 0.0f, 0.0f), Vector4(100.0f,0.0f,0.0f,0.0f));
						dwarfInvincible = true;																		// Make dwarf invincible for a period
					}
				}
				else if (dwarfInView) {									// If dwarf is in sight, head directly towards it
					switch ((*beastItr).previousMove) {
						case up:
							(*beastItr).moveUp();
							break;
						case down:
							(*beastItr).moveDown();
							break;
						case left:
							(*beastItr).moveLeft();
							break;
						case right:
							(*beastItr).moveRight();
							break;
					}
				}
				else if (wallCount == 3) {							// If beast is in a corner
					switch ((*beastItr).previousMove) {
						case none:									// If this is its first move, see where there is no wall and move in that direction
							if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up)) (*beastItr).moveUp();
							else if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down)) (*beastItr).moveDown();
							else if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left)) (*beastItr).moveLeft();
							else if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right)) (*beastItr).moveRight();
							break;
						case up:									// Else move in the opposite direction than the previous move so we are not longer trapped
							(*beastItr).previousMove2 = (*beastItr).previousMove;
							(*beastItr).previousMove = down;
							if (!((dwarfPos.x == (*beastItr).beastPos.x) && (dwarfPos.y == ((*beastItr).beastPos.y + 1)))) (*beastItr).beastPos.y++;
							break;
						case down:
							(*beastItr).previousMove2 = (*beastItr).previousMove;
							(*beastItr).previousMove = up;
							if (!((dwarfPos.x == (*beastItr).beastPos.x) && (dwarfPos.y == ((*beastItr).beastPos.y - 1)))) (*beastItr).beastPos.y--;
							break;
						case left:
							(*beastItr).previousMove2 = (*beastItr).previousMove;
							(*beastItr).previousMove = right;
							if (!((dwarfPos.x == ((*beastItr).beastPos.x + 1)) && (dwarfPos.y == (*beastItr).beastPos.y))) (*beastItr).beastPos.x++;
							break;
						case right:
							(*beastItr).previousMove2 = (*beastItr).previousMove;
							(*beastItr).previousMove = left;
							if (!((dwarfPos.x == ((*beastItr).beastPos.x - 1)) && (dwarfPos.y == (*beastItr).beastPos.y))) (*beastItr).beastPos.x--;
							break;
					}
				}
				else {
					while (moveLoop) {					// Move in a random direction that is not in the direction we have just moved and which is not occupied by the dwarf and there is not a wall in the way
						switch (moveDirection) {		// Loop until we find a successful direction
							case 0:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, up) && ((*beastItr).previousMove != down)) {
									if (!((dwarfPos.x == (*beastItr).beastPos.x) && (dwarfPos.y == ((*beastItr).beastPos.y - 1)))) (*beastItr).beastPos.y--;
									(*beastItr).previousMove2 = (*beastItr).previousMove;
									(*beastItr).previousMove = up;								
									moveLoop = false;
								}
								break;
							case 1:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, left) && ((*beastItr).previousMove != right)) {
									if (!((dwarfPos.x == ((*beastItr).beastPos.x - 1)) && (dwarfPos.y == (*beastItr).beastPos.y))) (*beastItr).beastPos.x--;
									(*beastItr).previousMove2 = (*beastItr).previousMove;
									(*beastItr).previousMove = left;
									moveLoop = false;
								}
								break;
							case 2:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, right) && ((*beastItr).previousMove != left)) {
									if (!((dwarfPos.x == ((*beastItr).beastPos.x + 1)) && (dwarfPos.y == (*beastItr).beastPos.y))) (*beastItr).beastPos.x++;
									(*beastItr).previousMove2 = (*beastItr).previousMove;
									(*beastItr).previousMove = right;
									moveLoop = false;
								}
								break;
							case 3:
								if (gameMaze->moveDirection((*beastItr).beastPos.x, (*beastItr).beastPos.y, down) && ((*beastItr).previousMove != up)) {
									if (!((dwarfPos.x == (*beastItr).beastPos.x) && (dwarfPos.y == ((*beastItr).beastPos.y + 1)))) (*beastItr).beastPos.y++;
									(*beastItr).previousMove2 = (*beastItr).previousMove;
									(*beastItr).previousMove = down;
									moveLoop = false;
								}
								break;
						}
						if (moveDirection == 3) moveDirection = 0; else moveDirection++;
					}
				}
			}
				
			switch ((*beastItr).previousMove) {				// Check the previous move and orientate the beast accordingly
				case up:
					objectRotation.RotationY(M_PI);
					break;
				case down:
					objectRotation.RotationY(0);
					break;
				case left:
					objectRotation.RotationY(-M_PI / 2);
					break;
				case right:
					objectRotation.RotationY(M_PI / 2);
					break;
			}
			offsetX = (((*beastItr).beastPos.x) * tileSize) + (tileSize / 2);				// Calculate the transformation matrices and render the beast
			offsetY = (((*beastItr).beastPos.y) * tileSize) + (tileSize / 2);
			objectScale.Scaling(beastScale);
			objectTranslate.Translation((topLeftX + offsetX), 0, (topLeftY + offsetY));
			objectWorld = objectScale * objectRotation * objectTranslate;
			beast.SetWorldMatrix(objectWorld);
			beast.Render();
		}
		if (currBeastAISlot >= 3500) currBeastAISlot = 0;			// After 0.2s the beast AI slot count is reset
		if (dwarfAttackTimer >= 3500 && dwarfAttack) {
			dwarfAttack = false;				// Dwarf attack effects should only happen for one pass through the slots after being performed. Therefore they are switched off here.
			dwarfAttackTimer = 0.0f;
		}
		else if (dwarfAttack) {
			dwarfAttackTimer += timeDiff;
		}
		
		if (dwarfRender) {						// If the dwarf is needed to be render set the world matrix and render
			dwarf.UploadTextures();
			dwarf.SetWorldMatrix(dwarfWorld);
			dwarf.Render();
		}
		
		// Display the text that tells the current game state
		FontTex.Upload(TEXBUF496);
		FontTex.Select();
		Font.printfC( -210,  190, 127, 127, 127, 127, "Lives: %d", numberLives);
		Font.printfC(   -8,  190, 127, 127, 127, 127, "Attacks: %d", numberAttacks);
		Font.printfC(  195,  190, 127, 127, 127, 127, "Treasure: %d / %d", collectedTreasures, totalTreasures);
		
		Font.printfC(  200, -240, 127, 127, 127, 127, "FPS: %.1f" , fps);
		
		Font.printfC(  220, 230, 127, 127, 127, 70, "Stuart Coulson - 0700037");
		
		fps = Timer.GetFPS();
		
		SPS2Manager.EndScene();
		
		// Update the lighting effect of the scene given the current action being performed
		// Green flash of light accompanying the attack action
		if (dwarfAttackEffect) {
			dwarfAttackEffectCount += timeDiff;
			if (dwarfAttackEffectCount > 2000) {
				dwarfAttackEffect = false;
				Pipeline.SetLight1(Vector4( 1.0f, -1.0f, 1.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
				Pipeline.SetLight2(Vector4( 0.0f, 1.0f, 0.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
				dwarfAttackEffectCount = 0;
			}
		}
		// Red flash of light accompanying the loss of a life
		if (dwarfDamageEffect) {
			dwarfDamageEffectCount += timeDiff;
			if (dwarfDamageEffectCount > 2000) {
				dwarfAttackEffect = false;
				Pipeline.SetLight1(Vector4( 1.0f, -1.0f, 1.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
				Pipeline.SetLight2(Vector4( 0.0f, 1.0f, 0.0f, 0.0f), Vector4(70.0f,70.0f,70.0f,0.0f));
				dwarfDamageEffectCount = 0;																	// Reset the damage effect counter
			}
		}
		
		// Control the flashing of the dwarf model to signify temporary invincibility. Occurs at start of level and after loosing a life.
		if (dwarfInvincible) {
			invincibleCounter += timeDiff;
			invincibleFlashCount += timeDiff;
			if (invincibleCounter > 40000) {
				dwarfRender = true;
				dwarfInvincible = false;
				invincibleCounter = 0;
				invincibleFlashCount = 0;
			}
			else if (invincibleFlashCount > 4000) {
				invincibleFlashCount = 0;
				dwarfRender = !dwarfRender;
			}
		}
		
		// If all the treasure chests have been collected and the dwarf is at the same position as the ladder then the player has won and we enter the win state
		if ((dwarfPos == ladderPos) && (collectedTreasures == totalTreasures)) {
			GameState = WIN;
		}
		// Player has lost all lives so the game enters lose state
		if (numberLives == 0) GameState = LOSE;

		dwarfUpdateCounter += timeDiff;			// Update the dwarf time counter
		
		// Constrain controls so updates are only applied every 0.1 seconds. This is so the dwarf is slowed to a managable rate but also makes sure that he retains the same speed despite the complexity of the scene.
		if (dwarfUpdateCounter >= 2000) {
			bool newPosEmpty = true;
			dwarfUpdateCounter = 0;
			
			// Move right
			if (pad[0].axes[PAD_AXIS_LX] >= 0.75f) {
				for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {
					if (((dwarfPos.x + 1) == (*beastItr).beastPos.x) && (dwarfPos.y == (*beastItr).beastPos.y)) newPosEmpty = false;
				}
				if (newPosEmpty && gameMaze->moveDirection(dwarfPos.x, dwarfPos.y, right)) dwarfPos.x +=1;
				dwarfDirection = right;
			}
						
			// Move left
			else if (pad[0].axes[PAD_AXIS_LX] <= -0.75f) {
				for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {
					if (((dwarfPos.x - 1) == (*beastItr).beastPos.x) && (dwarfPos.y == (*beastItr).beastPos.y)) newPosEmpty = false;
				}
				if (newPosEmpty && gameMaze->moveDirection(dwarfPos.x, dwarfPos.y, left)) dwarfPos.x -=1;
				dwarfDirection = left;
			}
			
			// Move up
			else if (pad[0].axes[PAD_AXIS_LY] <= -0.75f) {
				for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {
					if ((dwarfPos.x == (*beastItr).beastPos.x) && ((dwarfPos.y - 1) == (*beastItr).beastPos.y)) newPosEmpty = false;
				}
				if (newPosEmpty && gameMaze->moveDirection(dwarfPos.x, dwarfPos.y, up)) dwarfPos.y -=1;
				dwarfDirection = up;
			}
			
			// Move down
			else if (pad[0].axes[PAD_AXIS_LY] >= 0.75f) {
				for (beastItr = beastPosList.begin(); beastItr != beastPosList.end(); beastItr++) {
					if ((dwarfPos.x == (*beastItr).beastPos.x) && ((dwarfPos.y + 1) == (*beastItr).beastPos.y)) newPosEmpty = false;
				}
				if (newPosEmpty && gameMaze->moveDirection(dwarfPos.x, dwarfPos.y, down)) dwarfPos.y +=1;
				dwarfDirection = down;
			}
			
			// Orientate the dwarf correctly
			switch (dwarfDirection) {
				case up:
					objectRotation.RotationY(0);
					break;					
				case down:
					objectRotation.RotationY(M_PI);
					break;
				case left:
					objectRotation.RotationY(M_PI / 2);
					break;
				case right:
					objectRotation.RotationY(-M_PI / 2);
					break;
				default:
					objectRotation.RotationY(M_PI);
			}
			
			// Calculate the transformation matrices and world matrix
			Pipeline.PositionCamera(Vector4(((dwarfPos.x * tileSize) + (tileSize / 2) + topLeftX), 300.0f, ((dwarfPos.y * tileSize) + topLeftY + 250.0f), 1.0f), -0.9f, 0.0f);
			offsetX = (dwarfPos.x * tileSize) + (tileSize / 2);
			offsetY = (dwarfPos.y * tileSize) + (tileSize / 2);
			objectTranslate.Translation((topLeftX + offsetX), 0, (topLeftY + offsetY));
			objectScale.Scaling(dwarfScale);
			dwarfWorld = objectScale * objectRotation * objectTranslate;
			dwarfUpdateCounter = 0;
		}
		
		// Set the attack flag, and start the attack effect
		if((pad[0].pressed & PAD_CROSS) && (numberAttacks > 0) && !dwarfAttack) {
			dwarfAttack = true;
			dwarfAttackEffect = true;
			Pipeline.SetLight1(Vector4( 1.0f, -1.0f, 1.0f, 0.0f), Vector4(0.0f,128.0f,0.0f,0.0f));
			Pipeline.SetLight2(Vector4( 0.0f, 1.0f, 0.0f, 0.0f), Vector4(0.0f,100.0f,0.0f,0.0f));
			numberAttacks--;
		}
		
		// Press select to quit game
		if(pad[0].pressed & PAD_SELECT) {
			GameState = QUIT;
			delete gameMaze;
		}
		
		timeDiff = Timer.GetTimeDelta();
	}
}

void CGame::GameWin(void)															// All three end screens work the same, displaying a colour message for 250 frames
{
	static int FrameCounter = 0;
	const int NumFrames = 250;
	
	while ((GameState == WIN) && GetRunningStatus()) {
		if(FrameCounter++ == NumFrames)SetRunningStatus(false);
		
		VIFDynamicDMA.Fire();
		
		SPS2Manager.BeginScene();
		
		FontTex.Select();
		Font.printfC(  0, -40, 0, 127, 0, 127, "Congratulations, You Win!!!");
		Font.printfC(  0, 0, 0, 127, 0, 127, ":-D");
		Font.printfC(  0, 40, 0, 127, 0, 127, "GOODBYE");
		
		SPS2Manager.EndScene();
	}		
}

void CGame::GameLose(void)
{
	static int FrameCounter = 0;
	const int NumFrames = 250;
	
	while ((GameState == LOSE) && GetRunningStatus()) {
		if(FrameCounter++ == NumFrames)SetRunningStatus(false);
		
		VIFDynamicDMA.Fire();
		
		SPS2Manager.BeginScene();
		
		FontTex.Select();
		Font.printfC(  0, -40, 127, 0, 0, 127, "Comiserations, You Are Dead");
		Font.printfC(  0, 0, 127, 0, 0, 127, ":-(");
		Font.printfC(  0, 40, 127, 0, 0, 127, "GOODBYE");
		
		SPS2Manager.EndScene();
	}		
}

void CGame::GameQuit(void)
{
	static int FrameCounter = 0;
	const int NumFrames = 250;
	
	while ((GameState == QUIT) && GetRunningStatus()) {
		if(FrameCounter++ == NumFrames)SetRunningStatus(false);
		
		VIFDynamicDMA.Fire();
		
		SPS2Manager.BeginScene();
		
		FontTex.Select();
		Font.printfC(  0, -40, 0, 127, 127, 127, "You Gave Up, You Coward");
		Font.printfC(  0, 0, 0, 127, 127, 127, ":-P");
		Font.printfC(  0, 40, 0, 127, 127, 127, "GOODBYE");
		
		SPS2Manager.EndScene();
	}		
}

void CGame::Stop(void)
{
	pad_cleanup(PAD_0);
}

void CGame::SignalHandler(int Signal)
{
	SetRunningStatus(false);
}
	
	
