//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "WBMain.h"
//---------------------------------------------------------------------------

#include "mmsystem.h"  // PlaySound to play .wav files
#include "PRNG.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

//******************************************************************************
//******************************************************************************
//******************************************************************************
// Global stuff
//******************************************************************************

TfrmMain *frmMain;

// Set up some colors for the game
TColor water_color = clAqua;
TColor hit_color = clRed;
TColor miss_color = clBlue;
TColor boat_color = water_color;
TColor hidden_boat_color = clYellow;

int RandomInt(int low, int high)
{
	return rand() % (high - low + 1) + low;
}

/*
	Constructor.
*/
__fastcall TfrmMain::TfrmMain(TComponent* Owner) : TForm(Owner)

{
	pnlPier->DoubleBuffered = true;  // prevent flicker
	gridPier->DoubleBuffered = true; // prevent flicker
	Init();
	BoatsVisible = false;
}
//---------------------------------------------------------------------------

/*
	Plays a sound based on the type of "hit" that was made.
*/
void TfrmMain::PlayGameSound(CS170::WarBoats::ShotResult hittype)
{
		// Sound enabled?
	
	if (chkSound->Checked)
	{
		if (hittype == CS170::WarBoats::srHIT)
			PlaySound(HitSound, HInstance, SND_RESOURCE + SND_ASYNC);
		else if (hittype == CS170::WarBoats::srMISS)
			PlaySound(MissSound, HInstance, SND_RESOURCE + SND_ASYNC);
	}
}

/*
	Initializes the state of the game and the UI
*/
void TfrmMain::Init(void)
{
	for (int i = 0; i < MaxRows; i++)
		for (int j = 0; j < MaxColumns; j++)
			CellState[i][j] = 0;

	ShotsTaken = 0;
	GameOver = false;

	Rows = spnRows->Position;
	Columns = spnColumns->Position;

	RowCurr = -1;
	ColumnCurr = -1;

	RowPrev = -1;
	ColumnPrev = -1;

	AdjustGrid();
	UpdateCurrentCell();
}

/*
	Displays current row/column on the screen
*/
void TfrmMain::UpdateCurrentCell(void)
{
		// Invalid location, erase the display
	if (OffGrid || ColumnCurr == -1 || RowCurr == -1)
	{
		lblCurrentRow->Caption = "";
		lblCurrentColumn->Caption = "";
	}
	else
	{
		lblCurrentRow->Caption = IntToStr(RowCurr);
		lblCurrentColumn->Caption = IntToStr(ColumnCurr);
	}
}

/*
	If the grid is resized, need to update with the new sizes
*/
void TfrmMain::AdjustGrid(void)
{
	gridPier->ColCount = Columns;
	gridPier->RowCount = Rows;

	CellWidth = gridPier->Width / Columns;
	CellHeight = gridPier->Height / Rows;

	if (gridPier->GridLineWidth)
	{
		CellWidth--;
		CellHeight--;
	}

	gridPier->DefaultColWidth = CellWidth;
	gridPier->DefaultRowHeight = CellHeight;

	TGridRect rect = {{ColumnCurr, RowCurr}, {ColumnCurr, RowCurr}};
	gridPier->Selection = rect;
}

// Mostly from CS170 text driver
void TfrmMain::TakeShot(void)
{
	CS170::WarBoats::Point p;
	p.x = ColumnCurr;
	p.y = RowCurr;

	CS170::WarBoats::ShotResult sr = theOcean->TakeShot(p);
	const char *text;

	int &position = CellState[RowCurr][ColumnCurr];
	switch (sr)
	{
		case CS170::WarBoats::srHIT:
			text = "Hit!";
			position += CS170::WarBoats::HIT_OFFSET;
			ShotsTaken++;
			PlayGameSound(CS170::WarBoats::srHIT);
			break;
		case CS170::WarBoats::srMISS:
			text = "Missed.";
			ShotsTaken++;
			position = -1;
			PlayGameSound(CS170::WarBoats::srMISS);
			break;
		case CS170::WarBoats::srDUPLICATE:
			text = "You've tried that.";
			break;
		case CS170::WarBoats::srSUNK:
			SunkBoats++;
			ShotsTaken++;
			text = "You sunk my boat!";
			position += CS170::WarBoats::HIT_OFFSET;
			PlayGameSound(CS170::WarBoats::srHIT);
			break;
		case CS170::WarBoats::srILLEGAL:
				 text = "That's not a legal shot.";
				 break;
		default: // should never get here
			text = "I don't understand.";
	}

	AddStatus(text);
	if (SunkBoats == NumBoats)
	{
		AddStatus("Game over!");
		GameOver = true;
	}
	UpdateDisplay();
}

// Mostly from CS170 text driver
void TfrmMain::SetupBoatsRand(int num_boats, int xsize, int ysize)
{
	NumBoats = num_boats;
	SunkBoats = 0;
	ShotsTaken = 0;

		// Place the boats randomly in the ocean
	int boats_placed = 0;
	CS170::WarBoats::Ocean *ocean = new CS170::WarBoats::Ocean(num_boats, xsize, ysize);

	while (boats_placed < num_boats)
	{
		CS170::WarBoats::Boat boat;
		boat.ID = boats_placed + 1;
		CS170::WarBoats::BoatPlacement bp;
		do
		{
				// Pick a random orientation
			boat.orientation = CS170::Utils::Random(0, 1)
																 ? CS170::WarBoats::oHORIZONTAL
																 : CS170::WarBoats::oVERTICAL;

				// Pick a random location
			CS170::WarBoats::Point location;
			location.x = CS170::Utils::Random(0, xsize - 1);
			location.y = CS170::Utils::Random(0, ysize - 1);
			boat.position = location;

				// Place the boat
			bp = ocean->PlaceBoat(boat);

		}while (bp == CS170::WarBoats::bpREJECTED);
		boats_placed++;
	}
	theOcean = ocean;
	UpdateDisplay();
}

/*
	Copies the state of the board from the internal CS170::Ocean object
	into our own array.
*/
void TfrmMain::CopyGrid(void)
{
	const int *grid = theOcean->GetGrid();
	int width = spnColumns->Position;
	int height = spnRows->Position;

	for (int y = 0; y < height; y++)
	{
		for (int x = 0; x < width; x++)
		{
			CellState[y][x] = grid[y * width + x];
		}
	}
}

/*
	Displays text in the bottom of the app. Used mainly for debugging.
*/
void TfrmMain::AddStatus(const char *text)
{
	mmoStatus->Lines->Insert(0, text);
}

/*
	Called to update the display after each shot.
*/
void TfrmMain::UpdateDisplay(void)
{
	lblShotsTaken->Caption = IntToStr(ShotsTaken);
	lblBoatsRemaining->Caption = IntToStr(NumBoats - SunkBoats);
}

/*
	The mouse movement is tracked on the grid by highlighting the border of
	the cell that is under the mouse cursor.
*/
void TfrmMain::HighlightCell(void)
{
		// If we are in the same cell, do nothing
	if (RowPrev == RowCurr && ColumnPrev == ColumnCurr)
		return;

		// Otherwise, we've moved from one cell to the next.
		// Un-highlight the previous cell.
	TRect rect = gridPier->CellRect(ColumnPrev, RowPrev);
	TGridDrawState state;
	gridPierDrawCell(this, ColumnPrev, RowPrev, rect, state);

		// Off the board, do nothing
	if (RowCurr < 0 || RowCurr >= Rows || ColumnCurr < 0 || ColumnCurr >= Columns)
		return;

		// Highlight the new cell
	rect = gridPier->CellRect(ColumnCurr, RowCurr);
	gridPierDrawCell(this, ColumnCurr, RowCurr, rect, state);
}

/*
	Toggles the view of the boats. Cheat mode.
*/
void TfrmMain::ShowBoats(bool OnOff)
{
	if (OnOff)
		boat_color = hidden_boat_color;
	else
		boat_color = water_color;

	gridPier->Invalidate(); // Force a redraw of the grid.
}


//******************************************************************************
//******************************************************************************
//******************************************************************************
// Event Handlers
//******************************************************************************
void __fastcall TfrmMain::FormCreate(TObject *Sender)
{
	pgePier->Align = alClient;
	pnlPier->Align = alClient;
	gridPier->Align = alClient;

	pnlWaterBottom->BevelOuter = bvNone;
	Rows = spnRows->Position;
	Columns = spnColumns->Position;

	Width = 560;
	Height = 660;

	HitSound = "HIT1";
	MissSound = "MISS1";

	Randomize();
	int i = Random(1000);
	int j = Random(1000);

	CS170::Utils::srand(i, j);
	theOcean = 0;
	mmoStatus->Height = 0;
	Application->Icon = this->Icon;
	chkCheats->Checked = BoatsVisible;
	btnNewGameClick(this);
}
//---------------------------------------------------------------------------

/*
	When a key down event occurs, the form will get a chance at it first.
*/
void __fastcall TfrmMain::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
		// Backdoor to toggle 'cheat mode'
	if (Key == '0')
		if (Shift.Contains(ssCtrl) && Shift.Contains(ssShift))
			chkCheats->Checked = !chkCheats->Checked;
}
//---------------------------------------------------------------------------

/*
	Called each time a cell needs to be repainted.
*/
void __fastcall TfrmMain::gridPierDrawCell(TObject *Sender, int ACol, int ARow,
		TRect &Rect, TGridDrawState State)
{
		// Rectangle of cell to draw
	Rect.Left = Rect.Left - 1;
	Rect.Right = Rect.Right;
	Rect.Top = Rect.Top - 1;
	Rect.Bottom = Rect.Bottom;

		// Draw a 3 pixel wide border on the highlighted cell
	if (ACol == ColumnCurr && ARow == RowCurr && !OffGrid && (ACol != -1 && ARow != -1) )
	{
		gridPier->Canvas->Pen->Width = 3;
		gridPier->Canvas->Pen->Color = clBlack;
		Rect.left += gridPier->Canvas->Pen->Width - 1;
		Rect.Top += gridPier->Canvas->Pen->Width - 1;
		Rect.right -= gridPier->Canvas->Pen->Width - 1;;
		Rect.Bottom -= gridPier->Canvas->Pen->Width - 1;;
	}
	else // restore the default border
	{
		gridPier->Canvas->Pen->Width = 1;
		gridPier->Canvas->Pen->Color = clBlack;
	}

		// BUG FIX
	if ((ACol == -1) || (ARow == -1))
		return;

		// The cell is just part of the ocean
	if (CellState[ARow][ACol] == 0)
	{
		gridPier->Canvas->Brush->Color = water_color;
		gridPier->Canvas->Rectangle(Rect);
	}
		// The cell is a damaged boat
	else if (CellState[ARow][ACol] > CS170::WarBoats::HIT_OFFSET)
	{
		gridPier->Canvas->Brush->Color = hit_color;
		gridPier->Canvas->Rectangle(Rect);
	}
		// The cell is a damaged ocean
	else if (CellState[ARow][ACol] < 0)
	{
		gridPier->Canvas->Brush->Color = miss_color;
		gridPier->Canvas->Rectangle(Rect);
	}
		// The cell is a boat (cheats on)
	else if (CellState[ARow][ACol] > 0) // just a boat
	{
		gridPier->Canvas->Brush->Color = boat_color;
		gridPier->Canvas->Rectangle(Rect);
	}
}
//---------------------------------------------------------------------------

/*
	Called when a cell is selected (clicked on with the mouse to take a shot).
*/
void __fastcall TfrmMain::gridPierSelectCell(TObject *Sender, int ACol,	int ARow, bool &CanSelect)
{
	if (!theOcean || GameOver)
		return;

	RowCurr = ARow;
	ColumnCurr = ACol;
	TakeShot();
}
//---------------------------------------------------------------------------

/*
	Tracks the mouse movement across the grid so the cell under the mouse
	can be highlighted.
*/
void __fastcall TfrmMain::gridPierMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
	TGridCoord coord = gridPier->MouseCoord(X, Y);
	RowPrev = RowCurr;
	ColumnPrev = ColumnCurr;
	RowCurr = coord.Y;
	ColumnCurr = coord.X;
	UpdateCurrentCell();
	HighlightCell();
}
//---------------------------------------------------------------------------

/*
	When the mouse leaves the grid, we need to update the display.
*/
void __fastcall TfrmMain::gridPierMouseLeave(TObject *Sender)
{
	OffGrid = true;
	TRect rect = gridPier->CellRect(ColumnCurr, RowCurr);
	TGridDrawState state;
	gridPierDrawCell(this, ColumnCurr, RowCurr, rect, state);
	UpdateCurrentCell();
}
//---------------------------------------------------------------------------

/*
	When the mouse moves onto the grid, we need to start tracking it and update
	the display.
*/
void __fastcall TfrmMain::gridPierMouseEnter(TObject *Sender)
{
	OffGrid = false;
	TRect rect = gridPier->CellRect(ColumnCurr, RowCurr);
	TGridDrawState state;
	gridPierDrawCell(this, ColumnCurr, RowCurr, rect, state);
}
//---------------------------------------------------------------------------

/*
	Toggles the display of the boats. Cheat mode.
*/
void __fastcall TfrmMain::chkCheatsClick(TObject *Sender)
{
	ShowBoats(chkCheats->Checked);
}
//---------------------------------------------------------------------------

/*
	Handles the event when the sheet (in the PageControl) is resized.
*/
void __fastcall TfrmMain::shtPierResize(TObject *Sender)
{
	AdjustGrid();
}
//---------------------------------------------------------------------------

/*
	If the splitter bar is moved, the board will be resized and needs to be
	updated.
*/
void __fastcall TfrmMain::splitHorizontalMoved(TObject *Sender)
{
	AdjustGrid();
}
//---------------------------------------------------------------------------

/*
	When the user changes the number of rows/columns, need to update the
	display.
*/
void __fastcall TfrmMain::OnCellCountChange(TObject *Sender)
{
	Rows = spnRows->Position;
	Columns = spnColumns->Position;
	AdjustGrid();
}
//---------------------------------------------------------------------------

/*
	Handler when the user clicks "New Game" button
*/
void __fastcall TfrmMain::btnNewGameClick(TObject *Sender)
{
	Init();
	delete theOcean;
	SetupBoatsRand(spnBoats->Position, spnColumns->Position, spnRows->Position);
	CopyGrid();
	gridPier->Invalidate();
	gridPier->Enabled = true;
}
//---------------------------------------------------------------------------

/*
	Handler when the main window is deleted
*/
void __fastcall TfrmMain::FormDestroy(TObject *Sender)
{
	delete theOcean;	
}
//---------------------------------------------------------------------------

void __fastcall TfrmMain::chkAltSoundsClick(TObject *Sender)
{
	if (chkAltSounds->Checked)
	{
		HitSound = "HIT2";
		MissSound = "MISS2";
	}
	else
	{
		HitSound = "HIT1";
		MissSound = "MISS1";
	}
}
//---------------------------------------------------------------------------

