[C++] 1 level of inheritance problems

Started by
47 comments, last by sheep19 15 years, 9 months ago
Creating a new class is only necessary when that new class is adding significant new functionality or data. A samurai sword doesn't do anything differently to a sword, and it doesn't have any particularly different properties to a sword - the thing that's different is the data.

In fact, look at the Sword and Dagger classes in your first post. They both have no new member variables. Sword does literally nothing - you've just got the class there as a way of encoding values for the health/power/value/weight. Dagger's the same, except that it has a viewAttributes method - which, except for the word 'dagger' in it, would not look out of place in the base Weapon class.

The primary disadvantage to this is that whenever you want to change the details for a weapon, or add/delete a weapon, you have to rebuild your code. It also means that code changes to the base 'weapon' class might have to be accounted for by updating every individual other class...

The less code you write, the less opportunity there is for you to write it incorrectly.

It would be better if you went for a data-driven approach. First, you extend the 'Weapon' class to include the name of the weapon, and the code for displaying it, like this:

class Weapon{  std::wstring name;  int health, power, value, weight; public:  void displayAttributes() const  {   std::cout << name << ": Attributes:" << std::endl;   std::cout << "Health: " << health << " %" << std::endl;   std::cout << "Power: " << health * power << std::endl;   std::cout << "Value: " << value << " gold" << std::endl;   std::cout << "Weight: " << weight << " kilograms" << std::endl;  }


Then we'll extend the class to be able to load its data from a file, like so:
  static Weapon* FromFile(ifstream& input)  {   Weapon* wp = new Weapon();   getline(input, wp->name);   input >> wp->health >> wp->power >> wp->value >> wp->weight;  }};


FromFile will read a single weapon from a text file that looks something like:
Samurai Sword100 20 5 2Dagger30 5 1 1

So you can define all the weapons, and tweak them all, just by editing that file. You don't even need to rebuild the game, you just run it with the new file.

Lastly, it would be convenient that once you've loaded a weapon you can store it somewhere useful, and, given that these are 'templates' for weapons, create instances of each weapon. Let's create a WeaponLibrary:

class WeaponLibrary{  private: std::map<std::wstring, Weapon*> weapons;  protected: WeaponLibrary() {}  public: ~WeaponLibrary()  {    for(std::map<std::wstring, Weapon*>::iterator it = weapons.begin(); it != weapons.end(); ++it)    {      delete (*it).second;    }  }  public: static WeaponLibrary* FromFile(const std::string& filename)  {    WeaponLibrary* result = new WeaponLibrary();    ifstream infile(filename);    while(!infile.eof())    {      Weapon* wp = Weapon::FromFile(infile);      result->weapons[wp->name] = wp;    }    return result;  }  public: Weapon* CreateByName(const std::wstring& name)  {    Weapon* prototype = weapons[name];    return new Weapon(*prototype);  }};


WeaponLibrary is created by calling WeaponLibrary::FromFile, and giving it the name of the file storing all the weapon data. It loads in all the weapons, creating an instance of each one. When you ask it to give you a new weapon, you give it the name of the 'template' you want to use; it finds that weapon, and then creates a copy of it which it returns to you.

Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse

Advertisement
First, I want to thank you.

I guess my design was crap for a text game.

That's how it is now:

Weapon.h
#ifndef WEAPON_H#define WEAPON_Hclass Weapon{	friend std::ostream& operator << ( std::ostream& os, const Weapon* const w );public:	Weapon( int health = 100, std::string type = "IRON_SWORD" );	void setWeaponAttributes( const std::string& name = "IRON_SWORD" );private:	std::string m_Type;	int m_Health, m_Power, m_Value, m_Weight;};#endif


Weapon.cpp
#include <iostream>#include <fstream>#include <string>#include "Weapon.h"Weapon::Weapon( int health, std::string type ): m_Health( health ){	setWeaponAttributes( type );}void Weapon::setWeaponAttributes( const std::string& name ){	std::ifstream input;	input.open( "WEAPON_TYPES.txt" );	std::getline(input, name);	input >> m_Power >> m_Value >> m_Weight;}


I have made a text file to read sword types and obtain their attributes. I get an error though.

1>------ Build started: Project: PROJECT1, Configuration: Debug Win32 ------
1>Compiling...
1>Weapon.cpp
1>f:\my documents\visual studio 2005\projects\project1\project1\weapon.cpp(16) : error C2664: 'std::basic_istream<_Elem,_Traits> &std::getline<char,std::char_traits<char>,std::allocator<_Ty>>(std::basic_istream<_Elem,_Traits> &,std::basic_string<_Elem,_Traits,_Ax> &)' : cannot convert parameter 2 from 'const std::string' to 'std::basic_string<_Elem,_Traits,_Ax> &'
1> with
1> [
1> _Elem=char,
1> _Traits=std::char_traits<char>,
1> _Ty=char,
1> _Ax=std::allocator<char>
1> ]
1> and
1> [
1> _Elem=char,
1> _Traits=std::char_traits<char>,
1> _Ax=std::allocator<char>
1> ]
1> Conversion loses qualifiers
1>Build log was saved at "file://f:\My Documents\Visual Studio 2005\Projects\PROJECT1\PROJECT1\Debug\BuildLog.htm"
1>PROJECT1 - 1 error(s), 0 warning(s)
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========


It refers to the line: std::getline(input, name);

main.cpp
#include  <iostream>#include <string>#include "Weapon.h"std::ostream& operator << ( std::ostream& os, const Weapon* const w );int main(){	Weapon* w = new Weapon( 100, "IRON_SWORD" );		std::cout << w;	system("PAUSE");	return 0;}std::ostream& operator << ( std::ostream& os, const Weapon* const w ){	os << "Type: " << w->m_Type << "\n"		<< "Power: " << (w->m_Health) * (w->m_Power) / 100 << "\n"		<< "Health: " << w->m_Health << "%\n"		<< "Value: " << w->m_Value << " gold\n"		<< "Weight: " << w->m_Weight << " kilos\n\n"		;	return os;}


I will have another file to store the player's swords and other items (maybe in another file). Also I want to know the inventory of every NPC (or I'm I looking to far?). Should I have a text file for each NPC or one for all of them?
The error is from you trying to getline() into the argument name, which is a const reference, thus cannot be modified. The whole setWeaponAttributes() method doesn't make much sense to me. Perhaps you were supposed to getline(input,m_Type)?
What are you really trying to do when you read from the file? Are you trying to find a line of the file that matches the "type" of the weapon you're creating, and then read the corresponding data and set it?

Because what you're trying to do right now is read the first line of the file, and overwrite the "type" with it. The compiler complains because you explicitly said (by writing 'const std::string& name') that you didn't want to overwrite that value.

What does your file contain right now?
When you really think about parameterizing weapon types, you come to realize that they fit into very few broad catagories, and beyond that, they are best parameterized on things such as name, range, damage, rate of fire, etc.

Some of the broad categories include:
  • Melee Weapons (up-close, slicing, stabbing, bashing weapons.)
  • Ranged Weapons (long-range ballistic weapons, such as arrows, machine guns or rifles.
  • Smart Weapons (fire and forget weapons with complex behavior, such as mines or guided projectiles.)


Other things, such as a tank or AA gun are best modeled, IMO, as objects which have a weapon through composition, but that the player is able to interact with in the usual ways -- the player doesn't "have" an AA gun or tank, but they can operate them.

Even these broad categories are somewhat arbitrary and could be done away with in some designs, depending on how parameterized you make them.

throw table_exception("(? ???)? ? ???");

Quote:Original post by Zahlman
What are you really trying to do when you read from the file? Are you trying to find a line of the file that matches the "type" of the weapon you're creating, and then read the corresponding data and set it?


Yeah, that's what I'm trying to do.

Quote:What does your file contain right now?


IRON_SWORD
3 10 10
IRON_DAGGER
2 9 3
I think I got it working!

Weapon.cpp
#include <iostream>#include <fstream>#include <string>#include "Weapon.h"Weapon::Weapon( int health, std::string type ): m_Health( health ){	setWeaponAttributes( type );}void Weapon::setWeaponAttributes( const std::string& name ){	std::ifstream input;	input.open( "WEAPON_TYPES.txt" );	std::string text;	while( !input.eof() && text != name ) // while we haven't reached the end of the file and haven't found the name	{		input >> text; // get the name				if( text == name ) // check if it's what we are looking for		{			m_Type = text;			input >> m_Power >> m_Value >> m_Weight;			break;		}		else // if it's not, give negative values to make me understand the name wasn't found		{			m_Type = "WRONG_TYPE";			m_Power = -1;	m_Value = -1; m_Weight = 1;		}	}	input.close();}:)
Looks alright, but now you're skimming the same file multiple times. It would arguably be better to move the functionality in setWeaponAttributes out of the Weapon class and into a controller class such as WeaponFactory. You then open the data file only once, and generate a template for each weapon (... you need in a particular level for example).

Other than that, it looks like you listened well to some good advice given. Cheers!

EDIT: using said controller class, you can construct your weapons in multiple ways, two of which are through construction of the object; and through property setters:

struct WeaponDesc{    int Power;    int Value;    int Weight;};Weapon::Weapon( const std::string & type, const WeaponDesc & desc ){    m_Type = type;    m_Power = desc.Power;    m_Value = desc.Value;    m_Weight = desc.Weight;}


and

Weapon::Weapon( const std::string & type ){    m_Type = type;    m_Power = -1;    m_Value = -1;    m_Weight = -1;}Weapon::SetPower( int Power ){    m_Power = Power;}


(and so on for each property)

The advantage of the first one is that data is immutable once the weapon has been created.
Quote:Original post by Todo
Looks alright, but now you're skimming the same file multiple times. It would arguably be better to move the functionality in setWeaponAttributes out of the Weapon class and into a controller class such as WeaponFactory. You then open the data file only once, and generate a template for each weapon (... you need in a particular level for example).


Yeah, that's what I was getting at with my WeaponLibrary example.

Richard "Superpig" Fine - saving pigs from untimely fates - Microsoft DirectX MVP 2006/2007/2008/2009
"Shaders are not meant to do everything. Of course you can try to use it for everything, but it's like playing football using cabbage." - MickeyMouse

So, because I open the file multiple times (every time I instansiate (wrong spelling) an object), you recommend me to open it at the beginning of the game and load everything, then read what I need. Correct?

If yes, what about if I had a vector of Weapon(s) that I dynamically allocate in the beginning? Maybe I could have it private to the WeaponFactory class and just make those classes friends.

And again, thanks for the help.

This topic is closed to new replies.

Advertisement