Version of Robotron arcade game using LPC1768, a Gameduino shield, a serial EEPROM (for high scores), two microswitch joysticks and two buttons plus a box to put it in. 20 levels of mayhem.

Dependencies:   25LCxxx_SPI CommonTypes Gameduino mbed

Committer:
RichardE
Date:
Mon Jun 17 15:10:43 2013 +0000
Revision:
18:70190f956a24
Parent:
13:50779b12ff51
Improved response to button 1 when entering high scores (HighScoreEntry.cpp).

Who changed what in which revision?

UserRevisionLine numberNew contents of line
RichardE 0:5fa232ee5fdf 1 /*
RichardE 0:5fa232ee5fdf 2 * SOURCE FILE : LevelNormal.cpp
RichardE 0:5fa232ee5fdf 3 *
RichardE 0:5fa232ee5fdf 4 * Definition of class LevelNormal.
RichardE 0:5fa232ee5fdf 5 * Base class for all "normal" levels.
RichardE 0:5fa232ee5fdf 6 * i.e. Levels that are not special attract modes
RichardE 0:5fa232ee5fdf 7 * but have enemies who are trying to kill you
RichardE 0:5fa232ee5fdf 8 * and so on.
RichardE 0:5fa232ee5fdf 9 *
RichardE 0:5fa232ee5fdf 10 */
RichardE 0:5fa232ee5fdf 11
RichardE 5:0b0651ac7832 12 // Define this for debugging messages.
RichardE 5:0b0651ac7832 13 #define CHATTY
RichardE 5:0b0651ac7832 14
RichardE 5:0b0651ac7832 15 #ifdef CHATTY
RichardE 5:0b0651ac7832 16 #include "mbed.h"
RichardE 5:0b0651ac7832 17 extern Serial pc;
RichardE 5:0b0651ac7832 18 #endif
RichardE 5:0b0651ac7832 19
RichardE 0:5fa232ee5fdf 20 #include "LevelNormal.h"
RichardE 5:0b0651ac7832 21 #include "GameObjectLocator.h"
RichardE 5:0b0651ac7832 22 #include "FrameCounter.h"
RichardE 5:0b0651ac7832 23 #include "SpriteNumber.h"
RichardE 5:0b0651ac7832 24 #include "ArenaConst.h"
RichardE 10:bfa1c307c99d 25 #include "EnemyFactory.h"
RichardE 0:5fa232ee5fdf 26
RichardE 0:5fa232ee5fdf 27 // Current instance being processed.
RichardE 0:5fa232ee5fdf 28 LevelNormal *LevelNormal::currentInstance;
RichardE 0:5fa232ee5fdf 29
RichardE 0:5fa232ee5fdf 30 /***************/
RichardE 0:5fa232ee5fdf 31 /* CONSTRUCTOR */
RichardE 0:5fa232ee5fdf 32 /***************/
RichardE 10:bfa1c307c99d 33 // Pass pointer to level descriptor data in data parameter.
RichardE 12:81926431fea7 34 LevelNormal::LevelNormal( const LevelDescriptor *data ) :
RichardE 10:bfa1c307c99d 35 descriptorData( data )
RichardE 5:0b0651ac7832 36 {
RichardE 10:bfa1c307c99d 37 // Normal levels are always dynamically alocated.
RichardE 10:bfa1c307c99d 38 IsDynamicallyAllocated = true;
RichardE 0:5fa232ee5fdf 39 }
RichardE 0:5fa232ee5fdf 40
RichardE 0:5fa232ee5fdf 41 /**************/
RichardE 0:5fa232ee5fdf 42 /* DESTRUCTOR */
RichardE 0:5fa232ee5fdf 43 /**************/
RichardE 5:0b0651ac7832 44 LevelNormal::~LevelNormal()
RichardE 5:0b0651ac7832 45 {
RichardE 0:5fa232ee5fdf 46 }
RichardE 0:5fa232ee5fdf 47
RichardE 0:5fa232ee5fdf 48 /********************/
RichardE 0:5fa232ee5fdf 49 /* INITIALISE LEVEL */
RichardE 0:5fa232ee5fdf 50 /********************/
RichardE 5:0b0651ac7832 51 // Pass pointer to Gameduino to draw on in gd.
RichardE 13:50779b12ff51 52 // Note that this is called at the start of every level but also when the player
RichardE 13:50779b12ff51 53 // is killed and the level restarts with the remaining enemies.
RichardE 5:0b0651ac7832 54 void LevelNormal::InitialiseLevel( Gameduino *gd )
RichardE 5:0b0651ac7832 55 {
RichardE 0:5fa232ee5fdf 56 // Note that if you re-arrange the following code you may need to adjust the
RichardE 0:5fa232ee5fdf 57 // SpriteNumber enumeration in SpriteNumber.h.
RichardE 5:0b0651ac7832 58 UInt8 spriteNumber = FirstEnemySprite;
RichardE 5:0b0651ac7832 59 // Initialise enemies.
RichardE 10:bfa1c307c99d 60 GameObject::InitialiseAll( dataForLevel.Enemies, LevelData::MaxEnemies, &spriteNumber );
RichardE 5:0b0651ac7832 61 // Initialise humans.
RichardE 0:5fa232ee5fdf 62 spriteNumber = FirstHumanSprite;
RichardE 10:bfa1c307c99d 63 GameObject::InitialiseAll( dataForLevel.Humans, LevelData::MaxHumans, &spriteNumber );
RichardE 5:0b0651ac7832 64 // Use next free sprite number for player.
RichardE 5:0b0651ac7832 65 player->SpriteNumber = PlayerSprite;
RichardE 5:0b0651ac7832 66 // Do futher initialisation for all enemies.
RichardE 5:0b0651ac7832 67 EnemyObject *object;
RichardE 5:0b0651ac7832 68 for( UInt8 e = 0; e < LevelData::MaxEnemies; ++e ) {
RichardE 10:bfa1c307c99d 69 object = (EnemyObject*)dataForLevel.Enemies[ e ];
RichardE 5:0b0651ac7832 70 if( object != (EnemyObject*)NULL ) {
RichardE 13:50779b12ff51 71 // Perform any further initialisation required
RichardE 13:50779b12ff51 72 // at level start or restart.
RichardE 13:50779b12ff51 73 object->LevelRestart();
RichardE 0:5fa232ee5fdf 74 // Get enemy to chase the player.
RichardE 5:0b0651ac7832 75 object->SetChaseObject( player );
RichardE 0:5fa232ee5fdf 76 // Pass array of all enemies to this enemy.
RichardE 10:bfa1c307c99d 77 object->Enemies = dataForLevel.Enemies;
RichardE 0:5fa232ee5fdf 78 // If enemy is a brain then tell it about the humans to chase.
RichardE 0:5fa232ee5fdf 79 if( object->GetEnemyType() == Brain ) {
RichardE 10:bfa1c307c99d 80 ((BrainObject*)object)->HumansToChase = dataForLevel.Humans;
RichardE 0:5fa232ee5fdf 81 }
RichardE 5:0b0651ac7832 82 }
RichardE 0:5fa232ee5fdf 83 }
RichardE 5:0b0651ac7832 84 // Put player in the centre of the arena.
RichardE 5:0b0651ac7832 85 player->Xco = PLAYER_START_X;
RichardE 5:0b0651ac7832 86 player->Yco = PLAYER_START_Y;
RichardE 5:0b0651ac7832 87 // Kill off all player's bullets.
RichardE 5:0b0651ac7832 88 player->KillAllBullets( gd );
RichardE 5:0b0651ac7832 89 // Kill off all explosions.
RichardE 8:82d88f9381f3 90 ExplosionManager::Instance.KillAllExplosions();
RichardE 0:5fa232ee5fdf 91 }
RichardE 0:5fa232ee5fdf 92
RichardE 0:5fa232ee5fdf 93 /**********************************/
RichardE 0:5fa232ee5fdf 94 /* DRAW SCORE AND NUMBER OF LIVES */
RichardE 0:5fa232ee5fdf 95 /**********************************/
RichardE 5:0b0651ac7832 96 void LevelNormal::DrawScoreAndLives( void )
RichardE 5:0b0651ac7832 97 {
RichardE 5:0b0651ac7832 98 GDExtra::WriteBCDNumber( gd, 16, 0, player->Score, 8 );
RichardE 5:0b0651ac7832 99 // Display number of lives but limit this to 20 lives displayed.
RichardE 5:0b0651ac7832 100 UInt8 lives = ( player->Lives > 20 ) ? 20 : player->Lives;
RichardE 5:0b0651ac7832 101 gd->fill( Gameduino::RAM_PIC + VISIBLE_CHAR_WIDTH - lives, MiniPlayer, lives );
RichardE 0:5fa232ee5fdf 102 }
RichardE 0:5fa232ee5fdf 103
RichardE 0:5fa232ee5fdf 104 /******************/
RichardE 0:5fa232ee5fdf 105 /* DRAW THE LEVEL */
RichardE 0:5fa232ee5fdf 106 /******************/
RichardE 5:0b0651ac7832 107 void LevelNormal::DrawLevel( void )
RichardE 5:0b0651ac7832 108 {
RichardE 5:0b0651ac7832 109 // Set screen background to black.
RichardE 5:0b0651ac7832 110 gd->wr( Gameduino::BG_COLOR, Gameduino::RGB( 0, 0, 0 ) );
RichardE 5:0b0651ac7832 111 // Clear the screen to zero characters.
RichardE 5:0b0651ac7832 112 GDExtra::ClearScreen( gd, TransparentChar );
RichardE 5:0b0651ac7832 113 // Hide all sprties.
RichardE 5:0b0651ac7832 114 GDExtra::HideAllSprites( gd );
RichardE 5:0b0651ac7832 115 // Display level number.
RichardE 5:0b0651ac7832 116 GDExtra::WriteProgString( gd, 0, 0, StringData::LevelString );
RichardE 5:0b0651ac7832 117 GDExtra::WriteUInt16( gd, 6, 0, LevelNumber, 10, 2 );
RichardE 5:0b0651ac7832 118 // Display score.
RichardE 5:0b0651ac7832 119 GDExtra::WriteProgString( gd, 10, 0, StringData::ScoreString );
RichardE 5:0b0651ac7832 120 // Update score and lives.
RichardE 5:0b0651ac7832 121 DrawScoreAndLives();
RichardE 5:0b0651ac7832 122 // Draw border around screen.
RichardE 5:0b0651ac7832 123 CharFrame::Draw(
RichardE 5:0b0651ac7832 124 gd,
RichardE 5:0b0651ac7832 125 ARENA_BORDER_X,
RichardE 5:0b0651ac7832 126 ARENA_BORDER_Y,
RichardE 5:0b0651ac7832 127 ARENA_BORDER_WIDTH,
RichardE 5:0b0651ac7832 128 ARENA_BORDER_HEIGHT
RichardE 5:0b0651ac7832 129 );
RichardE 0:5fa232ee5fdf 130 }
RichardE 0:5fa232ee5fdf 131
RichardE 0:5fa232ee5fdf 132 /************************************************/
RichardE 0:5fa232ee5fdf 133 /* HANDLE COLLISIONS BETWEEN HUMANS AND ENEMIES */
RichardE 0:5fa232ee5fdf 134 /************************************************/
RichardE 0:5fa232ee5fdf 135 // Pass index of human in level's humans array in humanIndex.
RichardE 0:5fa232ee5fdf 136 // Pass sprite number of sprite that it hit in spriteNumber.
RichardE 5:0b0651ac7832 137 void LevelNormal::HandleHumanCollision( UInt8 humanIndex, UInt8 spriteNumber )
RichardE 5:0b0651ac7832 138 {
RichardE 5:0b0651ac7832 139 // Point to array of enemy object pointers.
RichardE 10:bfa1c307c99d 140 GameObject **enemies = currentInstance->dataForLevel.Enemies;
RichardE 5:0b0651ac7832 141 EnemyObject *enemy;
RichardE 5:0b0651ac7832 142 UInt8 enemyIndex, mutantIndex;
RichardE 5:0b0651ac7832 143 // Find an enemy with given sprite number.
RichardE 5:0b0651ac7832 144 if( GameObject::FindSpriteNumber( enemies, LevelData::MaxEnemies, spriteNumber, &enemyIndex ) ) {
RichardE 5:0b0651ac7832 145 // Found enemy. Check if it squashes humans.
RichardE 5:0b0651ac7832 146 enemy = (EnemyObject*)enemies[ enemyIndex ];
RichardE 0:5fa232ee5fdf 147 // Get hold of the human that is doomed.
RichardE 10:bfa1c307c99d 148 GameObject **humans = currentInstance->dataForLevel.Humans;
RichardE 0:5fa232ee5fdf 149 HumanObject *human = (HumanObject*)humans[ humanIndex ];
RichardE 0:5fa232ee5fdf 150 // Human must be walking around. Not rescued or already dead.
RichardE 0:5fa232ee5fdf 151 if( human->CurrentState == HumanObject::WalkingAbout ) {
RichardE 0:5fa232ee5fdf 152 if( enemy->SquashesHumans ) {
RichardE 0:5fa232ee5fdf 153 // Change human to dead state.
RichardE 0:5fa232ee5fdf 154 human->CurrentState = HumanObject::Dead;
RichardE 0:5fa232ee5fdf 155 // Make a noise.
RichardE 0:5fa232ee5fdf 156 SoundManager::Instance.PlaySound( Sounds::HumanDies, 0, 0 );
RichardE 5:0b0651ac7832 157 } else if( enemy->GetEnemyType() == Brain ) {
RichardE 10:bfa1c307c99d 158 // Kill off human by writing a NULL into the array.
RichardE 10:bfa1c307c99d 159 // DO NOT change human to dead state as this will generate a dead human animation
RichardE 10:bfa1c307c99d 160 // and this human is not dead, but a mutant.
RichardE 10:bfa1c307c99d 161 humans[ humanIndex ] = (HumanObject*)NULL;
RichardE 0:5fa232ee5fdf 162 // Find a free slot for a new enemy.
RichardE 0:5fa232ee5fdf 163 if( GameObject::FindUnusedObject( enemies, LevelData::MaxEnemies, &mutantIndex ) ) {
RichardE 10:bfa1c307c99d 164 // Write a pointer to a new mutant into the enemy array.
RichardE 10:bfa1c307c99d 165 MutantObject *mutant = (MutantObject*)EnemyFactory::Instance.MakeEnemy( Mutant );
RichardE 10:bfa1c307c99d 166 if( mutant != (MutantObject*)NULL ) {
RichardE 10:bfa1c307c99d 167 enemies[ mutantIndex ] = mutant;
RichardE 10:bfa1c307c99d 168 // Initialise mutant at coordinates of human and chasing the player.
RichardE 10:bfa1c307c99d 169 mutant->Start( human, currentInstance->player );
RichardE 10:bfa1c307c99d 170 // Make a noise.
RichardE 10:bfa1c307c99d 171 // TODO : SoundManager::Instance.PlaySound( Sounds::HumanMutates, 0, 0 );
RichardE 10:bfa1c307c99d 172 }
RichardE 5:0b0651ac7832 173 } else {
RichardE 0:5fa232ee5fdf 174 // Could not find a free slot for a new enemy so just erase the human sprite.
RichardE 8:82d88f9381f3 175 GDExtra::HideSprite( currentInstance->gd, human->SpriteNumber );
RichardE 0:5fa232ee5fdf 176 }
RichardE 0:5fa232ee5fdf 177 }
RichardE 0:5fa232ee5fdf 178 }
RichardE 0:5fa232ee5fdf 179 }
RichardE 0:5fa232ee5fdf 180 }
RichardE 0:5fa232ee5fdf 181
RichardE 0:5fa232ee5fdf 182 /********************************************************/
RichardE 0:5fa232ee5fdf 183 /* HANDLE COLLISIONS BETWEEN PLAYER BULLETS AND ENEMIES */
RichardE 0:5fa232ee5fdf 184 /********************************************************/
RichardE 0:5fa232ee5fdf 185 // Pass index of bullet in player's bullet array in bulletIndex.
RichardE 0:5fa232ee5fdf 186 // Pass sprite number of sprite that it hit in spriteNumber.
RichardE 5:0b0651ac7832 187 void LevelNormal::HandleBulletCollision( UInt8 bulletIndex, UInt8 spriteNumber )
RichardE 5:0b0651ac7832 188 {
RichardE 5:0b0651ac7832 189 // Point to array of enemy object pointers.
RichardE 10:bfa1c307c99d 190 GameObject **enemies = currentInstance->dataForLevel.Enemies;
RichardE 5:0b0651ac7832 191 EnemyObject *enemy;
RichardE 5:0b0651ac7832 192 UInt8 enemyIndex;
RichardE 5:0b0651ac7832 193 // Find an enemy with given sprite number.
RichardE 5:0b0651ac7832 194 if( GameObject::FindSpriteNumber( enemies, LevelData::MaxEnemies, spriteNumber, &enemyIndex ) ) {
RichardE 5:0b0651ac7832 195 // Found enemy. Check if it is indestructable.
RichardE 5:0b0651ac7832 196 enemy = (EnemyObject*)enemies[ enemyIndex ];
RichardE 10:bfa1c307c99d 197 // Remember coordinates for explosion.
RichardE 10:bfa1c307c99d 198 Int16 explodeX = enemy->Xco;
RichardE 10:bfa1c307c99d 199 Int16 explodeY = enemy->Yco;
RichardE 0:5fa232ee5fdf 200 if( enemy->HitPoints != EnemyObject::Indestructable ) {
RichardE 0:5fa232ee5fdf 201 // Enemy is not indestructable. Decrement hit points and die when it reaches zero.
RichardE 0:5fa232ee5fdf 202 enemy->HitPoints--;
RichardE 0:5fa232ee5fdf 203 if( enemy->HitPoints == 0 ) {
RichardE 0:5fa232ee5fdf 204 // Hide the enemy sprite.
RichardE 8:82d88f9381f3 205 GDExtra::HideSprite( currentInstance->gd, enemy->SpriteNumber );
RichardE 0:5fa232ee5fdf 206 // Add points to player's score.
RichardE 0:5fa232ee5fdf 207 currentInstance->player->AddToScore( enemy->GetPoints() );
RichardE 10:bfa1c307c99d 208 // Kill enemy by deleting it and then inserting a NULL into enemies array.
RichardE 10:bfa1c307c99d 209 EnemyFactory::Instance.DeleteEnemy( enemy );
RichardE 10:bfa1c307c99d 210 enemy = (EnemyObject*)NULL;
RichardE 10:bfa1c307c99d 211 enemies[ enemyIndex ] = (GameObject*)NULL;
RichardE 0:5fa232ee5fdf 212 }
RichardE 0:5fa232ee5fdf 213 }
RichardE 10:bfa1c307c99d 214 // If enemy is still alive then tell enemy it has been hit by a bullet.
RichardE 10:bfa1c307c99d 215 if( enemy != (EnemyObject*)NULL ) {
RichardE 10:bfa1c307c99d 216 enemy->RegisterHitByBullet();
RichardE 10:bfa1c307c99d 217 }
RichardE 0:5fa232ee5fdf 218 // Kill off the bullet.
RichardE 8:82d88f9381f3 219 currentInstance->player->KillBullet( currentInstance->gd, bulletIndex );
RichardE 0:5fa232ee5fdf 220 // Make a noise.
RichardE 0:5fa232ee5fdf 221 SoundManager::Instance.PlaySound( Sounds::Explosion, 0, 0 );
RichardE 10:bfa1c307c99d 222 // Start explosion animation.
RichardE 10:bfa1c307c99d 223 ExplosionManager::Instance.StartExplosion( explodeX, explodeY );
RichardE 5:0b0651ac7832 224 }
RichardE 0:5fa232ee5fdf 225 }
RichardE 0:5fa232ee5fdf 226
RichardE 0:5fa232ee5fdf 227 /*********************************************************/
RichardE 0:5fa232ee5fdf 228 /* CHECK FOR COLLISIONS BETWEEN PLAYER AND OTHER OBJECTS */
RichardE 0:5fa232ee5fdf 229 /*********************************************************/
RichardE 0:5fa232ee5fdf 230 // Pass pointer to a flag that will be set true if player is dead in isDead parameter.
RichardE 5:0b0651ac7832 231 void LevelNormal::CheckPlayerCollisions( bool *isDead )
RichardE 5:0b0651ac7832 232 {
RichardE 5:0b0651ac7832 233 UInt8 enemyIndex, humanIndex;
RichardE 5:0b0651ac7832 234 // Check if player sprite has hit another sprite.
RichardE 8:82d88f9381f3 235 UInt8 hitSpriteNumber = gd->rd( Gameduino::COLLISION + player->SpriteNumber );
RichardE 5:0b0651ac7832 236 // If you get 0xFF then no collision found.
RichardE 5:0b0651ac7832 237 if( hitSpriteNumber != 0xFF ) {
RichardE 5:0b0651ac7832 238 // Check for collision with an enemy.
RichardE 5:0b0651ac7832 239 if(
RichardE 5:0b0651ac7832 240 GameObject::FindSpriteNumber(
RichardE 10:bfa1c307c99d 241 dataForLevel.Enemies, LevelData::MaxEnemies, hitSpriteNumber, &enemyIndex
RichardE 5:0b0651ac7832 242 )
RichardE 5:0b0651ac7832 243 ) {
RichardE 5:0b0651ac7832 244 // Hit an enemy. Player is dead.
RichardE 5:0b0651ac7832 245 *isDead = true;
RichardE 5:0b0651ac7832 246 }
RichardE 5:0b0651ac7832 247 // Check for collision with a human that has not already been rescued or killed.
RichardE 5:0b0651ac7832 248 else if(
RichardE 5:0b0651ac7832 249 GameObject::FindSpriteNumber(
RichardE 10:bfa1c307c99d 250 dataForLevel.Humans, LevelData::MaxHumans, hitSpriteNumber, &humanIndex
RichardE 5:0b0651ac7832 251 )
RichardE 5:0b0651ac7832 252 ) {
RichardE 10:bfa1c307c99d 253 HumanObject *human = (HumanObject*)dataForLevel.Humans[ humanIndex ];
RichardE 0:5fa232ee5fdf 254 if( human->CurrentState == HumanObject::WalkingAbout ) {
RichardE 0:5fa232ee5fdf 255 // Change human state to rescued.
RichardE 0:5fa232ee5fdf 256 human->CurrentState = HumanObject::Rescued;
RichardE 0:5fa232ee5fdf 257 // Give player 50 points (in BCD!).
RichardE 0:5fa232ee5fdf 258 player->AddToScore( 0x50 );
RichardE 0:5fa232ee5fdf 259 // Make a noise.
RichardE 0:5fa232ee5fdf 260 SoundManager::Instance.PlaySound( Sounds::RescueHuman, 0, 0 );
RichardE 0:5fa232ee5fdf 261 }
RichardE 5:0b0651ac7832 262 }
RichardE 5:0b0651ac7832 263 }
RichardE 0:5fa232ee5fdf 264 }
RichardE 0:5fa232ee5fdf 265
RichardE 0:5fa232ee5fdf 266 /***********************************************************************************/
RichardE 0:5fa232ee5fdf 267 /* WAIT UNTIL SLOT FREE FOR A NEW SOUND, PLAY IT AND WAIT FOR ALL SOUNDS TO FINISH */
RichardE 0:5fa232ee5fdf 268 /***********************************************************************************/
RichardE 0:5fa232ee5fdf 269 // Pass sound to play in soundToPlay parameter.
RichardE 5:0b0651ac7832 270 void LevelNormal::PlaySoundAndWait( const UInt8 *soundToPlay )
RichardE 5:0b0651ac7832 271 {
RichardE 5:0b0651ac7832 272 // Keep trying to play sound until it works and meanwhile
RichardE 5:0b0651ac7832 273 // keep currently playing sounds going.
RichardE 5:0b0651ac7832 274 while( ! SoundManager::Instance.PlaySound( soundToPlay, 0, 0 ) ) {
RichardE 5:0b0651ac7832 275 // Update sound manager.
RichardE 5:0b0651ac7832 276 SoundManager::Instance.Update();
RichardE 5:0b0651ac7832 277 // Wait for frame flyback.
RichardE 9:fa7e7b37b632 278 gd->waitvblank();
RichardE 5:0b0651ac7832 279 }
RichardE 5:0b0651ac7832 280 // Now wait until all sounds have finished.
RichardE 5:0b0651ac7832 281 while( SoundManager::Instance.CountSoundsPlaying() > 0 ) {
RichardE 5:0b0651ac7832 282 // Update sound manager.
RichardE 5:0b0651ac7832 283 SoundManager::Instance.Update();
RichardE 5:0b0651ac7832 284 // Wait for frame flyback.
RichardE 9:fa7e7b37b632 285 gd->waitvblank();
RichardE 5:0b0651ac7832 286 }
RichardE 0:5fa232ee5fdf 287 }
RichardE 0:5fa232ee5fdf 288
RichardE 10:bfa1c307c99d 289 /**************/
RichardE 10:bfa1c307c99d 290 /* PLAY LEVEL */
RichardE 10:bfa1c307c99d 291 /**************/
RichardE 10:bfa1c307c99d 292 // Returns code indicating how level ended.
RichardE 10:bfa1c307c99d 293 Level::LevelExitCode LevelNormal::Play( void ) {
RichardE 12:81926431fea7 294 UInt8 enemyCount = 0;
RichardE 10:bfa1c307c99d 295 GameObject **ptr = dataForLevel.Enemies;
RichardE 10:bfa1c307c99d 296 // Repeat for all enemy types.
RichardE 10:bfa1c307c99d 297 for( UInt8 et = 0; et < (int)EnemyTypeCount; ++et ) {
RichardE 10:bfa1c307c99d 298 // Get number of this enemy type on this level.
RichardE 12:81926431fea7 299 UInt8 eCount = descriptorData->GetEnemyCount( (EnemyType)et );
RichardE 10:bfa1c307c99d 300 // Create required number of enemies.
RichardE 10:bfa1c307c99d 301 for( UInt8 eNum = 0; eNum < eCount; ++eNum ) {
RichardE 10:bfa1c307c99d 302 if( enemyCount < LevelData::MaxEnemies ) {
RichardE 10:bfa1c307c99d 303 GameObject *newEnemy = EnemyFactory::Instance.MakeEnemy( (EnemyType)et );
RichardE 10:bfa1c307c99d 304 if( newEnemy != (GameObject*)NULL ) {
RichardE 10:bfa1c307c99d 305 *ptr++ = newEnemy;
RichardE 10:bfa1c307c99d 306 enemyCount++;
RichardE 10:bfa1c307c99d 307 }
RichardE 10:bfa1c307c99d 308 }
RichardE 10:bfa1c307c99d 309 }
RichardE 10:bfa1c307c99d 310 }
RichardE 12:81926431fea7 311 // Create required number of humans.
RichardE 12:81926431fea7 312 HumanObject humans[ LevelData::MaxHumans ];
RichardE 10:bfa1c307c99d 313 ptr = dataForLevel.Humans;
RichardE 12:81926431fea7 314 UInt8 humanCount = descriptorData->HumanCount;
RichardE 12:81926431fea7 315 humanCount = ( humanCount > LevelData::MaxHumans ) ? LevelData::MaxHumans : humanCount;
RichardE 10:bfa1c307c99d 316 for( UInt8 i = 0; i < humanCount; ++i ) {
RichardE 10:bfa1c307c99d 317 *ptr++ = humans + i;
RichardE 10:bfa1c307c99d 318 }
RichardE 10:bfa1c307c99d 319 // Play the level. Returns when game is over or level is complete, but NOT
RichardE 10:bfa1c307c99d 320 // if the player gets killed and has lives remaining.
RichardE 10:bfa1c307c99d 321 Level::LevelExitCode exitCode = PlayLoop();
RichardE 10:bfa1c307c99d 322 // Make sure all memory allocated to enemies is freed.
RichardE 10:bfa1c307c99d 323 ptr = dataForLevel.Enemies;
RichardE 10:bfa1c307c99d 324 for( UInt8 i = 0; i < LevelData::MaxEnemies; ++i ) {
RichardE 10:bfa1c307c99d 325 if( *ptr != (GameObject*)NULL ) {
RichardE 10:bfa1c307c99d 326 EnemyFactory::Instance.DeleteEnemy( (EnemyObject*)*ptr );
RichardE 10:bfa1c307c99d 327 *ptr = (GameObject*)NULL;
RichardE 10:bfa1c307c99d 328 }
RichardE 10:bfa1c307c99d 329 ptr++;
RichardE 10:bfa1c307c99d 330 }
RichardE 10:bfa1c307c99d 331 return exitCode;
RichardE 10:bfa1c307c99d 332 }
RichardE 10:bfa1c307c99d 333
RichardE 0:5fa232ee5fdf 334 /*************/
RichardE 0:5fa232ee5fdf 335 /* PLAY LOOP */
RichardE 0:5fa232ee5fdf 336 /*************/
RichardE 0:5fa232ee5fdf 337 // Returns code indicating how level ended.
RichardE 0:5fa232ee5fdf 338 // This method should be called from the Play method after the
RichardE 0:5fa232ee5fdf 339 // level data has been initialised and the return value returned
RichardE 0:5fa232ee5fdf 340 // by the Play method.
RichardE 5:0b0651ac7832 341 Level::LevelExitCode LevelNormal::PlayLoop( void )
RichardE 5:0b0651ac7832 342 {
RichardE 5:0b0651ac7832 343 // Do nothing if Gameduino has not been specified, level data is NULL or player has not been specified.
RichardE 10:bfa1c307c99d 344 if( ( gd != (Gameduino*)NULL ) || ( player == (PlayerObject*)NULL ) ) {
RichardE 5:0b0651ac7832 345 // Point static pointer to current instance.
RichardE 5:0b0651ac7832 346 currentInstance = this;
RichardE 5:0b0651ac7832 347 // Do some initialisation first.
RichardE 5:0b0651ac7832 348 InitialiseLevel( gd );
RichardE 5:0b0651ac7832 349 // Redraw the screen.
RichardE 5:0b0651ac7832 350 DrawLevel();
RichardE 5:0b0651ac7832 351 // Wait for frame flyback once before entering loop so collision data is recalculated.
RichardE 5:0b0651ac7832 352 // At this point there should not be any sprites on the screen so no collisions
RichardE 5:0b0651ac7832 353 // should be found.
RichardE 5:0b0651ac7832 354 gd->waitvblank();
RichardE 5:0b0651ac7832 355 // Repeat until all enemies are dead or player is dead.
RichardE 5:0b0651ac7832 356 bool allEnemiesAreDead = false;
RichardE 5:0b0651ac7832 357 bool playerIsDead = false;
RichardE 5:0b0651ac7832 358 bool gameIsOver = false;
RichardE 5:0b0651ac7832 359 bool firstDraw = true;
RichardE 5:0b0651ac7832 360 while( ! allEnemiesAreDead && ! gameIsOver ) {
RichardE 5:0b0651ac7832 361 // Update sound manager.
RichardE 9:fa7e7b37b632 362 SoundManager::Instance.Update();
RichardE 5:0b0651ac7832 363 // Wait for frame flyback.
RichardE 5:0b0651ac7832 364 gd->waitvblank();
RichardE 5:0b0651ac7832 365 // Check for collisions between player and other objects.
RichardE 5:0b0651ac7832 366 CheckPlayerCollisions( &playerIsDead );
RichardE 5:0b0651ac7832 367 // Check for collisions between humans and enemies that squash.
RichardE 10:bfa1c307c99d 368 GameObject::FindCollisions( gd, dataForLevel.Humans, LevelData::MaxHumans, &LevelNormal::HandleHumanCollision );
RichardE 5:0b0651ac7832 369 // Check for collisions between player bullets and enemies.
RichardE 8:82d88f9381f3 370 GameObject::FindCollisions( gd, player->GetBullets(), BulletManager::MaxBullets, &LevelNormal::HandleBulletCollision );
RichardE 5:0b0651ac7832 371 // Redraw the player's score and number of lives.
RichardE 5:0b0651ac7832 372 DrawScoreAndLives();
RichardE 7:e72691603fd3 373 // Draw all the enemies.
RichardE 10:bfa1c307c99d 374 GameObject::DrawAll( gd, dataForLevel.Enemies, LevelData::MaxEnemies );
RichardE 5:0b0651ac7832 375 // Draw all the humans.
RichardE 10:bfa1c307c99d 376 GameObject::DrawAll( gd, dataForLevel.Humans, LevelData::MaxHumans );
RichardE 5:0b0651ac7832 377 // Draw all the explosions.
RichardE 8:82d88f9381f3 378 GameObject::DrawAll( gd, ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions );
RichardE 5:0b0651ac7832 379 // Draw the player.
RichardE 5:0b0651ac7832 380 player->Draw( gd );
RichardE 5:0b0651ac7832 381 // Draw the player's bullets.
RichardE 6:8bbdb70bc11c 382 GameObject::DrawAll( gd, player->GetBullets(), BulletManager::MaxBullets );
RichardE 5:0b0651ac7832 383 // Increment the frame counter.
RichardE 5:0b0651ac7832 384 FrameCounter++;
RichardE 5:0b0651ac7832 385 // After first redraw play level start sound and wait for it to end.
RichardE 5:0b0651ac7832 386 if( firstDraw ) {
RichardE 9:fa7e7b37b632 387 PlaySoundAndWait( Sounds::StartLevel );
RichardE 5:0b0651ac7832 388 firstDraw = false;
RichardE 1:dfd5eaaf96a3 389 }
RichardE 5:0b0651ac7832 390 // If player was killed then play death march and wait for it to finish.
RichardE 5:0b0651ac7832 391 if( playerIsDead ) {
RichardE 5:0b0651ac7832 392 // Player got killed.
RichardE 9:fa7e7b37b632 393 PlaySoundAndWait( Sounds::PlayerDead );
RichardE 5:0b0651ac7832 394 // One less life for player.
RichardE 5:0b0651ac7832 395 if( player->Lives > 0 ) {
RichardE 5:0b0651ac7832 396 player->Lives--;
RichardE 5:0b0651ac7832 397 }
RichardE 5:0b0651ac7832 398 // Game is over when player has no more lives.
RichardE 5:0b0651ac7832 399 gameIsOver = ( player->Lives == 0 );
RichardE 5:0b0651ac7832 400 // If game is not over then re-initialise level using any remaining enemies.
RichardE 5:0b0651ac7832 401 if( ! gameIsOver ) {
RichardE 5:0b0651ac7832 402 // Remove all objects that do not survive a level restart (like enemy bullets).
RichardE 10:bfa1c307c99d 403 GameObject::RemoveUnretainedObjects( dataForLevel.Enemies, LevelData::MaxEnemies );
RichardE 5:0b0651ac7832 404 InitialiseLevel( gd );
RichardE 5:0b0651ac7832 405 DrawLevel();
RichardE 5:0b0651ac7832 406 gd->waitvblank();
RichardE 5:0b0651ac7832 407 playerIsDead = false;
RichardE 5:0b0651ac7832 408 firstDraw = true;
RichardE 5:0b0651ac7832 409 }
RichardE 5:0b0651ac7832 410 }
RichardE 5:0b0651ac7832 411 else {
RichardE 5:0b0651ac7832 412 // Move all the enemies and check if all dead.
RichardE 10:bfa1c307c99d 413 allEnemiesAreDead = ! GameObject::MoveAll( dataForLevel.Enemies, LevelData::MaxEnemies );
RichardE 5:0b0651ac7832 414 // If there are still some enemies alive then check if those that remain are indestructable.
RichardE 5:0b0651ac7832 415 // If only indestructable enemies survive then act as if all enemies are dead.
RichardE 5:0b0651ac7832 416 // You need to do this or you would never be able to complete a level that had indestructable
RichardE 5:0b0651ac7832 417 // enemies on it.
RichardE 5:0b0651ac7832 418 if( ! allEnemiesAreDead ) {
RichardE 5:0b0651ac7832 419 allEnemiesAreDead = EnemyObject::AreAllIndestructable(
RichardE 10:bfa1c307c99d 420 (const EnemyObject**)dataForLevel.Enemies,
RichardE 6:8bbdb70bc11c 421 LevelData::MaxEnemies
RichardE 6:8bbdb70bc11c 422 );
RichardE 5:0b0651ac7832 423 }
RichardE 5:0b0651ac7832 424 // Move all the humans.
RichardE 10:bfa1c307c99d 425 GameObject::MoveAll( dataForLevel.Humans, LevelData::MaxHumans );
RichardE 5:0b0651ac7832 426 // Move (update) all the explosions.
RichardE 5:0b0651ac7832 427 GameObject::MoveAll( ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions );
RichardE 5:0b0651ac7832 428 // Read the player's controls.
RichardE 5:0b0651ac7832 429 player->ReadControls();
RichardE 5:0b0651ac7832 430 // Move the player.
RichardE 5:0b0651ac7832 431 player->Move();
RichardE 5:0b0651ac7832 432 // Move the player's bullets.
RichardE 5:0b0651ac7832 433 GameObject::MoveAll( player->GetBullets(), BulletManager::MaxBullets );
RichardE 0:5fa232ee5fdf 434 }
RichardE 1:dfd5eaaf96a3 435 }
RichardE 5:0b0651ac7832 436 // Player completed level or game is over.
RichardE 5:0b0651ac7832 437 return gameIsOver ? GameOver : Completed;
RichardE 0:5fa232ee5fdf 438 }
RichardE 5:0b0651ac7832 439 else {
RichardE 5:0b0651ac7832 440 // Level data or player were not specified.
RichardE 5:0b0651ac7832 441 return Completed;
RichardE 5:0b0651ac7832 442 }
RichardE 0:5fa232ee5fdf 443 }