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:
Sun Jun 09 19:34:56 2013 +0000
Revision:
10:bfa1c307c99d
Parent:
9:fa7e7b37b632
Child:
12:81926431fea7
Changed how levels are represented so that dynamic allocation of memory is used. Maple version couldn't do this. Still only 2 levels. Use EnemyFactory whenever creating or destroying enemies.

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