/*
OneLoneCoder.com - Code-It-Yourself! Worms Part #3
"stuuup-iid...." - @Javidx9

Disclaimer
~~~~~~~~~~
I don't care what you use this for. It's intended to be educational, and perhaps
to the oddly minded - a little bit of fun. Please hack this, change it and use it
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
bad that happens as a result of your actions. However, if good stuff happens, I
would appreciate a shout out, or at least give the blog some publicity for me.
Cheers!

Background
~~~~~~~~~~
Worms is a classic game where several teams of worms use a variety of weaponry
to elimiate each other from a randomly generated terrain.

This code is the third part of a series that show how to make your own Worms game
from scratch in C++!

Controls A = Aim Left, S = Aim Right, Z = Jump, Space = Charge Weapon, TAB = Zoom

Author
~~~~~~
Twitter: @javidx9
Blog: www.onelonecoder.com

Video:
~~~~~~
Part #1 https://youtu.be/EHlaJvQpW3U
Part #2 https://youtu.be/pV2qYJjCdxM
Part #3 https://youtu.be/NKK5tIRZqyQ

Last Updated: 19/12/2017
*/

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

#include "olcConsoleGameEngine.h"


class cPhysicsObject
{
public:
	cPhysicsObject(float x = 0.0f, float y = 0.0f)
	{
		px = x;
		py = y;
	}

public:
	float px = 0.0f;				// Position
	float py = 0.0f;
	float vx = 0.0f;				// Velocity
	float vy = 0.0f;
	float ax = 0.0f;				// Acceleration
	float ay = 0.0f;
	float radius = 4.0f;			// Bounding rectangle for collisions
	float fFriction = 0.0f;			// Actually, a dampening factor is a more accurate name
	int nBounceBeforeDeath = -1;	// How many time object can bounce before death
	bool bDead;						// Flag to indicate object should be removed
	bool bStable = false;			// Has object stopped moving

	// Make class abstract
	virtual void Draw(olcConsoleGameEngine *engine, float fOffsetX, float fOffsetY, bool bPixel = false) = 0;
	virtual int BounceDeathAction() = 0;
	virtual bool Damage(float d) = 0;	
};

class cDebris : public cPhysicsObject // a small rock that bounces
{
public:
	cDebris(float x = 0.0f, float y = 0.0f) : cPhysicsObject(x, y)
	{
		// Set velocity to random direction and size for "boom" effect
		vx = 10.0f * cosf(((float)rand() / (float)RAND_MAX) * 2.0f * 3.14159f);
		vy = 10.0f * sinf(((float)rand() / (float)RAND_MAX) * 2.0f * 3.14159f);
		radius = 1.0f;
		fFriction = 0.8f;
		bDead = false;
		bStable = false;
		nBounceBeforeDeath = 2; // After 2 bounces, dispose
	}

	virtual void Draw(olcConsoleGameEngine *engine, float fOffsetX, float fOffsetY, bool bPixel = false)
	{
		engine->DrawWireFrameModel(vecModel, px - fOffsetX, py - fOffsetY, atan2f(vy, vx), bPixel ? 0.5f : radius, FG_DARK_GREEN);
	}

	virtual int BounceDeathAction()
	{
		return 0; // Nothing, just fade
	}

	virtual bool Damage(float d)
	{
		return true; // Cannot be damaged
	}

private:
	static vector<pair<float, float>> vecModel;

};

vector<pair<float, float>> DefineDebris()
{
	// A small unit rectangle
	vector<pair<float, float>> vecModel;
	vecModel.push_back({ 0.0f, 0.0f });
	vecModel.push_back({ 1.0f, 0.0f });
	vecModel.push_back({ 1.0f, 1.0f });
	vecModel.push_back({ 0.0f, 1.0f });
	return vecModel;
}

vector<pair<float, float>> cDebris::vecModel = DefineDebris();

class cMissile : public cPhysicsObject // A projectile weapon
{
public:
	cMissile(float x = 0.0f, float y = 0.0f, float _vx = 0.0f, float _vy = 0.0f) : cPhysicsObject(x, y)
	{
		radius = 2.5f;
		fFriction = 0.5f;
		vx = _vx;
		vy = _vy;
		bDead = false;
		nBounceBeforeDeath = 1;
		bStable = false;
	}

	virtual void Draw(olcConsoleGameEngine *engine, float fOffsetX, float fOffsetY, bool bPixel = false)
	{
		engine->DrawWireFrameModel(vecModel, px - fOffsetX, py - fOffsetY, atan2f(vy, vx), bPixel ? 0.5f : radius, FG_BLACK);
	}

	virtual int BounceDeathAction()
	{
		return 20; // Explode Big
	}

	virtual bool Damage(float d)
	{
		return true;
	}

private:
	static vector<pair<float, float>> vecModel;
};

vector<pair<float, float>> DefineMissile()
{
	// Defines a rocket like shape
	vector<pair<float, float>> vecModel;
	vecModel.push_back({ 0.0f, 0.0f });
	vecModel.push_back({ 1.0f, 1.0f });
	vecModel.push_back({ 2.0f, 1.0f });
	vecModel.push_back({ 2.5f, 0.0f });
	vecModel.push_back({ 2.0f, -1.0f });
	vecModel.push_back({ 1.0f, -1.0f });
	vecModel.push_back({ 0.0f, 0.0f });
	vecModel.push_back({ -1.0f, -1.0f });
	vecModel.push_back({ -2.5f, -1.0f });
	vecModel.push_back({ -2.0f, 0.0f });
	vecModel.push_back({ -2.5f, 1.0f });
	vecModel.push_back({ -1.0f, 1.0f });

	// Scale points to make shape unit(ish) sized
	for (auto &v : vecModel)
	{
		v.first /= 1.5f; v.second /= 1.5f;
	}
	return vecModel;
}

vector<pair<float, float>> cMissile::vecModel = DefineMissile();

class cWorm : public cPhysicsObject // A unit, or worm
{
public:
	cWorm(float x = 0.0f, float y = 0.0f) : cPhysicsObject(x, y)
	{
		radius = 3.5f;
		fFriction = 0.2f;
		bDead = false;
		nBounceBeforeDeath = -1;
		bStable = false;

		// load sprite data from sprite file
		if (sprWorm == nullptr)
			sprWorm = new olcSprite(L"worms1.spr");
	}

	virtual void Draw(olcConsoleGameEngine *engine, float fOffsetX, float fOffsetY, bool bPixel = false)
	{
		if (bIsPlayable) // Draw Worm Sprite with health bar, in team colours
		{
			engine->DrawPartialSprite(px - fOffsetX - radius, py - fOffsetY - radius, sprWorm, nTeam * 8, 0, 8, 8);

			// Draw health bar for worm
			for (int i = 0; i < 11 * fHealth; i++)
			{
				engine->Draw(px - 5 + i - fOffsetX, py + 5 - fOffsetY, PIXEL_SOLID, FG_BLUE);
				engine->Draw(px - 5 + i - fOffsetX, py + 6 - fOffsetY, PIXEL_SOLID, FG_BLUE);
			}
		}
		else // Draw tombstone sprite for team colour
		{
			engine->DrawPartialSprite(px - fOffsetX - radius, py - fOffsetY - radius, sprWorm, nTeam * 8, 8, 8, 8);
		}
	}

	virtual int BounceDeathAction()
	{
		return 0; // Nothing
	}

	virtual bool Damage(float d) // Reduce worm's health by said amount
	{
		fHealth -= d;
		if (fHealth <= 0)
		{ // Worm has died, no longer playable
			fHealth = 0.0f;
			bIsPlayable = false;
		}
		return fHealth > 0;
	}

public:
	float fShootAngle = 0.0f;
	float fHealth = 1.0f;
	int nTeam = 0;	// ID of which team this worm belongs to
	bool bIsPlayable = true;

private:
	static olcSprite *sprWorm;
};

olcSprite* cWorm::sprWorm = nullptr;



class cTeam // Defines a group of worms
{
public:
	vector<cWorm*> vecMembers;
	int nCurrentMember = 0;		// Index into vector for current worms turn
	int nTeamSize = 0;			// Total number of worms in team

	bool IsTeamAlive()
	{	
		// Iterate through all team members, if any of them have >0 health, return true;
		bool bAllDead = false;
		for (auto w : vecMembers)
			bAllDead |= (w->fHealth > 0.0f);
		return bAllDead;
	}

	cWorm* GetNextMember()
	{
		// Return a pointer to the next team member that is valid for control
		do {
			nCurrentMember++;
			if (nCurrentMember >= nTeamSize) nCurrentMember = 0;
		} while (vecMembers[nCurrentMember]->fHealth <= 0);
		return vecMembers[nCurrentMember];
	}	
};


// Main Game Engine Class
class OneLoneCoder_Worms : public olcConsoleGameEngine // The game
{
public:
	OneLoneCoder_Worms()
	{
		m_sAppName = L"Worms";
	}

private:
	// Terrain size
	int nMapWidth = 1024;
	int nMapHeight = 512;
	char *map = nullptr;

	// Camera Coordinates
	float fCameraPosX = 0.0f;
	float fCameraPosY = 0.0f;
	float fCameraPosXTarget = 0.0f;
	float fCameraPosYTarget = 0.0f;

	// list of things that exist in game world
	list<unique_ptr<cPhysicsObject>> listObjects;

	cPhysicsObject* pObjectUnderControl = nullptr;		// Pointer to object currently under control
	cPhysicsObject* pCameraTrackingObject = nullptr;	// Pointer to object that camera should track

	// Flags that govern/are set by game state machine
	bool bZoomOut = false;					// Render whole map
	bool bGameIsStable = false;				// All physics objects are stable
	bool bEnablePlayerControl = true;		// The player is in control, keyboard input enabled
	bool bEnableComputerControl = false;	// The AI is in control
	bool bEnergising = false;				// Weapon is charging
	bool bFireWeapon = false;				// Weapon should be discharged
	bool bShowCountDown = false;			// Display turn time counter on screen
	bool bPlayerHasFired = false;			// Weapon has been discharged

	float fEnergyLevel = 0.0f;				// Energy accumulated through charging (player only)
	float fTurnTime = 0.0f;					// Time left to take turn

	// Vector to store teams
	vector<cTeam> vecTeams;

	// Current team being controlled
	int nCurrentTeam = 0;

	// AI control flags
	bool bAI_Jump = false;				// AI has pressed "JUMP" key
	bool bAI_AimLeft = false;			// AI has pressed "AIM_LEFT" key
	bool bAI_AimRight = false;			// AI has pressed "AIM_RIGHT" key
	bool bAI_Energise = false;			// AI has pressed "FIRE" key

	
	float fAITargetAngle = 0.0f;		// Angle AI should aim for
	float fAITargetEnergy = 0.0f;		// Energy level AI should aim for
	float fAISafePosition = 0.0f;		// X-Coordinate considered safe for AI to move to
	cWorm* pAITargetWorm = nullptr;		// Pointer to worm AI has selected as target
	float fAITargetX = 0.0f;			// Coordinates of target missile location
	float fAITargetY = 0.0f;

	enum GAME_STATE
	{
		GS_RESET = 0,
		GS_GENERATE_TERRAIN = 1,
		GS_GENERATING_TERRAIN,		
		GS_ALLOCATE_UNITS,
		GS_ALLOCATING_UNITS,
		GS_START_PLAY,
		GS_CAMERA_MODE,
		GS_GAME_OVER1,
		GS_GAME_OVER2
	} nGameState, nNextState;


	enum AI_STATE
	{
		AI_ASSESS_ENVIRONMENT = 0,
		AI_MOVE,
		AI_CHOOSE_TARGET,
		AI_POSITION_FOR_TARGET,
		AI_AIM,
		AI_FIRE,
	} nAIState, nAINextState;

	virtual bool OnUserCreate()
	{
		// Create Map
		map = new  char[nMapWidth * nMapHeight];
		memset(map, 0, nMapWidth*nMapHeight * sizeof( char));
		
		// Set initial states for state machines
		nGameState = GS_RESET;
		nNextState = GS_RESET;
		nAIState = AI_ASSESS_ENVIRONMENT;
		nAINextState = AI_ASSESS_ENVIRONMENT;

		bGameIsStable = false;
		return true;
	}

	virtual bool OnUserUpdate(float fElapsedTime)
	{
		// Tab key toggles between whole map view and up close view
		if (m_keys[VK_TAB].bReleased)
			bZoomOut = !bZoomOut;

		// Mouse Edge Map Scroll
		float fMapScrollSpeed = 400.0f;
		if (m_mousePosX < 5) fCameraPosX -= fMapScrollSpeed * fElapsedTime;
		if (m_mousePosX > ScreenWidth() - 5) fCameraPosX += fMapScrollSpeed * fElapsedTime;
		if (m_mousePosY < 5) fCameraPosY -= fMapScrollSpeed * fElapsedTime;
		if (m_mousePosY > ScreenHeight() - 5) fCameraPosY += fMapScrollSpeed * fElapsedTime;

		// Control Supervisor
		switch (nGameState)
		{
		case GS_RESET:
			{
				bEnablePlayerControl = false;
				bGameIsStable = false;
				bPlayerHasFired = false;
				bShowCountDown = false;			
				nNextState = GS_GENERATE_TERRAIN;
			}
			break;
		
		case GS_GENERATE_TERRAIN:
			{
				bZoomOut = true;
				CreateMap();
				bGameIsStable = false;
				bShowCountDown = false;
				nNextState = GS_GENERATING_TERRAIN;
			}
			break;

		case GS_GENERATING_TERRAIN:
			{
				bShowCountDown = false;
				if (bGameIsStable)
					nNextState = GS_ALLOCATE_UNITS;
			}
			break;

		case GS_ALLOCATE_UNITS:
			{
				// Deploy teams
				int nTeams = 4;
				int nWormsPerTeam = 4;

				// Calculate spacing of worms and teams
				float fSpacePerTeam = (float)nMapWidth / (float)nTeams;
				float fSpacePerWorm = fSpacePerTeam / (nWormsPerTeam * 2.0f);

				// Create teams
				for (int t = 0; t < nTeams; t++)
				{
					vecTeams.emplace_back(cTeam());
					float fTeamMiddle = (fSpacePerTeam / 2.0f) + (t * fSpacePerTeam);
					for (int w = 0; w < nWormsPerTeam; w++)
					{
						float fWormX = fTeamMiddle - ((fSpacePerWorm * (float)nWormsPerTeam) / 2.0f) + w * fSpacePerWorm;
						float fWormY = 0.0f;

						// Add worms to teams
						cWorm *worm = new cWorm(fWormX,fWormY);
						worm->nTeam = t;
						listObjects.push_back(unique_ptr<cWorm>(worm));
						vecTeams[t].vecMembers.push_back(worm);
						vecTeams[t].nTeamSize = nWormsPerTeam;
					}
					
					vecTeams[t].nCurrentMember = 0;
				}
				
				// Select players first worm for control and camera tracking
				pObjectUnderControl = vecTeams[0].vecMembers[vecTeams[0].nCurrentMember];
				pCameraTrackingObject = pObjectUnderControl;
				bShowCountDown = false;
				nNextState = GS_ALLOCATING_UNITS;
			}
			break;

		case GS_ALLOCATING_UNITS: // Wait for units to "parachute" in
			{
				if (bGameIsStable)
				{
					bEnablePlayerControl = true;
					bEnableComputerControl = false;
					fTurnTime = 15.0f;
					bZoomOut = false;
					nNextState = GS_START_PLAY;
				}
			}
			break;

		case GS_START_PLAY:
			{
				bShowCountDown = true;

				// If player has discharged weapon, or turn time is up, move on to next state
				if (bPlayerHasFired || fTurnTime <= 0.0f)
					nNextState = GS_CAMERA_MODE;
			}
			break;

		case GS_CAMERA_MODE: // Camera follows object of interest until the physics engine has settled
			{
				bEnableComputerControl = false;
				bEnablePlayerControl = false;
				bPlayerHasFired = false;
				bShowCountDown = false;
				fEnergyLevel = 0.0f;
				
				if (bGameIsStable) // Once settled, choose next worm
				{					
					// Get Next Team, if there is no next team, game is over
					int nOldTeam = nCurrentTeam;
					do {
						nCurrentTeam++;
						nCurrentTeam %= vecTeams.size();
					} while (!vecTeams[nCurrentTeam].IsTeamAlive());
					
					// Lock controls if AI team is currently playing	
					if (nCurrentTeam == 0) // Player Team
					{
						bEnablePlayerControl = true;	// Swap these around for complete AI battle
						bEnableComputerControl = false;
					}
					else // AI Team
					{
						bEnablePlayerControl = false;
						bEnableComputerControl = true;
					}

					// Set control and camera 
					pObjectUnderControl = vecTeams[nCurrentTeam].GetNextMember();
					pCameraTrackingObject = pObjectUnderControl;
					fTurnTime = 15.0f;
					bZoomOut = false;
					nNextState = GS_START_PLAY;

					// If no different team could be found...
					if (nCurrentTeam == nOldTeam)
					{
						// ...Game is over, Current Team have won!
						nNextState = GS_GAME_OVER1;
					}
				}
			}
			break;

			case GS_GAME_OVER1: // Zoom out and launch loads of missiles!
			{
				bEnableComputerControl = false;
				bEnablePlayerControl = false;
				bZoomOut = true;
				bShowCountDown = false;

				for (int i = 0; i < 100; i ++)
				{
					int nBombX = rand() % nMapWidth;
					int nBombY = rand() % (nMapHeight / 2);
					listObjects.push_back(unique_ptr<cMissile>(new cMissile(nBombX, nBombY, 0.0f, 0.5f)));
				}

				nNextState = GS_GAME_OVER2;
			}
			break;

			case GS_GAME_OVER2: // Stay here and wait for chaos to settle
			{
				bEnableComputerControl = false;
				bEnablePlayerControl = false;
				// No exit from this state!
			}
			break;

		}

		// AI State Machine
		if (bEnableComputerControl)
		{
			switch (nAIState)
			{
			case AI_ASSESS_ENVIRONMENT:
			{

				int nAction = rand() % 3;
				if (nAction == 0) // Play Defensive - move away from team
				{
					// Find nearest ally, walk away from them
					float fNearestAllyDistance = INFINITY; float fDirection = 0;
					cWorm *origin = (cWorm*)pObjectUnderControl;

					for (auto w : vecTeams[nCurrentTeam].vecMembers)
					{
						if (w != pObjectUnderControl)
						{
							if (fabs(w->px - origin->px) < fNearestAllyDistance)
							{
								fNearestAllyDistance = fabs(w->px - origin->px);
								fDirection = (w->px - origin->px) < 0.0f ? 1.0f : -1.0f;
							}
						}
					}

					if (fNearestAllyDistance < 50.0f)
						fAISafePosition = origin->px + fDirection * 80.0f;
					else
						fAISafePosition = origin->px;
				}

				if (nAction == 1) // Play Ballsy - move towards middle
				{
					cWorm *origin = (cWorm*)pObjectUnderControl;
					float fDirection = ((float)(nMapWidth / 2.0f) - origin->px) < 0.0f ? -1.0f : 1.0f;
					fAISafePosition = origin->px + fDirection * 200.0f;
				}

				if (nAction == 2) // Play Dumb - don't move
				{
					cWorm *origin = (cWorm*)pObjectUnderControl;
					fAISafePosition = origin->px;
				}

				// Clamp so dont walk off map
				if (fAISafePosition <= 20.0f) fAISafePosition = 20.0f;
				if (fAISafePosition >= nMapWidth - 20.0f) fAISafePosition = nMapWidth - 20.0f;	
				nAINextState = AI_MOVE;
			}
			break;

			case AI_MOVE:
			{
				cWorm *origin = (cWorm*)pObjectUnderControl;
				if (fTurnTime >= 8.0f && origin->px != fAISafePosition)
				{
					// Walk towards target until it is in range					
					if (fAISafePosition < origin->px && bGameIsStable)
					{
						origin->fShootAngle = -3.14159f * 0.6f;
						bAI_Jump = true;
						nAINextState = AI_MOVE;
					}

					if (fAISafePosition > origin->px && bGameIsStable)
					{
						origin->fShootAngle = -3.14159f * 0.4f;
						bAI_Jump = true;
						nAINextState = AI_MOVE;
					}
				}
				else
					nAINextState = AI_CHOOSE_TARGET;
			}
			break;

			case AI_CHOOSE_TARGET: // Worm finished moving, choose target
			{
				bAI_Jump = false;

				// Select Team that is not itself
				cWorm *origin = (cWorm*)pObjectUnderControl;
				int nCurrentTeam = origin->nTeam;
				int nTargetTeam = 0;
				do {
					nTargetTeam = rand() % vecTeams.size();
				} while (nTargetTeam == nCurrentTeam || !vecTeams[nTargetTeam].IsTeamAlive());

				// Aggressive strategy is to aim for opponent unit with most health	
				cWorm *mostHealthyWorm = vecTeams[nTargetTeam].vecMembers[0];
				for (auto w : vecTeams[nTargetTeam].vecMembers)
					if (w->fHealth > mostHealthyWorm->fHealth)
						mostHealthyWorm = w;

				pAITargetWorm = mostHealthyWorm;
				fAITargetX = mostHealthyWorm->px;
				fAITargetY = mostHealthyWorm->py;
				nAINextState = AI_POSITION_FOR_TARGET;						
			}
			break;

			case AI_POSITION_FOR_TARGET: // Calculate trajectory for target, if the worm needs to move, do so
			{
				cWorm *origin = (cWorm*)pObjectUnderControl;
				float dy = -(fAITargetY - origin->py);
				float dx = -(fAITargetX - origin->px);
				float fSpeed = 30.0f;
				float fGravity = 2.0f;

				bAI_Jump = false;

				float a = fSpeed * fSpeed*fSpeed*fSpeed - fGravity * (fGravity * dx * dx + 2.0f * dy * fSpeed * fSpeed);

				if (a < 0) // Target is out of range
				{
					if (fTurnTime >= 5.0f)
					{
						// Walk towards target until it is in range
						if (pAITargetWorm->px < origin->px && bGameIsStable)
						{
							origin->fShootAngle = -3.14159f * 0.6f;
							bAI_Jump = true;
							nAINextState = AI_POSITION_FOR_TARGET;
						}

						if (pAITargetWorm->px > origin->px && bGameIsStable)
						{
							origin->fShootAngle = -3.14159f * 0.4f;
							bAI_Jump = true;
							nAINextState = AI_POSITION_FOR_TARGET;
						}
					}
					else
					{
						// Worm is stuck, so just fire in direction of enemy!
						// Its dangerous to self, but may clear a blockage
						fAITargetAngle = origin->fShootAngle;
						fAITargetEnergy = 0.75f;
						nAINextState = AI_AIM;
					}
				}
				else
				{
					// Worm is close enough, calculate trajectory
					float b1 = fSpeed * fSpeed + sqrtf(a);
					float b2 = fSpeed * fSpeed - sqrtf(a);

					float fTheta1 = atanf(b1 / (fGravity * dx)); // Max Height
					float fTheta2 = atanf(b2 / (fGravity * dx)); // Min Height

					// We'll use max as its a greater chance of avoiding obstacles
					fAITargetAngle = fTheta1 - (dx > 0 ? 3.14159f : 0.0f);
					float fFireX = cosf(fAITargetAngle);
					float fFireY = sinf(fAITargetAngle);

					// AI is clamped to 3/4 power
					fAITargetEnergy = 0.75f;
					nAINextState = AI_AIM;
				}
			}
			break;

			case AI_AIM: // Line up aim cursor
			{
				cWorm *worm = (cWorm*)pObjectUnderControl;
				
				bAI_AimLeft = false;
				bAI_AimRight = false;
				bAI_Jump = false;
				
				if (worm->fShootAngle < fAITargetAngle)
					bAI_AimRight = true;
				else
					bAI_AimLeft = true;

				// Once cursors are aligned, fire - some noise could be
				// included here to give the AI a varying accuracy, and the
				// magnitude of the noise could be linked to game difficulty
				if (fabs(worm->fShootAngle - fAITargetAngle) <= 0.001f)
				{
					bAI_AimLeft = false;
					bAI_AimRight = false;
					fEnergyLevel = 0.0f;
					nAINextState = AI_FIRE;
				}
				else
					nAINextState = AI_AIM;
			}
			break;

			case AI_FIRE:
			{
				bAI_Energise = true;
				bFireWeapon = false;
				bEnergising = true;
				
				if (fEnergyLevel >= fAITargetEnergy)
				{
					bFireWeapon = true;
					bAI_Energise = false;
					bEnergising = false;
					bEnableComputerControl = false;
					nAINextState = AI_ASSESS_ENVIRONMENT;
				}
			}
			break;

			}			
		}

		// Decrease Turn Time
		fTurnTime -= fElapsedTime;

		if (pObjectUnderControl != nullptr)
		{
			pObjectUnderControl->ax = 0.0f;

			if (pObjectUnderControl->bStable)
			{
				if ((bEnablePlayerControl && m_keys[L'Z'].bPressed) || (bEnableComputerControl && bAI_Jump))
				{
					float a = ((cWorm*)pObjectUnderControl)->fShootAngle;

					pObjectUnderControl->vx = 4.0f * cosf(a);
					pObjectUnderControl->vy = 8.0f * sinf(a);
					pObjectUnderControl->bStable = false;

					bAI_Jump = false;
				}

				if ((bEnablePlayerControl && m_keys[L'S'].bHeld) || (bEnableComputerControl && bAI_AimRight))
				{
					cWorm* worm = (cWorm*)pObjectUnderControl;
					worm->fShootAngle += 1.0f * fElapsedTime;
					if (worm->fShootAngle > 3.14159f) worm->fShootAngle -= 3.14159f * 2.0f;
				}

				if ((bEnablePlayerControl && m_keys[L'A'].bHeld) || (bEnableComputerControl && bAI_AimLeft))
				{
					cWorm* worm = (cWorm*)pObjectUnderControl;
					worm->fShootAngle -= 1.0f * fElapsedTime;
					if (worm->fShootAngle < -3.14159f) worm->fShootAngle += 3.14159f * 2.0f;
				}

				if ((bEnablePlayerControl && m_keys[VK_SPACE].bPressed))
				{
					bFireWeapon = false;
					bEnergising = true;
					fEnergyLevel = 0.0f;
				}

				if ((bEnablePlayerControl && m_keys[VK_SPACE].bHeld) || (bEnableComputerControl && bAI_Energise))
				{
					if (bEnergising)
					{
						fEnergyLevel += 0.75f * fElapsedTime;
						if (fEnergyLevel >= 1.0f)
						{
							fEnergyLevel = 1.0f;
							bFireWeapon = true;
						}
					}
				}

				if ((bEnablePlayerControl && m_keys[VK_SPACE].bReleased))
				{
					if (bEnergising)
					{
						bFireWeapon = true;
					}

					bEnergising = false;
				}
			}
		
			if (pCameraTrackingObject != nullptr)
			{
				fCameraPosXTarget = pCameraTrackingObject->px - ScreenWidth() / 2;
				fCameraPosYTarget = pCameraTrackingObject->py - ScreenHeight() / 2;
				fCameraPosX += (fCameraPosXTarget - fCameraPosX) * 15.0f * fElapsedTime;				
				fCameraPosY += (fCameraPosYTarget - fCameraPosY) * 15.0f * fElapsedTime;
			}

			if (bFireWeapon)
			{
				cWorm* worm = (cWorm*)pObjectUnderControl;

				// Get Weapon Origin
				float ox = worm->px;
				float oy = worm->py;

				// Get Weapon Direction
				float dx = cosf(worm->fShootAngle);
				float dy = sinf(worm->fShootAngle);

				// Create Weapon Object
				cMissile *m = new cMissile(ox, oy, dx * 40.0f * fEnergyLevel, dy * 40.0f * fEnergyLevel);
				pCameraTrackingObject = m;
				listObjects.push_back(unique_ptr<cMissile>(m));

				// Reset flags involved with firing weapon
				bFireWeapon = false;
				fEnergyLevel = 0.0f;
				bEnergising = false;
				bPlayerHasFired = true;

				if (rand() % 100 >= 50)
					bZoomOut = true;
			}
		}

		// Clamp map boundaries
		if (fCameraPosX < 0) fCameraPosX = 0;
		if (fCameraPosX >= nMapWidth - ScreenWidth()) fCameraPosX = nMapWidth - ScreenWidth();
		if (fCameraPosY < 0) fCameraPosY = 0;
		if (fCameraPosY >= nMapHeight - ScreenHeight()) fCameraPosY = nMapHeight - ScreenHeight();

		// Do 10 physics iterations per frame
		for (int z = 0; z < 10; z++)
		{
			// Update physics of all physical objects
			for (auto &p : listObjects)
			{
				// Apply Gravity
				p->ay += 2.0f;

				// Update Velocity
				p->vx += p->ax * fElapsedTime;
				p->vy += p->ay * fElapsedTime;

				// Update Position
				float fPotentialX = p->px + p->vx * fElapsedTime;
				float fPotentialY = p->py + p->vy * fElapsedTime;

				// Reset Acceleration
				p->ax = 0.0f;
				p->ay = 0.0f;

				p->bStable = false;
					
				// Collision Check With Map
				float fAngle = atan2f(p->vy, p->vx);
				float fResponseX = 0;
				float fResponseY = 0;
				bool bCollision = false;
				for (float r = fAngle - 3.14159f / 2.0f; r < fAngle + 3.14159f / 2.0f; r += 3.14159f / 4.0f)
				{
					// Iterate through semicircle of objects radius rotated to direction of travel
					float fTestPosX = (p->radius) * cosf(r) + fPotentialX;
					float fTestPosY = (p->radius) * sinf(r) + fPotentialY;

					if (fTestPosX >= nMapWidth) fTestPosX = nMapWidth - 1;
					if (fTestPosY >= nMapHeight) fTestPosY = nMapHeight - 1;
					if (fTestPosX < 0) fTestPosX = 0;
					if (fTestPosY < 0) fTestPosY = 0;

					// Test if any points on semicircle intersect with terrain
					if (map[(int)fTestPosY * nMapWidth + (int)fTestPosX] > 0)
					{
						// Accumulate collision points to give an escape response vector
						// Effectively, normal to the areas of contact
						fResponseX += fPotentialX - fTestPosX;
						fResponseY += fPotentialY - fTestPosY;
						bCollision = true;
					}
				}
					
				float fMagVelocity = sqrtf(p->vx*p->vx + p->vy*p->vy);
				float fMagResponse = sqrtf(fResponseX*fResponseX + fResponseY*fResponseY);

				if (p->px < 0 || p->px > nMapWidth || p->py <0 || p->py > nMapHeight)
					p->bDead = true;

				// Find angle of collision
				if (bCollision)
				{
					p->bStable = true;
					
					// Calculate reflection vector of objects velocity vector, using response vector as normal
					float dot = p->vx * (fResponseX / fMagResponse) + p->vy * (fResponseY / fMagResponse);

					// Use friction coefficient to dampen response (approximating energy loss)
					p->vx = p->fFriction * (-2.0f * dot * (fResponseX / fMagResponse) + p->vx);
					p->vy = p->fFriction * (-2.0f * dot * (fResponseY / fMagResponse) + p->vy);

					//Some objects will "die" after several bounces
					if (p->nBounceBeforeDeath > 0)
					{
						p->nBounceBeforeDeath--;
						p->bDead = p->nBounceBeforeDeath == 0;
						if (p->bDead)
						{
							// Action upon object death
							// = 0 Nothing
							// > 0 Explosion 
							int nResponse = p->BounceDeathAction();
							if (nResponse > 0)
							{
								Boom(p->px, p->py, nResponse);
								pCameraTrackingObject = nullptr;
							}								
						}
					}
				}
				else
				{
					p->px = fPotentialX;
					p->py = fPotentialY;
				}

				// Turn off movement when tiny
				if (fMagVelocity < 0.1f) p->bStable = true;		
			}

			// Remove dead objects from the list, so they are not processed further. As the object
			// is a unique pointer, it will go out of scope too, deleting the object automatically. Nice :-)
			listObjects.remove_if([](unique_ptr<cPhysicsObject> &o){return o->bDead;});
		}

		// Draw Landscape
		if (!bZoomOut)
		{
			for (int x = 0; x < ScreenWidth(); x++)
				for (int y = 0; y < ScreenHeight(); y++)
				{
					switch (map[(y + (int)fCameraPosY)*nMapWidth + (x + (int)fCameraPosX)])
					{
					case -1:Draw(x, y, PIXEL_SOLID, FG_DARK_BLUE); break;
					case -2:Draw(x, y, PIXEL_QUARTER, FG_BLUE | BG_DARK_BLUE); break;
					case -3:Draw(x, y, PIXEL_HALF, FG_BLUE | BG_DARK_BLUE); break;
					case -4:Draw(x, y, PIXEL_THREEQUARTERS, FG_BLUE | BG_DARK_BLUE); break;
					case -5:Draw(x, y, PIXEL_SOLID, FG_BLUE); break;
					case -6:Draw(x, y, PIXEL_QUARTER, FG_CYAN | BG_BLUE); break;
					case -7:Draw(x, y, PIXEL_HALF, FG_CYAN | BG_BLUE); break;
					case -8:Draw(x, y, PIXEL_THREEQUARTERS, FG_CYAN | BG_BLUE); break;
					case 0:	Draw(x, y, PIXEL_SOLID, FG_CYAN); break;
					case 1:	Draw(x, y, PIXEL_SOLID, FG_DARK_GREEN);	break;
					}
				}

			// Draw objects - they draw themselves
			for (auto &p : listObjects)
			{
				p->Draw(this, fCameraPosX, fCameraPosY);

				cWorm* worm = (cWorm*)pObjectUnderControl;
				if (p.get() == worm)
				{
					// Draw Crosshair
					float cx = worm->px + 8.0f * cosf(worm->fShootAngle) - fCameraPosX;
					float cy = worm->py + 8.0f * sinf(worm->fShootAngle) - fCameraPosY;

					Draw(cx, cy, PIXEL_SOLID, FG_BLACK);
					Draw(cx + 1, cy, PIXEL_SOLID, FG_BLACK);
					Draw(cx - 1, cy, PIXEL_SOLID, FG_BLACK);
					Draw(cx, cy + 1, PIXEL_SOLID, FG_BLACK);
					Draw(cx, cy - 1, PIXEL_SOLID, FG_BLACK);

					//DrawString(cx - 3, cy - 3, to_wstring(bEnergyLevel), FG_WHITE);
					for (int i = 0; i < 11 * fEnergyLevel; i++)
					{
						Draw(worm->px - 5 + i - fCameraPosX, worm->py - 12 - fCameraPosY, PIXEL_SOLID, FG_GREEN);
						Draw(worm->px - 5 + i - fCameraPosX, worm->py - 11 - fCameraPosY, PIXEL_SOLID, FG_RED);
					}
				}
			}
		}
		else
		{
			for (int x = 0; x < ScreenWidth(); x++)
				for (int y = 0; y < ScreenHeight(); y++)
				{
					float fx = (float)x/(float)ScreenWidth() * (float)nMapWidth;
					float fy = (float)y/(float)ScreenHeight() * (float)nMapHeight;

					switch (map[((int)fy)*nMapWidth + ((int)fx)])
					{
					case -1:Draw(x, y, PIXEL_SOLID, FG_DARK_BLUE); break;
					case -2:Draw(x, y, PIXEL_QUARTER, FG_BLUE | BG_DARK_BLUE); break;
					case -3:Draw(x, y, PIXEL_HALF, FG_BLUE | BG_DARK_BLUE); break;
					case -4:Draw(x, y, PIXEL_THREEQUARTERS, FG_BLUE | BG_DARK_BLUE); break;
					case -5:Draw(x, y, PIXEL_SOLID, FG_BLUE); break;
					case -6:Draw(x, y, PIXEL_QUARTER, FG_CYAN | BG_BLUE); break;
					case -7:Draw(x, y, PIXEL_HALF, FG_CYAN | BG_BLUE); break;
					case -8:Draw(x, y, PIXEL_THREEQUARTERS, FG_CYAN | BG_BLUE); break;
					case 0:	Draw(x, y, PIXEL_SOLID, FG_CYAN);break;
					case 1:	Draw(x, y, PIXEL_SOLID, FG_DARK_GREEN);	break;
					}
				}

			for (auto &p : listObjects)
				p->Draw(this, p->px-(p->px / (float)nMapWidth) * (float)ScreenWidth(), 
					p->py-(p->py / (float)nMapHeight) * (float)ScreenHeight(), true);
		}

	
		// Check For game state stability
		bGameIsStable = true;
		for (auto &p : listObjects)
			if (!p->bStable)
			{
				bGameIsStable = false;
				break;
			}

		// This little marker is handy for debugging
		//if (bGameIsStable)
		//	Fill(2, 2, 6, 6, PIXEL_SOLID, FG_RED);

		// Draw Team Health Bars
		for (size_t t = 0; t < vecTeams.size(); t++)
		{
			float fTotalHealth = 0.0f;
			float fMaxHealth = (float)vecTeams[t].nTeamSize;
			for (auto w : vecTeams[t].vecMembers) // Accumulate team health
				fTotalHealth += w->fHealth;

			int cols[] = { FG_RED, FG_BLUE, FG_MAGENTA, FG_GREEN };
			Fill(4, 4 + t * 4, (fTotalHealth / fMaxHealth) * (float)(ScreenWidth() - 8) + 4, 4 + t * 4 + 3, PIXEL_SOLID, cols[t]);
		}

		// Mystery Code !! Displays Seven-Segment Display for countdown timer
		// Display Turn Time - Check out the discord #challenges for explanation!
		// Thanks to all those awesome discord chatters who took part! I've kept
		// this minimised and obfuscated to maintain the spirit of the challenge!
		if (bShowCountDown)
		{
			wchar_t d[]=L"w$]m.k{\%\x7Fo";int tx=4,ty=vecTeams.size()*4+8;
			for(int r=0;r<13;r++){for(int c=0;c<((fTurnTime<10.0f)?1:2);c++){
			int a=to_wstring(fTurnTime)[c]-48;if(!(r%6)){DrawStringAlpha(tx,
			ty,wstring((d[a]&(1<<(r/2))?L" #####  ":L"        ")),FG_BLACK);
			tx+=8;}else{DrawStringAlpha(tx,ty,wstring((d[a]&(1<<(r<6?1:4))?
			L"#     ":L"      ")),FG_BLACK);tx+=6;DrawStringAlpha(tx,ty,wstring
			((d[a]&(1<<(r<6?2:5))?L"# ":L"  ")),FG_BLACK);tx+=2;}}ty++;tx=4;}
		}

		// Update State Machine
		nGameState = nNextState;
		nAIState = nAINextState;

		return true;
	}


	void Boom(float fWorldX, float fWorldY, float fRadius)
	{
		auto CircleBresenham = [&](int xc, int yc, int r)
		{
			int x = 0;
			int y = r;
			int p = 3 - 2 * r;
			if (!r) return;

			auto drawline = [&](int sx, int ex, int ny) 
			{
				for (int i = sx; i < ex; i++)
					if(ny >=0 && ny < nMapHeight && i >=0 && i < nMapWidth)
						map[ny*nMapWidth + i] = 0; 
			};

			while (y >= x) // only formulate 1/8 of circle
			{
				//Filled Circle
				drawline(xc - x, xc + x, yc - y);
				drawline(xc - y, xc + y, yc - x);
				drawline(xc - x, xc + x, yc + y);
				drawline(xc - y, xc + y, yc + x);
				if (p < 0) p += 4 * x++ + 6;
				else p += 4 * (x++ - y--) + 10;
			}
		};

		int bx = (int)fWorldX;
		int by = (int)fWorldY;

		// Erase Terrain to form crater
		CircleBresenham(fWorldX, fWorldY, fRadius);

		// Shockwave other entities in range
		for (auto &p : listObjects)
		{
			float dx = p->px - fWorldX;
			float dy = p->py - fWorldY;
			float fDist = sqrt(dx*dx + dy*dy);
			if (fDist < 0.0001f) fDist = 0.0001f;
			if (fDist < fRadius)
			{
				p->vx = (dx / fDist) * fRadius;
				p->vy = (dy / fDist) * fRadius;
				p->Damage(((fRadius - fDist) / fRadius) * 0.8f); // Corrected ;)
				p->bStable = false;				
			}
		}

		// Launch debris
		for (int i = 0; i < (int)fRadius; i++)
			listObjects.push_back(unique_ptr<cDebris>(new cDebris(fWorldX, fWorldY)));
	}

	// Taken from Perlin Noise Video https://youtu.be/6-0UaeJBumA
	void PerlinNoise1D(int nCount, float *fSeed, int nOctaves, float fBias, float *fOutput)
	{
		// Used 1D Perlin Noise
		for (int x = 0; x < nCount; x++)
		{
			float fNoise = 0.0f;
			float fScaleAcc = 0.0f;
			float fScale = 1.0f;

			for (int o = 0; o < nOctaves; o++)
			{
				int nPitch = nCount >> o;
				int nSample1 = (x / nPitch) * nPitch;
				int nSample2 = (nSample1 + nPitch) % nCount;
				float fBlend = (float)(x - nSample1) / (float)nPitch;
				float fSample = (1.0f - fBlend) * fSeed[nSample1] + fBlend * fSeed[nSample2];
				fScaleAcc += fScale;
				fNoise += fSample * fScale;
				fScale = fScale / fBias;
			}

			// Scale to seed range
			fOutput[x] = fNoise / fScaleAcc;
		}
	}

	void CreateMap()
	{
		// Used 1D Perlin Noise
		float *fSurface = new float[nMapWidth];
		float *fNoiseSeed = new float[nMapWidth];
		for (int i = 0; i < nMapWidth; i++) 
			fNoiseSeed[i] = (float)rand() / (float)RAND_MAX;

		fNoiseSeed[0] = 0.5f;
		PerlinNoise1D(nMapWidth, fNoiseSeed, 8, 2.0f, fSurface);

		for (int x = 0; x < nMapWidth; x++)
			for (int y = 0; y < nMapHeight; y++)
			{
				if (y >= fSurface[x] * nMapHeight)
					map[y * nMapWidth + x] = 1;
				else
				{
					// Shade the sky according to altitude - we only do top 1/3 of map
					// as the Boom() function will just paint in 0 (cyan)
					if ((float)y < (float)nMapHeight / 3.0f)
						map[y * nMapWidth + x] = (-8.0f * ((float)y / (nMapHeight / 3.0f))) -1.0f;
					else
						map[y * nMapWidth + x] = 0;
				}
			}
	
		delete[] fSurface;
		delete[] fNoiseSeed;
	}


};



int main()
{
	OneLoneCoder_Worms game;
	game.ConstructConsole(256, 160, 6, 6);
	game.Start();
	return 0;
}