Click to See Complete Forum and Search --> : Item class and file i/o


ace4016
February 3rd, 2005, 02:19 AM
Hi, i have created a base class to create 4 similar classes, and since i am new to class design when is comes to stuff like polymorphism and inheritance. Can you guys give me pointers to make this class better and/or more friendly for storing the data so the items can be read back at a later time during game play?

here's the header file containing the class:

#ifndef ITEMS_H
#define ITEMS_H

enum ITEM_TYPE{
ITEM_DEFAULT,
WEAPON_SWORD,
WEAPON_SWORD_SHIELD,
WEAPON_LONG_SWORD,
WEAPON_DBL_SWORD,
WEAPON_AXE,
WEAPON_AXE_SHIELD,
WEAPON_DBL_AXE,
WEAPON_HAMMER,
WEAPON_HAMMMER_SHIELD,
WEAPON_SHORT_BALDE,
WEAPON_DBL_SHORT_BLADE,
WEAPON_BOW,
WEAPON_CROSSBOW,
ARMOR_HEAD,
ARMOR_TORSO,
ARMOR_ARMS,
ARMOR_HANDS,
ARMOR_LEGS,
ARMOR_BOOTS,
EMBLEM_EARTH,
EMBLEM_FIRE,
EMBLEM_WATER,
EMBLEM_WIND,
EMBLEM_DARK,
EMBLEM_LIGHT,
EMBLEM_THUNDER,
POWERUP_HP_UP,
POWERUP_ATT_UP,
POWERUP_DEF_UP,
POWERUP_SPC_UP,
POWERUP_SPD_UP,
POWERUP_ACC_UP,
POWERUP_RAGE,
POWERUP_FOCUS,
POWERUP_ADRENIL
};

enum EFFECT_TYPE{
EFFECT_NONE,
EFFECT_RAGE,
EFFECT_COUNTER,
EFFECT_DBL_STRIKE,
EFFECT_POISON,
EFFECT_SUICIDE,
EFFECT_HYPER
};

class Items{
public:
virtual void setCost(int cost) = 0;
virtual void setLevel(int lev) = 0;
virtual void setStats(int att, int def, int spec, int spd, int acc) = 0;
virtual void setType(ITEM_TYPE typ) = 0;
virtual void setName(char* nam) = 0;
virtual void setEffect(EFFECT_TYPE eff) = 0;

virtual int getCost() = 0;
virtual void getStats(int statarray[5]) = 0;
virtual int getLevel() = 0;
virtual ITEM_TYPE getType() = 0;
virtual char* getName() = 0;
virtual EFFECT_TYPE getEffect() = 0;
};




class Weapon : public Items{
int price;
int level;
int attack;
int defense;
int special;
int speed;
int accuracy;
ITEM_TYPE type;
char* name;
EFFECT_TYPE effect;
public:
Weapon() : price(0), level(0), attack(0), defense(0), special(0),
speed(0), accuracy(0), type(ITEM_DEFAULT), name(NULL), effect(EFFECT_NONE) {}
~Weapon() {}

void setCost(int cost){
price = cost;
}

void setLevel(int lev){
level = lev;
}

void setStats(int att, int def, int spec, int spd, int acc){
attack = att;
defense = def;
special = spec;
speed = spd;
accuracy = acc;
}

void setType(ITEM_TYPE typ){
type = typ;
}

void setName(char* nam){
name = nam;
}

void setEffect(EFFECT_TYPE eff)
{
effect = eff;
}

int getCost(){
return price;
}

void getStats(int statarray[5]){
statarray[0] = attack;
statarray[1]= defense;
statarray[2] = special;
statarray[3] = speed;
statarray[4] = accuracy;
}

int getLevel(){
return level;
}

ITEM_TYPE getType(){
return type;
}

char* getName(){
return name;
}

EFFECT_TYPE getEffect(){
return effect;
}
};



class Armor : public Items{
int price;
int level;
int attack;
int defense;
int special;
int speed;
int accuracy;
ITEM_TYPE type;
char* name;
EFFECT_TYPE effect;
public:
Armor() : price(0), level(0), attack(0), defense(0), special(0),
speed(0), accuracy(0), type(ITEM_DEFAULT), name(NULL), effect(EFFECT_NONE) {}
~Armor() {}

void setCost(int cost){
price = cost;
}

void setLevel(int lev){
level = lev;
}

void setStats(int att, int def, int spec, int spd, int acc){
attack = att;
defense = def;
special = spec;
speed = spd;
accuracy = acc;
}

void setType(ITEM_TYPE typ){
type = typ;
}

void setChar(char* nam){
name = nam;
}
void setEffect(EFFECT_TYPE eff){
effect = eff;
}


int getCost(){
return price;
}

void getStats(int statarray[5]){
statarray[0] = attack;
statarray[1]= defense;
statarray[2] = special;
statarray[3] = speed;
statarray[4] = accuracy;
}

int getLevel(){
return level;
}

ITEM_TYPE getType(){
return type;
}

char* getName(){
return name;
}

EFFECT_TYPE getEffect(){
return effect;
}
};



class Emblem : public Items{
int price;
int level;
int attack;
int defense;
int special;
int speed;
int accuracy;
ITEM_TYPE type;
char* name;
EFFECT_TYPE effect;
public:
Emblem() : price(0), level(0), attack(0), defense(0), special(0),
speed(0), accuracy(0), type(ITEM_DEFAULT), name(NULL), effect(EFFECT_NONE) {}
~Emblem() {}

void setCost(int cost){
price = cost;
}

void setLevel(int lev){
level = lev;
}

void setStats(int att, int def, int spec, int spd, int acc){
attack = att;
defense = def;
special = spec;
speed = spd;
accuracy = acc;
}

void setType(ITEM_TYPE typ){
type = typ;
}

void setChar(char* nam){
name = nam;
}

void setEffect(EFFECT_TYPE eff){
effect = eff;
}

int getCost(){
return price;
}

void getStats(int statarray[5]){
statarray[0] = attack;
statarray[1]= defense;
statarray[2] = special;
statarray[3] = speed;
statarray[4] = accuracy;
}

int getLevel(){
return level;
}

ITEM_TYPE getType(){
return type;
}

char* getName(){
return name;
}

EFFECT_TYPE getEffect(){
return effect;
}
};




class PowerUp : public Items{
int price;
int level;
int attack;
int defense;
int special;
int speed;
int accuracy;
ITEM_TYPE type;
char* name;
EFFECT_TYPE effect;
public:
PowerUp() : price(0), level(0), attack(0), defense(0), special(0),
speed(0), accuracy(0), type(ITEM_DEFAULT), name(NULL), effect(EFFECT_NONE) {}
~PowerUp() {}

void setCost(int cost){
price = cost;
}

void setLevel(int lev){
level = lev;
}

void setStats(int att, int def, int spec, int spd, int acc){
attack = att;
defense = def;
special = spec;
speed = spd;
accuracy = acc;
}

void setType(ITEM_TYPE typ){
type = typ;
}

void setChar(char* nam){
name = nam;
}

void setEffect(EFFECT_TYPE eff){
effect = eff;
}

int getCost(){
return price;
}

void getStats(int statarray[5]){
statarray[0] = attack;
statarray[1]= defense;
statarray[2] = special;
statarray[3] = speed;
statarray[4] = accuracy;
}

int getLevel(){
return level;
}

ITEM_TYPE getType(){
return type;
}

char* getName(){
return name;
}

EFFECT_TYPE getEffect(){
return effect;
}
};
#endif // ITEMS_H


also, do you know any good file i/o tutorials or methods i can use to format a text file easily, especially when reading a file containing large amounts of data?

Ejaz
February 3rd, 2005, 02:36 AM
Just make few changes in your design, like Items is an interface, it has all the pure virtual functions, so all the decendents must have to implement them. But you see that other class (Weapons and others), have same data members (price etc) and same methods (set/get).

So, if you create a class say CItemObject from Item (its good if you use CItem) and put the data members (price etc) and there methods (set/get) as member of CItemObject and then you inherit the class Weapon (CWeapon) from CItemObject, so that all the derived classes get them from CItemObject. Only the sepcialized members that are not suppose to be the part of the CItemObject (say weapon range etc) can be part of the specific class with there methods to manipulate.

For File I/O, you'll get tons of material from google, just search for "C++ File !/O".

ace4016
February 3rd, 2005, 03:06 AM
Thanks. About the file i/o, I can never find what i need on it. I searched google for many hours trying to figure out how to find a better format then just put all the data on its own line and read line by line hoping that everything is in the right order.

Ejaz
February 3rd, 2005, 03:19 AM
Well, you know there is nothing like "free lunch" out there :), you can create your own class based upon the C++ I/O and use it.

ace4016
February 3rd, 2005, 04:47 AM
Thanks. My entire file i/o and filing system is making what i thought would be a simple text based rpg more difficult to get past step 2. Not too many file i/o tutorials that teach you past the very very basics of putting data in and taking it back out, and that is with only one file. Really this thread was to try to get more information on file i/o because i must save many different types of items and characters.

Well I will continue searching the web and maybe the library for file i/o information, because if I don't find something this whole project goes to waste.

HighCommander4
February 3rd, 2005, 01:17 PM
About the file i/o, I can never find what i need on it. I searched google for many hours trying to figure out how to find a better format then just put all the data on its own line and read line by line hoping that everything is in the right order.

Depending on what kind of information you're trying to read from a file, it may be more appropriate to store the data in binary format. This way you don't have to worry about things like newlines and delimiters. Simply create a structure that holds one unit of data, then you can input/output that structure to/from a file by using the binary read/write functions:

struct my_data
{
...
};
...
my_data data;
...
// write the entire structure in one statement
data.write((char*)&my_data, sizeof(my_data));
...
// now read the structure
data.read((char*)&my_data, sizeof(my_data));

Note that you will have to make special accomodations if your data structure contains members like strings or STL containers. In this case, you could write read() and write() member functions that read/write individual items from STL containers, etc.

ace4016
February 3rd, 2005, 08:57 PM
My first version to the code to make and read the file involved binary files, but whenever i read the data only the numbers would come out clearly, the name of the weapon/item would come out as junk symbols. I didnt use a struct though, heres what I did:

writes file:

#include <iostream>

#include <fstream>

#include "Items.h"

using namespace std;

int main(){

ofstream fout("fire_sabre.WEP", ios::binary);

Weapon item;

item.setCost(900);

item.setLevel(24);

item.setStats(12, 13, 10, 15, 11);

item.setType(WEAPON_SWORD);

item.setName("Fire Sabre");

item.setEffect(EFFECT_RAGE);

if(fout.write((char*)&item, sizeof(Weapon)))

cout << "\n\nWeapon created!" << endl;

fout.close();

cout << "the name of this Weapon is: " << item.getName() << endl;

system("pause");

}



reads file:

#include <iostream>

#include <fstream>

#include "Items.h"

using namespace std;

int main(){

ifstream fin("fire_sabre.WEP", ios::in | ios::binary);

Weapon sword;

fin.read((char*)&sword, sizeof(Weapon));

int stats[5];

sword.getStats(stats);



cout << "The name of this weapon is " << sword.getName() << "\n"

<< "The cost of this weapon is " << sword.getCost() << "\n"

<< "The level of this weapon is " << sword.getLevel() << "\n"

<< "The stats are\nAttack: " << stats[0]

<< "\nDefense: " << stats[1]

<< "\nSpecial: " << stats[2]

<< "\nSpeed: " << stats[3]

<< "\nAccuracy: " << stats[4] << endl;

cout << "\nThe weapon type is " << sword.getType()

<< "\nThe weapon effect is " << sword.getEffect() << endl;

system("pause");

}



All that happens is that the name is outputted as junk but the rest of the variables come out the way they are suppose to.

I would still need to come up with a way to read all the files and load them into the program during runtime so that all items are available to the player. That right now is my biggest problem since i am almost ok with the ascii version of the files.

HighCommander4
February 3rd, 2005, 09:55 PM
All that happens is that the name is outputted as junk but the rest of the variables come out the way they are suppose to.

As I said, you need to make special accomodations for strings. Whether you're storing strings as a char pointer or as an std::string, when you write() the structure, only the pointer (and in the case of std::string, other variables) gets written to the file - not the actual string. Here is an example of how you can make such accomodations:

struct item
{
char* name;
int var1;
int var2;
...
void write(ofstream& os)
{
// first write length of name so reading function knows how many chars to read
int tmp = strlen(name);
os.write((char*) &temp, sizeof(tmp));
// now write the actual name
os.write(name, tmp);
// write other variables the usual way
os.write((char*) &var1, sizeof(var1));
os.write((char*) &var2, sizeof(var2));
...
}
void read(ifstream& is)
{
// read leght variable
int tmp;
is.read((char*)&tmp, sizeof(tmp));
// now you know the length of the string
// assuming you haven't allocated memory yet, do so now
name = new char[tmp];
// now read the string
is.read(name, tmp);
// read other variables the usual way
is.read((char*) &var1, sizeof(var1));
is.read((char*) &var2, sizeof(var2));
...
}
};

It may seem a little tedious, what with having to record the length of a string and so on. Nevertheless, I still find it much easier than outputting in text format where you have to worry about putting a delimiter between everything, and how everything is formatted when you output it, etc. That's not to mention that if you have a lot of numerical data it is more space-efficient to store it in binary format.

There are of course many ways you can make this i/o process simpler. Using an std::string instead of a char* will make it somewhat easier, since you won't have to allocate memory yourself, but if you really want to make it easy, you can define your own read() and write() functions for strings, that output each character individually using ofstream::put(), including the null character. This way, you don't have to record the length, since a matching read() function will read character-by-character using ifstream::get() until it encounters the null character. Coupled with the fact that std::string takes care of memory management for you, a simple function like that will make writing entire structures in binary format easy.

I would still need to come up with a way to read all the files and load them into the program during runtime so that all items are available to the player.

If you have a lot of the same data structure in one file, you can easily store it in your program in an std::vector. You can even write a function that will read an entire array of structured from a file, into a vector.

ace4016
February 4th, 2005, 03:31 AM
So, from what i am undertsanding from your code, I save the name of the item seperately from the rest of the data of the class/struct? I undestand what is going on but it seems like it can get a bit confusing (to me atleast) when you want to add all the weapons into one file which i think i will have to do instead of making a bunch of single files containing one item each. Maybe I just need to study the code a little bit more.

About using strings, I tried to use them but I was having a lot of prolems with strings. I can never get strings to work in the place of char. I can't properly save strings to a file either, they come out as garbage as well. I'll figure something out.

HighCommander4
February 4th, 2005, 07:35 PM
So, from what i am undertsanding from your code, I save the name of the item seperately from the rest of the data of the class/struct?

Well, basically you would save every data member of the struct separately, but have a function do this, preferably a member function of that struct, like the write() function is my example. The reason for this is that if you just write the entire structure that contains a char*, only the actual pointer would be saved but not the string.

About using strings, I tried to use them but I was having a lot of prolems with strings. I can never get strings to work in the place of char.

If you try to mix up std::strings with pointers-to-char, and functions that use pointers-to-char, you will have problems. Stick to one or the other. IMO, you should stick to std::string, because it's so much easier to use than C-style strings.

Consider the difference in how you can use the two:

1) Input a string into a char array of exactly the length of the string.

With C-style strings:
char* str;
char temp[80];
cin.getline(temp, 80);
str = new char[strlen(temp) + 1];
strncpy(str, temp, strlen(temp) + 1);

Tha above solution uses excess memory since it uses an additional 80-byte buffer. It will fail if the input string is longer than 79 characters.

With std::string:
std::string str;
getline(cin, str);

This will read an unlimited amount of characters and automatically resize the string to fit the data.

2) Comparing two strings

With C-style strings:
char str1[20] = "hello";
char str2[20] = "hello";
if (strcmp(str1, str2) == 0) { ... }

With std::string:
string str1 = "hello";
string str2 = "hello";
if (str1 == str2) { ... }

It is very much worth using std::strings.

I can't properly save strings to a file either, they come out as garbage as well.

This is how you write an std::string to a file:

string str;
// fill str with data
ofstream fout;
fout.write(str.c_str(), str.size());

How you read it will depend on whether you've also written the string's length to file, or if you made sure the null character was written to the file right after the string and stopped reading at the null character.

ace4016
February 4th, 2005, 08:09 PM
Thanks for that clear up with strings, they are alot easier to work with.

If I were to first add the size of the string then read the string, would I also have to do that for the other variavbles even though they are of type int? Also, if i went with the \n approach to add the \n i would just use << "\n" to output the newline to the file?

lol, one more question:

std::string str;
getline(cin, str);


Is that the same cin from iostream header or is it suppose to be a fstream object?

HighCommander4
February 4th, 2005, 08:37 PM
If I were to first add the size of the string then read the string, would I also have to do that for the other variavbles even though they are of type int?

No, because other variables have a fixed size. You would, however, have do keep track of size if you were writing the contents of an std::vector or another STL container.

Also, if i went with the \n approach to add the \n i would just use << "\n" to output the newline to the file?

I was talking about adding a null character at the end of a string when writing a string, not a newline character. Remember that newline chatacters can occur anywhere in strings so they don't necessarily mark the end of a string. Here is an example of how you could read/write a string without haviong to record its size separately:

void write(const string& str, ofstream& os)
{
os.write(str.c_str(), str.size()); // write string
os.put('\0'); // write null character at end of string
}

void read(const string& str, ifstream& is)
{
char ch;
while ((ch = is.get()) != '\0') // read char-by-char until you reach '\0'
str += ch; // append each character to the string
}

Then, in the read and write member functions of your data structure, you could read and write the strings like this:

struct item
{
std::string name;
int var1;
int var2;
...
void write(ofstream& os)
{
// write string
::write(name, os);
// write other variables the usual way
os.write((char*) &var1, sizeof(var1));
os.write((char*) &var2, sizeof(var2));
...
}
void read(ifstream& is)
{
// read string
::read(name, is);
// read other variables the usual way
is.read((char*) &var1, sizeof(var1));
is.read((char*) &var2, sizeof(var2));
...
}
};

lol, one more question:

Code:

std::string str;
getline(cin, str);


Is that the same cin from iostream header or is it suppose to be a fstream object?

In my example I was talking about reading a string inputted by the user, so you'd need to use the cin object. If you want to read a string from a file, you can use the same function, replacing cin with an ifstream object.

ace4016
February 4th, 2005, 08:41 PM
Thanks, i think I got it now (hopefully).

ace4016
February 4th, 2005, 09:53 PM
Sorry to bother you guys again. I got the file to write and read perfectly, but the while( !fin.eof() ) loop is going through an infinite loop. Was there something i did wrong when i wrote the file or is it the loop?

HighCommander4
February 5th, 2005, 02:09 PM
Sorry to bother you guys again. I got the file to write and read perfectly, but the while( !fin.eof() ) loop is going through an infinite loop. Was there something i did wrong when i wrote the file or is it the loop?

The loop while(!fin.eof()) does not detect the end-of-file the way you'd expect it to. Use

while (fin.peek() != EOF)

instead.

ace4016
February 5th, 2005, 02:16 PM
Thanks, everything works now. One save/load system down.....one to go, but I think i'll take what i learn from this to use on the charatcer, hopefully i wont have any trouble that i can't handle. Thanks once again.