Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Another vector+class problem?

This thread is locked; no one can reply to it. rss feed Print
Another vector+class problem?
Albin Engström
Member #8,110
December 2006
avatar

Problem :'(:

I'm now working on transferring enemies from a vector of classes called queued_enemies to a vector of classes that looks exactly like the class containing the processed enemies called enemies.

vector<enemy> queued_enemies;//queued enemies.
vector<enemy> enemies;//processed enemies.

when a condition between a value in the queued_enemies vector and a value representing level progress is equal they trigger a set of functions which does the following:

  game::enemies::enemies.push_back(enemy(
  game::enemies::queued_enemies[enyNR].visual,
  game::enemies::queued_enemies[enyNR].Xpos,
  game::enemies::queued_enemies[enyNR].Ypos,
  game::enemies::queued_enemies[enyNR].Xspeed,
  game::enemies::queued_enemies[enyNR].Yspeed,
  game::enemies::queued_enemies[enyNR].Xacc,
  game::enemies::queued_enemies[enyNR].Yacc,
  game::enemies::queued_enemies[enyNR].SpawnTime,
  game::enemies::queued_enemies[enyNR].mov_progress,
  game::enemies::queued_enemies[enyNR].mov_scheme
  ));
      
  game::enemies::queued_enemies.erase(game::enemies::queued_enemies.begin()+enyNR);

What I'm doing(trying to do) is that I'm moving an element from one vector to another.
but for some reason i get REALLY strange results.. as strange effects from bitmaps
used by enemies created this way, i can't understand why the enemies created this way acts so strangely. if the data passes correctly it should be acting correctly! :S.. I'm totally lost, and this time i really mean it..

any suggestions on a better way of transferring elements?

another problem is that the following condition:

if(game::enemies::queued_enemies[enyNR].SpawnTime == game::progress*1000)

which triggers the functions i mentioned above doesn't work! :(, i'm thinking that maybe SpawnTime is equal to 0 or NULL but it can't be 0 and i don't know the effects of NULL. trying to check a value from an element that doesn't exist would result in a crash right?

The condition isn't used of course.

I'll upload and answer to anything that would help with solving this!
THANK YOU VERY MUCH!!!

X-G
Member #856
December 2000
avatar

1) Why are you copying them in such a convoluted scheme instead of just using the copy constructor?
2) CODE PLEASE.
3) NULL and 0 are (practically) the same value.

--
Since 2008-Jun-18, democracy in Sweden is dead. | 悪霊退散!悪霊退散!怨霊、物の怪、困った時は ドーマン!セーマン!ドーマン!セーマン! 直ぐに呼びましょう陰陽師レッツゴー!

Albin Engström
Member #8,110
December 2006
avatar

X-G said:

Why are you copying them in such a convoluted scheme instead of just using the copy constructor?

Probably because i didn't know of any copy constructor... - -. what is the copy constructor?

X-G said:

CODE PLEASE

YES SIR!..

hmm, i would if i could but i can't figure out what could be relevant and not, here's everything i could think of:

1void process_level()
2{
3 game::progress+=0.001;
4
5 if(game::enemies::queued_enemies.size() > 0)
6 for(int enyNR = game::enemies::queued_enemies.size(); enyNR != 0; enyNR--)
7 {
8 if(game::enemies::queued_enemies[enyNR].SpawnTime == game::progress*1000)
9 {
10 game::enemies::enemies.push_back(enemy(
11 game::enemies::queued_enemies[enyNR].visual,
12 game::enemies::queued_enemies[enyNR].Xpos,
13 game::enemies::queued_enemies[enyNR].Ypos,
14 game::enemies::queued_enemies[enyNR].Xspeed,
15 game::enemies::queued_enemies[enyNR].Yspeed,
16 game::enemies::queued_enemies[enyNR].Xacc,
17 game::enemies::queued_enemies[enyNR].Yacc,
18 game::enemies::queued_enemies[enyNR].SpawnTime,
19 game::enemies::queued_enemies[enyNR].mov_progress,
20 game::enemies::queued_enemies[enyNR].mov_scheme
21 ));
22 game::enemies::queued_enemies.erase(game::enemies::queued_enemies.begin()+enyNR);
23 }
24 }
25
26};

this section is supposed to handle all the events of the level such as spawning and such..

    if(sys::console::input == "NEW TEST A                                ")
    {
     game::enemies::queued_enemies.push_back(enemy(3, 200, -100, 0, 0.012, 0, 0, 7000, 0, 0));
    }

Adding to the quene using the horrible bad excuse called "console" that i'm using to test stuff.

    if(sys::console::input == "NEW ENEMY D                               ")
    game::enemies::enemies.push_back(enemy(3, 200, 100, 0, 0.001, 0, 0, 0, 0, 0));

Adding enemies to the processed enemies. adding enemies the 'normal' way.
this way work perfectly.

need even more? this is pretty much everything concerning the transfer.

Onewing
Member #6,152
August 2005
avatar

First off, this looks awfully suspicious:

game::enemies::enemies

Second, I would use a vector of pointers to your different enemy instances and then just move the pointer from vector to vector. Also, if all your doing is "processing" the enemies, why not just have a flag in the class that says whether it is queued or not instead?

[edit]

Quote:

what is the copy constructor?

You should code it in your class. Look it up on google, you'll see...

------------
Solo-Games.org | My Tech Blog: The Digital Helm

Kibiz0r
Member #6,203
September 2005
avatar

Every class has these methods, even if you don't explicitly declare and define them.

class Enemy
{
public:
    Enemy(); //default constructor
    Enemy(const Enemy& o); //copy constructor
    ~Enemy(); //default destructor

    Enemy& operator=(const Enemy& rhs); //copy assignment operator
};

Albin Engström
Member #8,110
December 2006
avatar

See edit i made on the post before this.

Onewing said:

First off, this looks awfully suspicious:

yes, the namespace has the same name as the vector. problem? ???

Onewing said:

Second, I would use a vector of pointers to your different enemy instances and then just move the pointer from vector to vector. Also, if all your doing is "processing" the enemies, why not just have a flag in the class that says whether it is queued or not instead?

I thought it would be better this way since all the enemies works the same way yet the uniqueness will be to great to make one vector for every type.. the vector holding the enemies that should be processed is used in for loops more than one time, instead of checking the flag more than one time i do this to only have to check it one time. i might be thinking wrong..

Kibiz0r said:

Every class has these methods, even if you don't explicitly declare and define them.

never heard of the copy constructor until now. will look it up.

Kibiz0r
Member #6,203
September 2005
avatar

Okay, took some time to soak in the code you posted.

First impression: It's a crime against programming. ;D

Second impression:

I bet the graphical problem is that you release the sprite in the destructor, which is called when you erase the enemy from the queued vector. However, the copy in the enemies vector has a copy of the pointer, and therefore points to the memory you just kersploded!

Store pointers in the vectors, not the objects themselves. But don't use naked pointers, use a smart pointer like the shared_ptr from Boost.org. Or the TR1 smart pointer (whatever it's called), if you like it better for some reason.

The shared_ptr keeps track of how many pointers are referencing it and when the last pointer is gone, it calls the destructor of whatever it points to. This is handy because you don't have to worry about the messy destructor problem with the sprite data, but it still cleans up after itself when you .erase() it.

Use .at() instead of [], .at() does bounds-checking.

Check out iter_swap()

It calls one copy constructor and two operator= assignments.

That ought to be cheaper than calling .erase(), ~Enemy(), Enemy(), and push_back()

At least, I think so.

Edit: Also, don't use for (int i ...) loops with vectors, use iterators in the form of:

for (std::vector<Enemy>::iterator i = enemies.begin(); i != enemies.end(); i++)
{
    //do stuff
}

Just be careful about using .erase() in it. If you use .erase(), do it like this:

for (std::vector<Enemy>::iterator i = enemies.begin(); i != enemies.end();)
{
    if ((*i)->KillMe())
        i = enemies.erase(i);
    else
    {
        //do stuff
    }
}

Note that, when using this form, you don't need to check the size of the vector, because if it is 0, i will be equal to enemies.end() and will bail out immediately.

Phew. How come I've been helping people all day, but my ONE topic has 2 replies, one of which is from me?

Albin Engström
Member #8,110
December 2006
avatar

Kibiz0r said:

It's a crime against programming

great.. ^^'

Kibiz0r said:

I bet the graphical problem is that you release the sprite in the destructor, which is called when you erase the enemy from the queued vector. However, the copy in the enemies vector has a copy of the pointer, and therefore points to the memory you just kersploded!

Enemies doesn't hold their own sprites, they have a int value that represents their looks. :/

Kibiz0r said:

Store pointers in the vectors, not the objects themselves. But don't use naked pointers, use a smart pointer like the shared_ptr from Boost.org. Or the TR1 smart pointer (whatever it's called), if you like it better for some reason.

Ok.. but what would i point to?.. and what's the difference between a naked pointer and a smart pointer?...

Kibiz0r said:

Use .at() instead of [], .at() does bounds-checking.

Bounds checking??

Kibiz0r said:

Check out iter_swap() [cppreference.com]

It calls one copy constructor and two operator= assignment

That ought to be cheaper than calling .erase(), ~Enemy(), Enemy(), and push_back()

At least, I think so.

iter_swap: isn't there a iter_move?? :)

Thanks for your help!!

Kibiz0r
Member #6,203
September 2005
avatar

Oh yeah, I forgot you were also removing and adding... uh, I've never tried it, but maybe swapping with .end() might work if the STL has a certain element of magic to it. But probably not.

Best bet is to make the super-cheap move of copying the pointer over, then erasing it from the original vector.

Bounds-checking is to make sure you don't try to access something that doesn't exist ie. isn't in the vector. Really just a habit thing.

Quote:

Enemies doesn't hold their own sprites, they have a int value that represents their looks. :/

Then we NEED MOAR KODE!

Quote:

Ok.. but what would i point to?.. and what's the difference between a naked pointer and a smart pointer?...

Point to a dynamically-allocated object.

typedef boost::shared_ptr<Enemy> EnemyPtr;
EnemyPtr p = EnemyPtr(new Enemy());

A naked pointer is just a type and a memory address. A smart pointer keeps track of how many pointers are pointing to the data, so shared data doesn't mysteriously disappear. It also calls delete automatically when no more pointers point to the data.

Albin Engström
Member #8,110
December 2006
avatar

Kibiz0r said:

Best bet is to make the super-cheap move of copying the pointer over, then erasing it from the original vector.

sounds ideal, but i don't get the "dynamic pointer to not a vector" (very confuding)... have to sleep before i get to that.

Kibiz0r said:

Bounds-checking is to make sure you don't try to access something that doesn't exist ie. isn't in the vector. Really just a habit thing.

Noted.

Kibiz0r said:

Then we NEED MOAR KODE!

and more code you'll get:

This is the code that draws the enemies... very complex isn't it?:

void draw_enemies(BITMAP *buffer)
{
  for(int nr = game::enemies::enemies.size()-1; nr != -1; nr--)
  {
    draw_sprite(buffer, (BITMAP*)datafiles::sprites_enemies[game::enemies::enemies[nr].visual].dat, game::enemies::enemies[nr].Xpos, game::enemies::enemies[nr].Ypos);
  }
};

the main ingame drawing loop..

void main_ingame_draw_loop(BITMAP *buffer)
{
  draw_enemies(buffer);
  draw_projectiles(buffer);
  draw_player(buffer);
  draw_interface(buffer);
};

I'm really starting to get dry here...

What can i say...:

void process_enemies()
{
  for(int enyNR = game::enemies::enemies.size()-1; enyNR != -1; enyNR--)
  {
    game::enemies::enemies[enyNR].Xpos += game::enemies::enemies[enyNR].Xspeed;
    game::enemies::enemies[enyNR].Ypos += game::enemies::enemies[enyNR].Yspeed;
    
    game::enemies::enemies[enyNR].Xspeed += game::enemies::enemies[enyNR].Xacc;
    game::enemies::enemies[enyNR].Yspeed += game::enemies::enemies[enyNR].Yacc;
  }
};

enemy constructor:

enemy::enemy(int Cvisual, int CXpos, int CYpos, float CXspeed, float CYspeed, float CXacc, float CYacc, int CSpawnTime, int Cmov_progress, int Cmov_scheme)
{
  Xpos = CXpos;
  
  SpawnTime = CSpawnTime;
  
  Xspeed = CXspeed;
  
  Yspeed = CYspeed;
  
  Ypos = CYpos;
  
  visual = Cvisual;
}

i really can't figure out any other important pieces.. any suggestions?

Kibiz0r said:

Point to a dynamically-allocated object.

typedef boost::shared_ptr<Enemy> EnemyPtr;
EnemyPtr p = EnemyPtr(new Enemy());

A naked pointer is just a type and a memory address. A smart pointer keeps track of how many pointers are pointing to the data, so shared data doesn't mysteriously disappear. It also calls delete automatically when no more pointers point to the data.

ouch!, i knew i shouldn't have tried to make a game before i know some more.. this is getting confusing, but i guess it's because i should have gone to sleep several hours ago..

when i wake up tomorrow i give it all a serious shot!
Thanks so much for you help! :)

Go to: