Archived

This topic is now archived and is closed to further replies.

Pseudo_Code

OOP quesiton

Recommended Posts

I''m not sure if I''m quite approaching this question correctly, but here goes: Let''s say I''m working on a fighter. I have a character class, and an attack class.
class Character
{
public:
     Character();//constructor
     ~Character();//destructor
     ...
     ...
     ...//some other members/member functions

     Attack attackone;//character''s first attack
     
     ...
     ...

     int strength;//The character''s strength
}//class Character

class Attack
{
public:
     Attack();//constructor
     ~Attack();//destructor
     ...
     ...
     ...//insert crap here

     void perform();//Perform the attack
}//class Attack
 
For the sake of the question, we''ll say that Attack::perform() is dependant on its character''s strength. It needs to somehow use the value of "strength" from the character object that it exists in to determine the damage that will be inflicted. I have no idea how much sense the previous sentence makes. Anyway, how would I go about doing this?

Share this post


Link to post
Share on other sites
There are a couple of ways to do this.


  1. The simplest way would be to pass the strength to the Attack::perform() . ie Attack::perform(int strength)
  2. If the strength is constant then construct the attack with a strength value.
  3. Before the attack set the strength ( sorta like #1 )


I am sure that there are a bunch more but those are just a couple off the top of my head.




-------
Andrew

Edited by - acraig on March 15, 2001 12:22:39 PM

Share this post


Link to post
Share on other sites
You could pass a reference to the character in the call to Attack:erform().

class Character{
public:
Character(); //constructor
~Character();//destructor
...
...
... //some other members / member functions
Attack attackone; //character''s first attack
...
...
int strength; //The character''s strength
}//class Character

class Attack
{
public:
Attack();//constructor
~Attack();//destructor
...
...
...//insert crap here
void perform(Character &character);//Perform the attack
}//class Attack

void Attack::perform(Character &character)
{
//perform character.attackone
character.strength = 100; //or whatever you wanna do with
//the character''s strength.
}

Share this post


Link to post
Share on other sites
OK, I typed out a really long response to this yesterday, but the forum was screwed and ate my post. I''ll give it another shot here.

One solution (not saying the best) is to have the attack know its parent character. I''ll show the code, then show how to implement it:
  
class Character; // forward declaration of class

class Attack
{
private:
Character &m_rCharacter; // reference to parent character

public:
// constructor

Attack (Character &parent) : m_rCharacter (parent) {/*..*/}
~Attack ();
void perform (); // perform attack
};

class Character
{
private:
Attack m_attackone; // attack "in size"
int m_strength;
public:
// constructor
Character () : m_attackone (*this) {/*..*/}
int getStrength () const { return m_strength; }
};

// cpp file
void Attack::perform ()
{
// get strength from parent (inline-supa-fast).
int strength = m_rCharacter.getStrength ();
/* ...do rest of function... */

}


OK, so there''s lots going on here. You''re creating a circular dependency between the two classes, which isn''t always a good thing, but might work for you.

You''re using an object of type Attack "in size" in the Character class. "In size" means you''re using an object, not a reference or pointer, so by the time the compiler gets to Character, it needs to know the exact size of Attack so it can incorporate it into the size of Character. This means that the Attack class _must_ be declared before the Character class.

However, the Attack class uses the Character class in its interface; since it''s just using a reference, you can get away with a forward declaration of Character (i.e. "class Character;") just before the declaration of Attack.

Attack has an internal reference to a character. Since this is a reference, its value must be set in the member initialization list of the Attack constructor. If you wanted to initialize it at some other time, you could change the reference to a pointer and do so.

Character, likewise, contains an Attack "in size"; since all constructors of members of a class are called before the class constructor is called, you must also initialize attackone in the member initialization list. Simple pass the dereferenced "this" pointer to attackone, and attackone''s constructor will store it as its m_rCharacter.

From now on, you''re free to use any of the public members of the Character object from within the Attack class. An example of how to do this is shown in Attack::perform ().

There are other ways to do this as other posters have shown. One is to pass the strength as a parameter, another is to pass a reference to Character each time. I prefer something like this method, but it does have the consequence of creating a circular dependency. Given more time, I believe I could construct a method that does not create that dependency, but I think it would require a third class (i.e. an "AttackManager").

So many ways to skin an OO cat.
(Hope the forum saves my reply this time)

Share this post


Link to post
Share on other sites
quote:
Original post by arsenius

AACCCCCCCCCHHHHHHHHHHhhhhh!!!!!!!!
OOP, keep it away! keep it away!!!

*arsenius runs behind a chair*

-arsenius
''after three days without programming, life becomes meaningless'' -The Tao of Programming



Your Tao of Programming has just enlightened me. Therefore, I will not have to test my programs anymore, because they''re perfect in themselves now.

But before you can realize Tao, you have to process the Evolution of a programmer!
http://www.ariel.com.au/jokes/The_Evolution_of_a_Programmer.html

Wunibald

Share this post


Link to post
Share on other sites
I would tend to recomment the first suggestion of passing a reference to your strength variable to Perform (ie, Attack::perform(int &strength).

The reason I choose this over circular dependancy is because I''d rather keep my classes as ignorant of each other as possible. The more classes are dependant on other classes, the less reusable your class is.


- Houdini

Share this post


Link to post
Share on other sites
quote:
Original post by Houdini

I would tend to recomment the first suggestion of passing a reference to your strength variable to Perform (ie, Attack::perform(int &strength).

The reason I choose this over circular dependancy is because I''d rather keep my classes as ignorant of each other as possible. The more classes are dependant on other classes, the less reusable your class is.



I agree with the reusability. But why would you want to pass a reference to an integer? Is Perform allowed to change strength? It seemed from the original post that it should not. Strength should be passed as an int, not as an int &.

Share this post


Link to post
Share on other sites
quote:
Original post by Stoffel

I agree with the reusability. But why would you want to pass a reference to an integer? Is Perform allowed to change strength? It seemed from the original post that it should not. Strength should be passed as an int, not as an int &.




It''s generally better to pass parameters by reference rather than value. The reasoning is that if you pass a large class to a function the function actually creates a seperate instance of the class and copies every variable from the original class into the new class. If you pass by reference then you are just passing a 4 byte memory address pointing to the original class, and no creating/copying of classes are involved.

Of course, passing an integer and passing a reference to an integer both passes 4 bytes, so in this case it TRUELY doesn''t matter. However, I always pass by reference both out of habit and to keep things uniform.

BTW, you are correct that the perform function I posted allowed the strength variable to be modified. It should have included a const:

Attack::perform(const int &strength);


- Houdini

Share this post


Link to post
Share on other sites
Hmm... Performing an action between 2 ''characters'' will probably require some middle tier for managing the action. In the existing model, how does ''object 1''( which is your character), know about ''object 2'' which it wants to interact with( ''object 2'' may be a door, or a chest, or another character[ NPC or PC ] ) ? Traditionally, the ability for 1 game object to act on another game object would depend on both objects properties. So, if the character wanted to ''attack'' another character, a middle tier( like an interaction engine, or something that is generic ) would get a pointer to both characters, and manage the data checking. Using this method, the Character object doesn''t have to know about all the other objects that it can interact with( but the middle tier, which may be the whole engine would know about all the objects ).

Share this post


Link to post
Share on other sites
I have to disagree with you Houdini, though I hope I''m doing so civilly (I''m certainly trying). My guidelines for passing variables to/from functions:
For inputs (will not be changed by function):
- always pass built-in types (char/bool/int/double/etc.) by value
- always pass user types (struct/class/union) by const reference or const pointer

You are wrong about a const reference to an int being the same as passing an integer.

Functionally, the int passed by value is a copy and can be used in a function, where as the const reference cannot. For instance, you would be able to do something like this:

void peform (int strength)
{
while (--strength)
{
do_some_action ();
}
}

You cannot do this with a const reference, because you aren''t allowed to change the value of strength.

Furthermore, and more importantly, it''s less efficient to use a const reference. The reference must be dereferenced before it is used, necessitating an extra move instruction; I have individually verified this with MSVC in release mode. If you use references for all parameters, your code will be slower.

Share this post


Link to post
Share on other sites
This is a little off-topic, but this was the first thing that came to mind... maybe its usefull...maybe it doesn''t make much sense...

  
class CCharacter
{
int iStrength;
protected:
virtual void OnPerform(int iStrength) { }
};
class CFighter : public CCharacter
{
protected:
void OnPerform(int iStrength)
{
}
};


Why exactly are you making an Attack class? Do you have many types of attacking, that differ that much? It looks kinda strange making a class for an attack.

Can you explain some more?...

Share this post


Link to post
Share on other sites
Each attack will have a different image, animation, type, ect. Some will take two "energy bars," others will take one. Some are specials, while others may be supers that consume built energy. There are a lot of varying aspects to different attacks.

Each character will also have three special attacks, and two super attacks.

I just thought that a class was appropriate.

Edited by - pseudo_code on March 16, 2001 7:22:51 PM

Share this post


Link to post
Share on other sites
Sounds to me like Attack::perform () also needs to modify the Character (consuming energy, etc.), so passing strength directly to perform will not work. Anyway, in the end perform might have ended up with half of the Character in the params

But since perform () needs both read and write access to both Attack and Character class , it seems to me that it rather belongs in the Character class. The Attack class should only have methods which solely concern the attack, like initialization.

class Character
{
private:
int strength;
Attack attack[6];

public:
performAttack (int nAttack) {/**/}
}


It might not be 100% POO, but IMHO the code will be a lot cleaner, since you don''t have to use in perform() the reference to the parent Character class and those 100% POO read/write Character methods.

Share this post


Link to post
Share on other sites