1.11M Members

Understanding Arrays and Classes

 
0
 

Ok, so currently I've been trying to recreate missile command, an old atari game. While I've had help from a group in creating the graphics for the game i.e menu, plotter class, etc. I've basically been flying solo as of the game's logic i.e missile detection, health, missiles spawning and all that good stuff.

The problem I've come accross is that while I have a what I think is a good basis for what classes to have, and how different tasks should be seperated; I'm not quite sure how the classes located in different files should be "communicating" with each other. The main area where this is a problem is comes in the form of two arrays.

The way my program is set up is I have a "Missile.cpp" that basically contains all the code for my Missile Class. This consists of:

    class Missile
        {
        private:
            int radius,speed,score;
            double xLocation,yLocation,xDestination,yDestination;
            bool hitMissile, hitCity; 

        public:
            Missile();

            void setRadius(int);
            void setSpeed(int);

            void setXLocation(double);
            void setYLocation(double);
            void setDestination(double xloc,double yloc);

            double getX();
            double getY();
            int getRadius();

            //missile detection
            bool hasHitCity();//tells city to check health/blow/do some animation depending on health
            bool hasHitMissile();//tells missile to be destroyed ( enemyMissileList[index].destroy();)
                                 //(this uses a huge if statement to check if the friendmissilearray
                                 //and it's radius come into contact w/ the enemies 
            //destruction
            void destroy();
        };

Originally I planned to have two arrays, one located in a class designated to spawning the offensive missiles and the other dedicated to the "cannon" or where the defensive missiles would come from.
Hence the two arrays: friendlyMissileArray[AMMO] (where ammo is the set ammount of missiles you have to fire)
and enemyMissileArray[NUM_MISSILES_ONSCREEN] (where num_missiles_onscreen is the potential amount of missiles able to be on the screen at one time) void destroy(); will basically reassign blown up enemy missiles to new x,y starting positions and have new x,y destinations (until some score is reached).

The problem I'm having is that my missile detection run in "Missile.cpp" depends on having information passed from what was originally two, but now i'm handling both offensive and defensive arrays in "Cannon.cpp" a seperate file. HOW DO I PASS THESE ARRAYS TO OTHER FILES FOR MANIPULATION??

THANK YOU FOR YOUR TIME,
You have no idea how much I would appreciate a reply! :) Happy Holidays btw!

SIDENOTE i tried to use the 'code' snippet but it keeps saying i entered the code wrong even though i read the guidelines... anybody tell me what i'm doing wrong? :P

 
0
 

shameless self-bump! :)

 
0
 

I think (from your description) that you would just have to declare the enemyMissileArray variable in header for Cannon.cpp. However...

(This next bit may or may not make sense to you, but it's the way I would do it. Ask if you don't understand)

I would probably go for making a MissileManager class, which would deal with handling the creation and destruction of Missile objects. You could have an IMissile interface, which is how everything would deal with missiles, so missile.h would look like:

class IMissile
{
public :
    /// Create a missile and add it to the manager
    static IMissile* Create()

    virtual void setRadius(int) = 0;
    virtual void setSpeed(int) = 0;
    virtual void setXLocation(double) = 0;
    virtual void setYLocation(double) = 0;
    virtual void setDestination(double xloc,double yloc) = 0;

    virtual double getX() const = 0;
    virtual double getY() const = 0;
    virtual int getRadius() const = 0;

    //missile detection
    virtual bool hasHitCity() const = 0;
    virtual bool hasHitMissile() const = 0;

    // Destroy a missile and remove it from the manager
    virtual void destroy() = 0;

protected :
    ~IMissile();
};

Then, your missile class would derive from this interface, and you'd rename it something like missile_impl.h:

class CMissile : public IMissile
{
    /* Your current missile class code here */
};

You'd also need to make a MissileManager class:

class CMissileManager
{
public :
    IMissile* Create()
    {
        M_missiles.push_back( std::make_shared< CMissile >() );
        return &M_missiles.back();
    }

    void Remove( IMissile& m )
    {
        for ( Container::iterator it = M_missiles.begin(); it != M_missiles.end(); ++it )
            if ( &m == it->get() )
                M_missiles.erase( it );
    }

    bool HitsAnotherMissile( const IMissile& m ) const;

private:
    typedef std::vector< std::shared_ptr< CMissile > > Container;
    Container M_missiles;
};

The manager class would have all the methods that related to "inter-missile" checks and behaviour, like the HitsAnotherMissile method above.

You then declare a global function GetMissileManager in the MissileManager header and implement it in the MissileManager.cpp. Like this:

MissileManager.h:

CMissileManager& GetMissileManager();

MissileManager.cpp:

CMissileManager missileManager;

CMissileManager& GetMissileManager()
{
    return missileManager;
}

The IMissile::Create method would then be implemented in missile_impl.cpp, along with the methods in CMissile. It would call the manager's Create method:

IMissile* IMissile::Create()
{
    return GetMissileManager().Create();
}

So, in all the files that you want to use missiles, you just include the interface header, missile.h, not the missile_impl.h header. This way, all the missile in the program are guaranteed to be in the manager - since the only way to get a missile is through the interface's Create method, which puts things in the manager. The CMissile::Destroy method would also call the manager's Remove method to remove itself from the manager. So, you know all your missiles are in the same place and the manager would handle all the interactions between them.

Have fun :)

 
0
 

Ok first of all I want to say thank you for going ABOVE AND BEYOND when answering my question :) .. Then I just want to ask about a few things, some of which are pretty basic:

My first question has to do with the line static IMissile* Create() it's my understanding that IMissile* Create() (line 5) just points to the location in memory where a missile is created i.e IMissile* IMissile::Create() is that correct?

Secondly, I wanted to ask (and this might be dumb) about the lines: virtual void setDestination(double xloc,double yloc) = 0;
and
virtual int getRadius() const = 0
First off when you set virtual void setDestination(double,double) = 0.. does that just initialize it to zero? Secondly, why do you declare virtual int getRadius() to be const? FINALLY, could you give me a brief explanation of virtual. I looked it up and get a basic understanding of it, but I figured you could probably explain it better.

The part of your code that I'm most confused about is your CMissileManager class. While I understand the concept of making a vector of CMissile type, I'm unclear what typedef std::vector<std::shared_ptr<CMissile>>Container; actually does. Could you maybe go into detail about what

class CMissileManager
{
public :
    IMissile* Create()
    {
        M_missiles.push_back( std::make_shared< CMissile >() );
        return &M_missiles.back();
    }
    void Remove( IMissile& m )
    {
        for ( Container::iterator it = M_missiles.begin(); it != M_missiles.end(); ++it )
            if ( &m == it->get() )
                M_missiles.erase( it );
    }
    bool HitsAnotherMissile( const IMissile& m ) const;
private:
    typedef std::vector< std::shared_ptr< CMissile > > Container;
    Container M_missiles;
};

actually does?
Thank you for your help, I appreciate it greatly! :)

 
0
 

it's my understanding that IMissile* Create() (line 5) just points to the location in memory where a missile is created i.e IMissile* IMissile::Create() is that correct?

No. The static keyword says that there is only one copy of the function that is used for all instances of the object. All instances of the object will use the same instance of the function Create(), and the function will return a pointer of type iMissile.

First off when you set virtual void setDestination(double,double) = 0.. does that just initialize it to zero?

No. That was my first impression too many years ago when I first started to learn c++ classes. You need to read up on "pure vitural functions". A pure virtual function is used in base classes with the intent that derived classes implement it. As a simple example lets say you have a base class named Animal and a derived class named Dog. Animal contains a pure virtual function named Speak. Now then, Animal can't possibley know how each animal type speaks so it leaves it up to the derived class to implement that. A Dog class will Speak by saying "Bark". A Cat class will say "Meow", etc. So whan Animal class calls Speak() the call will be passsed to the derived class.

class Animal
{
public:
   void SayHello() {Speak(); } // call pure virtual function
   virtual void Speak() = 0; // pure virtual function
 };

 class Dog : public Animal
 {
 public:
    virtual void Speak() { cout << "Bark!\n"; }
};


int main()
{
   Dog dog;
   dog.SayHello();
}
 
0
 

My first question has to do with the line static IMissile* Create() it's my understanding that IMissile* Create() (line 5) just points to the location in memory where a missile is created i.e IMissile* IMissile::Create() is that correct?

It is. This is an example of a factory pattern. You can Google "C++ Factory pattern" for more on this (Google "Design patterns" for what I mean by "pattern"). The important thing about the Create function in this context is that the location the pointer that it returns is a location known to the manager. In this example, I have used a smart pointer instead of a raw pointer. The smart pointer used here is a std::shared_ptr. You'll be able to find out about why you should use smart pointers of various kinds on the internet :)

Secondly, I wanted to ask (and this might be dumb) about the lines: virtual void setDestination(double xloc,double yloc) = 0;

It's not a dumb question. This is a pure virtual function. (Google "C++ pure function"). The = 0 doesn't have to with initalising any values to zero. This is a kind of notation that indicates that any class that inherits from this class must implement a function with this signature. Pure-virtual functions are the basis of interfaces in C++. In this case, it says that anything that is going to call itself a kind of missile, must have a setDestination method.

the virtual keyword just means that the implementation of a function will be determined at run-time. To understand what the effects of it are, you need to know roughly about how to use inheritance and *polymorphism" in C++ (if you haven't already, Google them too :) ). If you have a base class (one that other classes will inherit from) and it has a method:

class Base
{
public :
    virtual void Print() const { std::cout << "Base!" << std::endl; }
};

Now, you can have a number of classes (A and B, say) that inherit from Base, but they need the Print fuction to do different things:

class A : public Base
{
public :
    virtual void Print() const { std::cout << "A!" << std::endl; }
};

class B : public Base
{
public :
    virtual void Print() const { std::cout << "B!" << std::endl; }
};

Now, when you use the classes you can call their Print methods:

Base base;
base.Print();

A a;
a.Print();

B b;
b.Print();

This should have output like:

Base!
A!
B!

Now, the idea of polymorphism is that you can use a pointer to a base class to point to a derived class. That means that, at compile time, the compiler doesn't know whether a Base pointer is a pointer to a Base object, an A object or a B object. This has to be decided at runtime. This is what the virtual asks the program to do. So, if you do this:

Base* p = new A;  // To the compiler this could be a Base*, A* or B*
p->Print();   // Since the compiler doesn't know what this is exactly,
              // what happens when it is called must be decided at run-time

delete p;

p = new B;
p->Print();   // Same here

You should see the output

A!
B!

Even though the printing was done via a Base pointer in both cases.

I hope that makes some sense. How it works is more complicated and involves something called a virtual function table, but there's lots about that on the internet already, and you don't need to know how it works to know what it does :)

typedef is very simple, it allows you to alias a type in C++. In this case, I didn't want to type std::vector< std::shared_ptr< CMissile > > to get an iterator of the right type in a loop. By typedef-ing, I save some typing, because I can just use Container instead. Typedefs become important in templated code.

I have to go out now, so I'll leave the manager until later. I hope that's all some help though. Keep asking.

Have fun

 
0
 

WOW! I feel like I'm learning so much right now! I'm quite extatic this just made programming a lot more interesting! :D

Ok so what I'm getting from looking at your code is that your idea of how you would create Missiles in my game would be based off of inheritance? So for instance say I have multiple TYPES of missiles, each missile gets it's BASE traits off of the more general Missile Class.

My question is this: My original idea was to have the cannon manage an array of missiles, and then compare itself to a seperate array of enemy missiles (for collision detection). The enemy missiles would compare themselves to the cities (targets for collision). I think I understand that your idea would create a general code for ALL missiles. Is that correct?

In that case would I need to create a seperate instance of a missile in order for missiles spawned from my turret to not blow each other up? Or so that I'm not able to blow my own city up?

ALSO thank you both very much, and thank you for introducing me to that website Ancient Dragon :)

ALSO ALSO :P I included my function (originally in Missile.cpp) that was my original missile detection in one HUGE if statement haha, Don't give yourself a headache reading it but the general premise was that I would give every missile (and city) a radius, and if the points along that radius ((x,y)+/- radius) reach within the range of the other missile/city then that's when it would call .destroy()

Of course the problem was I couldn't figure out how to get the arrays out of their respective files and magically end up in my Missile.cpp file :P
Anyway, take a look and tell me what you think or maybe if there's a better way to do this w/o such a massive if statement :D

bool hasHitMissile()
{
    bool hasHitMissile = false;

    for(int f_Index = 0; f_Index < 14; f_Index ++)
    {
        //f =  friendly missiles xcoord,ycoord,radius
        double f_xCoord = friendlyMissileList[f_Index].getX();
        double f_yCoord = friendlyMissileList[f_Index].getY();
        double f_radius = friendlyMissileList[f_Index].getRadius();

        for(int e_Index = 0; e_Index < 7; e_Index ++)
        {
            //e = enemy missiles xcoord,ycoord,radius
            double e_xCoord = enemyMissileList[e_Index].getX();
            double e_yCoord = enemyMissileList[e_Index].getY();
            double e_radius = enemyMissileList[e_Index].getRadius();

            //see if hits another (enemy) missile
/******************************************************************************
This if statement can get somewhat confusing: to simplify I'll try to explain
- each missile has a radius, similar to a square. This loop checks to make sure
  that a single point on each side (in the middle of the side) is compared to
  the other missiles in the array to see if it "inside" the range of the other
  missile's radius: THE || marks the separate calculations between points: i.e


                                - - * - -
                                -       -
                                *   *   *     ..these points are the intersects
                                -       -       and we see if they are w/in
                                - - * - -       the range and domain of the
                                                enemy missile
******************************************************************************/
            if( (((f_xCoord + f_radius) >= (e_xCoord - e_radius)) &&
                ((f_xCoord + f_radius) <= (e_xCoord + e_radius)) &&
                ((f_yCoord <= (e_yCoord + e_radius)) &&
                 (f_yCoord >= (e_yCoord - e_radius))))

                 ||

                (((f_xCoord - f_radius) >= (e_xCoord - e_radius)) &&
                ((f_xCoord - f_radius) <= (e_xCoord + e_radius)) &&
                ((f_yCoord <= (e_yCoord + e_radius)) &&
                 (f_yCoord >= (e_yCoord - e_radius))))

                 ||

                (((f_yCoord - f_radius) >= (e_yCoord - e_radius)) &&
                ((f_yCoord - f_radius) <= (e_yCoord + e_radius)) &&
                ((f_xCoord <= (e_xCoord + e_radius)) &&
                 (f_xCoord >= (e_xCoord - e_radius))))

                 ||

                 (((f_yCoord + f_radius) >= (e_yCoord - e_radius)) &&
                ((f_yCoord + f_radius) <= (e_yCoord + e_radius)) &&
                ((f_xCoord <= (e_xCoord + e_radius)) &&
                 (f_xCoord >= (e_xCoord - e_radius)))))
                {
                    //then do some code

                }
 
0
 

Ok so what I'm getting from looking at your code is that your idea of how you would create Missiles in my game would be based off of inheritance? So for instance say I have multiple TYPES of missiles, each missile gets it's BASE traits off of the more general Missile Class.

That is one possibility of this design. However, sometimes it's useful to just have the extra layer of abstraction. In this case, what we're trying to do is guarantee that every missile object in the program is in the manager.

In your original design (using the bare array of missile objects), it would have been necessary for the programmer to remember to put each missile that is created into the array. The only guaranteed thing about that is that someone will forget to do it at some point! You'll then get bugs where missiles appear to not hit objects that they're supposed to hit, etc. The scheme of interface and manager used here makes it impossible for this to happen. It's impossible because you only include the inferface header in files that want to do things with missiles. This means that, in those files, you won't be able to do things like:

CMissile m;

If you try this (i.e. you're creating an unmanaged missile) then you'll get a compiler error about CMissile being undefined---because we only included the missile.h header and not missile_impl.h. The second thing about this design is that, in C++, it's an error to try and instantiate a class with one or more pure-virtual functions. The IMissile class has lots of pure-virtual functions, so if you try and do:

IMissile m;

(i.e. try to create an unmanaged IMissile object), then you'll again get a compiler error.

Even though you can't instantiate a class that has pure-virtual functions, you can create a pointer(or reference) to such a class. Ah ha! you might say, what if I do:

IMissile* m = new IMissile;

Well, this also fail. So, you see that using inheritance (and polymorphism) in this way, you can only call functions in the missile.h header and you can only get a pointer to an IMissile class:

IMissile* m = IMissile::Create();

Now we have a pointer to an IMissile and it's in the manager (because Create puts it there).

The final thing about how this system controls what the user can do with the IMissile class is evident if you consider what happens if you do:

// Create a missile in the manager
IMissile* m = IMissile::Create();

// Delete the pointer (!!)
delete m;

Uh-oh, the manager doesn't know that you just called delete on this pointer that it thinks it's manging. Bad things could happen. However, in this case you will get a compiler error. The thing to notice about the IMissile interface is that the destructor, ~IMissile(), is protected. This means that the destructor can't be accessed by delete and it fails to compile.

So, you can see how this extra layer of abstraction (the IMissile interface) allows us to make sure that all missiles are in the manager AND that all the missiles that the manager thinks are in the manager definitely are in the manager. There's no way around it without

  1. Including the missile_impl.h header in a file that uses missiles.
  2. doing some kind of horrible casting of pointers into other types that you can delete etc.

Either of these things should feel weird and unnatural, so you (or anyone adding to your code) will notice it and think twice :)

My question is this: My original idea was to have the cannon manage an array of missiles, and then compare itself to a seperate array of enemy missiles (for collision detection). The enemy missiles would compare themselves to the cities (targets for collision). I think I understand that your idea would create a general code for ALL missiles. Is that correct?

The concept of the missile manager class is kind of orthogonal to the physical system represented in the game. It's just a way to guarantee that we have a place to find EVERY missile created in the code. There should only be a single CMissileManager object in the game. In the scheme that I have shown here, this isn't actually guaranteed, you just have to rely on people calling GetMissileManager() to get at the manager. You could make an interface-type design for the manager that would guarantee if you wanted though.

Anyway, if you have a cannon object, it makes sense for that to hold a set of all the missiles it has fired:

class CCannon
{
public :
    /* Public methods here */

private :
    std::set< IMissile* > M_missiles;
};

(Look up std::set if you haven't seen it before :) ) Each time the cannon fires a missile it would call the IMissile::Create method and then put the resulting (interface) pointer in its set of missiles. The thing to realise is that the actual missile object is in the manager, but the set of pointers in the cannon allows you to keep track of which missiles in the manager are the ones fired by this cannon.

When you want to check for a missile-missile colision you are doing an inter-missile interaction. Since the missile manager knows about all the missiles, it is a convenient place to do such checks. Perhaps the manager could have an IMissile* CMissileManager::CollidesWith( const IMissile& m ) method? If that method finds a collision, it could return a pointer to the colliding missile. If not, it could return NULL.

[Make a point to notice how we're using pointers and references here. You HAVE to pass a missile in, so we make that a (const) reference. However, there isn't necessarily a collision, so we return a pointer, since it will be NULL if there isn't a collision]

The CollidesWith method would go through all the missiles in the manager and check for collisions with the missile that you pass in. If a collision is detected, then you would call the Destroy method on both missiles. Now, you might have destroyed some missiles. This means that you have to notify the cannons that fired those missiles that they have been destroyed. To do this you could store a reference to the cannon that fired the missile inside the actual missile and provide a method in the cannon class that removes a missile from it's list:

void CCannon::RemoveMissile( IMissile& m )
{
    m.Destory();
    M_missiles.erase( &m );
}

void CCannon::DestroyCollidingMissiles()
{
    for ( std::vector< IMissile* >::iterator m = M_missiles.begin(); m != M_missiles.end(); ++m )
    {
        IMissile* collidingMissile = GetMissileManager().CollidesWith( *m );
        if ( collidingMissile == nullptr )
            continue;   // No collision was found for this missile

        // A collision was found, Destroy the missiles.
        this->RemoveMissile( *m );

        CCannon& otherCannon = collidingMissile->GetCannon();
        otherCannon.RemoveMissile( *collidingMissile );
    }
}

I've used iterators here, so you could read up on those if you don't know about them (they're kind of like a pointer, but not quite).

So, now you would go through each cannon and call the DestroyCollidingMissiles method on each. This then takes care of finding the collisions (via the manager) and then removing collided missiles.

In that case would I need to create a seperate instance of a missile in order for missiles spawned from my turret to not blow each other up? Or so that I'm not able to blow my own city up?

I don't think so. You generally want to avoid having too many lists of things; they're a pain to keep up to date! In this case, I have a method of getting hold of the cannon from the missile, so we can modify the DestroyCollidingMissiles method to take advantage of that:

void CCannon::DestroyCollidingMissiles()
{
    for ( std::vector< IMissile* >::iterator m = M_missiles.begin(); m != M_missiles.end(); ++m )
    {
        IMissile* collidingMissile = GetMissileManager().CollidesWith( *m );

        if ( collidingMissile == nullptr )
            continue;   // No collision was found for this missile

        if ( collidingMissile->GetCannon() == *this )
            continue;   // Don't destroy missiles from the same cannon

        // A collision was found, Destroy the missiles.
        this->RemoveMissile( *m );

        CCannon& otherCannon = collidingMissile->GetCannon();
        otherCannon.RemoveMissile( *collidingMissile );
    }
}

Remember that we're in a method of CCannon, so we can use the this pointer to refer to ourself and compare against the cannon of the colliding missile. You could add some similar function to the CCannon object to get the city out and check that too. If you're not familiar with the this pointer, then it's not too difficult it's just a way of refering to the object we're currently inside. Have a read about it on Google.

I included my function (originally in Missile.cpp) that was my original missile detection in one HUGE if statement haha, Don't give yourself a headache reading it but the general premise was that I would give every missile (and city) a radius, and if the points along that radius ((x,y)+/- radius) reach within the range of the other missile/city then that's when it would call .destroy()

It looks messy, but at some point you sometimes have to just check a bunch of stuff. In this case you have to check all the coordinates and radii of the various missiles, so there might not be any nice way to tidy up this kind of thing :)

I hope that all makes some sense. I know it seems to be over-comlicating some things, but the aim of the game is to make it possible for the compiler to keep an eye on what we're doing. If it's impossible to make some mistakes then we can concentrate on other things :) Have fun.

You
This article has been dead for over six months: Start a new discussion instead
Post:
Start New Discussion
View similar articles that have also been tagged: