// Gameairhockey.cpp: implementation of the cGameAirhockey class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "gameairhockey.h"
#include "critter.h"
#include "critterarmed.h"
#include "critterwall.h"
#include "force.h"
#include "listener.h"
#include "pop.h" //for playSound
#include "spritebubble.h"
#include "spriteicon.h"
#include "graphics.h"
#include "critterviewer.h"

IMPLEMENT_SERIAL( cGameAirhockey, cGame, 0);
IMPLEMENT_SERIAL( cCritterHockeyRobot, cCritter, 0);
IMPLEMENT_SERIAL( cCritterHockeyPuck, cCritter, 0);
IMPLEMENT_SERIAL( cCritterHockeyPlayer, cCritter, 0);
IMPLEMENT_SERIAL( cCritterHockeyGoal, cCritterWall, 0);

#define GOALTHICKNESS 0.5
#define GOALRADIUS 1.5
#define GOALOFFSET 0.5*GOALTHICKNESS //(1.5 * GOALRADIUS)
#define ROBOTACCELERATION 50.0
#define ROBOTFRICTION 0.75
#define ROBOTMAXSPEED 50.0
#define COSINESMALLANGLE 0.9
//==============================cGameAirhockey=======

int cGameAirhockey::WINSCORE = 7;

cGameAirhockey::cGameAirhockey():
_ppuck(NULL),
_phockeyrobot(NULL),
_pmygoal(NULL),
_probotgoal(NULL)
{
	cCritter::MAXSPEED = 30.0;
	cCritter::MINRADIUS *= 2;
	_menuflags = (cGame::MENU_2D  | cGame::MENU_3D | cGame::MENU_BACKGROUND
		| cGame::MENU_CHANGELISTENER ); 
		//Disable MENU_SHAPE and MENU_BOUNCEWRAP and MENU_COUNT
	setWrapflag(cCritter::BOUNCE);
  	_border.set(28, 21);  //4:3 ratio. Bigger world makes smaller critters.
  //	_border.set(24, 18);  //4:3 ratio. Smaller world makes bigger critters.
//Set the backgroudn bitmap
	setBackgroundBitmap(IDB_BACKGROUND); /*  Sets the cSpriteBackground for the background bitmap.
		Note that you must call this after the _border has its size fully initialized. So each
		cGame child with a different border should call this again in its constructor. */

//Define _pplayer.
	setPlayer(new cCritterHockeyPlayer(this)); //Sets the _pplayer field
//Define _ppuck 
	_ppuck = new cCritterHockeyPuck(this);
//Define _phockeyrobot.  Need to define _ppuck before _phockeyrobot.
	_phockeyrobot = new cCritterHockeyRobot(this);
//Define _pmygoal. Need to define _phockeyrobot before _pmygoal
	_pmygoal = new cCritterHockeyGoal(cVector(_border.lox()+GOALOFFSET, -GOALRADIUS), //Low point
		cVector(_border.lox()+GOALOFFSET, GOALRADIUS), //High point
		GOALTHICKNESS, this);  //Thickness
	_pmygoal->setOpenside(BOX_LOY);/* cCritter wall views the two points you give
		it in the constructor as the neg and pos sides of the x axis, so it
		works out that "LOY" in the this system is right on the screen. */
	_pmygoal->setTarget(_phockeyrobot);//The guy who gets points when puck goes in pmygoal
//Define _probotgoal.  Need to define _pplayer before _probotgoal
	_probotgoal = new cCritterHockeyGoal(cVector(_border.hix()-GOALOFFSET, -GOALRADIUS),
		cVector(_border.hix()-GOALOFFSET, GOALRADIUS), GOALTHICKNESS, this);
	_probotgoal->setOpenside(BOX_HIY); /* cCritter wall views the two points you give
		it in the constructor as the neg and pos sides of the x axis, so it
		works out that "HIY" in the this system is left on the screen. */
	_probotgoal->setTarget(pplayer());//The guy who gets points when puck goes in probotgoal
}

void cGameAirhockey::seedCritters() 
{
	//There's actually nothing to do!  Everythign got set in the constructor!
}

void cGameAirhockey::initializeView(CPopView *pview)
{
	cGame::initializeView(pview);
//	pview->setGraphicsClass(RUNTIME_CLASS(cGraphicsOpenGL)); //Comment this in for a 3D start
//	pview->pviewpointcritter()->setTrackplayer(TRUE); 
		/* Always look at the player. Don't comment this in unless you plan to be zoomed in. */
		
}

void cGameAirhockey::initializeViewpoint(cCritterViewer *pviewer)
{
	if (pviewer->is3D())
	{
		pviewer->setViewpoint(cVector3(-2.0, 0.0, 1.0), _border.center());
			//These args are the (directiontoviewer, lookatpoint);
		pviewer->roll(PI/2.0); //rolls the viewer to right oritentation. 
	}
	else //2D case just copies base class.
		pviewer->setViewpoint(cVector::ZAXIS, _border.center());
}

void cGameAirhockey::reset()
{
	_phockeyrobot->reset(); //Set his score to 0
	cGame::reset(); //Calls pplayer()->reset() 
}

void cGameAirhockey::adjustGameParameters()
{	//Be sure to have the !_gameover check here or you will hit this over and over at game's end.
	if (!_gameover)
	{
		if (score() >= cGameAirhockey::WINSCORE || _phockeyrobot->score() >= cGameAirhockey::WINSCORE)
		{
			_gameover = TRUE;
			playSound("Tada"); 
		}
	}
}

CString cGameAirhockey::gameOverMessage()
{
	CString cStrScore;
	int playerscore = score();
	int robotscore = _phockeyrobot->score();
	cStrScore.Format("Your Score: %d.  Robot Score: %d.\n", playerscore, robotscore);
	CString cStrWinner = (playerscore > robotscore)?CString("You Won!\n"):CString("You Lost!\n");

	return 
		cStrScore + 
		cStrWinner + 
		CString("To Start a New Game...\n\n PRESS ENTER !");
}

CString cGameAirhockey::statusMessage()
{
	CString cStrStatusBar;
	int nUpdatesPerSecond;
	CString cStrUpdatespersecond;
	CString cStrScore;

	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("Player Score: %d.  Robot Score: %d.", score(),
		 _phockeyrobot->score());
	cStrStatusBar = cStrScore + "  "  + cStrUpdatespersecond; 
	return cStrStatusBar;
}

void cGameAirhockey::drawBackground(cGraphics *pgraphics, int drawflags)
{
	cGame::drawBackground(pgraphics, drawflags);
	cColorStyle colorstyle;
	colorstyle.setLineColor(cColorStyle::CN_BLACK);
	colorstyle.setLineWidth(0.1);
	pgraphics->drawline(cVector(0.0, _border.hiy(), 0.1), cVector(0.0, _border.loy(), 0.1), &colorstyle);
}

void cGameAirhockey::Serialize(CArchive& ar) /* This is a bit of work, as I have to match
	_ppuck , _phockeyrobot , _pmygoal , _probotgoal to the pointers in the cBiota array. */
{
	int puckindex, robotindex, mygoalindex, robotgoalindex;
	cGame::Serialize(ar);
	if (ar.IsStoring()) // Save 
 	{
		ar << _index(_ppuck) << _index(_phockeyrobot) << _index(_pmygoal) << _index(_probotgoal); 
	}
	else //Load
	{
		ar >> puckindex >> robotindex >> mygoalindex >> robotgoalindex;
			// I have to cast from cCritter*
		_ppuck = (cCritterHockeyPuck*)(_pbiota->GetAt(puckindex));
		_phockeyrobot = (cCritterHockeyRobot*)(_pbiota->GetAt(robotindex));
		_pmygoal = (cCritterHockeyGoal*)(_pbiota->GetAt(mygoalindex));
		_probotgoal = (cCritterHockeyGoal*)(_pbiota->GetAt(robotgoalindex));
	}
}

//====================cCritterHockeyGoal======================

BOOL cCritterHockeyGoal::collide(cCritter *pcritter)
{
	BOOL collided = cCritterWall::collide(pcritter);
	cGameAirhockey *phgame = (cGameAirhockey*)(pgame()); /* Note, I added a
		pgame() method to cCritter; it calls _powner->pgame(). */
	if (collided && outcode(pcritter->position()) == _openside &&
		pcritter == phgame->ppuck())
	{
		pcritter->reset(); //MOves puck back to cener
		pcritter->randomizeVelocity(); //Randomize puck vel.
		phgame->phockeyrobot()->moveTo(cVector(0.9*(phgame->border().hix()), 0));
		phgame->phockeyrobot()->setVelocity(cVector::ZEROVECTOR);
		_ptarget->addScore(1);
		if (_ptarget == phgame->pplayer()) //Player scored, send out a positive sound.
 			playSound("Ding"); 
		else //Robot scored, send out a negative sound.
 			playSound("Bonk"); 
	}
	return collided;
}

void cCritterHockeyGoal::draw(cGraphics *pgraphics, int drawflags)
{
	cCritterWall::draw(pgraphics, drawflags);
	int size = pgame()->pbiota()->count();
	cPolygon *prect = (cPolygon*)(psprite());
	cVector start, end;
	if (prect && prect->vertCount() >= 4) //cast worked
	{
		switch(_openside)
		{
			case BOX_LOY:
				start = prect->getVertex(2);
				end = prect->getVertex(3);
				break;
			case BOX_HIY:
				start = prect->getVertex(0);
				end = prect->getVertex(1);
				break;
			case BOX_LOX:
				start = prect->getVertex(1);
				end = prect->getVertex(2);
				break;
			case BOX_HIX:
			default:
				start = prect->getVertex(2);
				end = prect->getVertex(3);
				break;
		}
		cColorStyle guncolorstyle(psprite()->pcolorstyle());
		guncolorstyle.setLineWidth(0.1 * psprite()->radius());
		guncolorstyle.setLineColor(cColorStyle::CN_RED);
		pgraphics->pushMatrix();
		pgraphics->multMatrix(_attitude);
		pgraphics->drawline(start, end, &guncolorstyle);
		pgraphics->popMatrix();
	}
}

void cCritterHockeyGoal::Serialize(CArchive& ar)
{
	cCritterWall::Serialize(ar);
	if (ar.IsStoring()) // Save 
 	{
		ar << _openside; 
	}
	else //Load
	{
		ar >> _openside;
	}
}
//===================cCritterHockeyRobot=================

cCritterHockeyRobot::cCritterHockeyRobot(cGame *pownergame):
cCritter(pownergame)
{
	setSprite(new cSpriteIcon(IDB_FACE));
	setRadius(2*cCritter::MINRADIUS);
	setDensity(cCritter::INFINITEDENSITY);
	addForce(new cForceDrag(ROBOTFRICTION));
	setMaxspeed(ROBOTMAXSPEED);
	cGameAirhockey *phgame = (cGameAirhockey*)(pgame());
		/* This gives us access to all the fields of the game.  Note
			that we need the cast, as pgame() returns a cGame*. */
	if (phgame)
	{
		cCritter *ppuck = phgame->ppuck();
		addForce(new cForceObjectSeek(ppuck, ROBOTACCELERATION));
		setMoveBox(
			cRealBox(cVector(0.0, phgame->border().loy()), phgame->border().hicorner()));
			//Limit robot to right half.
	}
	moveTo(cVector(0.9*_movebox.hix(), 0)); //Not too near middle.
}

void cCritterHockeyRobot::update(CPopView *pactiveview, Real dt)
{
	cCritter::update(pactiveview, dt); /* This sets _acceleration on the basis
		of the cForceObjectSeek(_ppuck) you added in the
		constructor. We ALWAYS call the base class update. */
	cGameAirhockey *phgame = (cGameAirhockey*)(pgame());
		/* This gives us access to all the fields of the game.  Note
			that we need the cast, as pgame() returns a cGame*. */
	cCritter *ppuck = phgame->ppuck();
	cCritterHockeyGoal *probotgoal = phgame->probotgoal();
	Real puckx = ppuck->position().x();
	Real robotgoalx = probotgoal->position().x();
	Real pucky = ppuck->position().y();
	Real robotgoaly = probotgoal->position().y();
	cVector togoal = directionTo(probotgoal);
	cVector topuck = directionTo(ppuck);
/* If the puck is between robot and the goal go towards the goal,
	while avoiding hitting the puck into the goal yourself. */
	if (_position.x() <  puckx && puckx < robotgoalx && 
			(_position.y() > pucky && pucky > robotgoaly ||
			_position.y() < pucky && pucky < robotgoaly)
		)
	{
		_acceleration = ROBOTACCELERATION * togoal; //Head for the goal
		if (togoal % topuck > COSINESMALLANGLE &&  //Me, puck, goal in a line.
			tangent() % topuck >COSINESMALLANGLE) //Me moving to puck
			_velocity.turn( (1 & _personality)?PI/2:-PI/2); //Veer left or right.
	}
}

//======================cCritterHockeyPuck============

cCritterHockeyPuck::cCritterHockeyPuck(cGame *pownergame):
cCritter(pownergame)
{
	setSprite(new cSpriteBubble());
	psprite()->setFillColor(cColorStyle::CN_LIGHTRED);
	setRadius(cCritter::MINRADIUS);
	addForce(new cForceDrag(0.2));
	setMaxspeed(30.0);
}

//======================cCritterHockeyPlayer============

cCritterHockeyPlayer::cCritterHockeyPlayer(cGame *pownergame):
cCritter(pownergame)
{
	setMaxspeed(35.0); //It must move faster than the puck.
	setListener(new cListenerCursor()); /* Make sure to set the listener after you
		set the default maxspeed, as the cListenerCursor wants to reset the maxspeed. */
#define COMPOSITEPLAYERSPRITE/* Comment this in to make this guy have a sprite that's like a 
	circle (well, a polygno) with a triangle attached at the rear.  We use a sSpriteComposite
	with two cPolygon members. */
#ifdef COMPOSITEPLAYERSPRITE
	cSpriteComposite *pcomposite = new cSpriteComposite;
	cPolygon *ppolygon = new cPolygon(12);
	ppolygon->setFillColor(cColorStyle::CN_LIGHTBLUE);
	pcomposite->add(ppolygon);
	cPolygon *ptail = new cPolygon(3); //Default triangle.
	ptail->setFillColor(cColorStyle::CN_LIGHTYELLOW);
	ptail->setRadius(0.6); //Tail not so big.
	cMatrix tailattitude;
	tailattitude.setLastColumn(ppolygon->getVertex(6));
	tailattitude *= cMatrix::zRotation(PI);
	tailattitude.translate(cVector(0.0, 0.0, 0.1)); /* I move tail triangle
		up in the z direction a bit so that its face won't be in the 
		same plane as the face of the body disk, becuase overlapping planes
		make visually ugly "z-fighting" in 3D. */
	ptail->setSpriteAttitude(tailattitude);
	pcomposite->add(ptail);
	setSprite(pcomposite);
#else //not COMPOSITEPLAYERSPRITE, just use a "circle" 
	setSprite(new cSpriteBubblePie());
	psprite()->setFillColor(cColorStyle::CN_LIGHTBLUE);
#endif //COMPOSITEPLAYERSPRITE
	setRadius(1.5*cCritter::MINRADIUS);
	setDensity(1.0); /* Better to have lower density so you don't BLAST the
		puck just by touching it. */
	setAbsorberflag(TRUE); //So it doesn't get turned around by collisions.
	if (pownergame)
		setMoveBox(
			cRealBox(pownergame->border().locorner(), cVector(0.0, pownergame->border().hiy())));
		//Limit player to left half.
	moveTo(_movebox.center());
}

void cCritterHockeyPlayer::reset()
{
	cCritter::reset();
	moveTo(_movebox.center()); //Center of left half of screen.
}

