/*
* Copyright 2023, Ali Arya
* Anyone Can Code: Algorithmic Thinking
* Example 6.12c
*/

#include "Main.h"

#define SCREEN_W 640
#define SCREEN_H 480
#define PLAYER_W 48
#define GROUND_LEVEL 410		//based on the map
#define GRAVITY_ACCELERATION 1  //not the real 9.8. Proportional to frame rate
#define NUM_FRAMES 3

class GameObject
{
public:
	int x;
	int y;
	SDLX_Bitmap* shape;
	bool visible;
	int vy;
	int vx;

	GameObject()
	{
		shape = NULL;
		x = y = vx = vy = 0;
		visible = false;
	}
	void Init(char* fname, int xx, int yy)
	{
		shape = SDLX_LoadBitmap(fname);
		x = xx;
		y = yy;
		visible = true;
	}
	virtual void Move()
	{
		x += vx;
		y += vy;
	}
	virtual void Draw(int offset = 0)
	{
		if (visible)
			SDLX_DrawBitmap(shape, x - offset, y);
	}
	bool Collision(GameObject* obj)
	{
		int distanceX = abs(x - obj->x);
		int distanceY = abs(y - obj->y);
		if (distanceX < 10 && distanceY < 10)
			return true;
		else
			return false;
	}
};
class AnimatedObject : public virtual GameObject  // add virtual to avoid "dreaded diamond" problem
{
public:
	//for animation
	SDLX_Bitmap* frames[NUM_FRAMES];
	int currentFrame;
	AnimatedObject()
	{
		currentFrame = 0;
		for (int i = 0; i < NUM_FRAMES; i++)
			frames[i] = NULL;
	}
	void InitFrame(char* fname, int f, bool useMask = false, SDLX_Color* mask = NULL)
	{
		frames[f] = SDLX_LoadBitmap(fname, useMask, mask);
	}
	void AdvanceAnimation()
	{
		currentFrame++;
		if (currentFrame == NUM_FRAMES)
			currentFrame = 0;
	}
	void Draw(int offset = 0)
	{
		if (visible)
			SDLX_DrawBitmap(frames[currentFrame], x - offset, y);
	}
	void Move()
	{
		GameObject::Move();

		if (vx != 0)
			AdvanceAnimation();
	}
};
class Player : public AnimatedObject
{
public:
	//for jumping
	int y0;			//Y at the start of the jump
	int v0; 	//VY at the start of the jump
	int t;			//jumping time
	bool jumping;

	Player()
	{
		y0 = v0 = t = 0;
		jumping = false; 
	}
	void Move()
	{
		//animation
		GameObject::Move();
		if (vx != 0)
			AdvanceAnimation();

		//jump/fall
		int gorund = GROUND_LEVEL-frames[0]->height;  //can change along the map

		if (jumping)
		{
			int newy = y0 + v0 * t + GRAVITY_ACCELERATION * t * t / 2; //in jumping sy is the "initial" velocity
			if (newy > gorund)
			{
				y = gorund;
				jumping = false;
			}
			else
			{
				y = newy;
			}
			t++;
		}
	}
};
class Enemy : public virtual GameObject
{
public:
	Enemy()
	{

	}
	void Move()
	{
		vx = rand() % 5 - 2;
		vy = rand() % 5 - 2;

		GameObject::Move();
	}
};
class AnimatedEnemy : public AnimatedObject, public Enemy
{
public:
	AnimatedEnemy() { }
	void Move() {
		//from Enemy
		vx = rand() % 5 - 2;
		vy = rand() % 5 - 2;

		//from base
		GameObject::Move();

		//from AnimatedObject
		if (vx != 0)
			AdvanceAnimation();
	}
};

class Game
{
public:
	Player player;
	GameObject prize;
	Enemy enemy;
	AnimatedEnemy animEnemy;
	int offset;
	SDLX_Bitmap* map;
	int ground_level;
	int end_wait;
	bool quit;
	bool win;
	bool left;
	bool right;

	Game()	//same as Init()
	{
		//init SDL library and graphics window
		SDLX_Init("Simple 2D Game", 640, 480, true);

		//game map (background)
		map = SDLX_LoadBitmap("map.bmp", false, NULL); //do not use any mask for transparent color

		//control variables
		quit = false;
		win = false;
		right = false;
		left = false;
		offset = 0;
		ground_level = 380;
		end_wait = 0;

		//game objects
		player.Init((char*)"Player0.bmp", SCREEN_W / 2 - PLAYER_W / 2, GROUND_LEVEL-152);	//middle of screen
		SDLX_Color mask;
		mask.r = mask.b = 0;
		mask.g = 128;
		mask.a = 255;
		player.InitFrame((char*)"Player0.bmp", 0, true, &mask);
		player.InitFrame((char*)"Player1.bmp", 1, true, &mask);
		player.InitFrame((char*)"Player2.bmp", 2, true, &mask);
		enemy.Init((char*)"Enemy.bmp", 0.75 * SCREEN_W, GROUND_LEVEL-37);			//between player and end of screen
		prize.Init((char*)"Prize.bmp", 1.5 * SCREEN_W, GROUND_LEVEL-37);				//off screen
		//animated enemy
		animEnemy.Init((char*)"Player0.bmp", SCREEN_W / 3 - PLAYER_W / 2, GROUND_LEVEL - 152);	//middle of screen
		animEnemy.InitFrame((char*)"Player0.bmp", 0, true, &mask);
		animEnemy.InitFrame((char*)"Player1.bmp", 1, true, &mask);
		animEnemy.InitFrame((char*)"Player2.bmp", 2, true, &mask);
	}
	void Update()
	{
		//check if we hit the prize
		if (player.Collision(&prize))
		{
			quit = true;
			win = true;
			prize.visible = false;
			end_wait = 3000;
		}
		//check if we hit the enemy
		if (player.Collision(&enemy))
		{
			player.visible = false;
			quit = true;
			win = false;
			end_wait = 3000;
		}

		//check if there is any movement
		//player controlled by keyboard
		SDLX_Event e;
		//keyboard events
		if (SDLX_PollEvent(&e))
		{
			if (e.type == SDL_KEYDOWN)
			{
				if (e.keycode == SDLK_ESCAPE)
					quit = true;
				//update player  
				if (e.keycode == SDLK_RIGHT)
					right = true;
				if (e.keycode == SDLK_LEFT)
					left = true;
				if (e.keycode == SDLK_SPACE && player.jumping == false)
				{
					player.jumping = true;
					player.y0 = player.y;
					player.t = 0;
					player.v0 = -25;
				}
			}
			if (e.type == SDL_KEYUP)
			{
				if (e.keycode == SDLK_RIGHT)
					right = false;
				if (e.keycode == SDLK_LEFT)
					left = false;
			}
		}

		//update player
		player.vx = 0;
		if (right)
			player.vx += 3;
		if (left)
			player.vx -= 3;
		player.Move();

		//update enemy
		enemy.Move();
		animEnemy.Move();

		offset = player.x - (SCREEN_W / 2 - PLAYER_W / 2); //movement from original X
	}
	void Draw()
	{
		//note the order (last one will be drawn on top)
		SDLX_DrawBitmap(map, -1 * offset, 0);
		prize.Draw(offset);
		enemy.Draw(offset);
		animEnemy.Draw(offset);
		player.Draw(offset);

		//Update the screen
		SDLX_Render();
	}
	void Run()
	{
		while (!quit)
		{
			Update();
			Draw();
			SDLX_Delay(1);
		}

		//done
		SDLX_Delay(end_wait);
	}
};

int main(int argc, char* argv[])
{
	/////////////////////////////////////////////////////////////////
	// INIT part of the game
	// things that are done only once at the start (initialization)
	/////////////////////////////////////////////////////////////////

	Game game;

	/////////////////////////////////////////////////////////////////
	//--------Main game Loop---------- -
	// things that are done repeatedly
	/////////////////////////////////////////////////////////////////

	game.Run();


	return 0;
}