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

Revision:
10:bfa1c307c99d
Parent:
9:fa7e7b37b632
Child:
12:81926431fea7
--- a/LevelNormal.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/LevelNormal.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -22,6 +22,7 @@
 #include "FrameCounter.h"
 #include "SpriteNumber.h"
 #include "ArenaConst.h"
+#include "EnemyFactory.h"
 
 // Current instance being processed.
 LevelNormal *LevelNormal::currentInstance;
@@ -29,8 +30,12 @@
 /***************/
 /* CONSTRUCTOR */
 /***************/
-LevelNormal::LevelNormal()
+// Pass pointer to level descriptor data in data parameter.
+LevelNormal::LevelNormal( const UInt8 *data ) :
+    descriptorData( data )
 {
+    // Normal levels are always dynamically alocated.
+    IsDynamicallyAllocated = true;
 }
 
 /**************/
@@ -50,24 +55,24 @@
     // SpriteNumber enumeration in SpriteNumber.h.
     UInt8 spriteNumber = FirstEnemySprite;
     // Initialise enemies.
-    GameObject::InitialiseAll( DataForLevel->Enemies, LevelData::MaxEnemies, &spriteNumber );
+    GameObject::InitialiseAll( dataForLevel.Enemies, LevelData::MaxEnemies, &spriteNumber );
     // Initialise humans.
     spriteNumber = FirstHumanSprite;
-    GameObject::InitialiseAll( DataForLevel->Humans, LevelData::MaxHumans, &spriteNumber );
+    GameObject::InitialiseAll( dataForLevel.Humans, LevelData::MaxHumans, &spriteNumber );
     // Use next free sprite number for player.
     player->SpriteNumber = PlayerSprite;
     // Do futher initialisation for all enemies.
     EnemyObject *object;
     for( UInt8 e = 0; e < LevelData::MaxEnemies; ++e ) {
-        object = (EnemyObject*)DataForLevel->Enemies[ e ];
+        object = (EnemyObject*)dataForLevel.Enemies[ e ];
         if( object != (EnemyObject*)NULL ) {
             // Get enemy to chase the player.
             object->SetChaseObject( player );
             // Pass array of all enemies to this enemy.
-            object->Enemies = DataForLevel->Enemies;
+            object->Enemies = dataForLevel.Enemies;
             // If enemy is a brain then tell it about the humans to chase.
             if( object->GetEnemyType() == Brain ) {
-                ((BrainObject*)object)->HumansToChase = DataForLevel->Humans;
+                ((BrainObject*)object)->HumansToChase = dataForLevel.Humans;
             }
         }
     }
@@ -127,7 +132,7 @@
 void LevelNormal::HandleHumanCollision( UInt8 humanIndex, UInt8 spriteNumber )
 {
     // Point to array of enemy object pointers.
-    GameObject **enemies = currentInstance->DataForLevel->Enemies;
+    GameObject **enemies = currentInstance->dataForLevel.Enemies;
     EnemyObject *enemy;
     UInt8 enemyIndex, mutantIndex;
     // Find an enemy with given sprite number.
@@ -135,7 +140,7 @@
         // Found enemy. Check if it squashes humans.
         enemy = (EnemyObject*)enemies[ enemyIndex ];
         // Get hold of the human that is doomed.
-        GameObject **humans = currentInstance->DataForLevel->Humans;
+        GameObject **humans = currentInstance->dataForLevel.Humans;
         HumanObject *human = (HumanObject*)humans[ humanIndex ];
         // Human must be walking around. Not rescued or already dead.
         if( human->CurrentState == HumanObject::WalkingAbout ) {
@@ -145,18 +150,21 @@
                 // Make a noise.
                 SoundManager::Instance.PlaySound( Sounds::HumanDies, 0, 0 );
             } else if( enemy->GetEnemyType() == Brain ) {
-                // Kill human by inserting a null into humans array.
-                humans[ humanIndex ] = (GameObject*)NULL;
+                // Kill off human by writing a NULL into the array.
+                // DO NOT change human to dead state as this will generate a dead human animation
+                // and this human is not dead, but a mutant.
+                humans[ humanIndex ] = (HumanObject*)NULL;
                 // Find a free slot for a new enemy.
                 if( GameObject::FindUnusedObject( enemies, LevelData::MaxEnemies, &mutantIndex ) ) {
-                    // Write a pointer to a mutant with the same index as the human that just died
-                    // into the enemy array.
-                    MutantObject *mutant = currentInstance->DataForLevel->Mutants + humanIndex;
-                    enemies[ mutantIndex ] = mutant;
-                    // Initialise mutant at coordinates of human and chasing the player.
-                    mutant->Start( human, currentInstance->player );
-                    // Make a noise.
-                    // TODO : SoundManager::Instance.PlaySound( Sounds::HumanMutates, 0, 0 );
+                    // Write a pointer to a new mutant into the enemy array.
+                    MutantObject *mutant = (MutantObject*)EnemyFactory::Instance.MakeEnemy( Mutant );
+                    if( mutant != (MutantObject*)NULL ) {
+                        enemies[ mutantIndex ] = mutant;
+                        // Initialise mutant at coordinates of human and chasing the player.
+                        mutant->Start( human, currentInstance->player );
+                        // Make a noise.
+                        // TODO : SoundManager::Instance.PlaySound( Sounds::HumanMutates, 0, 0 );
+                    }
                 } else {
                     // Could not find a free slot for a new enemy so just erase the human sprite.
                     GDExtra::HideSprite( currentInstance->gd, human->SpriteNumber );
@@ -174,33 +182,40 @@
 void LevelNormal::HandleBulletCollision( UInt8 bulletIndex, UInt8 spriteNumber )
 {
     // Point to array of enemy object pointers.
-    GameObject **enemies = currentInstance->DataForLevel->Enemies;
+    GameObject **enemies = currentInstance->dataForLevel.Enemies;
     EnemyObject *enemy;
     UInt8 enemyIndex;
     // Find an enemy with given sprite number.
     if( GameObject::FindSpriteNumber( enemies, LevelData::MaxEnemies, spriteNumber, &enemyIndex ) ) {
         // Found enemy. Check if it is indestructable.
         enemy = (EnemyObject*)enemies[ enemyIndex ];
+        // Remember coordinates for explosion.
+        Int16 explodeX = enemy->Xco;
+        Int16 explodeY = enemy->Yco;
         if( enemy->HitPoints != EnemyObject::Indestructable ) {
             // Enemy is not indestructable. Decrement hit points and die when it reaches zero.
             enemy->HitPoints--;
             if( enemy->HitPoints == 0 ) {
-                // Kill enemy by inserting a NULL into enemies array.
-                enemies[ enemyIndex ] = (GameObject*)NULL;
                 // Hide the enemy sprite.
                 GDExtra::HideSprite( currentInstance->gd, enemy->SpriteNumber );
                 // Add points to player's score.
                 currentInstance->player->AddToScore( enemy->GetPoints() );
+                // Kill enemy by deleting it and then inserting a NULL into enemies array.
+                EnemyFactory::Instance.DeleteEnemy( enemy );
+                enemy = (EnemyObject*)NULL;
+                enemies[ enemyIndex ] = (GameObject*)NULL;
             }
         }
-        // Tell enemy it has been hit by a bullet.
-        enemy->RegisterHitByBullet();
+        // If enemy is still alive then tell enemy it has been hit by a bullet.
+        if( enemy != (EnemyObject*)NULL ) {
+            enemy->RegisterHitByBullet();
+        }
         // Kill off the bullet.
         currentInstance->player->KillBullet( currentInstance->gd, bulletIndex );
         // Make a noise.
         SoundManager::Instance.PlaySound( Sounds::Explosion, 0, 0 );
-        // Start explosion animation using coordinates of enemy.
-        ExplosionManager::Instance.StartExplosion( enemy->Xco, enemy->Yco    );
+        // Start explosion animation.
+        ExplosionManager::Instance.StartExplosion( explodeX, explodeY );
     }
 }
 
@@ -218,7 +233,7 @@
         // Check for collision with an enemy.
         if(
             GameObject::FindSpriteNumber(
-                DataForLevel->Enemies, LevelData::MaxEnemies, hitSpriteNumber, &enemyIndex
+                dataForLevel.Enemies, LevelData::MaxEnemies, hitSpriteNumber, &enemyIndex
             )
         ) {
             // Hit an enemy. Player is dead.
@@ -227,10 +242,10 @@
         // Check for collision with a human that has not already been rescued or killed.
         else if(
             GameObject::FindSpriteNumber(
-                DataForLevel->Humans, LevelData::MaxHumans, hitSpriteNumber, &humanIndex
+                dataForLevel.Humans, LevelData::MaxHumans, hitSpriteNumber, &humanIndex
             )
         ) {
-            HumanObject *human = (HumanObject*)DataForLevel->Humans[ humanIndex ];
+            HumanObject *human = (HumanObject*)dataForLevel.Humans[ humanIndex ];
             if( human->CurrentState == HumanObject::WalkingAbout ) {
                 // Change human state to rescued.
                 human->CurrentState = HumanObject::Rescued;
@@ -266,6 +281,51 @@
     }
 }
 
+/**************/
+/* PLAY LEVEL */
+/**************/
+// Returns code indicating how level ended.
+Level::LevelExitCode LevelNormal::Play( void ) {
+  UInt8 humanCount, enemyCount = 0;
+  GameObject **ptr = dataForLevel.Enemies;
+  // Repeat for all enemy types.
+  for( UInt8 et = 0; et < (int)EnemyTypeCount; ++et ) {
+    // Get number of this enemy type on this level.
+    UInt8 eCount = LevelDescriptor::GetEnemyCount( descriptorData, (EnemyType)et );
+    // Create required number of enemies.
+    for( UInt8 eNum = 0; eNum < eCount; ++eNum ) {
+        if( enemyCount < LevelData::MaxEnemies ) {
+          GameObject *newEnemy = EnemyFactory::Instance.MakeEnemy( (EnemyType)et );
+          if( newEnemy != (GameObject*)NULL ) {
+            *ptr++ = newEnemy;
+            enemyCount++;
+          }
+        }
+    }
+  }
+  // Humans.
+  #define HUMANCOUNT 6
+  HumanObject humans[ HUMANCOUNT ];
+  ptr = dataForLevel.Humans;
+  humanCount = ( HUMANCOUNT > LevelData::MaxHumans ) ? LevelData::MaxHumans : HUMANCOUNT;
+  for( UInt8 i = 0; i < humanCount; ++i ) {
+    *ptr++ = humans + i;
+  }
+  // Play the level. Returns when game is over or level is complete, but NOT
+  // if the player gets killed and has lives remaining.
+  Level::LevelExitCode exitCode = PlayLoop();
+  // Make sure all memory allocated to enemies is freed.
+  ptr = dataForLevel.Enemies;
+  for( UInt8 i = 0; i < LevelData::MaxEnemies; ++i ) {
+    if( *ptr != (GameObject*)NULL ) {
+      EnemyFactory::Instance.DeleteEnemy( (EnemyObject*)*ptr );
+      *ptr = (GameObject*)NULL;
+    }
+    ptr++;
+  }
+  return exitCode;
+}
+
 /*************/
 /* PLAY LOOP */
 /*************/
@@ -276,7 +336,7 @@
 Level::LevelExitCode LevelNormal::PlayLoop( void )
 {
     // Do nothing if Gameduino has not been specified, level data is NULL or player has not been specified.
-    if( ( gd != (Gameduino*)NULL ) || ( DataForLevel != (LevelData*)NULL ) || ( player == (PlayerObject*)NULL ) ) {
+    if( ( gd != (Gameduino*)NULL ) || ( player == (PlayerObject*)NULL ) ) {
         // Point static pointer to current instance.
         currentInstance = this;
         // Do some initialisation first.
@@ -300,15 +360,15 @@
             // Check for collisions between player and other objects.
             CheckPlayerCollisions( &playerIsDead );
             // Check for collisions between humans and enemies that squash.
-            GameObject::FindCollisions( gd, DataForLevel->Humans, LevelData::MaxHumans, &LevelNormal::HandleHumanCollision );
+            GameObject::FindCollisions( gd, dataForLevel.Humans, LevelData::MaxHumans, &LevelNormal::HandleHumanCollision );
             // Check for collisions between player bullets and enemies.
             GameObject::FindCollisions( gd, player->GetBullets(), BulletManager::MaxBullets, &LevelNormal::HandleBulletCollision );
             // Redraw the player's score and number of lives.
             DrawScoreAndLives();
             // Draw all the enemies.
-            GameObject::DrawAll( gd, DataForLevel->Enemies, LevelData::MaxEnemies );
+            GameObject::DrawAll( gd, dataForLevel.Enemies, LevelData::MaxEnemies );
             // Draw all the humans.
-            GameObject::DrawAll( gd, DataForLevel->Humans, LevelData::MaxHumans );
+            GameObject::DrawAll( gd, dataForLevel.Humans, LevelData::MaxHumans );
             // Draw all the explosions.
             GameObject::DrawAll( gd, ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions );
             // Draw the player.
@@ -341,7 +401,7 @@
                         pc.puts( "Game is over.\r\n" );
                     #endif
                     // Remove all objects that do not survive a level restart (like enemy bullets).
-                    GameObject::RemoveUnretainedObjects( DataForLevel->Enemies, LevelData::MaxEnemies );
+                    GameObject::RemoveUnretainedObjects( dataForLevel.Enemies, LevelData::MaxEnemies );
                     InitialiseLevel( gd );
                     DrawLevel();
                     gd->waitvblank();
@@ -351,19 +411,19 @@
             }
             else {
                 // Move all the enemies and check if all dead.
-                allEnemiesAreDead = ! GameObject::MoveAll( DataForLevel->Enemies, LevelData::MaxEnemies );
+                allEnemiesAreDead = ! GameObject::MoveAll( dataForLevel.Enemies, LevelData::MaxEnemies );
                 // If there are still some enemies alive then check if those that remain are indestructable.
                 // If only indestructable enemies survive then act as if all enemies are dead.
                 // You need to do this or you would never be able to complete a level that had indestructable
                 // enemies on it.
                 if( ! allEnemiesAreDead ) {
                     allEnemiesAreDead = EnemyObject::AreAllIndestructable(
-                        (const EnemyObject**)DataForLevel->Enemies,
+                        (const EnemyObject**)dataForLevel.Enemies,
                         LevelData::MaxEnemies
                     );
                 }
                 // Move all the humans.
-                GameObject::MoveAll( DataForLevel->Humans, LevelData::MaxHumans );
+                GameObject::MoveAll( dataForLevel.Humans, LevelData::MaxHumans );
                 // Move (update) all the explosions.
                 GameObject::MoveAll( ExplosionManager::Instance.GetExplosions(), ExplosionManager::MaxExplosions );
                 // Read the player's controls.