Subversion Repositories AndroidProjects

Rev

Blame | Last modification | View Log | RSS feed

#pragma warning(disable:4244)
#pragma warning(disable:4018)
#include "Board.h"
#include "GameApp.h"
#include "Res.h"
#include "LevelupEffect.h"
#include "GameOverEffect.h"
#include "OptionsDialog.h"

#include "SexyAppFramework/Graphics.h"
#include "SexyAppFramework/Color.h"
#include "SexyAppFramework/Rect.h"
#include "SexyAppFramework/ButtonWidget.h"
#include "SexyAppFramework/WidgetManager.h"
#include "SexyAppFramework/ImageFont.h"
#include "SexyAppFramework/SoundManager.h"
#include "SexyAppFramework/SoundInstance.h"
#include "SexyAppFramework/Buffer.h"
#include "SexyAppFramework/MusicInterface.h"


#define _USE_MATH_DEFINES
#include <math.h>

// VC6 workaround
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

using namespace Sexy;

// How much faster the beam gets when you eat a planet
const float BEAM_INC_SPEED      =       0.05f;

// Fastest the beam can go
const float MAX_BEAM_SPEED      =       2.5f;

// Table of random planet names
const int NUM_PLANET_NAMES      =       28;
const SexyString PLANET_NAME[] =
{_S("Deev-z"), _S("SEN-Hen"), _S("Wallach IX"), _S("Salusa Secundus"), _S("Ridiculous Prime"), _S("Architekt V"),
_S("Robot Republica"), _S("Ix"), _S("XOLDOG4000"), _S("Kliprok"), _S("TR-909"), _S("TR-808"), _S("TB-303"),
_S("DTR011"), _S("dTech"), _S("Rotwang"), _S("Sukhtek"), _S("Romulox"), _S("Dob Reevz"), _S("Skull XII"),
_S("Beefy Prime"), _S("Haas"), _S("Reifenrath"), _S("Gehner Subulon"), _S("ACE-DOGG"), _S("Charolastra"), _S("Nixd"), _S("BASS")};

// Table of random planet exports:
const int NUM_PLANET_EXPORTS =  23;
const SexyString PLANET_EXPORTS[] =
{_S("Happiness"), _S("Donkeys"), _S("Rabies"), _S("AstroPop"), _S("Idiocy"), _S("Minimal Techno"),
_S("Citizens"), _S("Pain-relieving Pants"), _S("The Quad-Laser"), _S("Septic Systems"), _S("Video Games"),
_S("Robots"), _S("Plaid"), _S("Octagons"), _S("Gingivitis"), _S("Recognizers"), _S("Electro"), _S("Sauce"),
_S("Kindness"), _S("Bison"), _S("Saline"), _S("Cholera"), _S("TyperShark")};


//////////////////////////////////////////////////////////////////////////
//      Inline functions
//////////////////////////////////////////////////////////////////////////

// Given an X coordinate, returns the grid column it maps to
inline int GetCol(float x)
{
        return (int) ((x - GRID_START_X) / GRID_PIX_SIZE);
}

// Given a Y coordinate, returns the grid row it maps to
inline int GetRow(float y)
{
        return (int) ((y - GRID_START_Y) / GRID_PIX_SIZE);
}

// Given a grid column, returns the X pixel of the left edge of it
inline float GetColPix(int col)
{
        return col * GRID_PIX_SIZE + GRID_START_X;
}

// Given a grid row, returns the Y pixel of the top edge of it
inline float GetRowPix(int row)
{
        return row * GRID_PIX_SIZE + GRID_START_Y;
}

// Given an X coordinate, adjusts it so that it is aligned
// with the left edge of the grid square it's over
inline float GetAlignedX(float x)
{
        return (float)GetCol(x) * GRID_PIX_SIZE + GRID_START_X;
}

// Given a Y coordinate, adjusts it so that it is aligned
// with the top edge of the grid square it's over
inline float GetAlignedY(float y)
{
        return (float)GetRow(y) * GRID_PIX_SIZE + GRID_START_Y;
}

// Checks to see if the Y coordinate is in the grid's bounds.
// Needs to know if Hun-garr is oriented vertically or not
inline bool YCoordInBounds(float y, bool vertical)
{
        int h = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;

        if ( (vertical && (y > GRID_START_Y + h) && (y < GRID_END_Y - h)) ||
                (!vertical && (y > GRID_START_Y + 7) && (y < GRID_END_Y - 4)) )
                return true;

        return false;
}

// Checks to see if the X coordinate is in the grid's bounds
inline bool XCoordInBounds(float x)
{
        if ((x > GRID_START_X + 9) && (x < GRID_END_X - 2))
                return true;

        return false;
}

// Checks to see if the column number points to a valid grid column
inline bool ValidCol(int col)
{
        return ((col >= 0) && (col < GRID_WIDTH));
}

// Checks to see if the row number points to a valid grid row
inline bool ValidRow(int row)
{
        return ((row >= 0) && (row < GRID_HEIGHT));
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::Board(GameApp* theApp)
{
        mApp = theApp;
        mHungarrIsVertical = true;     
        mLineSpeed = 1.0f;

        mNumPlanetsEaten = 0;
        mPercentComplete = 0;

        mBorderHue = 0;
        mFlashCount = 0;
        mLives = 3;
        mLevel = 1;
        mScore = 0;
        mBeamPulseAmt = 30;
        mBeamPulseVal = 128;

        // Put Hun-garr in a valid spot
        UpdateHungarrPosition(GetColPix(4), GetRowPix(4));

        // Create a 2D array to hold the grid fill state that's of size
        // GRID_WIDTH x GRID_HEIGHT
        mGridState = new GridTile* [GRID_HEIGHT];
       
        //vc6 workaround:
        int i;
        for (i = 0; i < GRID_HEIGHT; i++)
                mGridState[i] = new GridTile[GRID_WIDTH];

        mFillDirection = FILL_LEFT;
        mFillSpeed = 20.0f;
        mPlanetSpeed = 1.5f;
        mPopulationEaten = mTotalPopulationEaten = 0;
        mPauseLevel = 0;

        // Create our starfield
        for (i = 0; i < MAX_STARS; i++)
        {
                Star s;
                s.mX = Rand() % mApp->mWidth;
                s.mY = Rand() % mApp->mHeight;

                int r = Rand() % 3;
                switch (r)
                {
                        case 0: s.mSpeed = 0.5f; s.mColor = Color(200, 200, 200, 128); break;
                        case 1: s.mSpeed = 0.25f; s.mColor = Color(100, 100, 100, 128); break;
                        case 2: s.mSpeed = 0.1f; s.mColor = Color(50, 50, 50, 128); break;
                }

                mStarField[i] = s;
        }

        mLevelupEffect = new LevelupEffect();
        mGameOverEffect = new GameOverEffect();
        mOptionsBtn = NULL;


        // The shorting out, electrical sound of the beams moving. We use a SoundInstance pointer
        // because we want to loop the sound while the beam is moving, and stop it when done.
        // This is easiest done manually.
        mShortSound = mApp->mSoundManager->GetSoundInstance(SOUND_BEAM_MOVING);

        InitLevel(1);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
Board::~Board()
{
        // Frees up the memory allocated to our manual SoundInstance pointer. Required.
        mShortSound->Release();

        for (int i = GRID_HEIGHT; i > 0; --i)
                delete[] mGridState[i - 1];
        delete[] mGridState;

        delete mLevelupEffect;
        delete mGameOverEffect;
        delete mOptionsBtn;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Update()
{
        // If paused, return and don't markdirty, whcih prevents drawing
        // and the stealing of CPU cycles
        if (mPauseLevel > 0)
                return;

        Widget::Update();


        // HSL is an alternative to specifying an RGB color format.
        // Using HSL lets us easily do the hyper blinking crazy weird
        // flashing effect commonly found in old games, such as Robotron.
        // Below, we increment the value by 6 per update. The &0xFF is an
        // easy way to clamp the value between 0 and 255 instead of having to
        // do a separate if (mHue > 255) mHue -= 255. This lets the value
        // rollover and keep cycling.
        if (--mFlashCount > 0)
                mBorderHue = (mBorderHue + 6) % 0xFF;  

        if (mGameOverEffect->IsActive())
        {
                mGameOverEffect->Update();

                // If the game over effect is in the proper state, we will
                // be able to initialize the first level so that when it fades out,
                // the level will appear underneath it.
                if (mGameOverEffect->CanInitFirstLevel())
                {
                        //Reset the critical variables:
                        mBorderHue = 0;
                        mFlashCount = 0;
                        mLives = 3;
                        mLevel = 1;
                        mScore = 0;
                        mFillDirection = FILL_LEFT;
                        mFillSpeed = 20.0f;
                        mPlanetSpeed = 1.5f;
                        mPopulationEaten = mTotalPopulationEaten = 0;
                        mPauseLevel = 0;
                        mLineSpeed = 1.0f;
                        mPlanetIdxCount.clear();
                        mExportIdxCount.clear();
                        mOptionsBtn->SetVisible(true);
                        mApp->mMusicInterface->FadeIn(0);
                        InitLevel(1);
                }
        }

        // Make the beams that the player emits pulse with intensity
        mBeamPulseVal += mBeamPulseAmt;
        if (mBeamPulseVal >= 255)
        {
                mBeamPulseVal = 255;
                mBeamPulseAmt = -mBeamPulseAmt;
        }
        else if (mBeamPulseVal <= 0)
        {
                mBeamPulseVal = 0;
                mBeamPulseAmt = -mBeamPulseAmt;
        }
       
        MarkDirty();
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::UpdateF(float theFrac)
{
        if (mPauseLevel > 0)
                return;

        if (mFilling)
        {
                // If the beams have been released, update the filling of the grid
                float amt = mFillSpeed * theFrac;

                if (mFillDirection == FILL_RIGHT)
                        FillRight(amt);        
                else if (mFillDirection == FILL_LEFT)
                        FillLeft(amt);
                else if (mFillDirection == FILL_UP)
                        FillUp(amt);
                else if (mFillDirection == FILL_DOWN)
                        FillDown(amt);

                // Check what % full the filled regions are if it's done filling
                if (!mFilling)
                        UpdatePercentComplete();
               
        }

        // Make the bonus text float upwards and fade it out over time.
        std::vector<BonusText>::iterator it = mBonusText.begin();
        while (it != mBonusText.end())
        {
                BonusText* bt = &*it;
                bt->mY -= 1.00f * theFrac;
                bt->mHue = (bt->mHue + 5) % 0xFF;

                if (--bt->mAlpha <= 0)
                {
                        //Totally faded out, remove it
                        it = mBonusText.erase(it);
                }
                else
                        ++it;
        }


        // Move the starfield. If a start gets beyond the screen,
        // randomly place it offscreen again
        int i;
        for (i = 0; i < MAX_STARS; i++)
        {
                Star* s = &mStarField[i];
                s->mX += s->mSpeed;
                if (s->mX > mWidth)
                {
                        s->mX = -5;
                        s->mY = Rand() % mHeight;
                }
        }

        if ((!mMovingLine1.mDone || !mMovingLine2.mDone) && !mGameOverEffect->IsActive())
                MoveLines(theFrac);


        // If we're allowed to show the planets and the game isn't paused and the game
        // over effect isn't playing, then we can move the planets around
        if ((!mLevelupEffect->HidePlanets() || (mPauseLevel > 0)) && !mGameOverEffect->IsActive())
        {
                // Move the planets
                int w = IMAGE_PLANETS->GetCelWidth();
                int h = IMAGE_PLANETS->GetCelHeight();

                // Instead of playing the explosion sound every time a planet gets destroyed, we'll
                // only play it once. That way, if you destroy more than 1 planet in one go, you won't
                // hear the same sound played multipled times at once, which would result in this loud,
                // hideous, flanging sound.
                bool playSound = false;
                for (int i = 0; i < mPlanets.size(); i++)
                {
                        Planet* p = &mPlanets[i];

                        // Again, the timer is used solely for incrementing the animation frames
                        ++p->mTimer;

                        if (!p->mExploding)
                        {
                                if (MovePlanet(p, theFrac))
                                        playSound = true;                       // Returns true if the planet is to explode
                        }
                        else
                        {
                                if ((p->mTimer % p->mExplodeSpeed) == 0)
                                {
                                        if (++p->mExplodeFrame >= IMAGE_BOMB_RADIAL_DEATH->mNumCols)
                                        {
                                                mPlanets.erase(mPlanets.begin() + i);
                                                --i;
                                        }
                                }
                        }
                }

                if (playSound)
                        mApp->PlaySample(SOUND_PLANET);
        }

        // update and move the particles. When they have reached
        // their last frame, remove them.
        for (i = 0; i < mParticles.size(); i++)
        {
                Particle* p = &mParticles[i];
                ++p->mTimer;

                p->mX += p->mVX * theFrac;
                p->mY += p->mVY * theFrac;
                p->mVY += 0.1f;
                if (p->mTimer % 6 == 0)
                {
                        if (++p->mFrame >= IMAGE_PARTICLE_LIGHTNING->mNumCols)
                        {
                                mParticles.erase(mParticles.begin() + i);
                                --i;
                        }
                }
        }

        if (mLevelupEffect->IsActive())
        {
                mLevelupEffect->Update(theFrac);

                // If the proper state is reached in the level up effect, then we can begin
                // setting up the next level.
                if (mLevelupEffect->StartNextLevel())
                {
                        // Just finished, start the next level
                        mOptionsBtn->SetVisible(true);
                        InitLevel(mLevel + 1);
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::FillRight(float amt)
{
        bool change = true;

        // Fill the entire line, moving rightward
        for (int y = mFillRegion.mTop; y <= mFillRegion.mBottom; y++)
        {
                GridTile* gt = &mGridState[y][mFillRegion.mLeft];

                // We only want to fill those pieces that are in the GRID_FILLING state
                if (gt->mFillState == GRID_FILLING)
                {
                        // This piece is filling up, expand its width
                        gt->mFillRect.mWidth += amt;

                        // If the width exceeds that of the grid piece, overflow the result
                        // into the tile next to it so that the filling appears continuous.
                        if (gt->mFillRect.mWidth >= GRID_PIX_SIZE)
                        {
                                float overflow = gt->mFillRect.mWidth - GRID_PIX_SIZE;
                                gt->mFillState = GRID_FILLED;

                                gt->mFillRect.mWidth = GRID_PIX_SIZE;

                                //overflow into next column, if the next column is within our
                                //fill region and if the piece is in the normal tile state.
                                if (mFillRegion.mLeft + 1 <= mFillRegion.mRight)
                                        if (mGridState[y][mFillRegion.mLeft + 1].mFillState == GRID_NORMAL)
                                                mGridState[y][mFillRegion.mLeft + 1].mFillRect.mWidth += overflow;
                        }
                        else
                                change = false;
                }              
        }

        //if "change" is true, then move one column right and begin filling in that column next
        //time this function is called. If there are no more columns to fill, we're done.

        if (change && (++mFillRegion.mLeft > mFillRegion.mRight))
                mFilling = false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::FillLeft(float amt)
{
        float leftX = GetColPix(mFillRegion.mRight);
        bool change = true;

        //This algorithm works just like FillRight except that it's filling
        //from the right side of the grid piece instead of the left, so there's
        //a couple extra calculations.

        for (int y = mFillRegion.mTop; y <= mFillRegion.mBottom; y++)
        {
                GridTile* gt = &mGridState[y][mFillRegion.mRight];

                if (gt->mFillState == GRID_FILLING)
                {
                        gt->mFillRect.mWidth += amt;
                        gt->mFillRect.mX -= amt;

                        if ((gt->mFillRect.mWidth >= GRID_PIX_SIZE) || (gt->mFillRect.mX < leftX))
                        {
                                float overflow = gt->mFillRect.mWidth - GRID_PIX_SIZE;
                                gt->mFillState = GRID_FILLED;

                                gt->mFillRect.mWidth = GRID_PIX_SIZE;
                                gt->mFillRect.mX = leftX;

                                //overflow into next column
                                if (mFillRegion.mRight - 1 >= mFillRegion.mLeft)
                                {
                                        if (mGridState[y][mFillRegion.mRight - 1].mFillState == GRID_NORMAL)
                                        {
                                                mGridState[y][mFillRegion.mRight - 1].mFillRect.mWidth += overflow;
                                                mGridState[y][mFillRegion.mRight - 1].mFillRect.mX -= overflow;
                                        }
                                }
                        }
                        else
                                change = false;
                }
        }

        if (change && (--mFillRegion.mRight < mFillRegion.mLeft))
                mFilling = false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::FillUp(float amt)
{
        bool change = true;
        float topY = GetRowPix(mFillRegion.mBottom);

        //This algorithm works just like FillRight except that it's filling
        //from the bottom side of the grid piece instead of the left, so there's
        //a couple extra calculations.

        for (int x = mFillRegion.mLeft; x <= mFillRegion.mRight; x++)
        {
                GridTile* gt = &mGridState[mFillRegion.mBottom][x];

                if (gt->mFillState == GRID_FILLING)
                {
                        gt->mFillRect.mHeight += amt;
                        gt->mFillRect.mY -= amt;

                        if ((gt->mFillRect.mHeight >= GRID_PIX_SIZE) || (gt->mFillRect.mY < topY))
                        {
                                float overflow = gt->mFillRect.mHeight - GRID_PIX_SIZE;
                                gt->mFillState = GRID_FILLED;

                                gt->mFillRect.mHeight = GRID_PIX_SIZE;
                                gt->mFillRect.mY = topY;

                                //overflow into next row
                                if (mFillRegion.mBottom - 1 > mFillRegion.mTop)
                                {
                                        if (mGridState[mFillRegion.mBottom - 1][x].mFillState == GRID_NORMAL)
                                        {
                                                mGridState[mFillRegion.mBottom - 1][x].mFillRect.mHeight += overflow;
                                                mGridState[mFillRegion.mBottom - 1][x].mFillRect.mY -= overflow;
                                        }
                                }
                        }
                        else
                                change = false;
                }
        }

        if (change && (--mFillRegion.mBottom < mFillRegion.mTop))
                mFilling = false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::FillDown(float amt)
{
        bool change = true;

        //This algorithm works just like FillRight except that it's filling
        //from the top side of the grid piece instead of the left.

        for (int x = mFillRegion.mLeft; x <= mFillRegion.mRight; x++)
        {
                GridTile* gt = &mGridState[mFillRegion.mTop][x];

                if (gt->mFillState == GRID_FILLING)
                {
                        gt->mFillRect.mHeight += amt;

                        if (gt->mFillRect.mHeight >= GRID_PIX_SIZE)
                        {
                                float overflow = gt->mFillRect.mHeight - GRID_PIX_SIZE;
                                gt->mFillState = GRID_FILLED;

                                gt->mFillRect.mHeight = GRID_PIX_SIZE;

                                //overflow into next row
                                if (mFillRegion.mTop + 1 <= mFillRegion.mBottom)
                                        if (mGridState[mFillRegion.mTop + 1][x].mFillState == GRID_NORMAL)
                                                mGridState[mFillRegion.mTop + 1][x].mFillRect.mHeight += overflow;
                        }
                        else
                                change = false;
                }
        }

        if (change && (++mFillRegion.mTop > mFillRegion.mBottom))
                mFilling = false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MoveLines(float theFrac)
{

        // Move the lines, differently depending on if they are oriented verticall
        // or horizontally. When a line reaches its target, it is done, and planets
        // can bounce off of it. When both lines are done, we will calculate the region
        // to fill.

        int numDone = 0;
        if (!mMovingLine1.mDone)
        {
                float amt = -mLineSpeed * theFrac;

                if (mMovingLine1.mIsVertical)
                {
                        mMovingLine1.mY += amt;
                        mMovingLine1.mHeight += fabsf(amt);

                        if (mMovingLine1.mY <= mMovingLine1.mTargetY)
                        {
                                mMovingLine1.mY = mMovingLine1.mTargetY;
                                mMovingLine1.mDone = true;
                                ++numDone;
                        }
                }
                else
                {
                        mMovingLine1.mX += amt;
                        mMovingLine1.mWidth += fabsf(amt);

                        if (mMovingLine1.mX <= mMovingLine1.mTargetX)
                        {
                                mMovingLine1.mX = mMovingLine1.mTargetX;
                                mMovingLine1.mDone = true;
                                ++numDone;
                        }
                }
        }

        if (!mMovingLine2.mDone)
        {
                float amt = mLineSpeed * theFrac;

                if (mMovingLine2.mIsVertical)
                {
                        mMovingLine2.mHeight += amt;

                        if (mMovingLine2.mY + mMovingLine2.mHeight >= mMovingLine2.mTargetY)
                        {
                                mMovingLine2.mHeight = mMovingLine2.mTargetY - mMovingLine2.mY;
                                mMovingLine2.mDone = true;
                                ++numDone;
                        }
                }
                else
                {
                        mMovingLine2.mWidth += amt;

                        if (mMovingLine2.mX + mMovingLine2.mWidth >= mMovingLine2.mTargetX)
                        {
                                mMovingLine2.mWidth = mMovingLine2.mTargetX - mMovingLine2.mX;
                                mMovingLine2.mDone = true;
                                ++numDone;
                        }
                }
        }      

        // While at least one of the lines is still moving, make a bunch of sparks shower off
        // the edge of them. In non-3d mode, we'll only emit half the sparks to reduce the CPU time consumed.
        if (!mMovingLine2.mDone || !mMovingLine1.mDone)
        {
                int modVal = gSexyAppBase->Is3DAccelerated() ? 2 : 4;
                if (mUpdateCnt % modVal == 0)
                        EmitSparks();
        }

        // If both are done at the same time, or both are done but perhaps one completed earlier than the other,
        // then it's time to compute the fill region.
        if ((numDone == 2) || ((numDone == 1) && mMovingLine1.mDone && mMovingLine2.mDone))
        {
                mFilling = true;

                CalculateFillRegions();
        }      

        // Quit playing the electrical shorting out sound when both lines are broken or
        // done or any combination of the two.
        if ((mMovingLine1.mDone && mMovingLine2.mDone) ||
                (mMovingLine1.mBroken && mMovingLine2.mDone) ||
                (mMovingLine2.mBroken && mMovingLine1.mDone))
                mShortSound->Stop();
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Draw(Graphics* g)
{
        g->SetColor(Color::Black);
        g->FillRect(0, 0, mWidth, mHeight);

        int incAmt = gSexyAppBase->Is3DAccelerated() ? 1 : 2;

        // Draw less starts if not in 3D mode to reduce CPU usage, since they aren't a critical feature
        for (int i = 0; i < MAX_STARS; i += incAmt)
        {
                Star* s = &mStarField[i];
                g->SetColor(s->mColor);
                g->FillRect(s->mX, s->mY, 1, 1);
        }      

        // We don't draw the other game elements under certain conditions, like
        // if the level up and game over effects are in a few particular states.
        if (!mLevelupEffect->HideBoard() && !mGameOverEffect->HideBoard())
        {
                DrawGrid(g);
                DrawUI(g);

                // To prevent cheating, don't draw planets if the game is paused.
                // Also don't show them during certain points of the level up effect
                if (!mLevelupEffect->HidePlanets() || (mPauseLevel > 0))
                        DrawPlanets(g);

                if (!mGameOverEffect->IsActive())
                        DrawMovingBeams(g);            

                DrawHungarr(g);
        }      

        if (mLevelupEffect->IsActive())
                mLevelupEffect->Draw(g);

        if (mGameOverEffect->IsActive())
                mGameOverEffect->Draw(g);

        if (mPauseLevel > 0)
        {
                // Paused: draw an overlay
                g->SetColor(Color(0, 0, 0, 128));
                g->FillRect(0, 0, mWidth, mHeight);
                g->SetColor(Color::White);
                g->SetFont(FONT_HUNGARR);
                g->DrawString(_S("PAUSED"), mWidth / 2 - FONT_HUNGARR->StringWidth(_S("PAUSED")) / 2, mHeight / 2);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawGrid(Graphics* g)
{
        // Draw an outline around the whole grid region. See LevelUpEffect.cpp or
        // GameOverEffect.cpp for full details on using HSL instead of RGB for color and
        // why it's useful.
        if (mFlashCount > 0)
                g->SetColor(mApp->HSLToRGB(mBorderHue, 255, 128) & 0xFFFFFFFF);
        else
                g->SetColor(Color(255, 0, 0, 64));

        // grid outline:
        g->FillRect(0, GRID_START_Y, GRID_START_X, mHeight - GRID_START_Y);
        g->FillRect(0, GRID_START_Y - GRID_PIX_SIZE, mWidth, GRID_PIX_SIZE);
        g->FillRect(GRID_END_X, GRID_START_Y, mWidth - GRID_END_X, mHeight - GRID_START_Y);
        g->FillRect(GRID_START_X, GRID_END_Y, GRID_END_X - GRID_START_X, mHeight - GRID_END_Y);


        // To make a weird pattern, a few of the grid pieces will be more brightly
        // colored than the others, if they are in the normal state.
        bool startBright = true;
        for (int y = 0; y < GRID_HEIGHT; y++)
        {
                int drawY = GetRowPix(y);

                for (int x = 0; x < GRID_WIDTH; x++)
                {
                        int drawX = GetColPix(x);
                        int state = mGridState[y][x].mFillState;
                        if (state == GRID_FILLING)
                        {
                                // The grid piece is in the process of filling up. Draw a different colored rectangle for the
                                // filled in part, and then draw the rest normally.
                                FRect* fr = &mGridState[y][x].mFillRect;
                                Rect normalRect;

                                if (mFillDirection == FILL_RIGHT)
                                        normalRect = Rect(fr->mX + fr->mWidth, drawY, GRID_PIX_SIZE - fr->mWidth + 1, GRID_PIX_SIZE);
                                else if (mFillDirection == FILL_LEFT)
                                        normalRect = Rect(drawX, drawY, GRID_PIX_SIZE - fr->mWidth, GRID_PIX_SIZE);
                                else if (mFillDirection == FILL_UP)
                                        normalRect = Rect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE - fr->mHeight);
                                else
                                        normalRect = Rect(drawX, fr->mY + fr->mHeight, GRID_PIX_SIZE, GRID_PIX_SIZE - fr->mHeight + 1);


                                if ((normalRect.mWidth > 0) && (normalRect.mHeight > 0))
                                {                                      
                                        g->SetColor(Color(255, 255, 0, startBright && (x % 2 == 0) ? 128 : 64));
                                        g->FillRect(normalRect);
                                        g->SetColor(Color(0, 0, 0));
                                        g->DrawRect(normalRect);
                                }

                                g->SetColor(Color(255, 255, 0, 100));
                                FillRectF(g, *fr);
                        }
                        else if (state == GRID_NORMAL)
                        {
                                // Just draw the grid piece normally, with a black outline around it.
                               
                                g->SetColor(Color(255, 255, 0, startBright && (x % 3 == 0) ? 64 : 32));
                                g->FillRect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE);
                                g->SetColor(Color(0, 0, 0));
                                g->DrawRect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE);
                        }
                        else
                        {
                                // The piece is completely filled in, just fill the whole rectangle
                                g->SetColor(Color(255, 255, 0, 100));
                                FillRectF(g, mGridState[y][x].mFillRect);
                        }
                }

                startBright = !startBright;
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawUI(Graphics* g)
{
        int ascent = FONT_HUNGARR->GetAscent();
        int height = FONT_HUNGARR->GetHeight();

        g->SetFont(FONT_HUNGARR);
        SexyString s;
        int rightX = FONT_HUNGARR->StringWidth(_S("POPULATION CONSUMED: ")) + 5;
       
        int strWidth;
        s = _S("WORLDS DEVOURED: ");
        strWidth = FONT_HUNGARR->StringWidth(s);
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, rightX - strWidth, ascent);
        g->SetColor(Color(255, 0, 0, 200));
        g->DrawString(StrFormat(_S("%d"), mNumPlanetsEaten), rightX - 5, ascent);

        s = _S("POPULATION CONSUMED: ");
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, 5, height * 2);
        g->SetColor(Color(255, 0, 0, 200));
        g->DrawString(CommaSeperate(mPopulationEaten), rightX - 5, height * 2);

        s = _S("SCORE: ");
        strWidth = FONT_HUNGARR->StringWidth(s);
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, rightX - strWidth, height * 3);
        g->SetColor(Color(255, 255, 0, 200));
        g->DrawString(StrFormat(_S("%s"), CommaSeperate(mScore).c_str()), rightX - 5, height * 3);



        int x = 380;
        s = _S("SYSTEMS SUBJUGATED: ");
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, x, ascent);
        g->SetColor(Color(255, 0, 0, 200));
        g->DrawString(StrFormat(_S("%d%%"), mPercentComplete), x + FONT_HUNGARR->StringWidth(s), ascent);

        s = _S("LIVES: ");
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, x, height * 2);
        strWidth = FONT_HUNGARR->StringWidth(s);
        g->DrawImage(IMAGE_HUNGARR_SMALL, strWidth + x, ascent);
        g->SetColor(Color(255, 0, 0, 200));
        g->DrawString(StrFormat(_S("x%d"), mLives), x + 10 + strWidth + IMAGE_HUNGARR_SMALL->GetWidth(), height * 2);  

        s = _S("LEVEL: ");
        g->SetColor(Color(255, 255, 255, 128));
        g->DrawString(s, x, height * 3);
        g->SetColor(Color(255, 255, 0, 200));
        g->DrawString(StrFormat(_S("%d"), mLevel), x + FONT_HUNGARR->StringWidth(s), height * 3);

        for (int i = 0; i < mBonusText.size(); i++)
        {
                BonusText* bt = &mBonusText[i];
                g->SetColor( (mApp->HSLToRGB(bt->mHue, 255, 128) & 0xFFFFFF) | (bt->mAlpha << 24) );
                g->DrawString(bt->mText, bt->mX, bt->mY);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Beam1DrawHelper(Graphics* g)
{
        // In 3D mode we'll use the DrawImageF versions since they look nicer and
        // perform anti-aliasing, and make floating point movement appear smoother.
        // Since they are more taxing, we'll use the default integer routines if hardware
        // mode is not available.

        // The offsets you see were taken from the actual image itself. You'll notice
        // that the image has a lot of blank space around the actual beam, so we move things
        // around a bit to get a nice aligned look. You'd either just play around with these
        // numbers till it looked right, or your artist would inform you.
        if (mMovingLine1.mIsVertical)
        {
                if (gSexyAppBase->Is3DAccelerated())
                {
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mMovingLine1.mX - 8, mMovingLine1.mY,
                                Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), mMovingLine1.mHeight));
                }
                else
                {
                        g->DrawImage(IMAGE_HUNGARR_BEAM_UP, mMovingLine1.mX - 8, mMovingLine1.mY,
                                Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), mMovingLine1.mHeight));
                }
        }
        else
        {
                if (gSexyAppBase->Is3DAccelerated())
                {
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mMovingLine1.mX, mMovingLine1.mY - 8,
                                Rect(0, 0, mMovingLine1.mWidth, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
                }
                else
                {
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mMovingLine1.mX, mMovingLine1.mY - 8,
                                Rect(0, 0, mMovingLine1.mWidth, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Beam2DrawHelper(Graphics* g)
{
        // In 3D mode we'll use the DrawImageF versions since they look nicer and
        // perform anti-aliasing, and make floating point movement appear smoother.
        // Since they are more taxing, we'll use the default integer routines if hardware
        // mode is not available.

        // The offsets you see were taken from the actual image itself. You'll notice
        // that the image has a lot of blank space around the actual beam, so we move things
        // around a bit to get a nice aligned look. You'd either just play around with these
        // numbers till it looked right, or your artist would inform you.

        if (mMovingLine2.mIsVertical)
        {
                if (gSexyAppBase->Is3DAccelerated())
                {
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mMovingLine2.mX - 8, mMovingLine2.mY - 1,
                                Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - mMovingLine2.mHeight,
                                IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), mMovingLine2.mHeight));
                }
                else
                {
                        g->DrawImage(IMAGE_HUNGARR_BEAM_DOWN, mMovingLine2.mX - 8, mMovingLine2.mY - 1,
                                Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - mMovingLine2.mHeight,
                                IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), mMovingLine2.mHeight));
                }
        }
        else
        {
                if (gSexyAppBase->Is3DAccelerated())
                {
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mMovingLine2.mX - 1, mMovingLine2.mY - 8,
                                Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - mMovingLine2.mWidth, 0,
                                mMovingLine2.mWidth, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
                }
                else
                {
                        g->DrawImage(IMAGE_HUNGARR_BEAM_RIGHT, mMovingLine2.mX - 1, mMovingLine2.mY - 8,
                                Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - mMovingLine2.mWidth, 0,
                                mMovingLine2.mWidth, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawMovingBeams(Graphics* g)
{              
        // If the beams are moving, then draw them. Make them pulse too. You make a
        // pulsing effect like we did in the previous demos: draw the image a second time on
        // top of the original additively, and colorize the image, setting the RGB values to
        // a different intensity. The result is an image that gets brighter and dimmer over time.
        if (!mMovingLine1.mBroken && (!mMovingLine1.mDone || mFilling || !mMovingLine2.mDone))
        {              
                Beam1DrawHelper(g);

                if (!mMovingLine1.mDone)
                {
                        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
                        g->SetColorizeImages(true);
                        g->SetColor(Color(mBeamPulseVal, mBeamPulseVal, mBeamPulseVal));                       
                        Beam1DrawHelper(g);
                        g->SetColorizeImages(false);
                        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
                }

        }

        if (!mMovingLine2.mDone || mFilling || !mMovingLine1.mDone)
        {
                Beam2DrawHelper(g);

                if (!mMovingLine2.mDone)
                {
                        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
                        g->SetColorizeImages(true);
                        g->SetColor(Color(mBeamPulseVal, mBeamPulseVal, mBeamPulseVal));                       
                        Beam2DrawHelper(g);
                        g->SetColorizeImages(false);
                        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
                }
        }

        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
        for (int i = 0; i < mParticles.size(); i++)
        {
                Particle* p = &mParticles[i];          
                g->DrawImageCel(IMAGE_PARTICLE_LIGHTNING, p->mX, p->mY, p->mFrame);            
        }
        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawPlanets(Graphics* g)
{
        // If the user has 3d, we'll rotate the planets using the floating point
        // smooth rotation function. If they don't, we'll avoid rotating at all to
        // save on CPU cycles and keep the framerate up.
        int w = IMAGE_PLANETS->GetCelWidth();
        int h = IMAGE_PLANETS->GetCelHeight();
        for (int i = 0; i < mPlanets.size(); i++)
        {
                Planet* p = &mPlanets[i];              
                if (p->mExploding)
                {
                        g->DrawImageCel(IMAGE_BOMB_RADIAL_DEATH,
                                p->mX - (IMAGE_BOMB_RADIAL_DEATH->GetCelWidth() / 2 + w / 2),
                                p->mY - (IMAGE_BOMB_RADIAL_DEATH->GetCelHeight() / 2 + h / 2),
                                p->mExplodeFrame);
                }
                else
                {
                        Rect r = Rect(p->mImgCol * w, 0, w, IMAGE_PLANETS->GetCelHeight());

                        if (gSexyAppBase->Is3DAccelerated())
                                g->DrawImageRotatedF(IMAGE_PLANETS, p->mX, p->mY, p->mRotationAngle, &r);
                        else
                                g->DrawImage(IMAGE_PLANETS, p->mX, p->mY, r);
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawHungarrVertBeamsHelper(Graphics* g)
{
        // This draws the two little static beams that are always attached to
        // Hun-garr's bitmap. If the user has hardware acceleration, we'll
        // draw the beams pulsating. If not, we'll skip it since it's time consuming
        // and doesn't hurt the game any. It'd be worse to drop the framrate by a few FPS
        // for this effect.

        int h = IMAGE_HUNGARR_VERT->GetHeight() / 2;

        if (gSexyAppBase->Is3DAccelerated())
        {
                g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));

                g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));

                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
                g->SetColorizeImages(true);

                g->SetColor(Color(255, 255, 255, mBeamPulseVal));
                g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));

                g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));


                g->SetColorizeImages(false);
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
        }
        else
        {
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
                g->DrawImage(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));

                g->DrawImage(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawHungarrHorizBeamsHelper(Graphics* g)
{
        // This draws the two little static beams that are always attached to
        // Hun-garr's bitmap. If the user has hardware acceleration, we'll
        // draw the beams pulsating. If not, we'll skip it since it's time consuming
        // and doesn't hurt the game any. It'd be worse to drop the framrate by a few FPS
        // for this effect.

        int w = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;

        if (gSexyAppBase->Is3DAccelerated())
        {
                g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));

                g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
       
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
                g->SetColorizeImages(true);

                g->SetColor(Color(255, 255, 255, mBeamPulseVal));
                g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));

                g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));

                g->SetColorizeImages(false);
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
        }
        else
        {
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);

                g->DrawImage(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));

                g->DrawImage(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));

                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::DrawHungarr(Graphics* g)
{
        // again, we use the floating point functions instead of the integer ones
        // if the user has a 3d card.

        bool is3d = gSexyAppBase->Is3DAccelerated();

        if (mHungarrIsVertical)
        {                                      
                DrawHungarrVertBeamsHelper(g);

                if (is3d)
                        g->DrawImageF(IMAGE_HUNGARR_VERT, mHungarrX, mHungarrY);
                else
                        g->DrawImage(IMAGE_HUNGARR_VERT, mHungarrX, mHungarrY);
        }
        else
        {                              
                DrawHungarrHorizBeamsHelper(g);

                if (is3d)
                        g->DrawImageF(IMAGE_HUNGARR_HORIZ, mHungarrX, mHungarrY);
                else
                        g->DrawImage(IMAGE_HUNGARR_HORIZ, mHungarrX, mHungarrY);
        }      
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::AddedToManager(WidgetManager* theWidgetManager)
{      
        Widget::AddedToManager(theWidgetManager);


        mOptionsBtn = new ButtonWidget(1, this);       
        mOptionsBtn->SetFont(FONT_DEFAULT);
        mOptionsBtn->mLabel = _S("Options");
        mOptionsBtn->SetColor(ButtonWidget::COLOR_LABEL, Color::White);
        mOptionsBtn->SetColor(ButtonWidget::COLOR_LABEL_HILITE, Color::White); 

        mOptionsBtn->mOverImage = IMAGE_BUTTON_OVER;
        mOptionsBtn->mDownImage = IMAGE_BUTTON_DOWN;
        mOptionsBtn->mButtonImage = IMAGE_BUTTON_NORMAL;
        mOptionsBtn->mDoFinger = true;
        mOptionsBtn->Resize(gSexyAppBase->mWidth - IMAGE_BUTTON_NORMAL->GetWidth() - 10, FONT_HUNGARR->GetHeight() * 3 - 20,
                                IMAGE_BUTTON_NORMAL->GetWidth(), IMAGE_BUTTON_NORMAL->GetHeight());

        theWidgetManager->AddWidget(mOptionsBtn);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::RemovedFromManager(WidgetManager* theWidgetManager)
{
        Widget::RemovedFromManager(theWidgetManager);

        theWidgetManager->RemoveWidget(mOptionsBtn);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::ButtonDepress(int theId)
{
        // Play a sound whenever the options button is depressed/sad.
        if (theId == mOptionsBtn->mId)
        {
                // Stop the shorting sound if it's playing, otherwise it's annoying
                mShortSound->Stop();

                mApp->PlaySample(SOUND_BUTTON);
                Pause(true);
                OptionsDialog* od = new OptionsDialog(this);
                od->Resize(mWidth / 2 - 200, mHeight / 2 - 175, 400, 350);
                mApp->AddDialog(OptionsDialog::DIALOG_ID, od);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseMove(int x, int y)
{
        UpdateHungarrPosition(x, y);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseDrag(int x, int y)
{
        UpdateHungarrPosition(x, y);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::MouseDown(int x, int y, int theClickCount)
{              
        // if the level up effect is displaying stats, or the game over effect is too,
        // and the user clicked, then start the next phase
        if (mLevelupEffect->ShowingStats())
                mLevelupEffect->DoneViewingStats();

        if (mGameOverEffect->CanStartNewGame())
                mGameOverEffect->DoneViewingStats();

        // ignore mouse clicks when paused or an effect is on screen and the user has no reason to click
        if (mLevelupEffect->IsActive() || (mPauseLevel > 0) || (mGameOverEffect->IsActive() && !mGameOverEffect->CanStartNewGame()))
                return;

        // On a right click, if the click was within the grid bounds, switch hungarr's orientation
        if ((theClickCount < 0) && XCoordInBounds(x) && YCoordInBounds(y, mHungarrIsVertical))
                mHungarrIsVertical = !mHungarrIsVertical;
        else if ((theClickCount > 0) && mMovingLine1.mDone && mMovingLine2.mDone && !mFilling)
        {
                //left click, and there's no lines moving: drop two new lines

                // Make sure the user didn't click on a planet which would instantly kill them
                FRect hungarrRect = FRect(mHungarrX, mHungarrY, IMAGE_HUNGARR_HORIZ->mWidth, IMAGE_HUNGARR_HORIZ->mHeight);
                for (int i = 0; i < mPlanets.size(); i++)
                {
                        Planet* p = &mPlanets[i];
                        FRect planetRect = FRect(p->mX, p->mY, IMAGE_PLANETS->GetCelWidth(), IMAGE_PLANETS->GetCelHeight());
                        if (planetRect.Intersects(hungarrRect))
                                return;
                }

                mApp->PlaySample(SOUND_MAGZAP);

                // start the electrical shorting sound
                mShortSound->Play(true, false);

                mMovingLine1.mDone = mMovingLine2.mDone = false;
                mMovingLine1.mBroken = mMovingLine2.mBroken = false;
                int midX = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;
                int midY = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;

                //Align the XYs of the lines to the grid, and set the target coordinates to the
                //closest normal state tile.
                if (mHungarrIsVertical)
                {
                        mMovingLine1.mIsVertical = mMovingLine2.mIsVertical = true;                    
                        mMovingLine1.mX = mMovingLine2.mX = GetAlignedX(mHungarrX + midX);
                        mMovingLine1.mY = mMovingLine2.mY = GetAlignedY(mHungarrY + midY);
                        mMovingLine1.mHeight = 1;
                        mMovingLine2.mHeight = 13;
                        mMovingLine1.mWidth = mMovingLine2.mWidth = GRID_PIX_SIZE;
                        mMovingLine1.mTargetY = mMovingLine2.mTargetY = mMovingLine1.mY;
                        mMovingLine1.mTargetX = mMovingLine2.mTargetX = mMovingLine1.mX;

                        // Make sure the target coords end at a tile that's normal. If not, keep moving them
                        int row = GetRow(mMovingLine1.mTargetY);
                        int col = GetCol(mMovingLine1.mTargetX);
                       
                        // Tile immediately below is not valid
                        if (mGridState[row][col].mFillState != GRID_NORMAL)
                                return;

                        while ((row >= 0) && (mGridState[row][col].mFillState == GRID_NORMAL))
                        {
                                mMovingLine1.mTargetY -= GRID_PIX_SIZE;
                                --row;
                        }

                        // Make it end on the last valid tile. The loop above makes it leave
                        // on an invalid tile
                        mMovingLine1.mTargetY += GRID_PIX_SIZE;

                        row = GetRow(mMovingLine2.mTargetY);
                        col = GetCol(mMovingLine2.mTargetX);

                        while ((row < GRID_HEIGHT) && (mGridState[row][col].mFillState == GRID_NORMAL))
                        {
                                mMovingLine2.mTargetY += GRID_PIX_SIZE;
                                ++row;
                        }

                        if (mMovingLine1.mTargetY > mMovingLine2.mTargetY)
                                mMovingLine1.mDone = mMovingLine2.mDone = true;
                }
                else
                {
                        mMovingLine1.mIsVertical = mMovingLine2.mIsVertical = false;
                        mMovingLine1.mX = mMovingLine2.mX = GetAlignedX(mHungarrX + midX);
                        mMovingLine1.mY = mMovingLine2.mY = GetAlignedY(mHungarrY + midY);
                        mMovingLine1.mWidth = 1;
                        mMovingLine2.mWidth = 13;
                        mMovingLine1.mHeight = mMovingLine2.mHeight = GRID_PIX_SIZE;
                        mMovingLine1.mTargetX = mMovingLine2.mTargetX = mMovingLine1.mX;
                        mMovingLine1.mTargetY = mMovingLine2.mTargetY = mMovingLine1.mY;

                        // Make sure the target coords end at a tile that's normal. If not, keep moving them
                        int row = GetRow(mMovingLine1.mTargetY);
                        int col = GetCol(mMovingLine1.mTargetX);

                        // Tile immediately below is not valid...?
                        if (mGridState[row][col].mFillState != GRID_NORMAL)
                                return;

                        while ((col >= 0) && (mGridState[row][col].mFillState == GRID_NORMAL))
                        {
                                mMovingLine1.mTargetX -= GRID_PIX_SIZE;
                                --col;
                        }

                        // Make it end on the last valid tile. The loop above makes it leave
                        // on an invalid tile
                        mMovingLine1.mTargetX += GRID_PIX_SIZE;


                        row = GetRow(mMovingLine2.mTargetY);
                        col = GetCol(mMovingLine2.mTargetX);
                       
                        while ((col < GRID_WIDTH) && (mGridState[row][col].mFillState == GRID_NORMAL))
                        {
                                mMovingLine2.mTargetX += GRID_PIX_SIZE;
                                ++col;
                        }

                       
                        if (mMovingLine1.mTargetX > mMovingLine2.mTargetX)
                                mMovingLine1.mDone = mMovingLine2.mDone = true;
                }
        }

        UpdateHungarrPosition(x, y);

}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::UpdateHungarrPosition(int x, int y)
{
        // Place the Hun-garr bitmap and the two lines that stick out of him
        // so that Hun-garr is centered on the mouse cursor
        int midX = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;
        int midY = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;

        if (YCoordInBounds(y, mHungarrIsVertical))
        {              
                mHungarrY = y - midY;
                mLine1Y = mHungarrY + (!mHungarrIsVertical ? 7 : -12);         
                mLine2Y = mHungarrY + (!mHungarrIsVertical ? 7 : 35);
        }

        if (XCoordInBounds(x))
        {
                mHungarrX = x - midX;
                mLine1X = mHungarrX + (!mHungarrIsVertical ? -13 : 8);
                mLine2X = mHungarrX + (!mHungarrIsVertical ? 36 : 9);
        }      
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::CalculateFillRegions(void)
{
        int topRow, botRow, leftCol, rightCol;

        // The basic idea is this: Find the CLOSEST edge to where the user clicks that
        // meets the following conditions:
        // 1. If Hun-garr is vertical, the edge must be as tall as the column created
        //      by the two emitted lines (or more) and every grid piece must be normal
        // 2. If Hun-garr is horizontal, the edge must be as wide as the row created by
        //      the two emitted lines (or more) and every grid piece must be normal
        // 3. If either of the lines were broken, we instead just fill in the single
        //      line made by the non-broken line (the code won't execute if both are broken)


        if (mMovingLine1.mIsVertical)
        {
                topRow = GetRow(mMovingLine1.mTargetY);
                int col1 = GetCol(mMovingLine1.mTargetX);
                botRow = GetRow(mMovingLine2.mTargetY);

                if ((mMovingLine1.mBroken && !mMovingLine2.mBroken) ||
                        (!mMovingLine1.mBroken && mMovingLine2.mBroken))
                {
                        leftCol = col1;
                        rightCol = col1 + 1;
                        mFillDirection = FILL_RIGHT;
                        topRow = mMovingLine1.mBroken ? GetRow(mMovingLine2.mY) : topRow;
                        botRow = mMovingLine1.mBroken ? botRow : GetRow(mMovingLine2.mY);
                }
                else
                {
                        int rightEdge, leftEdge;
                        GetVerticalFillValues(col1, topRow, botRow, 1, &rightEdge);
                        GetVerticalFillValues(col1, topRow, botRow, -1, &leftEdge);

                        if ((rightEdge - col1) <= (col1 - leftEdge))
                        {
                                leftCol = col1;
                                rightCol = rightEdge + 1;
                                mFillDirection = FILL_RIGHT;                   
                        }
                        else
                        {
                                leftCol = leftEdge;
                                rightCol = col1 + 1;
                                mFillDirection = FILL_LEFT;
                        }
                }

        }
        else
        {
                leftCol = GetCol(mMovingLine1.mTargetX);
                rightCol = GetCol(mMovingLine2.mTargetX);
                int row1 = GetRow(mMovingLine1.mTargetY);

                if ((mMovingLine1.mBroken && !mMovingLine2.mBroken) ||
                        (!mMovingLine1.mBroken && mMovingLine2.mBroken))
                {
                        leftCol = mMovingLine1.mBroken ? GetCol(mMovingLine2.mX) : leftCol;
                        rightCol = mMovingLine1.mBroken ? rightCol : GetCol(mMovingLine2.mX);
                        topRow = row1;
                        botRow = row1 + 1;
                        mFillDirection = FILL_DOWN;
                }
                else
                {
                        int topEdge, botEdge;
                        GetHorizontalFillValues(row1, leftCol, rightCol, -1, &topEdge);
                        GetHorizontalFillValues(row1, leftCol, rightCol, 1, &botEdge);

                        if ((botEdge - row1) <= (row1 - topEdge))
                        {
                                topRow = row1;
                                botRow = botEdge + 1;
                                mFillDirection = FILL_DOWN;
                        }
                        else
                        {
                                topRow = topEdge;
                                botRow = row1 + 1;
                                mFillDirection = FILL_UP;
                        }
                }

        }      

        //Make a rectangular fill region: every block in it will eventually be filled.
        // Then, for all grid pieces in that region, if they are in the normal state,
        // set them to the filling state and initialize their mFillRect's
        mFillRegion.mLeft = leftCol;
        mFillRegion.mRight = rightCol - 1;
        mFillRegion.mTop = topRow;
        mFillRegion.mBottom = botRow - 1;

        for (int y = topRow; y < botRow; y++)
        {
                for (int x = leftCol; x < rightCol; x++)
                {
                        if (mGridState[y][x].mFillState == GRID_NORMAL)
                        {
                                mGridState[y][x].mFillState = GRID_FILLING;

                                switch (mFillDirection)
                                {
                                        case FILL_RIGHT:
                                                mGridState[y][x].mFillRect =
                                                        FRect(GetColPix(x), GetRowPix(y), 0, GRID_PIX_SIZE);
                                                break;

                                        case FILL_LEFT:
                                                mGridState[y][x].mFillRect =
                                                        FRect(GetColPix(x + 1), GetRowPix(y), 0, GRID_PIX_SIZE);
                                                break;

                                        case FILL_UP:
                                                mGridState[y][x].mFillRect =
                                                        FRect(GetColPix(x), GetRowPix(y + 1), GRID_PIX_SIZE, 0);
                                                break;

                                        case FILL_DOWN:
                                                mGridState[y][x].mFillRect =
                                                        FRect(GetColPix(x), GetRowPix(y), GRID_PIX_SIZE, 0);
                                                break;
                                }
                        }
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::FillRectF(Graphics* g, FRect fr)
{
        Rect r = Rect((int)fr.mX, (int)fr.mY, (int)fr.mWidth, (int)fr.mHeight);
        g->FillRect(r);
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::GetVerticalFillValues(int startCol, int topRow, int botRow, int dir, int* edge)
{
        // If dir == -1, left, if 1, right
        // See function header for algorithm description

        bool done = false;
        int col = startCol;

        while (!done)
        {
                bool found = true;
                for (int y = topRow; y < botRow; y++)
                {
                        if (mGridState[y][col].mFillState != GRID_FILLED)
                        {
                                found = false;
                                break;
                        }
                }
               
                if (!found)
                {                      
                        if ( ((dir > 0) && (++col >= GRID_WIDTH)) ||
                                ((dir < 0) && (--col < 0)) )
                        {
                                done = true;
                                *edge = col + (dir > 0 ? -1 : 1);
                        }
                }
                else
                {
                        *edge = col;
                        done = true;
                }

        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::GetHorizontalFillValues(int startRow, int leftCol, int rightCol, int dir, int* edge)
{
        // If dir == -1, up, if 1, down
        // See function header for algorithm description

        bool done = false;
        int row = startRow;

        while (!done)
        {

                bool found = true;
                for (int x = leftCol; x < rightCol; x++)
                {
                        if (mGridState[row][x].mFillState != GRID_FILLED)
                        {
                                found = false;
                                break;
                        }
                }

                if (!found)
                {
                        if ( ((dir > 0) && (++row >= GRID_HEIGHT)) ||
                                ((dir < 0) && (--row < 0)) )
                        {
                                done = true;
                                *edge = row + (dir > 0 ? -1 : 1);
                        }
                }
                else
                {
                        *edge = row;
                        done = true;
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::InitLevel(int level)
{
        mLives += level - mLevel;

        mPopulationEaten = 0;
        mPlanetSpeed += 0.15f;
        mLevel = level;
        mPlanets.clear();
        mFlashCount = 0;
        mPercentComplete = 0;
        mPlanetsEaten.clear();
        mBonusText.clear();
        mParticles.clear();

        mFilling = false;

        // reset the grid state
        int i;
        for (i = 0; i < GRID_HEIGHT; i++)
        {
                for (int x = 0; x < GRID_WIDTH; x++)
                {
                        mGridState[i][x].mFillRect = FRect(x * GRID_PIX_SIZE, GRID_START_Y + i * GRID_PIX_SIZE, 0, 0);
                        mGridState[i][x].mFillState = GRID_NORMAL;
                }
        }

        // Start with 2 planets. Then add 1 every other level
        int numPlanets = 2 + (mLevel / 2);
        for (i = 0; i < numPlanets; i++)
        {
                Planet p;

                // Choose a random name and export
                p.mNameIdx = Rand() % NUM_PLANET_NAMES;
                p.mExportIdx = Rand() % NUM_PLANET_EXPORTS;

                // a random number I made up for the population. Increases by a random amount each level.
                p.mPopulation = (mLevel * 133602) + 748819;

                // Position it randomly within the confines of the grid
                p.mX = GRID_START_X + 20 + (Rand() % (GRID_END_X - GRID_START_X - 20));
                p.mY = GRID_START_Y + 20 + (Rand() % (GRID_END_Y - GRID_START_Y - 20));

                // Get a random angle for the planet to travel in. It's easier to do RAND on
                // degrees, so convert them to radians after choosing an angle
                float a = (Rand() % 360) * M_PI / 180.0f;
                p.mVX = mPlanetSpeed * cosf(a);
                p.mVY = -mPlanetSpeed * sinf(a);

                // don't let the speed be too close to 0 though, it's lame if the planet
                // bounces just straight vertically or horizontally
                if ((p.mVX >= -0.1) && (p.mVX <= 0.1))
                        p.mVX = 0.3f;

                if ((p.mVY >= -0.1) && (p.mVY <= 0.1))
                        p.mVY = 0.3f;

                // Set a random initial rotation angle and speed to rotate at.
                // All angle manipulation is in radians.
                p.mRotationAngle = (Rand() % 360) * M_PI / 180.0f;
                p.mRotateSpeed = (float)(Rand() % 100) / 1000.0f;
               
                // Choose a random image. There's 11 images, each is just 1 frame.
                p.mImgCol = Rand() % IMAGE_PLANETS->mNumCols;

                mPlanets.push_back(p);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
bool Board::MovePlanet(Planet* p, float theFrac)
{
        // This is a pretty simple collision detection routine and is good enough for this
        // game. It's also a lot easier to understand than other more accurate but more
        // complex methods. The basic idea is to check the two blocks in the direction of
        // travel to see if they are solid or not, and if so, we reverse course, and
        // don't move the planet to that new coordinate.

        int w = IMAGE_PLANETS->GetCelWidth();
        int h = IMAGE_PLANETS->GetCelHeight();
        bool playSample = false;

        // Update rotation
        p->mRotationAngle += p->mRotateSpeed;

        // Don't move it yet...compute where it WOULD be if the move was valid
        float newx = p->mX + p->mVX * theFrac;
        float newy = p->mY + p->mVY * theFrac;

        // If moving right, we'll check the grid piece right of the
        // planet to see if we hit it. Otherwise, we'll just use the grid piece that maps to
        // where the new X coordinate is:      
        float checkx = p->mVX > 0 ? newx + GRID_PIX_SIZE : newx;
        int col = GetCol(checkx);

        // We're going to check both the current row and row below it:
        int row = GetRow(p->mY);
        int nextrow = ValidRow(row + 1) ? row + 1 : row;

        if (ValidCol(col) && ValidRow(row))
        {
                int state1 = mGridState[row][col].mFillState;
                int state2 = mGridState[nextrow][col].mFillState;
               
                if ((state1 == GRID_NORMAL) && (state2 == GRID_NORMAL) && (newx > GRID_START_X))
                        p->mX = newx;   // valid grid space
                else if (((state1 == GRID_FILLING) || (state2 == GRID_FILLING)) && (newx > GRID_START_X))
                {
                        // planet entered a grid space that is in the process of being filled, so make it explode
                        p->mExploding = true;
                        GivePlanetBonus(p);                    
                        return true;
                }
                else
                {
                        // planet hit a filled in space, reverse the X velocity
                        playSample = true;
                        p->mVX = -p->mVX;
                }
        }
        else
        {
                // Not valid cases would be if the planet hit the edges of the board, where there
                // aren't any grid tiles. If so, just bounce it off the wall.
                playSample = true;
                p->mVX = -p->mVX;
        }
       

        // Now for the Y direction. The principal is the same as above.
        int checky = p->mVY > 0 ? newy + GRID_PIX_SIZE : newy;
        row = GetRow(checky);
        col = GetCol(p->mX);
        int nextcol = ValidCol(col + 1) ? col + 1 : col;       

        if (ValidCol(col) && ValidRow(row))
        {
                int state1 = mGridState[row][col].mFillState;
                int state2 = mGridState[row][nextcol].mFillState;

                if ((state1 == GRID_NORMAL) && (state2 == GRID_NORMAL) && (newy > GRID_START_Y))
                        p->mY = newy;
                else if (((state1 == GRID_FILLING) || (state2 == GRID_FILLING)) && (newy > GRID_START_Y))
                {
                        p->mExploding = true;
                        GivePlanetBonus(p);
                        return true;
                }
                else
                {
                        playSample = true;
                        p->mVY = -p->mVY;
                }
        }
        else
        {
                p->mVY = -p->mVY;
                playSample = true;
        }

        // When a planet hits a wall/filled in grid piece, bounce it and play a sound
        if (playSample)
                mApp->PlaySample(SOUND_PLANET_HIT);

        CheckPlanetBeamCollision(p);

        return false;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::CheckPlanetBeamCollision(Planet* p)
{
        // We're just going to do a rectangular collision check on each of the beams
        // and the given planet. Because the visible "beam" part of the image is smaller
        // than the image width/height, and the same goes for the planet too, you'll notice
        // that offsets are used to constrain the collision rectangles to make a more fair algorithm.
        FRect pr = FRect(p->mX + 4, p->mY + 4, 11, 11);

        FRect beam1Rect;

        if (!mMovingLine1.mIsVertical)
                beam1Rect = FRect(mMovingLine1.mX + 7, mMovingLine1.mY + 12, mMovingLine1.mWidth - 12, 8);
        else
                beam1Rect = FRect(mMovingLine1.mX + 12, mMovingLine1.mY + 10, 9, mMovingLine1.mHeight);

        // Only allow the user to lose 1 life max: if both beams break you don't lose 2. If the beam breaks,
        // set a flag and set its target to be its current location (indicating that it's done moving)
        if (pr.Intersects(beam1Rect))
        {
                if (!mMovingLine1.mDone)
                {
                        mMovingLine1.mBroken = true;
                        mApp->PlaySample(SOUND_BEAM_HIT);

                        if (!mMovingLine2.mBroken)
                                LostLife();

                        if (!mMovingLine1.mIsVertical)
                                mMovingLine1.mTargetX = mMovingLine1.mX = mMovingLine1.mX + mMovingLine1.mWidth;
                        else
                                mMovingLine1.mTargetY = mMovingLine1.mY = mMovingLine1.mY + mMovingLine1.mHeight;
                }
                else if (!mMovingLine1.mBroken && mMovingLine1.mDone && !mMovingLine2.mDone)
                {
                        // bounce off of it
                        if (mMovingLine1.mIsVertical)
                        {
                                p->mVX *= -1.0f;
                                p->mX += p->mVX;
                        }
                        else
                        {
                                p->mVY *= -1.0f;
                                p->mY += p->mVY;
                        }
                }
        }

        FRect beam2Rect;

        if (!mMovingLine2.mIsVertical)
                beam2Rect = FRect(mMovingLine2.mX, mMovingLine2.mY + 12, mMovingLine2.mWidth - 7, 8);
        else
                beam2Rect = FRect(mMovingLine2.mX + 12, mMovingLine2.mY, 9, mMovingLine2.mHeight);

        if (pr.Intersects(beam2Rect))
        {
                if (!mMovingLine2.mDone)
                {
                        mMovingLine2.mBroken = true;
                        mApp->PlaySample(SOUND_BEAM_HIT);

                        if (!mMovingLine1.mBroken)
                                LostLife();

                        if (!mMovingLine2.mIsVertical)
                                mMovingLine2.mTargetX = mMovingLine2.mX;
                        else
                                mMovingLine2.mTargetY = mMovingLine2.mY;
                }
                else if (!mMovingLine2.mBroken && mMovingLine2.mDone && !mMovingLine1.mDone)
                {
                        // bounce off of it
                        if (mMovingLine2.mIsVertical)
                        {
                                p->mVX *= -1.0f;
                                p->mX += p->mVX;
                        }
                        else
                        {
                                p->mVY *= -1.0f;
                                p->mY += p->mVY;
                        }
                }
        }

        if ((mMovingLine1.mDone && mMovingLine2.mDone) ||
                (mMovingLine1.mBroken && mMovingLine2.mDone) ||
                (mMovingLine2.mBroken && mMovingLine1.mDone))
                mShortSound->Stop();
}      

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::Pause(bool p)
{
        if (p)
        {
                // Since when we're paused we don't update each frame, call
                // MarkDirty here so that we ensure the "PAUSED" overlay appears
                MarkDirty();
                ++mPauseLevel;

                // Don't play the looping circuit sound
                mShortSound->Stop();
        }
        else
        {
                if (--mPauseLevel == 0)
                {
                        // If any of the lines are moving, re-play the shorting sound
                        if (!mMovingLine1.mDone || !mMovingLine2.mDone)
                                mShortSound->Play(true, false);
                }
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::KeyChar(char theChar)
{
        if (theChar == ' ')
                Pause(mPauseLevel == 0);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::GivePlanetBonus(Planet* p)
{
        mTotalPopulationEaten += p->mPopulation;
        mPopulationEaten += p->mPopulation;
        mLineSpeed += BEAM_INC_SPEED;
        if (mLineSpeed > MAX_BEAM_SPEED)
                mLineSpeed = MAX_BEAM_SPEED;

        ++mNumPlanetsEaten;
       
        SexyString pName = PLANET_NAME[p->mNameIdx];
        SexyString pExport = PLANET_EXPORTS[p->mExportIdx];
        int points = mLevel * 1000;
        AddBonusText(StrFormat(_S("%s: +%d"), pName.c_str(), points), p->mX, p->mY);
        mScore += points;

        mPlanetsEaten.push_back(pName);
        mPlanetsEaten.push_back(pExport.c_str());
        mPlanetsEaten.push_back(CommaSeperate(p->mPopulation));

        std::map<int, int>::iterator it = mPlanetIdxCount.find(p->mNameIdx);
        if (it == mPlanetIdxCount.end())
                mPlanetIdxCount[p->mNameIdx] = 1;
        else
                ++it->second;

        it = mExportIdxCount.find(p->mExportIdx);
        if (it == mExportIdxCount.end())
                mExportIdxCount[p->mExportIdx] = 1;
        else
                ++it->second;
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::UpdatePercentComplete(void)
{
        if (mGameOverEffect->IsActive())
                return;

        int total = GRID_WIDTH * GRID_HEIGHT;
        int actual = 0;

        for (int y = 0; y < GRID_HEIGHT; y++)
                for (int x = 0; x < GRID_WIDTH; x++)
                        if (mGridState[y][x].mFillState == GRID_FILLED)
                                ++actual;

        int newAmount = (int) (((float)actual / (float)total) * 100.0f);

        int pctCleared = newAmount - mPercentComplete;

        // Make the edges of the grid flash the larger the filled region was, but not
        // for more than 3 seconds.
        mFlashCount = pctCleared * 10;
        if (mFlashCount > 300)
                mFlashCount = 300;

        // Points are exponential, so the larger a fill region, the much larger the score
        int points = pctCleared * pctCleared * 20;
        mScore += points;
        if (points > 0)
        {
                mApp->PlaySample(SOUND_REGION_FILLED);
                AddBonusText(StrFormat(_S("+%d"), points));
        }

        mPercentComplete = newAmount;

        if (mPercentComplete >= LEVELUP_PERCENT)
        {
                // Time to level up, set up the stats
                LevelupStats ls;
                ls.mLevelCompleted = mLevel;
                ls.mPercentComplete = mPercentComplete;
                ls.mPopulationEaten = mPopulationEaten;
                ls.mPlanetsEaten = mPlanetsEaten;

                // Award a bonus for extra region filling action
                if (mPercentComplete >= COMPLETION_BONUS_PCT)
                        mScore += COMPLETION_BONUS * mLevel;

                mOptionsBtn->SetVisible(false);
                mLevelupEffect->Activate(ls);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::AddBonusText(SexyString t)
{
        AddBonusText(t, mWidth / 2 - FONT_HUNGARR->StringWidth(t) / 2,
                                    (mHeight - GRID_START_Y) / 2 - FONT_HUNGARR->GetHeight() / 2);
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::AddBonusText(SexyString t, float x, float y)
{
        BonusText bt;
        bt.mText = t;
        bt.mX = x;
        bt.mY = y;

        mBonusText.push_back(bt);
}


//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::LostLife(void)
{      

        if (--mLives <= 0)
        {
                mLives = 0;

                // Game over. Set up the stats:
                mOptionsBtn->SetVisible(false);
                EndGameStats es;
                es.mLevel = mLevel;
                es.mNumPlanetsEaten = mNumPlanetsEaten;
                es.mPopulationConsumed = mTotalPopulationEaten;
                es.mScore = mScore;
               
                // Find which planet and export were consumed the most:
                int idx = -1;
                int count = 0;
                int i;
                for (i = 0; i < NUM_PLANET_NAMES; i++)
                {
                        std::map<int, int>::iterator it = mPlanetIdxCount.find(i);
                        if (it != mPlanetIdxCount.end())
                        {
                                if (it->second > count)
                                {
                                        count = it->second;
                                        idx = i;
                                }
                        }
                }

                if (idx != -1)
                        es.mFavoritePlanet = PLANET_NAME[idx];
                else
                        es.mFavoritePlanet = _S("N/A");

                idx = -1;
                count = 0;
                for (i = 0; i < NUM_PLANET_EXPORTS; i++)
                {
                        std::map<int, int>::iterator it = mExportIdxCount.find(i);
                        if (it != mExportIdxCount.end())
                        {
                                if (it->second > count)
                                {
                                        count = it->second;
                                        idx = i;
                                }
                        }
                }

                if (idx != -1)
                        es.mFavoriteExport = PLANET_EXPORTS[idx];
                else
                        es.mFavoriteExport = _S("N/A");

                // Fade out the music
                mApp->mMusicInterface->FadeOut(0, true);

                mShortSound->Stop();

                mGameOverEffect->Activate(es);
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::EmitSparks(void)
{
        // This basically emits sparks between various angles, depending on
        // the orientation of the line. The angle values I had to play around with
        // until I found ones that I liked, and there's no magic secret formula for
        // coming up with them, unless you just know how to guess well. Again,
        // the Rand() function is easier dealt with using degrees, so we convert them
        // to radians after choosing an angle. Then, using some basic math, we compute
        // the separate XY velocities for the projectiles. In some cases, projectiles use
        // diferent velocities, and again, that was just due to trying to get the nicest look
        // and involved some playing around with to figure out. The offsets are to make the sparks
        // appear to come out of the bulbous part of the line.
        if (!mMovingLine1.mDone && !mMovingLine1.mBroken)
        {
                if (mMovingLine1.mIsVertical)
                {
                        // between 90 and 180 degrees for left side emission
                        float angle = (90 + (Rand() % 90)) * M_PI / 180.0f;    
                        float vx = cosf(angle) * 2.0f;
                        float vy = -sinf(angle) * 2.0f;
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));

                        // between 0 and 90 degrees for right side emission
                        angle = (Rand() % 90) * M_PI / 180.0f; 
                        vx = cosf(angle) * 2.0f;
                        vy = -sinf(angle) * 2.0f;
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
                }
                else
                {
                        // between 280 and 320 degrees for bottom side emission
                        float angle = (280 + (Rand() % 40)) * M_PI / 180.0f;   
                        float vx = cosf(angle) * 4.0f;
                        float vy = -sinf(angle) * 2.0f;
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));

                        // between 50 and 90 degrees for top side emission
                        angle = (50 + (Rand() % 40)) * M_PI / 180.0f;  
                        vx = cosf(angle) * 4.0f;
                        vy = -sinf(angle) * 3.0f;
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
                }              
        }

        if (!mMovingLine2.mDone && !mMovingLine2.mBroken)
        {
                if (mMovingLine2.mIsVertical)
                {
                        // between 50 and 90 degrees for left side emission
                        float angle = (50 + (Rand() % 40)) * M_PI / 180.0f;    
                        float vx = cosf(angle) * 3.0f;
                        float vy = -sinf(angle) * 4.0f;
                        mParticles.push_back(Particle(mMovingLine2.mX + 1, mMovingLine2.mY + mMovingLine2.mHeight - 17, vx, vy));

                        // between 120 and 160 degrees for right side emission
                        angle = (120 + (Rand() % 40)) * M_PI / 180.0f; 
                        vx = cosf(angle) * 2.0f;
                        vy = -sinf(angle) * 4.0f;
                        mParticles.push_back(Particle(mMovingLine2.mX + 1, mMovingLine2.mY + mMovingLine2.mHeight - 17, vx, vy));
                }
                else
                {
                        // between 90 and 140 degrees for top side emission
                        float angle = (90 + (Rand() % 50)) * M_PI / 180.0f;    
                        float vx = cosf(angle) * 4.0f;
                        float vy = -sinf(angle) * 3.0f;
                        mParticles.push_back(Particle(mMovingLine2.mX + mMovingLine2.mWidth - 20, mMovingLine2.mY + 2, vx, vy));

                        // between 220 and 260 degrees for bottom side emission
                        angle = (220 + (Rand() % 40)) * M_PI / 180.0f; 
                        vx = cosf(angle) * 4.0f;
                        vy = -sinf(angle) * 4.0f;
                        mParticles.push_back(Particle(mMovingLine2.mX + mMovingLine2.mWidth - 20, mMovingLine2.mY + 2, vx, vy));
                }              
        }
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void Board::OptionsDialogDone()
{
        // If any of the lines are moving, re-play the shorting sound
        if (!mMovingLine1.mDone || !mMovingLine2.mDone)
                mShortSound->Play(true, false);

        Pause(false);

        // Give focus back to the board so that it processes keyboard input
        mApp->mWidgetManager->SetFocus(this);
}