Missile Command

Retro

Project Overview

  • Project Type: Retro Console Game Clone
  • Role: Gameplay Programmer
  • Software: Visual Studio 2017
  • Languages: C++
  • Download link: Play Game
  • Programming Reel: Watch

Project Brief

This project, just like the Pong Game took two iterations to complete; With and without a hash-map to deal with deleted objects and avoid passing raw pointers that could potentially lead to memory leaks or a program crash


Responsibilities/Achievements

  • - Understand how a hash-map works and implement it to deal with object referencing throughout the program
  • - Implement a game inventory to deal with the handles used by the game objects
  • - Develop a game object factory using the design patterns such as abstract factory and singleton
  • - Use linear algebra to replicate how missiles move
  • - Check point to circle collisions to determine enemy missile explosions

Code Samples


/*
* Carlos Adan Cortes De la Fuente
* All rights reserved. Copyright (c)
* Email: dev@carlosadan.com
*/

std::hash<std::string> s_hash; // Hash creator of type string

class GameObjectFactory : public IMissileExplosionEvent
{
private:
    exEngineInterface* mEngine;

    // Counters to properly create a unique handle name
    int mMissileCounter;
    int mExplosionCounter;
    int mCityCounter;

public:
    // Constructor
    GameObjectFactory(exEngineInterface* pEngine) : mEngine(pEngine), 
	mMissileCounter(0),
	mExplosionCounter(0),
    mCityCounter(0)
    {
        World::mExplosionListeners.push_back(this); // Registers itself to the explosion events
	    srand((unsigned int)time(NULL)); // Makes the random number generator work
    }

    // Destructor
    ~GameObjectFactory() {}

    // Static Game Objects
    void CreateCrosshair() 
    {
        GameObject* pGO = new GameObject(s_hash("Crosshair"));

        COGTransform* transform = new COGTransform(pGO, { 0.0f, 0.0f });
        pGO->AddComponent(transform);

        COGMouse* mouse = new COGMouse(pGO);
        pGO->AddComponent(mouse);
        
        COGBoxShape* shape = new COGBoxShape(pGO, 20.0f, 20.0f);
        pGO->AddComponent(shape);

        COGRender* render = new COGRender(pGO, mEngine, RenderType::Line);
        pGO->AddComponent(render);

        pGO->Initialize();
    }

    void CreateScore()
    {
        GameObject* pGO = new GameObject(s_hash("Score"));

        COGTransform* transform = new COGTransform(pGO, { 310.0f, 25.0f });
        pGO->AddComponent(transform);

        COGText* text = new COGText(pGO, mEngine->LoadFont("pixel.ttf", 20));
        pGO->AddComponent(text);

        COGRender* render = new COGRender(pGO, mEngine, RenderType::Font);
        pGO->AddComponent(render);

        pGO->Initialize();
    }

    // Dynamic Game Objects
    GameObjectHandle CreateCity(float xPos)
    {
        // Get random value to add to the hash
        int hashRandom = rand() % 1001;
        GameObject* pGO = new GameObject(s_hash(std::to_string(hashRandom) + "-" + std::to_string(mCityCounter) + "City"));
        mCityCounter++;

        COGTransform* transform = new COGTransform(pGO, { xPos, 585.0f });
        pGO->AddComponent(transform);

        COGBoxShape* shape = new COGBoxShape(pGO, 50.0f, 30.0f);
        pGO->AddComponent(shape);

        COGPhysics* physics = new COGPhysics(pGO, false);
        pGO->AddComponent(physics);

        COGRender* render = new COGRender(pGO, mEngine, RenderType::Fill);
        render->GetColor().mColor[0] = 37;
        render->GetColor().mColor[1] = 106; 
        render->GetColor().mColor[2] = 219;
        pGO->AddComponent(render);

        pGO->Initialize();

        return pGO->GetHandle();
    }

    void CreateFriendlyMissile(exVector2 vTarget)
    {
        // Get random value to add to the hash
        int hashRandom = rand() % 1001;
        GameObject* pGO = new GameObject(s_hash(std::to_string(hashRandom) + "-" + std::to_string(mMissileCounter) + "FriendlyMissile"));
        mMissileCounter++;

        // Determines
        float xPos;
        // Divides the screen in 3 sections to spawn the missiles
        if (vTarget.x <= 267)
        {
            xPos = 50.0f;
        }
        else if (vTarget.x > 267 && vTarget.x <= 533)
        {
            xPos = 400.0f;
        }
        else
        {
            xPos = 750.0f;
        }
        COGTransform* transform = new COGTransform(pGO, { xPos, 600.0f });
        pGO->AddComponent(transform);

        COGLineShape* shape = new COGLineShape(pGO, { xPos, 600.0f });
        pGO->AddComponent(shape);

        COGPhysics* physics = new COGPhysics(pGO, false);
        pGO->AddComponent(physics);
        
        COGMissile* missile = new COGMissile(pGO, vTarget, MissileType::Friendly);
        pGO->AddComponent(missile);

        COGRender* render = new COGRender(pGO, mEngine, RenderType::Fill);
        render->GetColor().mColor[0] = 38;
        render->GetColor().mColor[1] = 214;
        render->GetColor().mColor[2] = 76;
        pGO->AddComponent(render);

        pGO->Initialize();
    }

    void CreateEnemyMisile()
    {
        // Similar to friendly missile but targeting a specific existing city
    }

    void CreateExplosion(exVector2 vOrigin)
    {
        // Get random value to add to the hash
        int hashRandom = rand() % 1001;
        GameObject* pGO = new GameObject(s_hash(std::to_string(hashRandom) + "-" + std::to_string(mExplosionCounter) + "Explosion"));
        mExplosionCounter++;

        COGTransform* transform = new COGTransform(pGO, vOrigin);
        pGO->AddComponent(transform);

        COGCircleShape* shape = new COGCircleShape(pGO, 0.0f);
        pGO->AddComponent(shape);

        COGPhysics* physics = new COGPhysics(pGO, true);
        pGO->AddComponent(physics);

        COGExplosion* explosion = new COGExplosion(pGO);
        pGO->AddComponent(explosion);

        COGRender* render = new COGRender(pGO, mEngine, RenderType::Fill);
        render->GetColor().mColor[0] = 224;
        render->GetColor().mColor[1] = 149;
        render->GetColor().mColor[2] = 11;
        pGO->AddComponent(render);

        pGO->Initialize();
    }

    // I Missile Explosion Event Interface
    virtual void OnExplosionStart(exVector2 vOrigin) override
    {
        CreateExplosion(vOrigin);
    }
};
										

/*
* Carlos Adan Cortes De la Fuente
* All rights reserved. Copyright (c)
* Email: dev@carlosadan.com
*/

typedef unsigned int Hash;

class GameObject
{
private:
    std::vector<Component*> mComponents;
public:
    Hash mHash;

    GameObject(Hash hash) : mHash(hash)
    {
        GameObjectInventory::GetInstance()->Register(this);
    }

    ~GameObject()
    {
        for (Component* pComponent : mComponents)
        {
            pComponent->Destroy();
            delete pComponent;
        }
        GameObjectInventory::GetInstance()->Unregister(this);
    }

    void Initialize()
    {
        for (Component* pComponent : mComponents)
        {
            pComponent->Initialize();
        }
    }

    void AddComponent(Component* pComponent)
    {
        mComponents.push_back(pComponent);
    }

    GameObjectHandle GetHandle()
    {
        GameObjectHandle temp;
        temp.mHash = mHash;

        return temp;
    }

    template<class T>
    T* FindComponent(ComponentType eType)
    {
        for (Component* pComponent : mComponents)
        {
            if (pComponent->GetType() == eType)
            {
                return (T*)pComponent;
            }
        }
        return nullptr;
    }
};
										

/*
* Carlos Adan Cortes De la Fuente
* All rights reserved. Copyright (c)
* Email: dev@carlosadan.com
*/

typedef unsigned int Hash;

class GameObjectHandle
{
public:
    Hash mHash;

    GameObjectHandle() {}
    ~GameObjectHandle() {}

    bool IsValid() const { return GameObjectInventory::GetInstance()->Exists(mHash); }
    GameObject* Get() const { return GameObjectInventory::GetInstance()->Lookup(mHash); }
};                                            
										

/*
* Carlos Adan Cortes De la Fuente
* All rights reserved. Copyright (c)
* Email: dev@carlosadan.com
*/											

class GameObjectInventory
{
private:
    static GameObjectInventory* mInstance;
    HashMap<GameObject*, 256> mMap;
    std::vector<GameObjectHandle> mStaleObjects;

public:
    GameObjectInventory() {}
    ~GameObjectInventory() {}

    // Singleton Pattern Implementation

    // Accesor method for the instance
    static GameObjectInventory* GetInstance() 
    {
        if (mInstance == nullptr)
        {
            mInstance = new GameObjectInventory();
        }
        return mInstance;
    }

    // Destructor method for the class
    static void DestroyInstance()
    {
        if (mInstance != nullptr)
        {
            delete mInstance;
            mInstance = nullptr;
        }
    }

    // Removes the copy constructor
    GameObjectInventory(const GameObjectInventory&) = delete;
    // Removes the copy assignment operator
    GameObjectInventory& operator= (const GameObjectInventory) = delete;

    // Handle System Manipulation
    void Register(GameObject* pGO) { mMap.InsertNoCheck(pGO); }
    void Unregister(GameObject* pGO) { mMap.Delete(pGO); }
    bool Exists(Hash hash) const { return (mMap.Find(hash) != nullptr);  }
    GameObject* Lookup(Hash hash) { return mMap.Find(hash); }
    const GameObject* Lookup(Hash hash) const { return mMap.Find(hash); }

    /* Deletion of Game Objects */
    void deleteOnFrameEnd(GameObjectHandle goHandle) { mStaleObjects.push_back(goHandle); }
    void deleteStaleObjects()
    {
        // Loops to delete stale game objects using the handle
        if (mStaleObjects.size() > 0)
        {
            for (GameObjectHandle goHandle : mStaleObjects)
            {
                if (goHandle.IsValid())
                {
                    GameObject* staleObject = goHandle.Get();
                    delete staleObject;
                }
            }
            mStaleObjects.clear(); // Clears the game objects to be deleted
        }
    }
};                                            

// Singleton Patter Implementation
GameObjectInventory* GameObjectInventory::mInstance = nullptr;
										

/*
* Carlos Adan Cortes De la Fuente
* All rights reserved. Copyright (c)
* Email: dev@carlosadan.com
*/
											
// Used integer as a mask to access the different buckets containing lists of game objects
typedef unsigned int Hash; 

template<class T, int SIZE>
class HashMap
{
private:
    Hash mMask;
    std::list<T> mBuckets[SIZE];

    int Translate(Hash hash) const
    {
        return (hash & mMask); // Makes the bitwise operation to get the bucket index 
    }

public:
    HashMap() 
    {
        mMask = SIZE - 1;
    }
    ~HashMap() {}

    void InsertNoCheck(T& data)
    {
        int index = Translate(data->mHash);
        mBuckets[index].push_back(data);
    }

    T Find(Hash hash)
    {
        int index = Translate(hash);
        std::list<T>& bucket = mBuckets[index];

        for (auto& value : bucket)
        {
            if (value->mHash == hash)
            {
                return value;
            }
        }

        return nullptr;
    }

    const T Find(Hash hash) const
    {
        int index = Translate(hash);
        const std::list<T>& bucket = mBuckets[index];

        for (auto& value : bucket)
        {
            if (value->mHash == hash)
            {
                return value;
            }
        }

        return nullptr;
    }

    void Delete(T data)
    {
        int index = Translate(data->mHash);
        std::list<T>& bucket = mBuckets[index];		
        bucket.remove(data);
    }
};