//  Title: Fish in the Water
// Author: Joe Klemmer
// Course: SG210-D1
//   File: "Fish in the Water" main program

#include "game.h"
#include <cstring>

//background image
LPDIRECT3DSURFACE9 back;

//sprite handler
LPD3DXSPRITE sprite_handler;

// load the font sprite
LPDIRECT3DTEXTURE9 font_image;

// ship sprite
LPDIRECT3DTEXTURE9 ship_image;
SPRITE ship;

// sub sprite
LPDIRECT3DTEXTURE9 sub_image;
SPRITE sub;

// torpedo sprite
LPDIRECT3DTEXTURE9 torpedo_image;
SPRITE torpedo;

// explosion sprite
LPDIRECT3DTEXTURE9 explosion_image;
SPRITE explosion;

// the wave sound pointers
CSound *sound_explosion;
CSound *sound_music;

//misc
int holdx, holdy;
long start = GetTickCount();
HRESULT result;

// was the torpedo fired?
bool torpedoAway;
// should we display the explosion images?
bool explosionAnimation;
// did we manually exit the game?
bool abortGame;

// counter to keep track of how many frames
// control has passed through the Game_Run routine
int frameCounter;
// counter to hold the number of torpedos
// and the number of ships and subs
int torpedoCounter;
int shipCounter;
int subCounter;

// game score
int score;

// timer
int cycleTimer;

// keep track of the game state (we hope)
enum GAMESTATE
{
    PAUSE = 0,
    RUNNING = 1,
    GAMEOVER = 2,
	STARTUP = 3
};
GAMESTATE state = STARTUP;

// this is a set of functions to display a font 
// that can be used for text output on the screen
// this code is copied, with some modifications,
// from the "Bash" project in Johnathan Harbour's
// book "Beginning Game Program/2e"
void drawChar(int x, int y, char c, LPDIRECT3DTEXTURE9 lpfont, 
			  int cols, int width, int height)
{
	sprite_handler->Begin(D3DXSPRITE_ALPHABLEND);

	//create vector to update sprite position
	D3DXVECTOR3 position((float)x, (float)y, 0.0f);

	//ASCII code of smallfont.bmp starts with 32 (space)
	int index = c - 32;

	//configure the rect
	RECT srcRect;
	srcRect.left = (index % cols) * width;
	srcRect.top = (index / cols) * height;
	srcRect.right = srcRect.left + width;
	srcRect.bottom = srcRect.top + height;

	//draw the sprite
	sprite_handler->Draw(
		lpfont, 
		&srcRect,
		NULL,
		&position,
		D3DCOLOR_XRGB(255,255,255));

	sprite_handler->End();
}

void drawText(int x, int y, char *text, LPDIRECT3DTEXTURE9 lpfont, 
			  int cols, int width, int height)
{
	for (unsigned int n = 0; n < strlen(text); n++)
	{
		drawChar(x + n * 8, y, text[n], lpfont, cols, width, height);
	}
}

void displayStats(void)
{
	static char s[80]="";

	// title (maybe)
	sprintf(s, APPTITLE);
	drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, 5, s, font_image, 20, 8, 12);

	// ship counter
	sprintf(s, "SHIPS: %d", shipCounter);
	drawText(5, SCREEN_HEIGHT - 75, s, font_image, 20, 8, 12);

	// timer
	sprintf(s, "TIMER: %d", cycleTimer/1000);
	drawText(5, SCREEN_HEIGHT - 60, s, font_image, 20, 8, 12);

	// score
	sprintf(s, "SCORE: %d", score);
	drawText(5, SCREEN_HEIGHT - 45, s, font_image, 20, 8, 12);

	// lives
	sprintf(s, "LIVES: %d", subCounter);
	drawText(5, SCREEN_HEIGHT - 30, s, font_image, 20, 8, 12);
	
	// torpedos
	sprintf(s, "TORPEDOS: %d", torpedoCounter);
	drawText(5, SCREEN_HEIGHT - 15, s, font_image, 20, 8, 12);
}

void displayInfo(void)
{
	static char s[80]="";

	sprintf(s, "Press \"P\" to pause the game and \"U\" to resume playing (unpause)");
	drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 - 100, s, font_image, 20, 8, 12);
}

void displaySplash(void)
{
	static char s[80]="";

	sprintf(s, "Welcome to \"Fish in the Water\"");
	drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 - 120, s, font_image, 20, 8, 12);

	sprintf(s, "Your mission is to sink the ships with your torpedos.");
	drawText(128, SCREEN_HEIGHT / 2 - 90, s, font_image, 20, 8, 12);

	sprintf(s, "Shoot torpedos using the \"Space Bar\".");
	drawText(128, SCREEN_HEIGHT / 2 - 70, s, font_image, 20, 8, 12);

	sprintf(s, "To quit the game before it is finished, press the \"Escape\" key.");
	drawText(128, SCREEN_HEIGHT / 2 - 50, s, font_image, 20, 8, 12);

	sprintf(s, "Move your sub with the left and right arrow keys.");
	drawText(128, SCREEN_HEIGHT / 2 - 30, s, font_image, 20, 8, 12);

	sprintf(s, "You can also use the mouse to move the sub and left button to fire.");
	drawText(128, SCREEN_HEIGHT / 2 - 10, s, font_image, 20, 8, 12);

	sprintf(s, "To start playing, press the \"U\" key now.");
	drawText(128, SCREEN_HEIGHT / 2 + 10, s, font_image, 20, 8, 12);

	sprintf(s, "GOOD LUCK!");
	drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 90, s, font_image, 20, 8, 12);
}

int loadFont(void)
{
	HRESULT result;

	//create the sprite handler
	result = D3DXCreateSprite(d3ddev, &sprite_handler);
	if (result != D3D_OK)
		return 0;

	//load the font
	font_image = LoadTexture(FONT_SM, D3DCOLOR_XRGB(0,0,0));
	if (font_image == NULL)
		return 0;

	return 1;
}

// this is an attempt to end the game cleanly in fullscreen mode
void Game_Status(HWND hwnd)
{
	static char s[80]="";

	// user quit game
	if (abortGame)
	{
		sprintf(s, "ABORT | You have chosen to quit the game");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2, s, font_image, 20, 8, 12);
		sprintf(s, "Press \"Return\" to exit");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 20, s, font_image, 20, 8, 12);

		state = GAMEOVER;
	}

	// all ships sunk
	if (shipCounter == 0)
	{
		sprintf(s, "GAME OVER | Congratulations! You sunk all the ships!");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2, s, font_image, 20, 8, 12);
		sprintf(s, "You scored %d points", score);
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 20, s, font_image, 20, 8, 12);
		sprintf(s, "Press \"Return\" to exit the game");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 40, s, font_image, 20, 8, 12);

		state = GAMEOVER;
	}

	// no more lives left
	if (subCounter == 0)
	{
		sprintf(s, "GAME OVER | You have run out of lives");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2, s, font_image, 20, 8, 12);
		sprintf(s, "You scored %d points", score);
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 20, s, font_image, 20, 8, 12);
		sprintf(s, "Press \"Return\" to exit the game");
		drawText(SCREEN_WIDTH / 2 - strlen(s) * 8 / 2, SCREEN_HEIGHT / 2 + 40, s, font_image, 20, 8, 12);

		state = GAMEOVER;
	}

	// pause
	if (Key_Down(DIK_P))
		state = PAUSE;

	// unpause
	if (Key_Down(DIK_U))
		state = RUNNING;
}

//initializes the game
int Game_Init(HWND hwnd)
{
	// initialize all variables to defaults
	torpedoAway = false;
	explosionAnimation = false;
	abortGame = false;
	torpedoCounter = 6;
	shipCounter = 15;
	subCounter = 3;
	frameCounter = 0;
	score = 0;
	cycleTimer = MAX_TIME;

	// seed random number generator
	//
	// this specific code is from Dr. Dobb's Journal's C Snippet #10
	// available at http://www.drdobbs.com/cpp/222600299 
	// the purpose of this code is to seed with a more random random seed
	// * Joe Klemmer
	srand(((unsigned int)time(NULL)) | 1);

	// make sure the font bitmap was loaded
	if (!loadFont()) return 0;

	//initialize mouse
	if (!Init_Mouse(hwnd))
	{
		MessageBox(hwnd, "Error initializing the mouse", "Error", MB_OK);
		return 0;
	}

	//initialize keyboard
	if (!Init_Keyboard(hwnd))
	{
		MessageBox(hwnd, "Error initializing the keyboard", "Error", MB_OK);
		return 0;
	}

	//create sprite handler object
	result = D3DXCreateSprite(d3ddev, &sprite_handler);
	if (result != D3D_OK)
	{
		return 0;
	}

	//load the background image
	back = LoadSurface(BACKGROUND, NULL);
	if (back == NULL)
	{
		return 0;
	}

	// load the explosion sprite
	explosion_image = LoadTexture(EXPLOSION, D3DCOLOR_XRGB(0,255,0));
	if (explosion_image == NULL)
	{
		return 0;
	}

	// set the explosion's properties
	explosion.x = SCREEN_WIDTH + 50;
	explosion.y = SCREEN_HEIGHT + 50;
	explosion.width = 66;
	explosion.height = 64;
	explosion.movex = 0;
	explosion.movey = 0;
	explosion.animcount = 0;
	explosion.animdelay = 5;
	explosion.curframe = 0;
	explosion.lastframe = 5;

	// load the sub sprite
	sub_image = LoadTexture(SUB, D3DCOLOR_XRGB(255,255,255));
	if (sub_image == NULL)
	{
		return 0;
	}

	// set the sub's properties
	sub.x = (SCREEN_WIDTH / 2) - 50;
	sub.y = SCREEN_HEIGHT - 40;
	sub.width = 27;
	sub.height = 95;
	sub.movex = 0;
	sub.movey = 8;

	// load the torpedo sprite
	torpedo_image = LoadTexture(TORPEDO, D3DCOLOR_XRGB(255,0,255));
	if (torpedo_image == NULL)
	{
		return 0;
	}

	// set the torpedo's properties
	torpedo.x = (SCREEN_WIDTH / 2) - 50;
	torpedo.y = SCREEN_HEIGHT + 35;
	torpedo.width = 14;
	torpedo.height = 34;
	torpedo.movex = 0;
	torpedo.movey = 8;

	// load both ship sprites just to make sure they load correctlly
	ship_image = LoadTexture(SHIPL, D3DCOLOR_XRGB(255,0,255));
	if (ship_image == NULL)
	{
		return 0;
	}
	ship_image = LoadTexture(SHIPR, D3DCOLOR_XRGB(255,0,255));
	if (ship_image == NULL)
	{
		return 0;
	}

	// set the ship's properties
	ship.x = 1;
	ship.y = rand()%(SCREEN_HEIGHT - 100);
	ship.width = 48;
	ship.height = 22;
	ship.movex = 2;
	ship.movey = 3;

	// load explosion wave file
	sound_explosion = LoadSound(EXPLODE);
	if (sound_explosion == NULL)
	{
		return 0;
	}

	// 1) load the background music file
	// 2) play background music at a low volume
	// 3) loop the track until the game is over
	sound_music = LoadSound(MUSIC);
	if (sound_music == NULL)
	{
		return 0;
	}
	sound_music->Play(0, DSBPLAY_LOOPING, 4);

	//return okay
	return 1;
}

// check for sprite collisions
// this is a very nice facility
int Collision(SPRITE sprite1, SPRITE sprite2)
{
    RECT rect1;
    rect1.left = sprite1.x+1;
    rect1.top = sprite1.y+1;
    rect1.right = sprite1.x + sprite1.width-1;
    rect1.bottom = sprite1.y + sprite1.height-1;

    RECT rect2;
    rect2.left = sprite2.x+1;
    rect2.top = sprite2.y+1;
    rect2.right = sprite2.x + sprite2.width-1;
    rect2.bottom = sprite2.y + sprite2.height-1;

    RECT dest;
    return IntersectRect(&dest, &rect1, &rect2);
}

//the main game loop
void Game_Run(HWND hwnd)
{
	//make sure the Direct3D device is valid
	if (d3ddev == NULL)
	{
		return;
	}

	//update mouse and keyboard
	Poll_Mouse();
	Poll_Keyboard();

	// if the game is not paused or ended it is running
	if (state == RUNNING)
	{
		// start the timer
		cycleTimer--;

		// move the sub with the mouse
		sub.x += Mouse_X();

		// if all the torpedos miss or the timer runs out the sub loses a life.
		// when all the lives are gone the game is over.
		if (cycleTimer == 0 || torpedoCounter == 0)
		{
			// decrement the sub counter
			subCounter--;
			// reset timer and torpedos to defaults
			cycleTimer = MAX_TIME;
			torpedoCounter = 6;
		}

		//after short delay, ready for next frame?
		//this keeps the game running at a steady frame rate
		if (GetTickCount() - start >= 30)
		{
			//reset timing
			start = GetTickCount();

			// this little ditty is setup so that every FRAMES passes
			// through the sceen the ships speed will randomly change
			// (this could also be chosen at random, if I'm feeling
			// adventurous enough)
			if (frameCounter++ == FRAMES)
			{
				// reset the frame counter
				frameCounter = 0;

				// change the speed the ship moves along the x-axis
				// make sure things are moving in the proper direction
				if (ship.movex > 0)
				{
					ship.movex = rand()%4 + 2;
				}
				else
				{
					ship.movex = (rand()%4 + 2) * -1;
				}
			}

			//move the ship sprite if there is no explosion
			if (!explosionAnimation)
			{
				ship.x += ship.movex;
				ship.y += ship.movey;
			}

			// turn the ship at screen edges
			if (ship.x > SCREEN_WIDTH - ship.width)
			{
				ship.movex *= -1;
				ship_image = LoadTexture(SHIPL, D3DCOLOR_XRGB(255,0,255));
			}
			else if (ship.x < 0)
			{
				ship.movex *= -1;
				ship_image = LoadTexture(SHIPR, D3DCOLOR_XRGB(255,0,255));
			}

			// keep the ship inside the window height
			if (ship.y > (SCREEN_HEIGHT - ship.height) - 100)
			{
				ship.movey *= -1;
			}
			else if (ship.y < 0)
			{
				ship.movey *= -1;
			}

			// keep the sub inside the screen
			if (sub.x > SCREEN_WIDTH - sub.width)
			{
				sub.x = SCREEN_WIDTH - sub.width;
			}
			else if (sub.x < 0)
			{
				sub.x = 0;
			}

			// the torpedo was fired
			if (torpedoAway)
			{
				torpedo.y -= torpedo.movey;
			}

			// if the torpedo completely misses the ship
			// reset its position, decrement the counter
			// and set torpedo away to false
			if (torpedo.y < -35)
			{
				torpedoAway = false;
				torpedoCounter--;
				torpedo.x = (SCREEN_WIDTH / 2) - 52;
				torpedo.y = SCREEN_HEIGHT + 35;
			}

			// the fish is in the water!
			if ((Key_Down(DIK_SPACE) || Mouse_Button(0)) && !torpedoAway)
			{
				torpedoAway = true;
				torpedo.x = sub.x;
			}

			//check for left arrow key
			if (Key_Down(DIK_LEFT))
			{
				sub.x -= 10;
			}

			//check for right arrow key
			if (Key_Down(DIK_RIGHT))
			{
				sub.x += 10;
			}

			// see if torpedo hit the ship
			// if so, update the score and setup
			// for the exploasion
			if (Collision(torpedo, ship))
			{
				score += (10 + (cycleTimer / 1000) + (torpedoCounter * 2));
				explosionAnimation = true;
				explosion_image = LoadTexture(EXPLOSION, D3DCOLOR_XRGB(255,0,255));
			}

			// it's a hit so sink the ship
			if (explosionAnimation)
			{
				// this will play the explosion sound,
				// run through the explosion animation
				// and reset the torpedo & ship
				if (explosion.curframe <= explosion.lastframe)
				{
					PlaySound(sound_explosion);
					torpedoAway = false;
					ship_image = NULL;
					torpedo.y = SCREEN_HEIGHT + 35;

					// set explosion .x and .y
					explosion.x = ship.x;
					explosion.y = ship.y;

					// this slows down the explosion animation
					// so that it looks better
					if (++explosion.animcount > explosion.animdelay)
					{
						explosion.animcount = 0;
						explosion.curframe++;
					}
				}
				else
				{
					// reset explosion
					explosion.curframe = 0;
					explosion_image = NULL;
					explosionAnimation = false;

					// reset things to there defaults
					cycleTimer = MAX_TIME;
					torpedoCounter = 6;
					shipCounter--;
					ship.y = rand()%(SCREEN_HEIGHT - 100);

					// this starts the ship on either side of the screen
					// and makes sure that the ship is facing in the propper
					// direction
					if (rand()%2 == 0)
					{
						ship.x = SCREEN_WIDTH - ship.width;
						ship_image = LoadTexture(SHIPL, D3DCOLOR_XRGB(255,0,255));
					}
					else
					{
						ship.x = 1;
						ship_image = LoadTexture(SHIPR, D3DCOLOR_XRGB(255,0,255));
					}
				}
			}
		}
	}

	//start rendering the display
	if (d3ddev->BeginScene())
	{
		// position vector
		D3DXVECTOR3 position(0,0,0);   

		//erase the entire background
		d3ddev->StretchRect(back, NULL, backbuffer, NULL, D3DTEXF_NONE);

		//start sprite handler
		sprite_handler->Begin(D3DXSPRITE_ALPHABLEND);

		//draw the ship
		position.x = (float)ship.x;
		position.y = (float)ship.y;
		sprite_handler->Draw(
			ship_image, 
			NULL,
			NULL,
			&position,
			D3DCOLOR_XRGB(255,255,255));

		//draw the torpedo
        position.x = (float)torpedo.x;
        position.y = (float)torpedo.y;
        sprite_handler->Draw(
            torpedo_image,
            NULL,
            NULL,
            &position,
            D3DCOLOR_XRGB(255,255,255));

		//draw the sub
        position.x = (float)sub.x;
        position.y = (float)sub.y;
        sprite_handler->Draw(
            sub_image,
            NULL,
            NULL,
            &position,
            D3DCOLOR_XRGB(255,255,255));

		// configure the rect for the explosion source tile
		RECT srcRect;
		int columns = 7;
		srcRect.left = (explosion.curframe % columns) * explosion.width;
		srcRect.top = (explosion.curframe / columns) * explosion.height;
		srcRect.right = srcRect.left + explosion.width;
		srcRect.bottom = srcRect.top + explosion.height;

		// draw the explosion
        position.x = (float)explosion.x;
        position.y = (float)explosion.y;
        sprite_handler->Draw(
            explosion_image,
            &srcRect,
            NULL,
            &position,
            D3DCOLOR_XRGB(255,255,255));

		// display the stats/information
		displayStats();

        //stop drawing
        sprite_handler->End();

		// start up splash screen
		if (state == STARTUP)
			displaySplash();

		// are we still playing the game?
		// check for exit conditions
		Game_Status(hwnd);

		// display the pause message
		if (state == PAUSE)
		{
			displayInfo();
		}

		// user quit game
		if (Key_Down(DIK_ESCAPE) ||
			Mouse_Button(1))
		{
			abortGame = true;
			state = GAMEOVER;
		}

		// exit game?
		if (state == GAMEOVER)
		{
			if (Key_Down(DIK_RETURN))
			{
				sound_music->Stop();
				PostMessage(hwnd, WM_DESTROY, 0, 0);
			}
		}

		//stop rendering
        d3ddev->EndScene();
    }

    //display the back buffer on the screen
    d3ddev->Present(NULL, NULL, NULL, NULL);
}

//frees memory and cleans up before the game ends
void Game_End(HWND hwnd)
{
    if (ship_image != NULL)
        ship_image->Release();

    if (torpedo_image != NULL)
		torpedo_image->Release();

    if (sub_image != NULL)
		sub_image->Release();

    if (explosion_image != NULL)
		explosion_image->Release();

	if (font_image != NULL)
		font_image->Release();

	if (back != NULL)
        back->Release();

    if (sprite_handler != NULL)
        sprite_handler->Release();
}