Subversion Repositories AndroidProjects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
244 chris 1
#pragma warning(disable:4244) 
2
#pragma warning(disable:4018)
3
#include "Board.h"
4
#include "GameApp.h"
5
#include "Res.h"
6
#include "LevelupEffect.h"
7
#include "GameOverEffect.h"
8
#include "OptionsDialog.h"
9
 
10
#include "SexyAppFramework/Graphics.h"
11
#include "SexyAppFramework/Color.h"
12
#include "SexyAppFramework/Rect.h"
13
#include "SexyAppFramework/ButtonWidget.h"
14
#include "SexyAppFramework/WidgetManager.h"
15
#include "SexyAppFramework/ImageFont.h"
16
#include "SexyAppFramework/SoundManager.h"
17
#include "SexyAppFramework/SoundInstance.h"
18
#include "SexyAppFramework/Buffer.h"
19
#include "SexyAppFramework/MusicInterface.h"
20
 
21
 
22
#define _USE_MATH_DEFINES
23
#include <math.h>
24
 
25
// VC6 workaround
26
#ifndef M_PI
27
#define M_PI 3.14159265358979323846
28
#endif
29
 
30
using namespace Sexy;
31
 
32
// How much faster the beam gets when you eat a planet
33
const float BEAM_INC_SPEED      =       0.05f;
34
 
35
// Fastest the beam can go
36
const float MAX_BEAM_SPEED      =       2.5f;
37
 
38
// Table of random planet names
39
const int NUM_PLANET_NAMES      =       28;
40
const SexyString PLANET_NAME[] =
41
{_S("Deev-z"), _S("SEN-Hen"), _S("Wallach IX"), _S("Salusa Secundus"), _S("Ridiculous Prime"), _S("Architekt V"),
42
_S("Robot Republica"), _S("Ix"), _S("XOLDOG4000"), _S("Kliprok"), _S("TR-909"), _S("TR-808"), _S("TB-303"),
43
_S("DTR011"), _S("dTech"), _S("Rotwang"), _S("Sukhtek"), _S("Romulox"), _S("Dob Reevz"), _S("Skull XII"),
44
_S("Beefy Prime"), _S("Haas"), _S("Reifenrath"), _S("Gehner Subulon"), _S("ACE-DOGG"), _S("Charolastra"), _S("Nixd"), _S("BASS")};
45
 
46
// Table of random planet exports:
47
const int NUM_PLANET_EXPORTS =  23;
48
const SexyString PLANET_EXPORTS[] =
49
{_S("Happiness"), _S("Donkeys"), _S("Rabies"), _S("AstroPop"), _S("Idiocy"), _S("Minimal Techno"),
50
_S("Citizens"), _S("Pain-relieving Pants"), _S("The Quad-Laser"), _S("Septic Systems"), _S("Video Games"),
51
_S("Robots"), _S("Plaid"), _S("Octagons"), _S("Gingivitis"), _S("Recognizers"), _S("Electro"), _S("Sauce"),
52
_S("Kindness"), _S("Bison"), _S("Saline"), _S("Cholera"), _S("TyperShark")};
53
 
54
 
55
//////////////////////////////////////////////////////////////////////////
56
//      Inline functions
57
//////////////////////////////////////////////////////////////////////////
58
 
59
// Given an X coordinate, returns the grid column it maps to
60
inline int GetCol(float x)
61
{
62
        return (int) ((x - GRID_START_X) / GRID_PIX_SIZE);
63
}
64
 
65
// Given a Y coordinate, returns the grid row it maps to
66
inline int GetRow(float y)
67
{
68
        return (int) ((y - GRID_START_Y) / GRID_PIX_SIZE);
69
}
70
 
71
// Given a grid column, returns the X pixel of the left edge of it
72
inline float GetColPix(int col)
73
{
74
        return col * GRID_PIX_SIZE + GRID_START_X;
75
}
76
 
77
// Given a grid row, returns the Y pixel of the top edge of it
78
inline float GetRowPix(int row)
79
{
80
        return row * GRID_PIX_SIZE + GRID_START_Y;
81
}
82
 
83
// Given an X coordinate, adjusts it so that it is aligned
84
// with the left edge of the grid square it's over
85
inline float GetAlignedX(float x)
86
{
87
        return (float)GetCol(x) * GRID_PIX_SIZE + GRID_START_X;
88
}
89
 
90
// Given a Y coordinate, adjusts it so that it is aligned
91
// with the top edge of the grid square it's over
92
inline float GetAlignedY(float y)
93
{
94
        return (float)GetRow(y) * GRID_PIX_SIZE + GRID_START_Y;
95
}
96
 
97
// Checks to see if the Y coordinate is in the grid's bounds.
98
// Needs to know if Hun-garr is oriented vertically or not
99
inline bool YCoordInBounds(float y, bool vertical)
100
{
101
        int h = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;
102
 
103
        if ( (vertical && (y > GRID_START_Y + h) && (y < GRID_END_Y - h)) ||
104
                (!vertical && (y > GRID_START_Y + 7) && (y < GRID_END_Y - 4)) )
105
                return true;
106
 
107
        return false;
108
}
109
 
110
// Checks to see if the X coordinate is in the grid's bounds
111
inline bool XCoordInBounds(float x)
112
{
113
        if ((x > GRID_START_X + 9) && (x < GRID_END_X - 2))
114
                return true;
115
 
116
        return false;
117
}
118
 
119
// Checks to see if the column number points to a valid grid column
120
inline bool ValidCol(int col)
121
{
122
        return ((col >= 0) && (col < GRID_WIDTH));
123
}
124
 
125
// Checks to see if the row number points to a valid grid row
126
inline bool ValidRow(int row)
127
{
128
        return ((row >= 0) && (row < GRID_HEIGHT));
129
}
130
 
131
//////////////////////////////////////////////////////////////////////////
132
//////////////////////////////////////////////////////////////////////////
133
Board::Board(GameApp* theApp)
134
{
135
        mApp = theApp;
136
        mHungarrIsVertical = true;     
137
        mLineSpeed = 1.0f;
138
 
139
        mNumPlanetsEaten = 0;
140
        mPercentComplete = 0;
141
 
142
        mBorderHue = 0;
143
        mFlashCount = 0;
144
        mLives = 3;
145
        mLevel = 1;
146
        mScore = 0;
147
        mBeamPulseAmt = 30;
148
        mBeamPulseVal = 128;
149
 
150
        // Put Hun-garr in a valid spot
151
        UpdateHungarrPosition(GetColPix(4), GetRowPix(4));
152
 
153
        // Create a 2D array to hold the grid fill state that's of size
154
        // GRID_WIDTH x GRID_HEIGHT
155
        mGridState = new GridTile* [GRID_HEIGHT];
156
 
157
        //vc6 workaround:
158
        int i;
159
        for (i = 0; i < GRID_HEIGHT; i++)
160
                mGridState[i] = new GridTile[GRID_WIDTH];
161
 
162
        mFillDirection = FILL_LEFT;
163
        mFillSpeed = 20.0f;
164
        mPlanetSpeed = 1.5f;
165
        mPopulationEaten = mTotalPopulationEaten = 0;
166
        mPauseLevel = 0;
167
 
168
        // Create our starfield
169
        for (i = 0; i < MAX_STARS; i++)
170
        {
171
                Star s;
172
                s.mX = Rand() % mApp->mWidth;
173
                s.mY = Rand() % mApp->mHeight;
174
 
175
                int r = Rand() % 3;
176
                switch (r)
177
                {
178
                        case 0: s.mSpeed = 0.5f; s.mColor = Color(200, 200, 200, 128); break;
179
                        case 1: s.mSpeed = 0.25f; s.mColor = Color(100, 100, 100, 128); break;
180
                        case 2: s.mSpeed = 0.1f; s.mColor = Color(50, 50, 50, 128); break;
181
                }
182
 
183
                mStarField[i] = s;
184
        }
185
 
186
        mLevelupEffect = new LevelupEffect();
187
        mGameOverEffect = new GameOverEffect();
188
        mOptionsBtn = NULL;
189
 
190
 
191
        // The shorting out, electrical sound of the beams moving. We use a SoundInstance pointer
192
        // because we want to loop the sound while the beam is moving, and stop it when done.
193
        // This is easiest done manually.
194
        mShortSound = mApp->mSoundManager->GetSoundInstance(SOUND_BEAM_MOVING);
195
 
196
        InitLevel(1);
197
}
198
 
199
//////////////////////////////////////////////////////////////////////////
200
//////////////////////////////////////////////////////////////////////////
201
Board::~Board()
202
{
203
        // Frees up the memory allocated to our manual SoundInstance pointer. Required.
204
        mShortSound->Release();
205
 
206
        for (int i = GRID_HEIGHT; i > 0; --i)
207
                delete[] mGridState[i - 1];
208
        delete[] mGridState;
209
 
210
        delete mLevelupEffect;
211
        delete mGameOverEffect;
212
        delete mOptionsBtn;
213
}
214
 
215
//////////////////////////////////////////////////////////////////////////
216
//////////////////////////////////////////////////////////////////////////
217
void Board::Update()
218
{
219
        // If paused, return and don't markdirty, whcih prevents drawing
220
        // and the stealing of CPU cycles
221
        if (mPauseLevel > 0)
222
                return;
223
 
224
        Widget::Update();
225
 
226
 
227
        // HSL is an alternative to specifying an RGB color format.
228
        // Using HSL lets us easily do the hyper blinking crazy weird
229
        // flashing effect commonly found in old games, such as Robotron.
230
        // Below, we increment the value by 6 per update. The &0xFF is an 
231
        // easy way to clamp the value between 0 and 255 instead of having to
232
        // do a separate if (mHue > 255) mHue -= 255. This lets the value
233
        // rollover and keep cycling.
234
        if (--mFlashCount > 0)
235
                mBorderHue = (mBorderHue + 6) % 0xFF;  
236
 
237
        if (mGameOverEffect->IsActive())
238
        {
239
                mGameOverEffect->Update();
240
 
241
                // If the game over effect is in the proper state, we will
242
                // be able to initialize the first level so that when it fades out,
243
                // the level will appear underneath it.
244
                if (mGameOverEffect->CanInitFirstLevel())
245
                {
246
                        //Reset the critical variables:
247
                        mBorderHue = 0;
248
                        mFlashCount = 0;
249
                        mLives = 3;
250
                        mLevel = 1;
251
                        mScore = 0;
252
                        mFillDirection = FILL_LEFT;
253
                        mFillSpeed = 20.0f;
254
                        mPlanetSpeed = 1.5f;
255
                        mPopulationEaten = mTotalPopulationEaten = 0;
256
                        mPauseLevel = 0;
257
                        mLineSpeed = 1.0f;
258
                        mPlanetIdxCount.clear();
259
                        mExportIdxCount.clear();
260
                        mOptionsBtn->SetVisible(true);
261
                        mApp->mMusicInterface->FadeIn(0);
262
                        InitLevel(1);
263
                }
264
        }
265
 
266
        // Make the beams that the player emits pulse with intensity
267
        mBeamPulseVal += mBeamPulseAmt;
268
        if (mBeamPulseVal >= 255)
269
        {
270
                mBeamPulseVal = 255;
271
                mBeamPulseAmt = -mBeamPulseAmt;
272
        }
273
        else if (mBeamPulseVal <= 0)
274
        {
275
                mBeamPulseVal = 0;
276
                mBeamPulseAmt = -mBeamPulseAmt;
277
        }
278
 
279
        MarkDirty();
280
}
281
 
282
//////////////////////////////////////////////////////////////////////////
283
//////////////////////////////////////////////////////////////////////////
284
void Board::UpdateF(float theFrac)
285
{
286
        if (mPauseLevel > 0)
287
                return;
288
 
289
        if (mFilling)
290
        {
291
                // If the beams have been released, update the filling of the grid
292
                float amt = mFillSpeed * theFrac;
293
 
294
                if (mFillDirection == FILL_RIGHT)
295
                        FillRight(amt);        
296
                else if (mFillDirection == FILL_LEFT)
297
                        FillLeft(amt);
298
                else if (mFillDirection == FILL_UP)
299
                        FillUp(amt);
300
                else if (mFillDirection == FILL_DOWN)
301
                        FillDown(amt);
302
 
303
                // Check what % full the filled regions are if it's done filling
304
                if (!mFilling)
305
                        UpdatePercentComplete();
306
 
307
        }
308
 
309
        // Make the bonus text float upwards and fade it out over time.
310
        std::vector<BonusText>::iterator it = mBonusText.begin();
311
        while (it != mBonusText.end())
312
        {
313
                BonusText* bt = &*it;
314
                bt->mY -= 1.00f * theFrac;
315
                bt->mHue = (bt->mHue + 5) % 0xFF;
316
 
317
                if (--bt->mAlpha <= 0)
318
                {
319
                        //Totally faded out, remove it 
320
                        it = mBonusText.erase(it);
321
                }
322
                else
323
                        ++it;
324
        }
325
 
326
 
327
        // Move the starfield. If a start gets beyond the screen,
328
        // randomly place it offscreen again
329
        int i;
330
        for (i = 0; i < MAX_STARS; i++)
331
        {
332
                Star* s = &mStarField[i];
333
                s->mX += s->mSpeed;
334
                if (s->mX > mWidth)
335
                {
336
                        s->mX = -5;
337
                        s->mY = Rand() % mHeight;
338
                }
339
        }
340
 
341
        if ((!mMovingLine1.mDone || !mMovingLine2.mDone) && !mGameOverEffect->IsActive())
342
                MoveLines(theFrac);
343
 
344
 
345
        // If we're allowed to show the planets and the game isn't paused and the game
346
        // over effect isn't playing, then we can move the planets around
347
        if ((!mLevelupEffect->HidePlanets() || (mPauseLevel > 0)) && !mGameOverEffect->IsActive())
348
        {
349
                // Move the planets
350
                int w = IMAGE_PLANETS->GetCelWidth();
351
                int h = IMAGE_PLANETS->GetCelHeight();
352
 
353
                // Instead of playing the explosion sound every time a planet gets destroyed, we'll
354
                // only play it once. That way, if you destroy more than 1 planet in one go, you won't
355
                // hear the same sound played multipled times at once, which would result in this loud,
356
                // hideous, flanging sound.
357
                bool playSound = false;
358
                for (int i = 0; i < mPlanets.size(); i++)
359
                {
360
                        Planet* p = &mPlanets[i];
361
 
362
                        // Again, the timer is used solely for incrementing the animation frames
363
                        ++p->mTimer;
364
 
365
                        if (!p->mExploding)
366
                        {
367
                                if (MovePlanet(p, theFrac))
368
                                        playSound = true;                       // Returns true if the planet is to explode
369
                        }
370
                        else
371
                        {
372
                                if ((p->mTimer % p->mExplodeSpeed) == 0)
373
                                {
374
                                        if (++p->mExplodeFrame >= IMAGE_BOMB_RADIAL_DEATH->mNumCols)
375
                                        {
376
                                                mPlanets.erase(mPlanets.begin() + i);
377
                                                --i;
378
                                        }
379
                                }
380
                        }
381
                }
382
 
383
                if (playSound)
384
                        mApp->PlaySample(SOUND_PLANET);
385
        }
386
 
387
        // update and move the particles. When they have reached
388
        // their last frame, remove them.
389
        for (i = 0; i < mParticles.size(); i++)
390
        {
391
                Particle* p = &mParticles[i];
392
                ++p->mTimer;
393
 
394
                p->mX += p->mVX * theFrac;
395
                p->mY += p->mVY * theFrac;
396
                p->mVY += 0.1f;
397
                if (p->mTimer % 6 == 0)
398
                {
399
                        if (++p->mFrame >= IMAGE_PARTICLE_LIGHTNING->mNumCols)
400
                        {
401
                                mParticles.erase(mParticles.begin() + i);
402
                                --i;
403
                        }
404
                }
405
        }
406
 
407
        if (mLevelupEffect->IsActive())
408
        {
409
                mLevelupEffect->Update(theFrac);
410
 
411
                // If the proper state is reached in the level up effect, then we can begin
412
                // setting up the next level.
413
                if (mLevelupEffect->StartNextLevel())
414
                {
415
                        // Just finished, start the next level
416
                        mOptionsBtn->SetVisible(true);
417
                        InitLevel(mLevel + 1);
418
                }
419
        }
420
}
421
 
422
//////////////////////////////////////////////////////////////////////////
423
//////////////////////////////////////////////////////////////////////////
424
void Board::FillRight(float amt)
425
{
426
        bool change = true;
427
 
428
        // Fill the entire line, moving rightward
429
        for (int y = mFillRegion.mTop; y <= mFillRegion.mBottom; y++)
430
        {
431
                GridTile* gt = &mGridState[y][mFillRegion.mLeft];
432
 
433
                // We only want to fill those pieces that are in the GRID_FILLING state
434
                if (gt->mFillState == GRID_FILLING)
435
                {
436
                        // This piece is filling up, expand its width
437
                        gt->mFillRect.mWidth += amt;
438
 
439
                        // If the width exceeds that of the grid piece, overflow the result
440
                        // into the tile next to it so that the filling appears continuous.
441
                        if (gt->mFillRect.mWidth >= GRID_PIX_SIZE)
442
                        {
443
                                float overflow = gt->mFillRect.mWidth - GRID_PIX_SIZE;
444
                                gt->mFillState = GRID_FILLED;
445
 
446
                                gt->mFillRect.mWidth = GRID_PIX_SIZE;
447
 
448
                                //overflow into next column, if the next column is within our
449
                                //fill region and if the piece is in the normal tile state.
450
                                if (mFillRegion.mLeft + 1 <= mFillRegion.mRight)
451
                                        if (mGridState[y][mFillRegion.mLeft + 1].mFillState == GRID_NORMAL)
452
                                                mGridState[y][mFillRegion.mLeft + 1].mFillRect.mWidth += overflow;
453
                        }
454
                        else
455
                                change = false;
456
                }              
457
        }
458
 
459
        //if "change" is true, then move one column right and begin filling in that column next
460
        //time this function is called. If there are no more columns to fill, we're done.
461
 
462
        if (change && (++mFillRegion.mLeft > mFillRegion.mRight))
463
                mFilling = false;
464
}
465
 
466
//////////////////////////////////////////////////////////////////////////
467
//////////////////////////////////////////////////////////////////////////
468
void Board::FillLeft(float amt)
469
{
470
        float leftX = GetColPix(mFillRegion.mRight);
471
        bool change = true;
472
 
473
        //This algorithm works just like FillRight except that it's filling
474
        //from the right side of the grid piece instead of the left, so there's
475
        //a couple extra calculations.
476
 
477
        for (int y = mFillRegion.mTop; y <= mFillRegion.mBottom; y++)
478
        {
479
                GridTile* gt = &mGridState[y][mFillRegion.mRight];
480
 
481
                if (gt->mFillState == GRID_FILLING)
482
                {
483
                        gt->mFillRect.mWidth += amt;
484
                        gt->mFillRect.mX -= amt;
485
 
486
                        if ((gt->mFillRect.mWidth >= GRID_PIX_SIZE) || (gt->mFillRect.mX < leftX))
487
                        {
488
                                float overflow = gt->mFillRect.mWidth - GRID_PIX_SIZE;
489
                                gt->mFillState = GRID_FILLED;
490
 
491
                                gt->mFillRect.mWidth = GRID_PIX_SIZE;
492
                                gt->mFillRect.mX = leftX;
493
 
494
                                //overflow into next column
495
                                if (mFillRegion.mRight - 1 >= mFillRegion.mLeft)
496
                                {
497
                                        if (mGridState[y][mFillRegion.mRight - 1].mFillState == GRID_NORMAL)
498
                                        {
499
                                                mGridState[y][mFillRegion.mRight - 1].mFillRect.mWidth += overflow;
500
                                                mGridState[y][mFillRegion.mRight - 1].mFillRect.mX -= overflow;
501
                                        }
502
                                }
503
                        }
504
                        else
505
                                change = false;
506
                }
507
        }
508
 
509
        if (change && (--mFillRegion.mRight < mFillRegion.mLeft))
510
                mFilling = false;
511
}
512
 
513
//////////////////////////////////////////////////////////////////////////
514
//////////////////////////////////////////////////////////////////////////
515
void Board::FillUp(float amt)
516
{
517
        bool change = true;
518
        float topY = GetRowPix(mFillRegion.mBottom);
519
 
520
        //This algorithm works just like FillRight except that it's filling
521
        //from the bottom side of the grid piece instead of the left, so there's
522
        //a couple extra calculations.
523
 
524
        for (int x = mFillRegion.mLeft; x <= mFillRegion.mRight; x++)
525
        {
526
                GridTile* gt = &mGridState[mFillRegion.mBottom][x];
527
 
528
                if (gt->mFillState == GRID_FILLING)
529
                {
530
                        gt->mFillRect.mHeight += amt;
531
                        gt->mFillRect.mY -= amt;
532
 
533
                        if ((gt->mFillRect.mHeight >= GRID_PIX_SIZE) || (gt->mFillRect.mY < topY))
534
                        {
535
                                float overflow = gt->mFillRect.mHeight - GRID_PIX_SIZE;
536
                                gt->mFillState = GRID_FILLED;
537
 
538
                                gt->mFillRect.mHeight = GRID_PIX_SIZE;
539
                                gt->mFillRect.mY = topY;
540
 
541
                                //overflow into next row
542
                                if (mFillRegion.mBottom - 1 > mFillRegion.mTop)
543
                                {
544
                                        if (mGridState[mFillRegion.mBottom - 1][x].mFillState == GRID_NORMAL)
545
                                        {
546
                                                mGridState[mFillRegion.mBottom - 1][x].mFillRect.mHeight += overflow;
547
                                                mGridState[mFillRegion.mBottom - 1][x].mFillRect.mY -= overflow;
548
                                        }
549
                                }
550
                        }
551
                        else
552
                                change = false;
553
                }
554
        }
555
 
556
        if (change && (--mFillRegion.mBottom < mFillRegion.mTop))
557
                mFilling = false;
558
}
559
 
560
//////////////////////////////////////////////////////////////////////////
561
//////////////////////////////////////////////////////////////////////////
562
void Board::FillDown(float amt)
563
{
564
        bool change = true;
565
 
566
        //This algorithm works just like FillRight except that it's filling
567
        //from the top side of the grid piece instead of the left.
568
 
569
        for (int x = mFillRegion.mLeft; x <= mFillRegion.mRight; x++)
570
        {
571
                GridTile* gt = &mGridState[mFillRegion.mTop][x];
572
 
573
                if (gt->mFillState == GRID_FILLING)
574
                {
575
                        gt->mFillRect.mHeight += amt;
576
 
577
                        if (gt->mFillRect.mHeight >= GRID_PIX_SIZE)
578
                        {
579
                                float overflow = gt->mFillRect.mHeight - GRID_PIX_SIZE;
580
                                gt->mFillState = GRID_FILLED;
581
 
582
                                gt->mFillRect.mHeight = GRID_PIX_SIZE;
583
 
584
                                //overflow into next row
585
                                if (mFillRegion.mTop + 1 <= mFillRegion.mBottom)
586
                                        if (mGridState[mFillRegion.mTop + 1][x].mFillState == GRID_NORMAL)
587
                                                mGridState[mFillRegion.mTop + 1][x].mFillRect.mHeight += overflow;
588
                        }
589
                        else
590
                                change = false;
591
                }
592
        }
593
 
594
        if (change && (++mFillRegion.mTop > mFillRegion.mBottom))
595
                mFilling = false;
596
}
597
 
598
//////////////////////////////////////////////////////////////////////////
599
//////////////////////////////////////////////////////////////////////////
600
void Board::MoveLines(float theFrac)
601
{
602
 
603
        // Move the lines, differently depending on if they are oriented verticall
604
        // or horizontally. When a line reaches its target, it is done, and planets
605
        // can bounce off of it. When both lines are done, we will calculate the region
606
        // to fill.
607
 
608
        int numDone = 0;
609
        if (!mMovingLine1.mDone)
610
        {
611
                float amt = -mLineSpeed * theFrac;
612
 
613
                if (mMovingLine1.mIsVertical)
614
                {
615
                        mMovingLine1.mY += amt;
616
                        mMovingLine1.mHeight += fabsf(amt);
617
 
618
                        if (mMovingLine1.mY <= mMovingLine1.mTargetY)
619
                        {
620
                                mMovingLine1.mY = mMovingLine1.mTargetY;
621
                                mMovingLine1.mDone = true;
622
                                ++numDone;
623
                        }
624
                }
625
                else
626
                {
627
                        mMovingLine1.mX += amt;
628
                        mMovingLine1.mWidth += fabsf(amt);
629
 
630
                        if (mMovingLine1.mX <= mMovingLine1.mTargetX)
631
                        {
632
                                mMovingLine1.mX = mMovingLine1.mTargetX;
633
                                mMovingLine1.mDone = true;
634
                                ++numDone;
635
                        }
636
                }
637
        }
638
 
639
        if (!mMovingLine2.mDone)
640
        {
641
                float amt = mLineSpeed * theFrac;
642
 
643
                if (mMovingLine2.mIsVertical)
644
                {
645
                        mMovingLine2.mHeight += amt;
646
 
647
                        if (mMovingLine2.mY + mMovingLine2.mHeight >= mMovingLine2.mTargetY)
648
                        {
649
                                mMovingLine2.mHeight = mMovingLine2.mTargetY - mMovingLine2.mY;
650
                                mMovingLine2.mDone = true;
651
                                ++numDone;
652
                        }
653
                }
654
                else
655
                {
656
                        mMovingLine2.mWidth += amt;
657
 
658
                        if (mMovingLine2.mX + mMovingLine2.mWidth >= mMovingLine2.mTargetX)
659
                        {
660
                                mMovingLine2.mWidth = mMovingLine2.mTargetX - mMovingLine2.mX;
661
                                mMovingLine2.mDone = true;
662
                                ++numDone;
663
                        }
664
                }
665
        }      
666
 
667
        // While at least one of the lines is still moving, make a bunch of sparks shower off
668
        // the edge of them. In non-3d mode, we'll only emit half the sparks to reduce the CPU time consumed.
669
        if (!mMovingLine2.mDone || !mMovingLine1.mDone)
670
        {
671
                int modVal = gSexyAppBase->Is3DAccelerated() ? 2 : 4;
672
                if (mUpdateCnt % modVal == 0)
673
                        EmitSparks();
674
        }
675
 
676
        // If both are done at the same time, or both are done but perhaps one completed earlier than the other,
677
        // then it's time to compute the fill region.
678
        if ((numDone == 2) || ((numDone == 1) && mMovingLine1.mDone && mMovingLine2.mDone))
679
        {
680
                mFilling = true;
681
 
682
                CalculateFillRegions();
683
        }      
684
 
685
        // Quit playing the electrical shorting out sound when both lines are broken or
686
        // done or any combination of the two.
687
        if ((mMovingLine1.mDone && mMovingLine2.mDone) ||
688
                (mMovingLine1.mBroken && mMovingLine2.mDone) ||
689
                (mMovingLine2.mBroken && mMovingLine1.mDone))
690
                mShortSound->Stop();
691
}
692
 
693
//////////////////////////////////////////////////////////////////////////
694
//////////////////////////////////////////////////////////////////////////
695
void Board::Draw(Graphics* g)
696
{
697
        g->SetColor(Color::Black);
698
        g->FillRect(0, 0, mWidth, mHeight);
699
 
700
        int incAmt = gSexyAppBase->Is3DAccelerated() ? 1 : 2;
701
 
702
        // Draw less starts if not in 3D mode to reduce CPU usage, since they aren't a critical feature
703
        for (int i = 0; i < MAX_STARS; i += incAmt)
704
        {
705
                Star* s = &mStarField[i];
706
                g->SetColor(s->mColor);
707
                g->FillRect(s->mX, s->mY, 1, 1);
708
        }      
709
 
710
        // We don't draw the other game elements under certain conditions, like
711
        // if the level up and game over effects are in a few particular states.
712
        if (!mLevelupEffect->HideBoard() && !mGameOverEffect->HideBoard())
713
        {
714
                DrawGrid(g);
715
                DrawUI(g);
716
 
717
                // To prevent cheating, don't draw planets if the game is paused.
718
                // Also don't show them during certain points of the level up effect
719
                if (!mLevelupEffect->HidePlanets() || (mPauseLevel > 0))
720
                        DrawPlanets(g);
721
 
722
                if (!mGameOverEffect->IsActive())
723
                        DrawMovingBeams(g);            
724
 
725
                DrawHungarr(g);
726
        }      
727
 
728
        if (mLevelupEffect->IsActive())
729
                mLevelupEffect->Draw(g);
730
 
731
        if (mGameOverEffect->IsActive())
732
                mGameOverEffect->Draw(g);
733
 
734
        if (mPauseLevel > 0)
735
        {
736
                // Paused: draw an overlay
737
                g->SetColor(Color(0, 0, 0, 128));
738
                g->FillRect(0, 0, mWidth, mHeight);
739
                g->SetColor(Color::White);
740
                g->SetFont(FONT_HUNGARR);
741
                g->DrawString(_S("PAUSED"), mWidth / 2 - FONT_HUNGARR->StringWidth(_S("PAUSED")) / 2, mHeight / 2);
742
        }
743
}
744
 
745
//////////////////////////////////////////////////////////////////////////
746
//////////////////////////////////////////////////////////////////////////
747
void Board::DrawGrid(Graphics* g)
748
{
749
        // Draw an outline around the whole grid region. See LevelUpEffect.cpp or
750
        // GameOverEffect.cpp for full details on using HSL instead of RGB for color and
751
        // why it's useful.
752
        if (mFlashCount > 0)
753
                g->SetColor(mApp->HSLToRGB(mBorderHue, 255, 128) & 0xFFFFFFFF);
754
        else
755
                g->SetColor(Color(255, 0, 0, 64));
756
 
757
        // grid outline:
758
        g->FillRect(0, GRID_START_Y, GRID_START_X, mHeight - GRID_START_Y);
759
        g->FillRect(0, GRID_START_Y - GRID_PIX_SIZE, mWidth, GRID_PIX_SIZE);
760
        g->FillRect(GRID_END_X, GRID_START_Y, mWidth - GRID_END_X, mHeight - GRID_START_Y);
761
        g->FillRect(GRID_START_X, GRID_END_Y, GRID_END_X - GRID_START_X, mHeight - GRID_END_Y);
762
 
763
 
764
        // To make a weird pattern, a few of the grid pieces will be more brightly
765
        // colored than the others, if they are in the normal state.
766
        bool startBright = true;
767
        for (int y = 0; y < GRID_HEIGHT; y++)
768
        {
769
                int drawY = GetRowPix(y);
770
 
771
                for (int x = 0; x < GRID_WIDTH; x++)
772
                {
773
                        int drawX = GetColPix(x);
774
                        int state = mGridState[y][x].mFillState;
775
                        if (state == GRID_FILLING)
776
                        {
777
                                // The grid piece is in the process of filling up. Draw a different colored rectangle for the
778
                                // filled in part, and then draw the rest normally.
779
                                FRect* fr = &mGridState[y][x].mFillRect;
780
                                Rect normalRect;
781
 
782
                                if (mFillDirection == FILL_RIGHT)
783
                                        normalRect = Rect(fr->mX + fr->mWidth, drawY, GRID_PIX_SIZE - fr->mWidth + 1, GRID_PIX_SIZE);
784
                                else if (mFillDirection == FILL_LEFT)
785
                                        normalRect = Rect(drawX, drawY, GRID_PIX_SIZE - fr->mWidth, GRID_PIX_SIZE);
786
                                else if (mFillDirection == FILL_UP)
787
                                        normalRect = Rect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE - fr->mHeight);
788
                                else
789
                                        normalRect = Rect(drawX, fr->mY + fr->mHeight, GRID_PIX_SIZE, GRID_PIX_SIZE - fr->mHeight + 1);
790
 
791
 
792
                                if ((normalRect.mWidth > 0) && (normalRect.mHeight > 0))
793
                                {                                      
794
                                        g->SetColor(Color(255, 255, 0, startBright && (x % 2 == 0) ? 128 : 64));
795
                                        g->FillRect(normalRect);
796
                                        g->SetColor(Color(0, 0, 0));
797
                                        g->DrawRect(normalRect);
798
                                }
799
 
800
                                g->SetColor(Color(255, 255, 0, 100));
801
                                FillRectF(g, *fr);
802
                        }
803
                        else if (state == GRID_NORMAL)
804
                        {
805
                                // Just draw the grid piece normally, with a black outline around it.
806
 
807
                                g->SetColor(Color(255, 255, 0, startBright && (x % 3 == 0) ? 64 : 32));
808
                                g->FillRect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE);
809
                                g->SetColor(Color(0, 0, 0));
810
                                g->DrawRect(drawX, drawY, GRID_PIX_SIZE, GRID_PIX_SIZE);
811
                        }
812
                        else
813
                        {
814
                                // The piece is completely filled in, just fill the whole rectangle
815
                                g->SetColor(Color(255, 255, 0, 100));
816
                                FillRectF(g, mGridState[y][x].mFillRect);
817
                        }
818
                }
819
 
820
                startBright = !startBright;
821
        }
822
}
823
 
824
//////////////////////////////////////////////////////////////////////////
825
//////////////////////////////////////////////////////////////////////////
826
void Board::DrawUI(Graphics* g)
827
{
828
        int ascent = FONT_HUNGARR->GetAscent();
829
        int height = FONT_HUNGARR->GetHeight();
830
 
831
        g->SetFont(FONT_HUNGARR);
832
        SexyString s;
833
        int rightX = FONT_HUNGARR->StringWidth(_S("POPULATION CONSUMED: ")) + 5;
834
 
835
        int strWidth;
836
        s = _S("WORLDS DEVOURED: ");
837
        strWidth = FONT_HUNGARR->StringWidth(s);
838
        g->SetColor(Color(255, 255, 255, 128));
839
        g->DrawString(s, rightX - strWidth, ascent);
840
        g->SetColor(Color(255, 0, 0, 200));
841
        g->DrawString(StrFormat(_S("%d"), mNumPlanetsEaten), rightX - 5, ascent);
842
 
843
        s = _S("POPULATION CONSUMED: ");
844
        g->SetColor(Color(255, 255, 255, 128));
845
        g->DrawString(s, 5, height * 2);
846
        g->SetColor(Color(255, 0, 0, 200));
847
        g->DrawString(CommaSeperate(mPopulationEaten), rightX - 5, height * 2);
848
 
849
        s = _S("SCORE: ");
850
        strWidth = FONT_HUNGARR->StringWidth(s);
851
        g->SetColor(Color(255, 255, 255, 128));
852
        g->DrawString(s, rightX - strWidth, height * 3);
853
        g->SetColor(Color(255, 255, 0, 200));
854
        g->DrawString(StrFormat(_S("%s"), CommaSeperate(mScore).c_str()), rightX - 5, height * 3);
855
 
856
 
857
 
858
        int x = 380;
859
        s = _S("SYSTEMS SUBJUGATED: ");
860
        g->SetColor(Color(255, 255, 255, 128));
861
        g->DrawString(s, x, ascent);
862
        g->SetColor(Color(255, 0, 0, 200));
863
        g->DrawString(StrFormat(_S("%d%%"), mPercentComplete), x + FONT_HUNGARR->StringWidth(s), ascent);
864
 
865
        s = _S("LIVES: ");
866
        g->SetColor(Color(255, 255, 255, 128));
867
        g->DrawString(s, x, height * 2);
868
        strWidth = FONT_HUNGARR->StringWidth(s);
869
        g->DrawImage(IMAGE_HUNGARR_SMALL, strWidth + x, ascent);
870
        g->SetColor(Color(255, 0, 0, 200));
871
        g->DrawString(StrFormat(_S("x%d"), mLives), x + 10 + strWidth + IMAGE_HUNGARR_SMALL->GetWidth(), height * 2);  
872
 
873
        s = _S("LEVEL: ");
874
        g->SetColor(Color(255, 255, 255, 128));
875
        g->DrawString(s, x, height * 3);
876
        g->SetColor(Color(255, 255, 0, 200));
877
        g->DrawString(StrFormat(_S("%d"), mLevel), x + FONT_HUNGARR->StringWidth(s), height * 3);
878
 
879
        for (int i = 0; i < mBonusText.size(); i++)
880
        {
881
                BonusText* bt = &mBonusText[i];
882
                g->SetColor( (mApp->HSLToRGB(bt->mHue, 255, 128) & 0xFFFFFF) | (bt->mAlpha << 24) );
883
                g->DrawString(bt->mText, bt->mX, bt->mY);
884
        }
885
}
886
 
887
//////////////////////////////////////////////////////////////////////////
888
//////////////////////////////////////////////////////////////////////////
889
void Board::Beam1DrawHelper(Graphics* g)
890
{
891
        // In 3D mode we'll use the DrawImageF versions since they look nicer and
892
        // perform anti-aliasing, and make floating point movement appear smoother.
893
        // Since they are more taxing, we'll use the default integer routines if hardware
894
        // mode is not available.
895
 
896
        // The offsets you see were taken from the actual image itself. You'll notice
897
        // that the image has a lot of blank space around the actual beam, so we move things
898
        // around a bit to get a nice aligned look. You'd either just play around with these
899
        // numbers till it looked right, or your artist would inform you.
900
        if (mMovingLine1.mIsVertical)
901
        {
902
                if (gSexyAppBase->Is3DAccelerated())
903
                {
904
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mMovingLine1.mX - 8, mMovingLine1.mY,
905
                                Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), mMovingLine1.mHeight));
906
                }
907
                else
908
                {
909
                        g->DrawImage(IMAGE_HUNGARR_BEAM_UP, mMovingLine1.mX - 8, mMovingLine1.mY,
910
                                Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), mMovingLine1.mHeight));
911
                }
912
        }
913
        else
914
        {
915
                if (gSexyAppBase->Is3DAccelerated())
916
                {
917
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mMovingLine1.mX, mMovingLine1.mY - 8,
918
                                Rect(0, 0, mMovingLine1.mWidth, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
919
                }
920
                else
921
                {
922
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mMovingLine1.mX, mMovingLine1.mY - 8,
923
                                Rect(0, 0, mMovingLine1.mWidth, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
924
                }
925
        }
926
}
927
 
928
//////////////////////////////////////////////////////////////////////////
929
//////////////////////////////////////////////////////////////////////////
930
void Board::Beam2DrawHelper(Graphics* g)
931
{
932
        // In 3D mode we'll use the DrawImageF versions since they look nicer and
933
        // perform anti-aliasing, and make floating point movement appear smoother.
934
        // Since they are more taxing, we'll use the default integer routines if hardware
935
        // mode is not available.
936
 
937
        // The offsets you see were taken from the actual image itself. You'll notice
938
        // that the image has a lot of blank space around the actual beam, so we move things
939
        // around a bit to get a nice aligned look. You'd either just play around with these
940
        // numbers till it looked right, or your artist would inform you.
941
 
942
        if (mMovingLine2.mIsVertical)
943
        {
944
                if (gSexyAppBase->Is3DAccelerated())
945
                {
946
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mMovingLine2.mX - 8, mMovingLine2.mY - 1,
947
                                Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - mMovingLine2.mHeight,
948
                                IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), mMovingLine2.mHeight));
949
                }
950
                else
951
                {
952
                        g->DrawImage(IMAGE_HUNGARR_BEAM_DOWN, mMovingLine2.mX - 8, mMovingLine2.mY - 1,
953
                                Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - mMovingLine2.mHeight,
954
                                IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), mMovingLine2.mHeight));
955
                }
956
        }
957
        else
958
        {
959
                if (gSexyAppBase->Is3DAccelerated())
960
                {
961
                        g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mMovingLine2.mX - 1, mMovingLine2.mY - 8,
962
                                Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - mMovingLine2.mWidth, 0,
963
                                mMovingLine2.mWidth, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
964
                }
965
                else
966
                {
967
                        g->DrawImage(IMAGE_HUNGARR_BEAM_RIGHT, mMovingLine2.mX - 1, mMovingLine2.mY - 8,
968
                                Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - mMovingLine2.mWidth, 0,
969
                                mMovingLine2.mWidth, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
970
                }
971
        }
972
}
973
 
974
//////////////////////////////////////////////////////////////////////////
975
//////////////////////////////////////////////////////////////////////////
976
void Board::DrawMovingBeams(Graphics* g)
977
{              
978
        // If the beams are moving, then draw them. Make them pulse too. You make a
979
        // pulsing effect like we did in the previous demos: draw the image a second time on
980
        // top of the original additively, and colorize the image, setting the RGB values to
981
        // a different intensity. The result is an image that gets brighter and dimmer over time.
982
        if (!mMovingLine1.mBroken && (!mMovingLine1.mDone || mFilling || !mMovingLine2.mDone))
983
        {              
984
                Beam1DrawHelper(g);
985
 
986
                if (!mMovingLine1.mDone)
987
                {
988
                        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
989
                        g->SetColorizeImages(true);
990
                        g->SetColor(Color(mBeamPulseVal, mBeamPulseVal, mBeamPulseVal));                       
991
                        Beam1DrawHelper(g);
992
                        g->SetColorizeImages(false);
993
                        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
994
                }
995
 
996
        }
997
 
998
        if (!mMovingLine2.mDone || mFilling || !mMovingLine1.mDone)
999
        {
1000
                Beam2DrawHelper(g);
1001
 
1002
                if (!mMovingLine2.mDone)
1003
                {
1004
                        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1005
                        g->SetColorizeImages(true);
1006
                        g->SetColor(Color(mBeamPulseVal, mBeamPulseVal, mBeamPulseVal));                       
1007
                        Beam2DrawHelper(g);
1008
                        g->SetColorizeImages(false);
1009
                        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1010
                }
1011
        }
1012
 
1013
        g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1014
        for (int i = 0; i < mParticles.size(); i++)
1015
        {
1016
                Particle* p = &mParticles[i];          
1017
                g->DrawImageCel(IMAGE_PARTICLE_LIGHTNING, p->mX, p->mY, p->mFrame);            
1018
        }
1019
        g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1020
}
1021
 
1022
//////////////////////////////////////////////////////////////////////////
1023
//////////////////////////////////////////////////////////////////////////
1024
void Board::DrawPlanets(Graphics* g)
1025
{
1026
        // If the user has 3d, we'll rotate the planets using the floating point
1027
        // smooth rotation function. If they don't, we'll avoid rotating at all to
1028
        // save on CPU cycles and keep the framerate up.
1029
        int w = IMAGE_PLANETS->GetCelWidth();
1030
        int h = IMAGE_PLANETS->GetCelHeight();
1031
        for (int i = 0; i < mPlanets.size(); i++)
1032
        {
1033
                Planet* p = &mPlanets[i];              
1034
                if (p->mExploding)
1035
                {
1036
                        g->DrawImageCel(IMAGE_BOMB_RADIAL_DEATH,
1037
                                p->mX - (IMAGE_BOMB_RADIAL_DEATH->GetCelWidth() / 2 + w / 2),
1038
                                p->mY - (IMAGE_BOMB_RADIAL_DEATH->GetCelHeight() / 2 + h / 2),
1039
                                p->mExplodeFrame);
1040
                }
1041
                else
1042
                {
1043
                        Rect r = Rect(p->mImgCol * w, 0, w, IMAGE_PLANETS->GetCelHeight());
1044
 
1045
                        if (gSexyAppBase->Is3DAccelerated())
1046
                                g->DrawImageRotatedF(IMAGE_PLANETS, p->mX, p->mY, p->mRotationAngle, &r);
1047
                        else
1048
                                g->DrawImage(IMAGE_PLANETS, p->mX, p->mY, r);
1049
                }
1050
        }
1051
}
1052
 
1053
//////////////////////////////////////////////////////////////////////////
1054
//////////////////////////////////////////////////////////////////////////
1055
void Board::DrawHungarrVertBeamsHelper(Graphics* g)
1056
{
1057
        // This draws the two little static beams that are always attached to
1058
        // Hun-garr's bitmap. If the user has hardware acceleration, we'll
1059
        // draw the beams pulsating. If not, we'll skip it since it's time consuming
1060
        // and doesn't hurt the game any. It'd be worse to drop the framrate by a few FPS
1061
        // for this effect.
1062
 
1063
        int h = IMAGE_HUNGARR_VERT->GetHeight() / 2;
1064
 
1065
        if (gSexyAppBase->Is3DAccelerated())
1066
        {
1067
                g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
1068
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));
1069
 
1070
                g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
1071
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));
1072
 
1073
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1074
                g->SetColorizeImages(true);
1075
 
1076
                g->SetColor(Color(255, 255, 255, mBeamPulseVal));
1077
                g->DrawImageF(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
1078
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));
1079
 
1080
                g->DrawImageF(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
1081
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));
1082
 
1083
 
1084
                g->SetColorizeImages(false);
1085
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1086
        }
1087
        else
1088
        {
1089
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1090
                g->DrawImage(IMAGE_HUNGARR_BEAM_UP, mLine1X, mLine1Y,
1091
                        Rect(0, 0, IMAGE_HUNGARR_BEAM_UP->GetWidth(), h));
1092
 
1093
                g->DrawImage(IMAGE_HUNGARR_BEAM_DOWN, mLine2X, mLine2Y,
1094
                        Rect(0, IMAGE_HUNGARR_BEAM_DOWN->GetHeight() - h, IMAGE_HUNGARR_BEAM_DOWN->GetWidth(), h));
1095
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1096
        }
1097
}
1098
 
1099
//////////////////////////////////////////////////////////////////////////
1100
//////////////////////////////////////////////////////////////////////////
1101
void Board::DrawHungarrHorizBeamsHelper(Graphics* g)
1102
{
1103
        // This draws the two little static beams that are always attached to
1104
        // Hun-garr's bitmap. If the user has hardware acceleration, we'll
1105
        // draw the beams pulsating. If not, we'll skip it since it's time consuming
1106
        // and doesn't hurt the game any. It'd be worse to drop the framrate by a few FPS
1107
        // for this effect.
1108
 
1109
        int w = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;
1110
 
1111
        if (gSexyAppBase->Is3DAccelerated())
1112
        {
1113
                g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
1114
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
1115
 
1116
                g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
1117
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
1118
 
1119
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1120
                g->SetColorizeImages(true);
1121
 
1122
                g->SetColor(Color(255, 255, 255, mBeamPulseVal));
1123
                g->DrawImageF(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
1124
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
1125
 
1126
                g->DrawImageF(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
1127
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
1128
 
1129
                g->SetColorizeImages(false);
1130
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1131
        }
1132
        else
1133
        {
1134
                g->SetDrawMode(Graphics::DRAWMODE_ADDITIVE);
1135
 
1136
                g->DrawImage(IMAGE_HUNGARR_BEAM_LEFT, mLine1X, mLine1Y,
1137
                        Rect(0, 0, w, IMAGE_HUNGARR_BEAM_LEFT->GetHeight()));
1138
 
1139
                g->DrawImage(IMAGE_HUNGARR_BEAM_RIGHT, mLine2X, mLine2Y,
1140
                        Rect(IMAGE_HUNGARR_BEAM_RIGHT->GetWidth() - w, 0, w, IMAGE_HUNGARR_BEAM_RIGHT->GetHeight()));
1141
 
1142
                g->SetDrawMode(Graphics::DRAWMODE_NORMAL);
1143
        }
1144
}
1145
 
1146
//////////////////////////////////////////////////////////////////////////
1147
//////////////////////////////////////////////////////////////////////////
1148
void Board::DrawHungarr(Graphics* g)
1149
{
1150
        // again, we use the floating point functions instead of the integer ones
1151
        // if the user has a 3d card.
1152
 
1153
        bool is3d = gSexyAppBase->Is3DAccelerated();
1154
 
1155
        if (mHungarrIsVertical)
1156
        {                                      
1157
                DrawHungarrVertBeamsHelper(g);
1158
 
1159
                if (is3d)
1160
                        g->DrawImageF(IMAGE_HUNGARR_VERT, mHungarrX, mHungarrY);
1161
                else
1162
                        g->DrawImage(IMAGE_HUNGARR_VERT, mHungarrX, mHungarrY);
1163
        }
1164
        else
1165
        {                              
1166
                DrawHungarrHorizBeamsHelper(g);
1167
 
1168
                if (is3d)
1169
                        g->DrawImageF(IMAGE_HUNGARR_HORIZ, mHungarrX, mHungarrY);
1170
                else
1171
                        g->DrawImage(IMAGE_HUNGARR_HORIZ, mHungarrX, mHungarrY);
1172
        }      
1173
}
1174
 
1175
//////////////////////////////////////////////////////////////////////////
1176
//////////////////////////////////////////////////////////////////////////
1177
void Board::AddedToManager(WidgetManager* theWidgetManager)
1178
{      
1179
        Widget::AddedToManager(theWidgetManager);
1180
 
1181
 
1182
        mOptionsBtn = new ButtonWidget(1, this);       
1183
        mOptionsBtn->SetFont(FONT_DEFAULT);
1184
        mOptionsBtn->mLabel = _S("Options");
1185
        mOptionsBtn->SetColor(ButtonWidget::COLOR_LABEL, Color::White);
1186
        mOptionsBtn->SetColor(ButtonWidget::COLOR_LABEL_HILITE, Color::White); 
1187
 
1188
        mOptionsBtn->mOverImage = IMAGE_BUTTON_OVER;
1189
        mOptionsBtn->mDownImage = IMAGE_BUTTON_DOWN;
1190
        mOptionsBtn->mButtonImage = IMAGE_BUTTON_NORMAL;
1191
        mOptionsBtn->mDoFinger = true;
1192
        mOptionsBtn->Resize(gSexyAppBase->mWidth - IMAGE_BUTTON_NORMAL->GetWidth() - 10, FONT_HUNGARR->GetHeight() * 3 - 20,
1193
                                IMAGE_BUTTON_NORMAL->GetWidth(), IMAGE_BUTTON_NORMAL->GetHeight());
1194
 
1195
        theWidgetManager->AddWidget(mOptionsBtn);
1196
}
1197
 
1198
//////////////////////////////////////////////////////////////////////////
1199
//////////////////////////////////////////////////////////////////////////
1200
void Board::RemovedFromManager(WidgetManager* theWidgetManager)
1201
{
1202
        Widget::RemovedFromManager(theWidgetManager);
1203
 
1204
        theWidgetManager->RemoveWidget(mOptionsBtn);
1205
}
1206
 
1207
//////////////////////////////////////////////////////////////////////////
1208
//////////////////////////////////////////////////////////////////////////
1209
void Board::ButtonDepress(int theId)
1210
{
1211
        // Play a sound whenever the options button is depressed/sad.
1212
        if (theId == mOptionsBtn->mId)
1213
        {
1214
                // Stop the shorting sound if it's playing, otherwise it's annoying
1215
                mShortSound->Stop();
1216
 
1217
                mApp->PlaySample(SOUND_BUTTON);
1218
                Pause(true);
1219
                OptionsDialog* od = new OptionsDialog(this);
1220
                od->Resize(mWidth / 2 - 200, mHeight / 2 - 175, 400, 350);
1221
                mApp->AddDialog(OptionsDialog::DIALOG_ID, od);
1222
        }
1223
}
1224
 
1225
//////////////////////////////////////////////////////////////////////////
1226
//////////////////////////////////////////////////////////////////////////
1227
void Board::MouseMove(int x, int y)
1228
{
1229
        UpdateHungarrPosition(x, y);
1230
}
1231
 
1232
//////////////////////////////////////////////////////////////////////////
1233
//////////////////////////////////////////////////////////////////////////
1234
void Board::MouseDrag(int x, int y)
1235
{
1236
        UpdateHungarrPosition(x, y);
1237
}
1238
 
1239
//////////////////////////////////////////////////////////////////////////
1240
//////////////////////////////////////////////////////////////////////////
1241
void Board::MouseDown(int x, int y, int theClickCount)
1242
{              
1243
        // if the level up effect is displaying stats, or the game over effect is too,
1244
        // and the user clicked, then start the next phase
1245
        if (mLevelupEffect->ShowingStats())
1246
                mLevelupEffect->DoneViewingStats();
1247
 
1248
        if (mGameOverEffect->CanStartNewGame())
1249
                mGameOverEffect->DoneViewingStats();
1250
 
1251
        // ignore mouse clicks when paused or an effect is on screen and the user has no reason to click
1252
        if (mLevelupEffect->IsActive() || (mPauseLevel > 0) || (mGameOverEffect->IsActive() && !mGameOverEffect->CanStartNewGame()))
1253
                return;
1254
 
1255
        // On a right click, if the click was within the grid bounds, switch hungarr's orientation
1256
        if ((theClickCount < 0) && XCoordInBounds(x) && YCoordInBounds(y, mHungarrIsVertical))
1257
                mHungarrIsVertical = !mHungarrIsVertical;
1258
        else if ((theClickCount > 0) && mMovingLine1.mDone && mMovingLine2.mDone && !mFilling)
1259
        {
1260
                //left click, and there's no lines moving: drop two new lines 
1261
 
1262
                // Make sure the user didn't click on a planet which would instantly kill them
1263
                FRect hungarrRect = FRect(mHungarrX, mHungarrY, IMAGE_HUNGARR_HORIZ->mWidth, IMAGE_HUNGARR_HORIZ->mHeight);
1264
                for (int i = 0; i < mPlanets.size(); i++)
1265
                {
1266
                        Planet* p = &mPlanets[i];
1267
                        FRect planetRect = FRect(p->mX, p->mY, IMAGE_PLANETS->GetCelWidth(), IMAGE_PLANETS->GetCelHeight());
1268
                        if (planetRect.Intersects(hungarrRect))
1269
                                return;
1270
                }
1271
 
1272
                mApp->PlaySample(SOUND_MAGZAP);
1273
 
1274
                // start the electrical shorting sound
1275
                mShortSound->Play(true, false);
1276
 
1277
                mMovingLine1.mDone = mMovingLine2.mDone = false;
1278
                mMovingLine1.mBroken = mMovingLine2.mBroken = false;
1279
                int midX = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;
1280
                int midY = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;
1281
 
1282
                //Align the XYs of the lines to the grid, and set the target coordinates to the
1283
                //closest normal state tile.
1284
                if (mHungarrIsVertical)
1285
                {
1286
                        mMovingLine1.mIsVertical = mMovingLine2.mIsVertical = true;                    
1287
                        mMovingLine1.mX = mMovingLine2.mX = GetAlignedX(mHungarrX + midX);
1288
                        mMovingLine1.mY = mMovingLine2.mY = GetAlignedY(mHungarrY + midY);
1289
                        mMovingLine1.mHeight = 1;
1290
                        mMovingLine2.mHeight = 13;
1291
                        mMovingLine1.mWidth = mMovingLine2.mWidth = GRID_PIX_SIZE;
1292
                        mMovingLine1.mTargetY = mMovingLine2.mTargetY = mMovingLine1.mY;
1293
                        mMovingLine1.mTargetX = mMovingLine2.mTargetX = mMovingLine1.mX;
1294
 
1295
                        // Make sure the target coords end at a tile that's normal. If not, keep moving them
1296
                        int row = GetRow(mMovingLine1.mTargetY);
1297
                        int col = GetCol(mMovingLine1.mTargetX);
1298
 
1299
                        // Tile immediately below is not valid
1300
                        if (mGridState[row][col].mFillState != GRID_NORMAL)
1301
                                return;
1302
 
1303
                        while ((row >= 0) && (mGridState[row][col].mFillState == GRID_NORMAL))
1304
                        {
1305
                                mMovingLine1.mTargetY -= GRID_PIX_SIZE;
1306
                                --row;
1307
                        }
1308
 
1309
                        // Make it end on the last valid tile. The loop above makes it leave
1310
                        // on an invalid tile
1311
                        mMovingLine1.mTargetY += GRID_PIX_SIZE;
1312
 
1313
                        row = GetRow(mMovingLine2.mTargetY);
1314
                        col = GetCol(mMovingLine2.mTargetX);
1315
 
1316
                        while ((row < GRID_HEIGHT) && (mGridState[row][col].mFillState == GRID_NORMAL))
1317
                        {
1318
                                mMovingLine2.mTargetY += GRID_PIX_SIZE;
1319
                                ++row;
1320
                        }
1321
 
1322
                        if (mMovingLine1.mTargetY > mMovingLine2.mTargetY)
1323
                                mMovingLine1.mDone = mMovingLine2.mDone = true;
1324
                }
1325
                else
1326
                {
1327
                        mMovingLine1.mIsVertical = mMovingLine2.mIsVertical = false;
1328
                        mMovingLine1.mX = mMovingLine2.mX = GetAlignedX(mHungarrX + midX);
1329
                        mMovingLine1.mY = mMovingLine2.mY = GetAlignedY(mHungarrY + midY);
1330
                        mMovingLine1.mWidth = 1;
1331
                        mMovingLine2.mWidth = 13;
1332
                        mMovingLine1.mHeight = mMovingLine2.mHeight = GRID_PIX_SIZE;
1333
                        mMovingLine1.mTargetX = mMovingLine2.mTargetX = mMovingLine1.mX;
1334
                        mMovingLine1.mTargetY = mMovingLine2.mTargetY = mMovingLine1.mY;
1335
 
1336
                        // Make sure the target coords end at a tile that's normal. If not, keep moving them
1337
                        int row = GetRow(mMovingLine1.mTargetY);
1338
                        int col = GetCol(mMovingLine1.mTargetX);
1339
 
1340
                        // Tile immediately below is not valid...?
1341
                        if (mGridState[row][col].mFillState != GRID_NORMAL)
1342
                                return;
1343
 
1344
                        while ((col >= 0) && (mGridState[row][col].mFillState == GRID_NORMAL))
1345
                        {
1346
                                mMovingLine1.mTargetX -= GRID_PIX_SIZE;
1347
                                --col;
1348
                        }
1349
 
1350
                        // Make it end on the last valid tile. The loop above makes it leave
1351
                        // on an invalid tile
1352
                        mMovingLine1.mTargetX += GRID_PIX_SIZE;
1353
 
1354
 
1355
                        row = GetRow(mMovingLine2.mTargetY);
1356
                        col = GetCol(mMovingLine2.mTargetX);
1357
 
1358
                        while ((col < GRID_WIDTH) && (mGridState[row][col].mFillState == GRID_NORMAL))
1359
                        {
1360
                                mMovingLine2.mTargetX += GRID_PIX_SIZE;
1361
                                ++col;
1362
                        }
1363
 
1364
 
1365
                        if (mMovingLine1.mTargetX > mMovingLine2.mTargetX)
1366
                                mMovingLine1.mDone = mMovingLine2.mDone = true;
1367
                }
1368
        }
1369
 
1370
        UpdateHungarrPosition(x, y);
1371
 
1372
}
1373
 
1374
//////////////////////////////////////////////////////////////////////////
1375
//////////////////////////////////////////////////////////////////////////
1376
void Board::UpdateHungarrPosition(int x, int y)
1377
{
1378
        // Place the Hun-garr bitmap and the two lines that stick out of him
1379
        // so that Hun-garr is centered on the mouse cursor
1380
        int midX = IMAGE_HUNGARR_HORIZ->GetWidth() / 2;
1381
        int midY = IMAGE_HUNGARR_HORIZ->GetHeight() / 2;
1382
 
1383
        if (YCoordInBounds(y, mHungarrIsVertical))
1384
        {              
1385
                mHungarrY = y - midY;
1386
                mLine1Y = mHungarrY + (!mHungarrIsVertical ? 7 : -12);         
1387
                mLine2Y = mHungarrY + (!mHungarrIsVertical ? 7 : 35);
1388
        }
1389
 
1390
        if (XCoordInBounds(x))
1391
        {
1392
                mHungarrX = x - midX;
1393
                mLine1X = mHungarrX + (!mHungarrIsVertical ? -13 : 8);
1394
                mLine2X = mHungarrX + (!mHungarrIsVertical ? 36 : 9);
1395
        }      
1396
}
1397
 
1398
//////////////////////////////////////////////////////////////////////////
1399
//////////////////////////////////////////////////////////////////////////
1400
void Board::CalculateFillRegions(void)
1401
{
1402
        int topRow, botRow, leftCol, rightCol;
1403
 
1404
        // The basic idea is this: Find the CLOSEST edge to where the user clicks that
1405
        // meets the following conditions:
1406
        // 1. If Hun-garr is vertical, the edge must be as tall as the column created
1407
        //      by the two emitted lines (or more) and every grid piece must be normal
1408
        // 2. If Hun-garr is horizontal, the edge must be as wide as the row created by
1409
        //      the two emitted lines (or more) and every grid piece must be normal
1410
        // 3. If either of the lines were broken, we instead just fill in the single
1411
        //      line made by the non-broken line (the code won't execute if both are broken)
1412
 
1413
 
1414
        if (mMovingLine1.mIsVertical)
1415
        {
1416
                topRow = GetRow(mMovingLine1.mTargetY);
1417
                int col1 = GetCol(mMovingLine1.mTargetX);
1418
                botRow = GetRow(mMovingLine2.mTargetY);
1419
 
1420
                if ((mMovingLine1.mBroken && !mMovingLine2.mBroken) ||
1421
                        (!mMovingLine1.mBroken && mMovingLine2.mBroken))
1422
                {
1423
                        leftCol = col1;
1424
                        rightCol = col1 + 1;
1425
                        mFillDirection = FILL_RIGHT;
1426
                        topRow = mMovingLine1.mBroken ? GetRow(mMovingLine2.mY) : topRow;
1427
                        botRow = mMovingLine1.mBroken ? botRow : GetRow(mMovingLine2.mY);
1428
                }
1429
                else
1430
                {
1431
                        int rightEdge, leftEdge;
1432
                        GetVerticalFillValues(col1, topRow, botRow, 1, &rightEdge);
1433
                        GetVerticalFillValues(col1, topRow, botRow, -1, &leftEdge);
1434
 
1435
                        if ((rightEdge - col1) <= (col1 - leftEdge))
1436
                        {
1437
                                leftCol = col1;
1438
                                rightCol = rightEdge + 1;
1439
                                mFillDirection = FILL_RIGHT;                   
1440
                        }
1441
                        else
1442
                        {
1443
                                leftCol = leftEdge;
1444
                                rightCol = col1 + 1;
1445
                                mFillDirection = FILL_LEFT;
1446
                        }
1447
                }
1448
 
1449
        }
1450
        else
1451
        {
1452
                leftCol = GetCol(mMovingLine1.mTargetX);
1453
                rightCol = GetCol(mMovingLine2.mTargetX);
1454
                int row1 = GetRow(mMovingLine1.mTargetY);
1455
 
1456
                if ((mMovingLine1.mBroken && !mMovingLine2.mBroken) ||
1457
                        (!mMovingLine1.mBroken && mMovingLine2.mBroken))
1458
                {
1459
                        leftCol = mMovingLine1.mBroken ? GetCol(mMovingLine2.mX) : leftCol;
1460
                        rightCol = mMovingLine1.mBroken ? rightCol : GetCol(mMovingLine2.mX);
1461
                        topRow = row1;
1462
                        botRow = row1 + 1;
1463
                        mFillDirection = FILL_DOWN;
1464
                }
1465
                else
1466
                {
1467
                        int topEdge, botEdge;
1468
                        GetHorizontalFillValues(row1, leftCol, rightCol, -1, &topEdge);
1469
                        GetHorizontalFillValues(row1, leftCol, rightCol, 1, &botEdge);
1470
 
1471
                        if ((botEdge - row1) <= (row1 - topEdge))
1472
                        {
1473
                                topRow = row1;
1474
                                botRow = botEdge + 1;
1475
                                mFillDirection = FILL_DOWN;
1476
                        }
1477
                        else
1478
                        {
1479
                                topRow = topEdge;
1480
                                botRow = row1 + 1;
1481
                                mFillDirection = FILL_UP;
1482
                        }
1483
                }
1484
 
1485
        }      
1486
 
1487
        //Make a rectangular fill region: every block in it will eventually be filled.
1488
        // Then, for all grid pieces in that region, if they are in the normal state,
1489
        // set them to the filling state and initialize their mFillRect's
1490
        mFillRegion.mLeft = leftCol;
1491
        mFillRegion.mRight = rightCol - 1;
1492
        mFillRegion.mTop = topRow;
1493
        mFillRegion.mBottom = botRow - 1;
1494
 
1495
        for (int y = topRow; y < botRow; y++)
1496
        {
1497
                for (int x = leftCol; x < rightCol; x++)
1498
                {
1499
                        if (mGridState[y][x].mFillState == GRID_NORMAL)
1500
                        {
1501
                                mGridState[y][x].mFillState = GRID_FILLING;
1502
 
1503
                                switch (mFillDirection)
1504
                                {
1505
                                        case FILL_RIGHT:
1506
                                                mGridState[y][x].mFillRect =
1507
                                                        FRect(GetColPix(x), GetRowPix(y), 0, GRID_PIX_SIZE);
1508
                                                break;
1509
 
1510
                                        case FILL_LEFT:
1511
                                                mGridState[y][x].mFillRect =
1512
                                                        FRect(GetColPix(x + 1), GetRowPix(y), 0, GRID_PIX_SIZE);
1513
                                                break;
1514
 
1515
                                        case FILL_UP:
1516
                                                mGridState[y][x].mFillRect =
1517
                                                        FRect(GetColPix(x), GetRowPix(y + 1), GRID_PIX_SIZE, 0);
1518
                                                break;
1519
 
1520
                                        case FILL_DOWN:
1521
                                                mGridState[y][x].mFillRect =
1522
                                                        FRect(GetColPix(x), GetRowPix(y), GRID_PIX_SIZE, 0);
1523
                                                break;
1524
                                }
1525
                        }
1526
                }
1527
        }
1528
}
1529
 
1530
//////////////////////////////////////////////////////////////////////////
1531
//////////////////////////////////////////////////////////////////////////
1532
void Board::FillRectF(Graphics* g, FRect fr)
1533
{
1534
        Rect r = Rect((int)fr.mX, (int)fr.mY, (int)fr.mWidth, (int)fr.mHeight);
1535
        g->FillRect(r);
1536
}
1537
 
1538
 
1539
//////////////////////////////////////////////////////////////////////////
1540
//////////////////////////////////////////////////////////////////////////
1541
void Board::GetVerticalFillValues(int startCol, int topRow, int botRow, int dir, int* edge)
1542
{
1543
        // If dir == -1, left, if 1, right
1544
        // See function header for algorithm description
1545
 
1546
        bool done = false;
1547
        int col = startCol;
1548
 
1549
        while (!done)
1550
        {
1551
                bool found = true;
1552
                for (int y = topRow; y < botRow; y++)
1553
                {
1554
                        if (mGridState[y][col].mFillState != GRID_FILLED)
1555
                        {
1556
                                found = false;
1557
                                break;
1558
                        }
1559
                }
1560
 
1561
                if (!found)
1562
                {                      
1563
                        if ( ((dir > 0) && (++col >= GRID_WIDTH)) ||
1564
                                ((dir < 0) && (--col < 0)) )
1565
                        {
1566
                                done = true;
1567
                                *edge = col + (dir > 0 ? -1 : 1);
1568
                        }
1569
                }
1570
                else
1571
                {
1572
                        *edge = col;
1573
                        done = true;
1574
                }
1575
 
1576
        }
1577
}
1578
 
1579
//////////////////////////////////////////////////////////////////////////
1580
//////////////////////////////////////////////////////////////////////////
1581
void Board::GetHorizontalFillValues(int startRow, int leftCol, int rightCol, int dir, int* edge)
1582
{
1583
        // If dir == -1, up, if 1, down
1584
        // See function header for algorithm description
1585
 
1586
        bool done = false;
1587
        int row = startRow;
1588
 
1589
        while (!done)
1590
        {
1591
 
1592
                bool found = true;
1593
                for (int x = leftCol; x < rightCol; x++)
1594
                {
1595
                        if (mGridState[row][x].mFillState != GRID_FILLED)
1596
                        {
1597
                                found = false;
1598
                                break;
1599
                        }
1600
                }
1601
 
1602
                if (!found)
1603
                {
1604
                        if ( ((dir > 0) && (++row >= GRID_HEIGHT)) ||
1605
                                ((dir < 0) && (--row < 0)) )
1606
                        {
1607
                                done = true;
1608
                                *edge = row + (dir > 0 ? -1 : 1);
1609
                        }
1610
                }
1611
                else
1612
                {
1613
                        *edge = row;
1614
                        done = true;
1615
                }
1616
        }
1617
}
1618
 
1619
//////////////////////////////////////////////////////////////////////////
1620
//////////////////////////////////////////////////////////////////////////
1621
void Board::InitLevel(int level)
1622
{
1623
        mLives += level - mLevel;
1624
 
1625
        mPopulationEaten = 0;
1626
        mPlanetSpeed += 0.15f;
1627
        mLevel = level;
1628
        mPlanets.clear();
1629
        mFlashCount = 0;
1630
        mPercentComplete = 0;
1631
        mPlanetsEaten.clear();
1632
        mBonusText.clear();
1633
        mParticles.clear();
1634
 
1635
        mFilling = false;
1636
 
1637
        // reset the grid state
1638
        int i;
1639
        for (i = 0; i < GRID_HEIGHT; i++)
1640
        {
1641
                for (int x = 0; x < GRID_WIDTH; x++)
1642
                {
1643
                        mGridState[i][x].mFillRect = FRect(x * GRID_PIX_SIZE, GRID_START_Y + i * GRID_PIX_SIZE, 0, 0);
1644
                        mGridState[i][x].mFillState = GRID_NORMAL;
1645
                }
1646
        }
1647
 
1648
        // Start with 2 planets. Then add 1 every other level
1649
        int numPlanets = 2 + (mLevel / 2);
1650
        for (i = 0; i < numPlanets; i++)
1651
        {
1652
                Planet p;
1653
 
1654
                // Choose a random name and export
1655
                p.mNameIdx = Rand() % NUM_PLANET_NAMES;
1656
                p.mExportIdx = Rand() % NUM_PLANET_EXPORTS;
1657
 
1658
                // a random number I made up for the population. Increases by a random amount each level.
1659
                p.mPopulation = (mLevel * 133602) + 748819;
1660
 
1661
                // Position it randomly within the confines of the grid
1662
                p.mX = GRID_START_X + 20 + (Rand() % (GRID_END_X - GRID_START_X - 20));
1663
                p.mY = GRID_START_Y + 20 + (Rand() % (GRID_END_Y - GRID_START_Y - 20));
1664
 
1665
                // Get a random angle for the planet to travel in. It's easier to do RAND on
1666
                // degrees, so convert them to radians after choosing an angle
1667
                float a = (Rand() % 360) * M_PI / 180.0f;
1668
                p.mVX = mPlanetSpeed * cosf(a);
1669
                p.mVY = -mPlanetSpeed * sinf(a);
1670
 
1671
                // don't let the speed be too close to 0 though, it's lame if the planet
1672
                // bounces just straight vertically or horizontally
1673
                if ((p.mVX >= -0.1) && (p.mVX <= 0.1))
1674
                        p.mVX = 0.3f;
1675
 
1676
                if ((p.mVY >= -0.1) && (p.mVY <= 0.1))
1677
                        p.mVY = 0.3f;
1678
 
1679
                // Set a random initial rotation angle and speed to rotate at.
1680
                // All angle manipulation is in radians.
1681
                p.mRotationAngle = (Rand() % 360) * M_PI / 180.0f;
1682
                p.mRotateSpeed = (float)(Rand() % 100) / 1000.0f;
1683
 
1684
                // Choose a random image. There's 11 images, each is just 1 frame.
1685
                p.mImgCol = Rand() % IMAGE_PLANETS->mNumCols;
1686
 
1687
                mPlanets.push_back(p);
1688
        }
1689
}
1690
 
1691
//////////////////////////////////////////////////////////////////////////
1692
//////////////////////////////////////////////////////////////////////////
1693
bool Board::MovePlanet(Planet* p, float theFrac)
1694
{
1695
        // This is a pretty simple collision detection routine and is good enough for this
1696
        // game. It's also a lot easier to understand than other more accurate but more
1697
        // complex methods. The basic idea is to check the two blocks in the direction of
1698
        // travel to see if they are solid or not, and if so, we reverse course, and
1699
        // don't move the planet to that new coordinate.
1700
 
1701
        int w = IMAGE_PLANETS->GetCelWidth();
1702
        int h = IMAGE_PLANETS->GetCelHeight();
1703
        bool playSample = false;
1704
 
1705
        // Update rotation
1706
        p->mRotationAngle += p->mRotateSpeed;
1707
 
1708
        // Don't move it yet...compute where it WOULD be if the move was valid
1709
        float newx = p->mX + p->mVX * theFrac;
1710
        float newy = p->mY + p->mVY * theFrac;
1711
 
1712
        // If moving right, we'll check the grid piece right of the
1713
        // planet to see if we hit it. Otherwise, we'll just use the grid piece that maps to
1714
        // where the new X coordinate is:       
1715
        float checkx = p->mVX > 0 ? newx + GRID_PIX_SIZE : newx;
1716
        int col = GetCol(checkx);
1717
 
1718
        // We're going to check both the current row and row below it:
1719
        int row = GetRow(p->mY);
1720
        int nextrow = ValidRow(row + 1) ? row + 1 : row;
1721
 
1722
        if (ValidCol(col) && ValidRow(row))
1723
        {
1724
                int state1 = mGridState[row][col].mFillState;
1725
                int state2 = mGridState[nextrow][col].mFillState;
1726
 
1727
                if ((state1 == GRID_NORMAL) && (state2 == GRID_NORMAL) && (newx > GRID_START_X))
1728
                        p->mX = newx;   // valid grid space
1729
                else if (((state1 == GRID_FILLING) || (state2 == GRID_FILLING)) && (newx > GRID_START_X))
1730
                {
1731
                        // planet entered a grid space that is in the process of being filled, so make it explode
1732
                        p->mExploding = true;
1733
                        GivePlanetBonus(p);                    
1734
                        return true;
1735
                }
1736
                else
1737
                {
1738
                        // planet hit a filled in space, reverse the X velocity
1739
                        playSample = true;
1740
                        p->mVX = -p->mVX;
1741
                }
1742
        }
1743
        else
1744
        {
1745
                // Not valid cases would be if the planet hit the edges of the board, where there
1746
                // aren't any grid tiles. If so, just bounce it off the wall.
1747
                playSample = true;
1748
                p->mVX = -p->mVX;
1749
        }
1750
 
1751
 
1752
        // Now for the Y direction. The principal is the same as above.
1753
        int checky = p->mVY > 0 ? newy + GRID_PIX_SIZE : newy;
1754
        row = GetRow(checky);
1755
        col = GetCol(p->mX);
1756
        int nextcol = ValidCol(col + 1) ? col + 1 : col;       
1757
 
1758
        if (ValidCol(col) && ValidRow(row))
1759
        {
1760
                int state1 = mGridState[row][col].mFillState;
1761
                int state2 = mGridState[row][nextcol].mFillState;
1762
 
1763
                if ((state1 == GRID_NORMAL) && (state2 == GRID_NORMAL) && (newy > GRID_START_Y))
1764
                        p->mY = newy;
1765
                else if (((state1 == GRID_FILLING) || (state2 == GRID_FILLING)) && (newy > GRID_START_Y))
1766
                {
1767
                        p->mExploding = true;
1768
                        GivePlanetBonus(p);
1769
                        return true;
1770
                }
1771
                else
1772
                {
1773
                        playSample = true;
1774
                        p->mVY = -p->mVY;
1775
                }
1776
        }
1777
        else
1778
        {
1779
                p->mVY = -p->mVY;
1780
                playSample = true;
1781
        }
1782
 
1783
        // When a planet hits a wall/filled in grid piece, bounce it and play a sound
1784
        if (playSample)
1785
                mApp->PlaySample(SOUND_PLANET_HIT);
1786
 
1787
        CheckPlanetBeamCollision(p);
1788
 
1789
        return false;
1790
}
1791
 
1792
//////////////////////////////////////////////////////////////////////////
1793
//////////////////////////////////////////////////////////////////////////
1794
void Board::CheckPlanetBeamCollision(Planet* p)
1795
{
1796
        // We're just going to do a rectangular collision check on each of the beams
1797
        // and the given planet. Because the visible "beam" part of the image is smaller
1798
        // than the image width/height, and the same goes for the planet too, you'll notice
1799
        // that offsets are used to constrain the collision rectangles to make a more fair algorithm.
1800
        FRect pr = FRect(p->mX + 4, p->mY + 4, 11, 11);
1801
 
1802
        FRect beam1Rect;
1803
 
1804
        if (!mMovingLine1.mIsVertical)
1805
                beam1Rect = FRect(mMovingLine1.mX + 7, mMovingLine1.mY + 12, mMovingLine1.mWidth - 12, 8);
1806
        else
1807
                beam1Rect = FRect(mMovingLine1.mX + 12, mMovingLine1.mY + 10, 9, mMovingLine1.mHeight);
1808
 
1809
        // Only allow the user to lose 1 life max: if both beams break you don't lose 2. If the beam breaks,
1810
        // set a flag and set its target to be its current location (indicating that it's done moving)
1811
        if (pr.Intersects(beam1Rect))
1812
        {
1813
                if (!mMovingLine1.mDone)
1814
                {
1815
                        mMovingLine1.mBroken = true;
1816
                        mApp->PlaySample(SOUND_BEAM_HIT);
1817
 
1818
                        if (!mMovingLine2.mBroken)
1819
                                LostLife();
1820
 
1821
                        if (!mMovingLine1.mIsVertical)
1822
                                mMovingLine1.mTargetX = mMovingLine1.mX = mMovingLine1.mX + mMovingLine1.mWidth;
1823
                        else
1824
                                mMovingLine1.mTargetY = mMovingLine1.mY = mMovingLine1.mY + mMovingLine1.mHeight;
1825
                }
1826
                else if (!mMovingLine1.mBroken && mMovingLine1.mDone && !mMovingLine2.mDone)
1827
                {
1828
                        // bounce off of it
1829
                        if (mMovingLine1.mIsVertical)
1830
                        {
1831
                                p->mVX *= -1.0f;
1832
                                p->mX += p->mVX;
1833
                        }
1834
                        else
1835
                        {
1836
                                p->mVY *= -1.0f;
1837
                                p->mY += p->mVY;
1838
                        }
1839
                }
1840
        }
1841
 
1842
        FRect beam2Rect;
1843
 
1844
        if (!mMovingLine2.mIsVertical)
1845
                beam2Rect = FRect(mMovingLine2.mX, mMovingLine2.mY + 12, mMovingLine2.mWidth - 7, 8);
1846
        else
1847
                beam2Rect = FRect(mMovingLine2.mX + 12, mMovingLine2.mY, 9, mMovingLine2.mHeight);
1848
 
1849
        if (pr.Intersects(beam2Rect))
1850
        {
1851
                if (!mMovingLine2.mDone)
1852
                {
1853
                        mMovingLine2.mBroken = true;
1854
                        mApp->PlaySample(SOUND_BEAM_HIT);
1855
 
1856
                        if (!mMovingLine1.mBroken)
1857
                                LostLife();
1858
 
1859
                        if (!mMovingLine2.mIsVertical)
1860
                                mMovingLine2.mTargetX = mMovingLine2.mX;
1861
                        else
1862
                                mMovingLine2.mTargetY = mMovingLine2.mY;
1863
                }
1864
                else if (!mMovingLine2.mBroken && mMovingLine2.mDone && !mMovingLine1.mDone)
1865
                {
1866
                        // bounce off of it
1867
                        if (mMovingLine2.mIsVertical)
1868
                        {
1869
                                p->mVX *= -1.0f;
1870
                                p->mX += p->mVX;
1871
                        }
1872
                        else
1873
                        {
1874
                                p->mVY *= -1.0f;
1875
                                p->mY += p->mVY;
1876
                        }
1877
                }
1878
        }
1879
 
1880
        if ((mMovingLine1.mDone && mMovingLine2.mDone) ||
1881
                (mMovingLine1.mBroken && mMovingLine2.mDone) ||
1882
                (mMovingLine2.mBroken && mMovingLine1.mDone))
1883
                mShortSound->Stop();
1884
}      
1885
 
1886
//////////////////////////////////////////////////////////////////////////
1887
//////////////////////////////////////////////////////////////////////////
1888
void Board::Pause(bool p)
1889
{
1890
        if (p)
1891
        {
1892
                // Since when we're paused we don't update each frame, call
1893
                // MarkDirty here so that we ensure the "PAUSED" overlay appears
1894
                MarkDirty();
1895
                ++mPauseLevel;
1896
 
1897
                // Don't play the looping circuit sound
1898
                mShortSound->Stop();
1899
        }
1900
        else
1901
        {
1902
                if (--mPauseLevel == 0)
1903
                {
1904
                        // If any of the lines are moving, re-play the shorting sound
1905
                        if (!mMovingLine1.mDone || !mMovingLine2.mDone)
1906
                                mShortSound->Play(true, false);
1907
                }
1908
        }
1909
}
1910
 
1911
//////////////////////////////////////////////////////////////////////////
1912
//////////////////////////////////////////////////////////////////////////
1913
void Board::KeyChar(char theChar)
1914
{
1915
        if (theChar == ' ')
1916
                Pause(mPauseLevel == 0);
1917
}
1918
 
1919
//////////////////////////////////////////////////////////////////////////
1920
//////////////////////////////////////////////////////////////////////////
1921
void Board::GivePlanetBonus(Planet* p)
1922
{
1923
        mTotalPopulationEaten += p->mPopulation;
1924
        mPopulationEaten += p->mPopulation;
1925
        mLineSpeed += BEAM_INC_SPEED;
1926
        if (mLineSpeed > MAX_BEAM_SPEED)
1927
                mLineSpeed = MAX_BEAM_SPEED;
1928
 
1929
        ++mNumPlanetsEaten;
1930
 
1931
        SexyString pName = PLANET_NAME[p->mNameIdx];
1932
        SexyString pExport = PLANET_EXPORTS[p->mExportIdx];
1933
        int points = mLevel * 1000;
1934
        AddBonusText(StrFormat(_S("%s: +%d"), pName.c_str(), points), p->mX, p->mY);
1935
        mScore += points;
1936
 
1937
        mPlanetsEaten.push_back(pName);
1938
        mPlanetsEaten.push_back(pExport.c_str());
1939
        mPlanetsEaten.push_back(CommaSeperate(p->mPopulation));
1940
 
1941
        std::map<int, int>::iterator it = mPlanetIdxCount.find(p->mNameIdx);
1942
        if (it == mPlanetIdxCount.end())
1943
                mPlanetIdxCount[p->mNameIdx] = 1;
1944
        else
1945
                ++it->second;
1946
 
1947
        it = mExportIdxCount.find(p->mExportIdx);
1948
        if (it == mExportIdxCount.end())
1949
                mExportIdxCount[p->mExportIdx] = 1;
1950
        else
1951
                ++it->second;
1952
}
1953
 
1954
//////////////////////////////////////////////////////////////////////////
1955
//////////////////////////////////////////////////////////////////////////
1956
void Board::UpdatePercentComplete(void)
1957
{
1958
        if (mGameOverEffect->IsActive())
1959
                return;
1960
 
1961
        int total = GRID_WIDTH * GRID_HEIGHT;
1962
        int actual = 0;
1963
 
1964
        for (int y = 0; y < GRID_HEIGHT; y++)
1965
                for (int x = 0; x < GRID_WIDTH; x++)
1966
                        if (mGridState[y][x].mFillState == GRID_FILLED)
1967
                                ++actual;
1968
 
1969
        int newAmount = (int) (((float)actual / (float)total) * 100.0f);
1970
 
1971
        int pctCleared = newAmount - mPercentComplete;
1972
 
1973
        // Make the edges of the grid flash the larger the filled region was, but not
1974
        // for more than 3 seconds.
1975
        mFlashCount = pctCleared * 10;
1976
        if (mFlashCount > 300)
1977
                mFlashCount = 300;
1978
 
1979
        // Points are exponential, so the larger a fill region, the much larger the score
1980
        int points = pctCleared * pctCleared * 20;
1981
        mScore += points;
1982
        if (points > 0)
1983
        {
1984
                mApp->PlaySample(SOUND_REGION_FILLED);
1985
                AddBonusText(StrFormat(_S("+%d"), points));
1986
        }
1987
 
1988
        mPercentComplete = newAmount;
1989
 
1990
        if (mPercentComplete >= LEVELUP_PERCENT)
1991
        {
1992
                // Time to level up, set up the stats
1993
                LevelupStats ls;
1994
                ls.mLevelCompleted = mLevel;
1995
                ls.mPercentComplete = mPercentComplete;
1996
                ls.mPopulationEaten = mPopulationEaten;
1997
                ls.mPlanetsEaten = mPlanetsEaten;
1998
 
1999
                // Award a bonus for extra region filling action
2000
                if (mPercentComplete >= COMPLETION_BONUS_PCT)
2001
                        mScore += COMPLETION_BONUS * mLevel;
2002
 
2003
                mOptionsBtn->SetVisible(false);
2004
                mLevelupEffect->Activate(ls);
2005
        }
2006
}
2007
 
2008
//////////////////////////////////////////////////////////////////////////
2009
//////////////////////////////////////////////////////////////////////////
2010
void Board::AddBonusText(SexyString t)
2011
{
2012
        AddBonusText(t, mWidth / 2 - FONT_HUNGARR->StringWidth(t) / 2,
2013
                                    (mHeight - GRID_START_Y) / 2 - FONT_HUNGARR->GetHeight() / 2);
2014
}
2015
 
2016
//////////////////////////////////////////////////////////////////////////
2017
//////////////////////////////////////////////////////////////////////////
2018
void Board::AddBonusText(SexyString t, float x, float y)
2019
{
2020
        BonusText bt;
2021
        bt.mText = t;
2022
        bt.mX = x;
2023
        bt.mY = y;
2024
 
2025
        mBonusText.push_back(bt);
2026
}
2027
 
2028
 
2029
//////////////////////////////////////////////////////////////////////////
2030
//////////////////////////////////////////////////////////////////////////
2031
void Board::LostLife(void)
2032
{      
2033
 
2034
        if (--mLives <= 0)
2035
        {
2036
                mLives = 0;
2037
 
2038
                // Game over. Set up the stats:
2039
                mOptionsBtn->SetVisible(false);
2040
                EndGameStats es;
2041
                es.mLevel = mLevel;
2042
                es.mNumPlanetsEaten = mNumPlanetsEaten;
2043
                es.mPopulationConsumed = mTotalPopulationEaten;
2044
                es.mScore = mScore;
2045
 
2046
                // Find which planet and export were consumed the most:
2047
                int idx = -1;
2048
                int count = 0;
2049
                int i;
2050
                for (i = 0; i < NUM_PLANET_NAMES; i++)
2051
                {
2052
                        std::map<int, int>::iterator it = mPlanetIdxCount.find(i);
2053
                        if (it != mPlanetIdxCount.end())
2054
                        {
2055
                                if (it->second > count)
2056
                                {
2057
                                        count = it->second;
2058
                                        idx = i;
2059
                                }
2060
                        }
2061
                }
2062
 
2063
                if (idx != -1)
2064
                        es.mFavoritePlanet = PLANET_NAME[idx];
2065
                else
2066
                        es.mFavoritePlanet = _S("N/A");
2067
 
2068
                idx = -1;
2069
                count = 0;
2070
                for (i = 0; i < NUM_PLANET_EXPORTS; i++)
2071
                {
2072
                        std::map<int, int>::iterator it = mExportIdxCount.find(i);
2073
                        if (it != mExportIdxCount.end())
2074
                        {
2075
                                if (it->second > count)
2076
                                {
2077
                                        count = it->second;
2078
                                        idx = i;
2079
                                }
2080
                        }
2081
                }
2082
 
2083
                if (idx != -1)
2084
                        es.mFavoriteExport = PLANET_EXPORTS[idx];
2085
                else
2086
                        es.mFavoriteExport = _S("N/A");
2087
 
2088
                // Fade out the music
2089
                mApp->mMusicInterface->FadeOut(0, true);
2090
 
2091
                mShortSound->Stop();
2092
 
2093
                mGameOverEffect->Activate(es);
2094
        }
2095
}
2096
 
2097
//////////////////////////////////////////////////////////////////////////
2098
//////////////////////////////////////////////////////////////////////////
2099
void Board::EmitSparks(void)
2100
{
2101
        // This basically emits sparks between various angles, depending on
2102
        // the orientation of the line. The angle values I had to play around with
2103
        // until I found ones that I liked, and there's no magic secret formula for 
2104
        // coming up with them, unless you just know how to guess well. Again, 
2105
        // the Rand() function is easier dealt with using degrees, so we convert them
2106
        // to radians after choosing an angle. Then, using some basic math, we compute
2107
        // the separate XY velocities for the projectiles. In some cases, projectiles use
2108
        // diferent velocities, and again, that was just due to trying to get the nicest look
2109
        // and involved some playing around with to figure out. The offsets are to make the sparks
2110
        // appear to come out of the bulbous part of the line.
2111
        if (!mMovingLine1.mDone && !mMovingLine1.mBroken)
2112
        {
2113
                if (mMovingLine1.mIsVertical)
2114
                {
2115
                        // between 90 and 180 degrees for left side emission
2116
                        float angle = (90 + (Rand() % 90)) * M_PI / 180.0f;    
2117
                        float vx = cosf(angle) * 2.0f;
2118
                        float vy = -sinf(angle) * 2.0f;
2119
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
2120
 
2121
                        // between 0 and 90 degrees for right side emission
2122
                        angle = (Rand() % 90) * M_PI / 180.0f; 
2123
                        vx = cosf(angle) * 2.0f;
2124
                        vy = -sinf(angle) * 2.0f;
2125
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
2126
                }
2127
                else
2128
                {
2129
                        // between 280 and 320 degrees for bottom side emission
2130
                        float angle = (280 + (Rand() % 40)) * M_PI / 180.0f;   
2131
                        float vx = cosf(angle) * 4.0f;
2132
                        float vy = -sinf(angle) * 2.0f;
2133
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
2134
 
2135
                        // between 50 and 90 degrees for top side emission
2136
                        angle = (50 + (Rand() % 40)) * M_PI / 180.0f;  
2137
                        vx = cosf(angle) * 4.0f;
2138
                        vy = -sinf(angle) * 3.0f;
2139
                        mParticles.push_back(Particle(mMovingLine1.mX + 5, mMovingLine1.mY + 8, vx, vy));
2140
                }              
2141
        }
2142
 
2143
        if (!mMovingLine2.mDone && !mMovingLine2.mBroken)
2144
        {
2145
                if (mMovingLine2.mIsVertical)
2146
                {
2147
                        // between 50 and 90 degrees for left side emission
2148
                        float angle = (50 + (Rand() % 40)) * M_PI / 180.0f;    
2149
                        float vx = cosf(angle) * 3.0f;
2150
                        float vy = -sinf(angle) * 4.0f;
2151
                        mParticles.push_back(Particle(mMovingLine2.mX + 1, mMovingLine2.mY + mMovingLine2.mHeight - 17, vx, vy));
2152
 
2153
                        // between 120 and 160 degrees for right side emission
2154
                        angle = (120 + (Rand() % 40)) * M_PI / 180.0f; 
2155
                        vx = cosf(angle) * 2.0f;
2156
                        vy = -sinf(angle) * 4.0f;
2157
                        mParticles.push_back(Particle(mMovingLine2.mX + 1, mMovingLine2.mY + mMovingLine2.mHeight - 17, vx, vy));
2158
                }
2159
                else
2160
                {
2161
                        // between 90 and 140 degrees for top side emission
2162
                        float angle = (90 + (Rand() % 50)) * M_PI / 180.0f;    
2163
                        float vx = cosf(angle) * 4.0f;
2164
                        float vy = -sinf(angle) * 3.0f;
2165
                        mParticles.push_back(Particle(mMovingLine2.mX + mMovingLine2.mWidth - 20, mMovingLine2.mY + 2, vx, vy));
2166
 
2167
                        // between 220 and 260 degrees for bottom side emission
2168
                        angle = (220 + (Rand() % 40)) * M_PI / 180.0f; 
2169
                        vx = cosf(angle) * 4.0f;
2170
                        vy = -sinf(angle) * 4.0f;
2171
                        mParticles.push_back(Particle(mMovingLine2.mX + mMovingLine2.mWidth - 20, mMovingLine2.mY + 2, vx, vy));
2172
                }              
2173
        }
2174
}
2175
 
2176
//////////////////////////////////////////////////////////////////////////
2177
//////////////////////////////////////////////////////////////////////////
2178
void Board::OptionsDialogDone()
2179
{
2180
        // If any of the lines are moving, re-play the shorting sound
2181
        if (!mMovingLine1.mDone || !mMovingLine2.mDone)
2182
                mShortSound->Play(true, false);
2183
 
2184
        Pause(false);
2185
 
2186
        // Give focus back to the board so that it processes keyboard input
2187
        mApp->mWidgetManager->SetFocus(this);
2188
}
2189