#include "stdafx.h"
#include "gamepicknpop.h"
#include "spritebubble.h"
#include "graphics.h"
#include "pop.h" //for CPopApp::playSound
#include "graphicsopengl.h"
#include "critterviewer.h"

IMPLEMENT_SERIAL( cGamePickNPop, cGame, 0 );
IMPLEMENT_SERIAL( cCritterPeanut, cCritter, 0 );
IMPLEMENT_SERIAL( cCritterJewel, cCritter, 0 );
IMPLEMENT_SERIAL( cCritterUnpackedJewel, cCritter, 0 );

//Size and speed statics.==============================
int cGamePickNPop::STARTCRITTERCOUNT = 30;
Real cGamePickNPop::CRITTERMINRADIUS = 0.4;
Real cGamePickNPop::CRITTERMAXRADIUS = 1.75;
Real cGamePickNPop::CRITTERMAXSPEED = 5.0;
Real cGamePickNPop::UNPACKEDJEWEL_MAXSPEED = 3.0; //Slow down these guys
Real cGamePickNPop::GAMETIME = 45;
//Other statics.==============================

/* We specify the graphical appearance of the packingbox and targetbox here.
The "WEIGHT" constants are numbers between 0.0 and 1.0 to  be multiplied times something.
The present values of the constants were arrived at simply by trying a bunch
of values till I found ones that looked allright. */

/* Our game uses two boxes, a _packingbox and a _targetbox.
 Rather than letting these extend all the way out to the _border of the world, we inset them by
a slight amount BLANK_EDGE_WEIGHT. We think of this as being like the matting around a framed
picture. 
	We have a _packingbox with the peanuts and jewels in it, and we can also have a _targetbox
that we have to drag the jewels into. We specify the  PACKINGBOX_WEIGHT of the usable area which
the _packingbox occupies. We define PACKINGBOX_TARGETBOX_SPACER_WEIGHT to be the size of the
space between the _packingbox and the _targetbox. Note that the bubbles' maximum sizes will
be clamped so that the fit into the _targetbox, so we should leave some room over for it.*/
Real cGamePickNPop::BLANK_EDGE_WEIGHT = 0.03; 
Real cGamePickNPop::PACKINGBOX_WEIGHT = 0.7; 
Real cGamePickNPop::PACKINGBOX_TARGETBOX_SPACER_WEIGHT = 0.03; 
	//Now some statics to define the graphical appearance of the boxes.
Real cGamePickNPop::BOX_LINEWIDTH_WEIGHT = 0.01; // thickness of the edge lines as a percent of window size.
COLORREF cGamePickNPop::PACKINGBOX_FILLCOLOR = cColorStyle::CN_LIGHTYELLOW;
COLORREF cGamePickNPop::PACKINGBOX_LINECOLOR = cColorStyle::CN_BROWN;
COLORREF cGamePickNPop::TARGETBOX_FILLCOLOR = cColorStyle::CN_LIGHTRED;
COLORREF cGamePickNPop::TARGETBOX_LINECOLOR = cColorStyle::CN_RED;
/* The last three statics have to do with the game idea where we have bubbles
that are desirable jewels packed in not so desireable Styrofoam peanuts.
The JEWEL_WEIGHT is is a number between 0.0 and 1.0 specifying
the probability that a given object is a jewel instead of a peanut.  That is,
if we have N objects, we expect JEWEL_WEIGHT * N of them to be jewels
and the rest to be peanuts. We distinguish them using the cCritterPeanut,
cCritterJewel, and cCritterUnpackedJewel classes.  These classes inherit from cCritter.
	I can score points by dragging jewels to the target box or by popping
peanuts.  I set the max score to be a big round MAX_SCORE like 1000.  Now of
this max score, I want (JEWEL_GAME_SCORE_WEIGHT)*MAX_SCORE of the points to
come from the jewels.  This expresses the relative importance of dragging
jewels vs. poppingpeanuts.  */
Real cGamePickNPop::JEWEL_WEIGHT = 0.25;  // Percent of objects which are jewels 
int cGamePickNPop::MAX_SCORE = 1000; //Best possible user score.
Real cGamePickNPop::JEWEL_GAME_SCORE_WEIGHT = 0.50; /* Percent of your your point total 
	that should come from jewels (the rest comes from peanuts) */
Real cGamePickNPop::JEWELBOXTOLERANCE = 0.8; /* This determines how close to
	precisely inside the target box a jewel has to be to score.  1.0 means every bit
	of it has to be in, 0.0 would mean only center has to be in. */


//---------------------cGamePickNPop----------------
cGamePickNPop::cGamePickNPop()
{
	cCritter::MINRADIUS = cGamePickNPop::CRITTERMINRADIUS;
	cCritter::MAXRADIUS = cGamePickNPop::CRITTERMAXRADIUS;
	cCritter::MAXSPEED = cGamePickNPop::CRITTERMAXSPEED;

	_plightingmodel->setEnableLighting(FALSE);
		 /* It turns out that  this modellooks
		better with lighting off, mainly because I think there's some kind of
		bug in our OpenGL lighting still.  Currently the edge colors seem to
		change when a critter bounces, adn really they shouldn't as the critter's
		position is the same. */

	_menuflags = (cGame::MENU_2D  | cGame::MENU_3D | cGame::MENU_COUNT); 
		//Disable MENU_SHAPE, _BOUNCEWRAP, _SHIELD, _BACKGROUND
	_seedcount = cGamePickNPop::STARTCRITTERCOUNT;
	_wrapflag = cCritter::BOUNCE;
	setPlayer(new cCritter(), FALSE); //FALSE makes player be offscreen.
	_arrayHCURSOR.SetSize(0);
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorDragger);
	_arrayHCURSOR.Add(((CPopApp*)::AfxGetApp())->_hCursorPin);
		/*We use framebox to set our game in a bit from the window border all
		the way around.  And then we let the _packingbox to be the left part of
		the framebox, skip a little space, and put _targetbox in the rest of
		the framebox.  We need to prepare _packingbox and _targetbox before calling seedBubbles,
		as that call will use them . */
	cRealBox framebox = _border.innerBox(cGamePickNPop::BLANK_EDGE_WEIGHT *
		_border.minsize());
	_packingbox = cRealBox(framebox.locorner(),
		cVector(framebox.lox()+PACKINGBOX_WEIGHT*framebox.xsize(), framebox.hiy()));
	_packingbox.pcolorstyle()->setFillColor(cGamePickNPop::PACKINGBOX_FILLCOLOR);
	_packingbox.pcolorstyle()->setLineWidthWeight(cGamePickNPop::BOX_LINEWIDTH_WEIGHT);
	_packingbox.pcolorstyle()->setLineColor(cGamePickNPop::PACKINGBOX_LINECOLOR);
	_targetbox = cRealBox(cVector(_packingbox.hix() +
		cGamePickNPop::PACKINGBOX_TARGETBOX_SPACER_WEIGHT*_border.minsize(), framebox.loy()),
		framebox.hicorner());
	_targetbox.pcolorstyle()->setFillColor(cGamePickNPop::TARGETBOX_FILLCOLOR);
	_targetbox.pcolorstyle()->setLineWidthWeight(cGamePickNPop::BOX_LINEWIDTH_WEIGHT);
	_targetbox.pcolorstyle()->setLineColor(cGamePickNPop::TARGETBOX_LINECOLOR);
		/* Just to be safe, let's make sure that the bubbles will fit in the targetbox, and if you 
			have to change the MAXRADIUS, make sure the MINRADIUS is still less. */
	if (cCritter::MAXRADIUS > 0.9 * _targetbox.minsize()/2.0)
		cCritter::MAXRADIUS =  0.9 * _targetbox.minsize()/2.0;
	if (cCritter::MINRADIUS >= cCritter::MAXRADIUS)
		cCritter::MINRADIUS = cCritter::MAXRADIUS/2.0;
//Give the boxes zsize, so we can stack our jewels and peanuts in the 3D case
	_border.setZRange(0.0, 10.0* cGame::DEFAULTZDEPTH + 2*cCritter::MAXRADIUS ); /* I add
		in the MAXRADIUS term so that my flat disk (but large radius) critters don't  get
		so clamped away from the floor and ceiling that they bunch in the middle.  Also
		I use 5 times the default depth  to give them plenty of room. */
	_packingbox.setZRange(0.0, _border.zsize());
	_targetbox.setZRange(0.0, _border.zsize());
}

void cGamePickNPop::Serialize(CArchive &ar)
{
	cGame::Serialize(ar);
	if (ar.IsStoring()) //Writing data.
		ar <<  _packingbox << _targetbox;
	else //Reading data.
		ar >> _packingbox >> _targetbox;
}


void cGamePickNPop::seedCritters()
{
	/* First we'll set the _bubble array to have room for count bubbles.  Then
	we'll add jewels and peanuts, randomizing their radii, positions, and colors
	as we go along.  In the case of PGT_3D, we go back adn change the radii at
	the end. */
	int i;
	int jewelstoadd, peanutstoadd;
	Real jewelprobability = cGamePickNPop::JEWEL_WEIGHT;
	int jewelvalue(0), peanutvalue(0);
	cCritter *pcritternew;

	/* I use the jewelprobability to decide how many jewels and how many
	peanuts to have.  These are the jewelstoadd and peanutstoadd numbers.
	We think of randomly drawing form this supply and adding them into the 
	game.I want my standard game score to be MAX_SCORE, with JEWEL_GAME_WEIGHT
	portion of the score coming	from the jewels and the rest and  from the peanuts.
	The scores have to be integers, so it may be that the total isn't quite 
	MAX_SCORE, so I will give the rest to the user as game-end bonus.  */ 
//-----------Get the counts and the scorevalues ready--------------
	jewelstoadd = int(jewelprobability * _seedcount);
	peanutstoadd = _seedcount - jewelstoadd;
	jewelvalue = int(_maxscore*cGamePickNPop::JEWEL_GAME_SCORE_WEIGHT)/(jewelstoadd?jewelstoadd:1);
	peanutvalue = (_maxscore - jewelvalue*jewelstoadd)/(peanutstoadd?peanutstoadd:1);
	_scorecorrection = 	_maxscore - (jewelstoadd*jewelvalue + peanutstoadd*peanutvalue);
		/* We'll add this in at the end, so that user's maximum score is the
		same as the targeted _maxscore). */
//-----------------------------Renew the _bubble contents ---------------
	_pbiota->purgeNonPlayerNonWallCritters(); // Need to delete any from last round
		/* Regarding the stacknig, it's worth mentionig that cBiota::draw draws the critters 
			in reverse order, last index to first, so the first-added members appear on top
			in 2D. We want the peanuts "on top", so we add them first.  Of course in 3D,
			the moveToZ is going to take care of this irregardles of what order the
			critters are drawn. */
	for(i=0; i<peanutstoadd; i++)
	{
		pcritternew = new cCritterPeanut(this); /* White bubble that we 
			call a "Peanut", can't move out of _packingbox */
		pcritternew->setValue(peanutvalue);
	}
	for (i=0; i<jewelstoadd; i++)
	{ //Make a pcritternew and then add it into _bubble at the bottom of loop.
		pcritternew = new cCritterJewel(this); /* Colored bubble that we call
			a "Jewel", can move all over within _border.*/
		pcritternew->setValue(jewelvalue);
	}
	zStackCritters();
}

void cGamePickNPop::adjustGameParameters()
{	//Be sure to have the !_gameover check here or you will hit this over and over at game's end.
	if (!_gameover)
	{
		if (
			//You've gotten rid of all peanuts and jewels from packing box.
		(!_pbiota->count(RUNTIME_CLASS(cCritterPeanut)) && !_pbiota->count(RUNTIME_CLASS(cCritterJewel))) ||
			//OR You're out of time.
		(age() > cGamePickNPop::GAMETIME)) //FALSE means don't count 
		{
			_gameover = TRUE;
			pplayer()->addScore(_scorecorrection); // So user can reach _maxscore 
			playSound("Tada");
		}
	}
}

CString cGamePickNPop::statusMessage()
{
	CString cStrStatusBar;
	int nUpdatesPerSecond;
	int crittercount;
	CString cStrUpdatespersecond;
	CString cStrCountPeanut;
	CString cStrCountJewel;
	CString cStrScore;
	CString cStrTimeToPlay;

	if (((CPopApp*)::AfxGetApp())->_animateflag)
	{
		nUpdatesPerSecond = int(((CPopApp*)::AfxGetApp())->_timer.updatesPerSecond());
		if (!nUpdatesPerSecond)
			cStrUpdatespersecond.Format("Less than one update per second.");
		else	
			cStrUpdatespersecond.Format("Updates per second: %d.", nUpdatesPerSecond);
		if (((CPopApp*)::AfxGetApp())->_timer.runningNearMinSpeed())
			cStrUpdatespersecond += " (Slower than Real Time)";
	}
	else
			cStrUpdatespersecond.Format("Animation is paused.");
	cStrScore.Format("Score: %d out of %d.", score(),  cGamePickNPop::MAX_SCORE);
	Real myage = age();
	Real timetogo = cGamePickNPop::GAMETIME;
	cStrTimeToPlay.Format("Secs Left: %d out of %d.", int(cGamePickNPop::GAMETIME - age()),
		int(cGamePickNPop::GAMETIME) );
	crittercount = _pbiota->count(RUNTIME_CLASS(cCritterPeanut));
	cStrCountPeanut.Format("Peanuts Left: %d.", crittercount);
	crittercount = _pbiota->count(RUNTIME_CLASS(cCritterJewel));
	cStrCountJewel.Format("Jewels Left: %d.", crittercount);
	cStrStatusBar = cStrTimeToPlay + " " + cStrScore + "  " + cStrCountPeanut + "  " +	cStrCountJewel + "  " +
		cStrUpdatespersecond; 
	return cStrStatusBar;
}

void cGamePickNPop::initializeView(CPopView *pview)
{
	cGame::initializeView(pview); //Always call the baseclass method.
	pview->setCursor(((CPopApp*)::AfxGetApp())->_hCursorDragger);
//	pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL)); //cGamePickNPop looks good in 3D.
}

void cGamePickNPop::initializeViewpoint(cCritterViewer *pviewer)
{
	//Same code as base class, but put it here for tweaking.
		/* The two args to setViewpoint are (directiontoviewer, lookatpoint).
			Note that directiontoviewer points FROM the origin TOWARDS the viewer. */
	if (pviewer->is3D())
		pviewer->setViewpoint(cVector3(0.0, -1.0, 2.0), _border.center());
			//Direction to viewer is down a bit, and back off less than that.
	else //2D case.
		pviewer->setViewpoint(cVector::ZAXIS, _border.center());
}

void cGamePickNPop::drawBackground(cGraphics *pgraphics, int drawflags)
{
//Draw the fillings of the boxes first, before the bubbles.
	cRealBox2 packingboxfloor(_packingbox);
	cRealBox2 targetboxfloor(_targetbox);

	packingboxfloor.pcolorstyle()->setEdged(FALSE);
	packingboxfloor.pcolorstyle()->setFilled(TRUE);
	packingboxfloor.draw(pgraphics, drawflags);

	targetboxfloor.pcolorstyle()->setEdged(FALSE);
	targetboxfloor.pcolorstyle()->setFilled(TRUE);
	targetboxfloor.draw(pgraphics, drawflags);
}

void cGamePickNPop::drawForeground(cGraphics *pgraphics, int drawflags)
{
//	cGame::drawForeground(pgraphics, drawflags);
	//Don't need the outer border box so don't call the base class drawForeground.
/* Draw the edges of the two boxes, letting them pop into 3D if pgraphics can show it. */
	_targetbox.pcolorstyle()->setEdged(TRUE);
	_targetbox.pcolorstyle()->setFilled(FALSE);
	_packingbox.pcolorstyle()->setEdged(TRUE);
	_packingbox.pcolorstyle()->setFilled(FALSE);
	_targetbox.draw(pgraphics, drawflags);
	_packingbox.draw(pgraphics, drawflags);

}

//====================cCritterPeanut===================
cCritterPeanut::cCritterPeanut(cGame *pownergame):
cCritter(pownergame)
{
	setSprite(new cSpriteBubbleGrayscale());
	cGamePickNPop *pgamepnp = (cGamePickNPop*)pownergame;
	if (pgamepnp) //Check cast worked to be safe
	{
		setMoveBox(pgamepnp->packingbox());
		setDragBox(pgamepnp->packingbox());
	}		
	mutate(cCritter::MF_POSITION | cSprite::MF_RADIUS | cPolygon::MF_COLOR);
	randomizeVelocity(cCritter::MINSPEED, _maxspeed, TRUE);
		//The TRUE means force this to be a 2D vector.
}

void cCritterPeanut::update(CPopView *pactiveview, Real dt)
{ 
	cCritter::update(pactiveview, dt);
	cVector safevelocity(_velocity);
	safevelocity.setZ(0.0);
	setVelocity(safevelocity);
}

void cCritterPeanut::die()
{
	playSound("Pop");
	pplayer()->addScore(_value);
	cCritter::die();
}

//====================cCritterJewel===================
cCritterJewel::cCritterJewel(cGame *pownergame):
cCritter(pownergame)
{
	cSpriteBubble* pbubble = new cSpriteBubble();
	pbubble->setLineWidthWeight(0.15);
	setSprite(pbubble);
	cGamePickNPop *pgamepnp = (cGamePickNPop*)pownergame;
	if (pgamepnp) //Check cast worked to be safe
	{
		setMoveBox(pgamepnp->packingbox()); //Note that we DON'T set the drag box.
		setDragBox(cRealBox(pgamepnp->packingbox().locorner(), pgamepnp->targetbox().hicorner()));
		/*I want the dragbox to just hold the packingbox and targetbox, and I don't want
		to use the whole _border, as otherwise some players will drag the disk too far, through
		the target and wonder why it doesn't stick. */ 
	}
	mutate(cCritter::MF_POSITION | cSprite::MF_ALL | cPolygon::MF_COLOR);
	randomizeVelocity(cCritter::MINSPEED, _maxspeed, TRUE);
		//The TRUE means force this to be a 2D vector.
}

void cCritterJewel::die()
{
	playSound("Bonk");
	pplayer()->addScore(-_value);
	cCritter::die();
}

void cCritterJewel::update(CPopView *pactiveview, Real dt)
{ 
	cGamePickNPop *pgamepnp = NULL;
//(1) Apply force if turned on.
	cCritter::update(pactiveview, dt); //Always call this.
	cVector safevelocity(_velocity); //To be safe, don't let any z get into velocity.
	safevelocity.setZ(0.0);
	setVelocity(safevelocity);
//(2) Check if in targetbox, and if so, replace yourself with a good jewel.
	if (pgame()->IsKindOf(RUNTIME_CLASS(cGamePickNPop)))
	/* We need to do the cast to access the targetbox field, and to be safe
		we check that the cast will work. */
		pgamepnp = (cGamePickNPop*)(pgame());
	else
		return;
	cRealBox effectivebox = pgamepnp->targetbox().innerBox(cGamePickNPop::JEWELBOXTOLERANCE*radius());
	if (!effectivebox.inside(_position))
		return;
	//Reaction to being inside _targetbox.
	playSound("Ding");
	cCritterUnpackedJewel *pcritternew = new cCritterUnpackedJewel(this);
	pcritternew->setMoveBox(pgamepnp->targetbox());
	pcritternew->setDragBox(pgamepnp->targetbox());
	delete_me();  /* Just tell cBiota to just remove the old critter. Don't use the
		 overloaded cCritterJewel::die to make a noise and subtract _value from score.*/
	pcritternew->add_me(_pownerbiota); //Tell cBiota to add the new critter.
	pgamepnp->pplayer()->addScore(_value);
}

//----------------------cCritterUnpackedJewel (Frozen Jewel) ---------------------
cCritterUnpackedJewel::cCritterUnpackedJewel(cGame *pownergame):
cCritter(pownergame)
{
	setSprite(new cSpriteBubblePie());
	_maxspeed =  cGamePickNPop::UNPACKEDJEWEL_MAXSPEED;
}

cCritterUnpackedJewel::cCritterUnpackedJewel(cCritter *pcritter)
{
	copy(pcritter);
	setSprite(new cSpriteBubblePie(_psprite));
	_maxspeed =  cGamePickNPop::UNPACKEDJEWEL_MAXSPEED;
	_value = 2*_value; //So if you pop this you get punished AND lose gain for promoting it.
}


void cCritterUnpackedJewel::update(CPopView *pactiveview, Real dt)
{ 
	cCritter::update(pactiveview, dt);
	cVector safevelocity(_velocity);
	safevelocity.setZ(0.0);
	setVelocity(safevelocity);
}

void cCritterUnpackedJewel::die()
{
	playSound("Bonk");
	pplayer()->addScore(-_value);
	cCritter::die();
}
