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

Files at this revision

API Documentation at this revision

Comitter:
RichardE
Date:
Sun Jun 09 19:34:56 2013 +0000
Parent:
9:fa7e7b37b632
Child:
11:2ddaf46e95cb
Commit message:
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.

Changed in this revision

BrainObject.cpp Show annotated file Show diff for this revision Revisions of this file
BrainObject.h Show annotated file Show diff for this revision Revisions of this file
EnemyFactory.cpp Show annotated file Show diff for this revision Revisions of this file
EnemyFactory.h Show annotated file Show diff for this revision Revisions of this file
EnemyType.h Show annotated file Show diff for this revision Revisions of this file
GameObject.cpp Show annotated file Show diff for this revision Revisions of this file
GameRobotRic.cpp Show annotated file Show diff for this revision Revisions of this file
Level.h Show annotated file Show diff for this revision Revisions of this file
Level0.cpp Show annotated file Show diff for this revision Revisions of this file
Level1.cpp Show diff for this revision Revisions of this file
Level1.h Show diff for this revision Revisions of this file
Level2.cpp Show diff for this revision Revisions of this file
Level2.h Show diff for this revision Revisions of this file
LevelCollection.cpp Show annotated file Show diff for this revision Revisions of this file
LevelCollection.h Show annotated file Show diff for this revision Revisions of this file
LevelData.h Show annotated file Show diff for this revision Revisions of this file
LevelDescriptor.cpp Show annotated file Show diff for this revision Revisions of this file
LevelDescriptor.h Show annotated file Show diff for this revision Revisions of this file
LevelNormal.cpp Show annotated file Show diff for this revision Revisions of this file
LevelNormal.h Show annotated file Show diff for this revision Revisions of this file
--- a/BrainObject.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/BrainObject.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -15,6 +15,7 @@
 #include "BulletVelocityCalculator.h"
 #include "HumanObject.h"
 #include "Random.h"
+#include "EnemyFactory.h"
 
 /***************/
 /* CONSTRUCTOR */
@@ -28,8 +29,6 @@
   MovementRestricted = true;
   // Restrict to boundary of arena.
   Bounds = &ArenaRectangle;
-  // Restrict bullet to boundary of arena.
-  bullet.Bounds = &ArenaRectangle;
 }
 
 /**************/
@@ -52,30 +51,37 @@
 /******************/
 // Pass sprite number to use in spriteNumber.
 // Returns pointer to bullet object or NULL on failure.
+// Memory is dynamically allocated since Brain bullet is an EnemyObject.
 BrainBulletObject *BrainObject::StartBullet( UInt8 spriteNumber ) {
-    // Give bullet same coordinates as the brain that fired it.
-    bullet.Xco = Xco;
-    bullet.Yco = Yco;
-    // Set correct sprite number.
-    bullet.SpriteNumber = spriteNumber;
-    // Must make it visible because last time bullet was
-    // killed off this property may have been set to false.
-    bullet.Visible = true;
-    // Similarly hit points may already be at zero.
-    // If you don't do this then bullet hit points gets decremented to -1 (0xFF)
-    // and it becomes indestructible.
-    bullet.HitPoints = 1;
-    // Calculate bullet velocities.
-    // Aim for a point somewhere in the vicinity of the chase object.
-    if( chaseObject != (GameObject*)NULL ) {
-        UInt16 targetX = chaseObject->Xco + (Int16)Random::Get( -128, +128 );
-        UInt16 targetY = chaseObject->Yco + (Int16)Random::Get( -128, +128 );
-        BulletVelocityCalculator::CalculateVelocities(
-            targetX - Xco, targetY - Yco,
-            80, &bullet.HVelocity, &bullet.VVelocity
-        );
+    // Create a new bullet.
+    BrainBulletObject *newBullet = (BrainBulletObject*)EnemyFactory::Instance.MakeEnemy( BrainBullet );
+    if( newBullet != (BrainBulletObject*)NULL ) {
+        // Give bullet same coordinates as the brain that fired it.
+        newBullet->Xco = Xco;
+        newBullet->Yco = Yco;
+        // Restrict bullet to boundary of arena.
+        newBullet->Bounds = &ArenaRectangle;
+        // Set correct sprite number.
+        newBullet->SpriteNumber = spriteNumber;
+        // Must make it visible because last time bullet was
+        // killed off this property may have been set to false.
+        newBullet->Visible = true;
+        // Similarly hit points may already be at zero.
+        // If you don't do this then bullet hit points gets decremented to -1 (0xFF)
+        // and it becomes indestructible.
+        newBullet->HitPoints = 1;
+        // Calculate bullet velocities.
+        // Aim for a point somewhere in the vicinity of the chase object.
+        if( chaseObject != (GameObject*)NULL ) {
+            UInt16 targetX = chaseObject->Xco + (Int16)Random::Get( -128, +128 );
+            UInt16 targetY = chaseObject->Yco + (Int16)Random::Get( -128, +128 );
+            BulletVelocityCalculator::CalculateVelocities(
+                targetX - Xco, targetY - Yco,
+                80, &(newBullet->HVelocity), &(newBullet->VVelocity)
+            );
+        }
     }
-    return •
+    return newBullet;
 }
 
 /************************/
--- a/BrainObject.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/BrainObject.h	Sun Jun 09 19:34:56 2013 +0000
@@ -67,7 +67,6 @@
         
         bool bulletActive;                // true if a bullet fired by this brain is zipping around
         UInt8 bulletIndex;                // index of active bullet in enemies array
-        BrainBulletObject bullet;         // the bullet belonging to this brain
         
         /******************************************************/
         /* DETERMINE IF A HUMAN IS A VALID TARGET FOR A BRAIN */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/EnemyFactory.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -0,0 +1,96 @@
+/*
+ * SOURCE FILE : EnemyFactory.cpp
+ *
+ * Definition of class EnemyFactory.
+ *
+ */
+
+#include "EnemyFactory.h"
+
+// Define this for debugging messages.
+#define CHATTY
+
+#ifdef CHATTY
+    #include "mbed.h"
+    extern Serial pc;
+#endif
+
+// An instance of this class.
+EnemyFactory EnemyFactory::Instance;
+
+/***************/
+/* CONSTRUCTOR */
+/***************/
+EnemyFactory::EnemyFactory() :
+    enemyCount( 0UL )
+{
+}
+
+/**************/
+/* DESTRUCTOR */
+/**************/
+EnemyFactory::~EnemyFactory()
+{
+}
+
+/*****************/
+/* MAKE AN ENEMY */
+/*****************/
+// Pass enemy type in et.
+// Returns a newly created object or NULL on failure.
+EnemyObject* EnemyFactory::MakeEnemy( EnemyType et ) {
+    EnemyObject *enemy;
+    switch( et ) {
+    
+    case Grunt :
+        enemy = new GruntObject();
+        break;
+        
+    case BlueMeany :
+        enemy = new BlueMeanyObject();
+        break;
+        
+    case Crusher :
+        enemy = new CrusherObject();
+        break;
+        
+    case Brain :
+        enemy = new BrainObject();
+        break;
+
+    case BrainBullet :
+        enemy = new BrainBulletObject();
+        break;
+                
+    case Mutant :
+        enemy = new MutantObject();
+        break;
+        
+    default :
+        enemy = (EnemyObject*)NULL;
+        break;
+        
+    }
+    
+    if( enemy != (EnemyObject*)NULL ) {
+        enemyCount++;
+        #ifdef CHATTY
+            pc.printf( "Enemy type %d created. %lu enemies exist.\r\n", (int)enemy->GetEnemyType(), enemyCount );
+        #endif
+    }
+    
+    return enemy;
+}
+
+/*******************/
+/* DELETE AN ENEMY */
+/*******************/
+// Pass pointer to enemy to delete.
+// ONLY USE THIS ON OBJECTS CREATED USING MakeEnemy method.
+void EnemyFactory::DeleteEnemy( EnemyObject *enemy ) {
+    enemyCount--;
+    #ifdef CHATTY
+        pc.printf( "Enemy type %d deleted. %lu enemies exist.\r\n", (int)enemy->GetEnemyType(), enemyCount );
+    #endif
+    delete enemy;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/EnemyFactory.h	Sun Jun 09 19:34:56 2013 +0000
@@ -0,0 +1,67 @@
+/*
+ * SOURCE FILE : EnemyFactory.h
+ *
+ * Definition of class EnemyFactory.
+ * Makes objects descended from EnemyObject.
+ * It is absolutely vital that all enemies are created and deleted this way if memory leaks are to be avoided.
+ * Don't forget pesky enemies like MutantObjects that only appear half way through a level. They still need
+ * to be created this way.
+ *
+ */
+
+#ifndef EnemyFactoryDefined
+
+  #define EnemyFactoryDefined
+
+  #include "EnemyType.h"
+  #include "GameObject.h"
+  #include "HumanObject.h"
+  #include "GruntObject.h"
+  #include "CrusherObject.h"
+  #include "BrainObject.h"
+  #include "MutantObject.h"
+  #include "BlueMeanyObject.h"
+
+  class EnemyFactory {
+
+  public :
+
+    // An instance of this class.
+    static EnemyFactory Instance;
+    
+    /***************/
+    /* CONSTRUCTOR */
+    /***************/
+    EnemyFactory();
+
+    /**************/
+    /* DESTRUCTOR */
+    /**************/
+    virtual ~EnemyFactory();
+
+    /*****************/
+    /* MAKE AN ENEMY */
+    /*****************/
+    // Pass enemy type in et.
+    // Returns a newly created object or NULL on failure.
+    EnemyObject* MakeEnemy( EnemyType et );
+
+    /*******************/
+    /* DELETE AN ENEMY */
+    /*******************/
+    // Pass pointer to enemy to delete.
+    // ONLY USE THIS ON OBJECTS CREATED USING MakeEnemy method.
+    void DeleteEnemy( EnemyObject *enemy );
+
+  private :
+  
+    // Number of enemies created.
+    unsigned long enemyCount;
+        
+  };
+
+#endif
+
+/* END of EnemyFactory.h */
+
+
--- a/EnemyType.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/EnemyType.h	Sun Jun 09 19:34:56 2013 +0000
@@ -1,23 +1,25 @@
-/*
- * SOURCE FILE : EnemyType.h
- *
- * Enumeration of all the possible enemy types.
- *
- */
-
-#ifndef EnemyTypeIncluded
-  
-  #define EnemyTypeIncluded
-
-  enum EnemyType {
-    Grunt,
-    BlueMeany,
-        Crusher,
-        Brain,
-        BrainBullet,
-  };
-  
-#endif
-
-/* END of EnemyType.h */
+/*
+ * SOURCE FILE : EnemyType.h
+ *
+ * Enumeration of all the possible enemy types.
+ *
+ */
+
+#ifndef EnemyTypeIncluded
+  
+  #define EnemyTypeIncluded
 
+  enum EnemyType {
+        Grunt,
+        BlueMeany,
+        Crusher,
+        Brain,
+        BrainBullet,
+        Mutant,
+        EnemyTypeCount          // number of different kinds of enemies, MUST COME LAST!
+  };
+  
+#endif
+
+/* END of EnemyType.h */
+
--- a/GameObject.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/GameObject.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -9,6 +9,7 @@
 #include "GameObjectLocator.h"
 #include "ArenaConst.h"
 #include "GDExtra.h"
+#include "EnemyFactory.h"
 
 /**********************************/
 /* INITIALISE AN ARRAY OF OBJECTS */
@@ -77,7 +78,11 @@
       // If not then it wants killing off and a NULL
       // should be written to the array of pointers
       // and the sprite should be hidden.
+      // If it is an enemy then it must be deleted using EnemyFactory.
       if( ! object->Visible ) {
+        if( object->GetType() == EnemyObjectType ) {
+            EnemyFactory::Instance.DeleteEnemy( (EnemyObject*)object );
+        }
         objects[ i ] = (GameObject*)NULL;
         GDExtra::HideSprite( gd, object->SpriteNumber );
       }
--- a/GameRobotRic.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/GameRobotRic.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -127,20 +127,22 @@
         level->SetGameduino( &gd );
         // Play the level.
         exitCode = level->Play();
+        // Free memory used by level.
+        levels.FreeLevel( level );
         // If player was killed then decrement lives otherwise
         // advance to next level.
         switch( exitCode ) {
-            case Level::GameOver :
-                // TODO : Do some sort of game over fuss.
-                CheckForHighScore( &gd, &highScores, player.Score );
-                // Go back to attract level and reset player lives and score.
-                levelNumber = LevelCollection::AttractLevel;
-                player.Lives = START_LIVES;
-                player.Score = 0;
-                break;
-            case Level::Completed :
-                levelNumber++;
-                break;
+        case Level::GameOver :
+            // TODO : Do some sort of game over fuss.
+            CheckForHighScore( &gd, &highScores, player.Score );
+            // Go back to attract level and reset player lives and score.
+            levelNumber = LevelCollection::AttractLevel;
+            player.Lives = START_LIVES;
+            player.Score = 0;
+            break;
+        case Level::Completed :
+            levelNumber++;
+            break;
         }
     }
 }
--- a/Level.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/Level.h	Sun Jun 09 19:34:56 2013 +0000
@@ -40,6 +40,9 @@
     // Number of this level.
     UInt8 LevelNumber;
     
+    // True if level was dynamically allocated.
+    bool IsDynamicallyAllocated;
+    
     /***************/
     /* CONSTRUCTOR */
     /***************/
--- a/Level0.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/Level0.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -12,6 +12,8 @@
 /* CONSTRUCTOR */
 /***************/
 Level0::Level0() {
+    // Level zero (attract mode) is NOT dynamically allocated.
+    IsDynamicallyAllocated = false;
 }
 
 /**************/
--- a/Level1.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * SOURCE FILE : Level1.cpp
- *
- * Definition of class Level1.
- * Machine written by program MapleRobotRicLevelGenerator.
- * DO NOT EDIT BY HAND!
- *
- */
-
-#include "Level1.h"
-
-/**************/
-/* PLAY LEVEL */
-/**************/
-// Returns code indicating how level ended.
-Level::LevelExitCode Level1::Play( void ) {
-  LevelData dataForThisLevel;
-  GameObject **ptr = dataForThisLevel.Enemies;
-  UInt8 i, humanCount, enemyCount = 0;
-  // Enemies of type GruntObject.
-  #define GRUNTOBJECTCOUNT 5
-  GruntObject gruntobjects[ GRUNTOBJECTCOUNT ];
-  for( i = 0; i < GRUNTOBJECTCOUNT; ++i ) {
-    if( enemyCount < LevelData::MaxEnemies ) {
-      *ptr++ = gruntobjects + i;
-      enemyCount++;
-    }
-  }
-  // Enemies of type CrusherObject.
-  #define CRUSHEROBJECTCOUNT 5
-  CrusherObject crusherobjects[ CRUSHEROBJECTCOUNT ];
-  for( i = 0; i < CRUSHEROBJECTCOUNT; ++i ) {
-    if( enemyCount < LevelData::MaxEnemies ) {
-      *ptr++ = crusherobjects + i;
-      enemyCount++;
-    }
-  }
-  // Humans.
-  #define HUMANCOUNT 5
-  HumanObject humans[ HUMANCOUNT ];
-  ptr = dataForThisLevel.Humans;
-  humanCount = ( HUMANCOUNT > LevelData::MaxHumans ) ? LevelData::MaxHumans : HUMANCOUNT;
-  for( i = 0; i < humanCount; ++i ) {
-    *ptr++ = humans + i;
-  }
-  DataForLevel = &dataForThisLevel;
-  return PlayLoop();
-}
-
--- a/Level1.h	Sun Jun 09 14:28:53 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * SOURCE FILE : Level1.h
- *
- * Definition of class Level1.
- * Machine written by program MapleRobotRicLevelGenerator.
- * DO NOT EDIT BY HAND!
- *
- */
-
-#ifndef Level1Defined
-
-  #define Level1Defined
-
-  #include "LevelNormal.h"
-
-  class Level1 : public LevelNormal {
-
-  public :
-
-    /**************/
-    /* PLAY LEVEL */
-    /**************/
-    // Returns code indicating how level ended.
-    virtual LevelExitCode Play( void );
-
-  };
-
-#endif
-
-/* END of Level1.h */
--- a/Level2.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/*
- * SOURCE FILE : Level2.cpp
- *
- * Definition of class Level2.
- * Machine written by program MapleRobotRicLevelGenerator.
- * DO NOT EDIT BY HAND!
- *
- */
-
-#include "Level2.h"
-
-/**************/
-/* PLAY LEVEL */
-/**************/
-// Returns code indicating how level ended.
-Level::LevelExitCode Level2::Play( void ) {
-  LevelData dataForThisLevel;
-  GameObject **ptr = dataForThisLevel.Enemies;
-  UInt8 i, humanCount, enemyCount = 0;
-  // Enemies of type GruntObject.
-  #define GRUNTOBJECTCOUNT 10
-  GruntObject gruntobjects[ GRUNTOBJECTCOUNT ];
-  for( i = 0; i < GRUNTOBJECTCOUNT; ++i ) {
-    if( enemyCount < LevelData::MaxEnemies ) {
-      *ptr++ = gruntobjects + i;
-      enemyCount++;
-    }
-  }
-  // Enemies of type BrainObject.
-  #define BRAINOBJECTCOUNT 2
-  BrainObject brainobjects[ BRAINOBJECTCOUNT ];
-  for( i = 0; i < BRAINOBJECTCOUNT; ++i ) {
-    if( enemyCount < LevelData::MaxEnemies ) {
-      *ptr++ = brainobjects + i;
-      enemyCount++;
-    }
-  }
-  // Humans.
-  #define HUMANCOUNT 6
-  HumanObject humans[ HUMANCOUNT ];
-  ptr = dataForThisLevel.Humans;
-  humanCount = ( HUMANCOUNT > LevelData::MaxHumans ) ? LevelData::MaxHumans : HUMANCOUNT;
-  for( i = 0; i < humanCount; ++i ) {
-    *ptr++ = humans + i;
-  }
-  DataForLevel = &dataForThisLevel;
-  return PlayLoop();
-}
--- a/Level2.h	Sun Jun 09 14:28:53 2013 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-/*
- * SOURCE FILE : Level2.h
- *
- * Definition of class Level2.
- * Machine written by program MapleRobotRicLevelGenerator.
- * DO NOT EDIT BY HAND!
- *
- */
-
-#ifndef Level2Defined
-
-  #define Level2Defined
-
-  #include "LevelNormal.h"
-
-  class Level2 : public LevelNormal {
-
-  public :
-
-    /**************/
-    /* PLAY LEVEL */
-    /**************/
-    // Returns code indicating how level ended.
-    virtual LevelExitCode Play( void );
-
-  };
-
-#endif
-
-/* END of Level2.h */
--- a/LevelCollection.cpp	Sun Jun 09 14:28:53 2013 +0000
+++ b/LevelCollection.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -8,29 +8,8 @@
 #include <stdio.h>
 #include "LevelCollection.h"
 #include "Level0.h"
-#include "Level1.h"
-#include "Level2.h"
-#if 0
-    #include "Level3.h"
-    #include "Level4.h"
-    #include "Level5.h"
-    #include "Level6.h"
-    #include "Level7.h"
-    #include "Level8.h"
-    #include "Level9.h"
-    #include "Level10.h"
-    #include "Level10.h"
-    #include "Level11.h"
-    #include "Level12.h"
-    #include "Level13.h"
-    #include "Level14.h"
-    #include "Level15.h"
-    #include "Level16.h"
-    #include "Level17.h"
-    #include "Level18.h"
-    #include "Level19.h"
-    #include "Level20.h"
-#endif
+#include "LevelNormal.h"
+#include "LevelDescriptor.h"
 
 /***************/
 /* CONSTRUCTOR */
@@ -53,58 +32,20 @@
   return LEVELCOUNT;
 }
 
-// Individual levels.
-static Level0 level0;  // Not a real level. This is attract mode.
-static Level1 level1;
-static Level2 level2;
-#if 0
-static Level3 level3;
-static Level4 level4;
-static Level5 level5;
-static Level6 level6;
-static Level7 level7;
-static Level8 level8;
-static Level9 level9;
-static Level10 level10;
-static Level11 level11;
-static Level12 level12;
-static Level13 level13;
-static Level14 level14;
-static Level15 level15;
-static Level16 level16;
-static Level17 level17;
-static Level18 level18;
-static Level19 level19;
-static Level20 level20;
-#endif
+// Level 0 which is NOT dynamically allocated.
+// Not a real level. This is attract mode.
+static Level0 level0;
 
-// Pointers to all the levels.
-// If I have got this right then each pointer in the array points to a
-// Level that is NOT const, but the array itself is const and cannot be modified.
-static Level* const levels[ LEVELCOUNT ] = {
-  &level0,
-  &level1,
-  &level2,
-#if 0
-    &level3,
-  &level4,
-  &level5,
-  &level6,
-    &level7,
-  &level8,
-  &level9,
-  &level10,
-  &level11,
-  &level12,
-    &level13,
-  &level14,
-  &level15,
-  &level16,
-    &level17,
-  &level18,
-  &level19,
-  &level20,
-#endif
+// Level descriptors.
+static const UInt8 ld0[] = { ENDDESCRIPTOR };
+static const UInt8 ld1[] = { Grunt, 5, Crusher, 5, ENDDESCRIPTOR };
+static const UInt8 ld2[] = { Grunt, 10, Brain, 2, ENDDESCRIPTOR };
+
+// Array pointing to level data for each level.
+static const UInt8* const levelDescriptors[ LEVELCOUNT ] = {
+    ld0,
+    ld1,
+    ld2,
 };
 
 /***************/
@@ -114,15 +55,29 @@
 // Returns pointer to level or NULL if no such level.
 Level *LevelCollection::GetLevel( UInt8 levelNumber ) {
   if( levelNumber >= LEVELCOUNT ) {
+    // Level number out of range.
     return (Level*)NULL;
   }
+  else if( levelNumber == 0 ) {
+    // Level zero is the attract mode and is not dynamically allocated.
+    return &level0;
+  }
   else {
-    // Fetch pointer to level from array and tag it with the
-    // correct level number before returning it.
-    Level *level = levels[ levelNumber ];
+    // Fetch level descriptor for this level and create a Level from it.
+    Level *level = new LevelNormal( levelDescriptors[ levelNumber ] );
+    // Tag it with the correct level number before returning it.
     level->LevelNumber = levelNumber;
     return level;
   }
 }
 
-
+/*******************************/
+/* FREE MEMORY USED BY A LEVEL */
+/*******************************/
+// Pass pointer to a level.
+// Frees memory used by level.
+void LevelCollection::FreeLevel( Level *level ) {
+    if( ( level != (Level*)NULL ) && level->IsDynamicallyAllocated ) {
+        delete level;
+    }
+}
--- a/LevelCollection.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/LevelCollection.h	Sun Jun 09 19:34:56 2013 +0000
@@ -1,51 +1,61 @@
-/*
- * SOURCE FILE : LevelCollection.h
- *
- * Definition of class LevelCollection.
- *
- */
-
-#ifndef LevelCollectionDefined
-
-  #define LevelCollectionDefined
-  
-  #include "Types.h"
-  #include "Level.h"
-
-  class LevelCollection {
-
-  public :
-
-    enum {
-      AttractLevel = 0,        // just ticking over encouraging player to start
-      FirstNormalLevel = 1,    // first real level excluding attract mode
-    };
-    
-    /***************/
-    /* CONSTRUCTOR */
-    /***************/
-    LevelCollection();
-
-    /**************/
-    /* DESTRUCTOR */
-    /**************/
-    virtual ~LevelCollection();
-
-    /************************/
-    /* GET NUMBER OF LEVELS */
-    /************************/
-    UInt8 GetLevelCount( void ) const;
-
-    /***************/
-    /* GET A LEVEL */
-    /***************/
-    // Pass level number in levelNumber.
-    // Returns pointer to level or NULL if no such level.
-    Level *GetLevel( UInt8 levelNumber );
-    
-  };
-
-#endif
-
-/* END of LevelCollection.h */
+/*
+ * SOURCE FILE : LevelCollection.h
+ *
+ * Definition of class LevelCollection.
+ *
+ */
+
+#ifndef LevelCollectionDefined
+
+  #define LevelCollectionDefined
+  
+  #include "Types.h"
+  #include "Level.h"
+
+  class LevelCollection {
+
+  public :
+
+    enum {
+      AttractLevel = 0,        // just ticking over encouraging player to start
+      FirstNormalLevel = 1,    // first real level excluding attract mode
+    };
+    
+    /***************/
+    /* CONSTRUCTOR */
+    /***************/
+    LevelCollection();
 
+    /**************/
+    /* DESTRUCTOR */
+    /**************/
+    virtual ~LevelCollection();
+
+    /************************/
+    /* GET NUMBER OF LEVELS */
+    /************************/
+    UInt8 GetLevelCount( void ) const;
+
+    /***************/
+    /* GET A LEVEL */
+    /***************/
+    // Pass level number in levelNumber.
+    // Returns pointer to level or NULL if no such level.
+    // Note that the level MAY be dynamically allocated and you must
+    // call FreeLevel when you have finished with the level to release
+    // memory used.
+    Level *GetLevel( UInt8 levelNumber );
+    
+    /*******************************/
+    /* FREE MEMORY USED BY A LEVEL */
+    /*******************************/
+    // Pass pointer to a level.
+    // Frees memory used by level.
+    void FreeLevel( Level *level );
+    
+  };
+
+#endif
+
+/* END of LevelCollection.h */
+
--- a/LevelData.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/LevelData.h	Sun Jun 09 19:34:56 2013 +0000
@@ -21,7 +21,6 @@
     enum {
       MaxEnemies = 64,           // Maximum number of enemies you can have in a level
       MaxHumans = 24,            // maximum number of humans you can have in a level
-      MaxMutants = MaxHumans,    // Maximum number of mutant humans you can have in a level
     };
     
     // Array containing pointers to all the enemies in a level.
@@ -32,11 +31,6 @@
     // A null pointer indicates an unused or rescued human.
     GameObject *Humans[ MaxHumans ];
     
-    // Array containing mutant humans (NOT pointers to mutants).
-    // Pointer to the mutants in this array are written into the Enemies array
-    // whenever a human is mutated by a brain.
-    MutantObject Mutants[ MaxMutants ];
-
     /***************/
     /* CONSTRUCTOR */
     /***************/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LevelDescriptor.cpp	Sun Jun 09 19:34:56 2013 +0000
@@ -0,0 +1,36 @@
+/*
+ * SOURCE FILE : LevelDescriptor.cpp
+ *
+ * Definition of class LevelDescriptor.
+ * Describes a level.
+ *
+ */
+
+#include "LevelDescriptor.h"
+
+/*****************************************/
+/* GET COUNT FOR A PARTICULAR ENEMY TYPE */
+/*****************************************/
+// Pass pointer to array containing data in data parameter.
+// The array alternates between enemy type and count and MUST
+// be terminated with a byte of value ENDDESCRIPTOR.
+// Pass type of enemy to fetch count for in et.
+// Returns number of enemies of the given type on this level.
+UInt8 LevelDescriptor::GetEnemyCount( const UInt8 *data, EnemyType et ) {
+    bool found = false;
+    while( ! found && ( *data != ENDDESCRIPTOR ) ) {
+        if( *data == (UInt8)et ) {
+            found = true;
+        }
+        else {
+            data += 2;
+        }
+    }        
+    if( found ) {
+        return data[ 1 ];
+    }
+    else {
+        return 0;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/LevelDescriptor.h	Sun Jun 09 19:34:56 2013 +0000
@@ -0,0 +1,41 @@
+/*
+ * SOURCE FILE : LevelDescriptor.h
+ *
+ * Definition of class LevelDescriptor.
+ * Describes a level.
+ *
+ */
+
+#ifndef LevelDescriptorDefined
+
+  #define LevelDescriptorDefined
+
+  #include "Types.h"
+  #include "EnemyType.h"
+
+  class LevelDescriptor {
+
+  public :
+
+    // Used to mark the end of the descriptor array.
+    #define ENDDESCRIPTOR ((UInt8)0xFF)
+    
+    /*****************************************/
+    /* GET COUNT FOR A PARTICULAR ENEMY TYPE */
+    /*****************************************/
+    // Pass pointer to array containing data in data parameter.
+    // The array alternates between enemy type and count and MUST
+    // be terminated with a byte of value ENDDESCRIPTOR.
+    // Pass type of enemy to fetch count for in et.
+    // Returns number of enemies of the given type on this level.
+    static UInt8 GetEnemyCount( const UInt8 *data, EnemyType et );
+    
+  private :
+
+  };
+
+#endif
+
+/* END of LevelDescriptor.h */
+
+
--- 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.
--- a/LevelNormal.h	Sun Jun 09 14:28:53 2013 +0000
+++ b/LevelNormal.h	Sun Jun 09 19:34:56 2013 +0000
@@ -15,6 +15,7 @@
 
   #include "Level.h"
   #include "LevelData.h"
+  #include "LevelDescriptor.h"
   #include "ExplosionManager.h"
   
   class LevelNormal : public Level {
@@ -24,19 +25,22 @@
     /***************/
     /* CONSTRUCTOR */
     /***************/
-    LevelNormal();
+    // Pass pointer to level descriptor data in data parameter.
+    LevelNormal( const UInt8 *data );
 
     /**************/
     /* DESTRUCTOR */
     /**************/
     virtual ~LevelNormal();
 
+    /**************/
+    /* PLAY LEVEL */
+    /**************/
+    // Returns code indicating how level ended.
+    virtual LevelExitCode Play( void );
+
   protected :
 
-    // Data defining the level.
-    // You should write to this before calling PlayLoop.
-    LevelData *DataForLevel;
-    
     /*************/
     /* PLAY LOOP */
     /*************/
@@ -48,6 +52,12 @@
     
   private :
 
+    // Data defining the level.
+    LevelData dataForLevel;
+    
+    // Descriptor array of bytes.
+    const UInt8 *descriptorData;
+    
     // Current instance being processed.
     static LevelNormal *currentInstance;