Sign in to follow this  
DarkAnima

Question about save files

Recommended Posts

I've been working on an RPG on and off for a while, and I'd been having difficulties with saving and loading. I'd been using some odd procedure, but switched over to a standard 'fout', 'fin' system. I ran into trouble, though, because pieces of data such as "short sword" that contain more than one word that are assigned to one variable (in this case, Player1.weapon). I was using strings to store the variables, and when I went to load my game, instead of 'Player1.weapon="Short Sword"' the game would recognize the space as a break and set 'Player1.weapon="Short"' and 'Player1.armor="sword"'. The tutorial on saving and loading that I was using said that changing values to character arrays would make it so that I could tell the program to input the save values as a line instead of switching to the next value at the end of each word in the save file, so I switched all my strings out for char*. Saving functions exactly the same (I've checked my save files to ensure that the change in variable type wasn't changing the data storage) however when I go to load, it crashes before it even starts loading the first value. Here is some of the save code:
fout << Player1.name;
fout << "\n" << Player2.name;
fout << "\n" << Player3.name;
fout << "\n" << Player4.name;
fout << "\n" << Player1.exp;
fout << "\n" << Player2.exp;
fout << "\n" << Player3.exp;
fout << "\n" << Player4.exp;

etc. and the start of the loading code:
void load()
{
cout << "enter the name of the file you wish to load.\n";
cin >> filename;
ifstream fin(filename);
//usually crashes somewhere around here
if (!fin)
	{
	cout << "that file doesn't exist.\n";
	load();
	}
else if (fin)
	{
	fin >> Player1.name;
	fin >> Player2.name;
	fin >> Player3.name;
	fin >> Player4.name;
	fin >> Player1.exp;
	fin >> Player2.exp;
	fin >> Player3.exp;
	fin >> Player4.exp;

etc. filename is a char[17] Anyone have any advice on this?

Share this post


Link to post
Share on other sites
you can use a different character than whitespace to seperate your strings.
Of course you should choose a character that does not occur in your strings ;) ( '|' perhaps)

[code]
fout << string1;
fout << '|';
fout << string2;
[\code]

for reading you can use the getline function:

char buffer[50];
fin >> getline(buffer,50,'|');
string1=buffer;
fin >> getline(buffer,50,'|');
string2=buffer;


btw: usually forums have some buttons to place tags - whats the code tag :?

Share this post


Link to post
Share on other sites
Quote:
Original post by MJP
Have you considered using an XML-based system? TinyXml is simple and easy to use for such purposes.


I have over 14000 lines of code in C++, I'm not about to switch to XML because I ran into one bug. Thanks for the suggestion, though.

Quote:
original post by Lastmerlin
you can use a different character than whitespace to seperate your strings.
Of course you should choose a character that does not occur in your strings ;) ( '|' perhaps)


I saw an example of code where a coma was used to separate the lines, but I already am saving well over 300 pieces of data, and that will probably go up to well over 1000 by the time I'm done, so without line breaks the save files will be really difficult to read if I want to go in and test out things later, or if there's some sort of bug in saving that I don't know about. Plus, that would mean 1000 more lines of code to input the break character and a lot of additional code in the loading section. Thanks for the suggestion, though, it should work if I can't find an easier way.

Share this post


Link to post
Share on other sites
Quote:

I have over 14000 lines of code in C++, I'm not about to switch to XML because I ran into one bug. Thanks for the suggestion, though.


He meant use TinyXML for your saving/loading purposes, not your entire game (which isn't even possible).


void load()
{
cout << "enter the name of the file you wish to load.\n";
cin >> filename;
ifstream fin(filename);
...


You've already declared the variable filename, right?


ifstream fin(filename);
//usually crashes somewhere around here
if (!fin)
{
cout << "that file doesn't exist.\n";
load();
}
else if (fin)
...


You should probably handle this differently, perhaps using the ifstream's is_open() function. Like this:

ifstream fin(filename);
std::string line;
if(fin.is_open()){
while(!fin.eof()){
std::getline(fin, line)
// Or however you want to do this
}
fin.close();
}
else {
// File didn't open, report error or do something else
}

Share this post


Link to post
Share on other sites
Quote:
Original post by Shakedown
Be sure to .close() your files when you're finished writing/reading to them

Meh. The ifstream destructor will take care of that.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by Shakedown
Be sure to .close() your files when you're finished writing/reading to them

Meh. The ifstream destructor will take care of that.


True.

Share this post


Link to post
Share on other sites
Quote:
Original post by Shakedown
Quote:

I have over 14000 lines of code in C++, I'm not about to switch to XML because I ran into one bug. Thanks for the suggestion, though.


He meant use TinyXML for your saving/loading purposes, not your entire game (which isn't even possible).


Oh, I've heard of XML as a language, but I was assuming that I couldn't mix and match C++ and XML. I have decent knowledge of C++ and Basic (enough to get me in trouble, at least ^_^) but I don't know a lot about other languages.


Quote:
Original post by Shakedown
You should probably handle this differently, perhaps using the ifstream's is_open() function.


I used that code, since it seems to handle things in a neater fashion, but it's still causing problems. I switched some variables around so that I began the load process with int values and then went to the char* values. It seemed to handle the ints fine, but when I got to the char*s, it crashed as always, giving me the following error: "unhandled exception in Song.exe (MSVCP60D.dll): 0xC0000005: Access violation"

Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnima
Quote:
Original post by Shakedown
Quote:

I have over 14000 lines of code in C++, I'm not about to switch to XML because I ran into one bug. Thanks for the suggestion, though.


He meant use TinyXML for your saving/loading purposes, not your entire game (which isn't even possible).


Oh, I've heard of XML as a language, but I was assuming that I couldn't mix and match C++ and XML. I have decent knowledge of C++ and Basic (enough to get me in trouble, at least ^_^) but I don't know a lot about other languages.


Quote:
Original post by Shakedown
You should probably handle this differently, perhaps using the ifstream's is_open() function.


I used that code, since it seems to handle things in a neater fashion, but it's still causing problems. I switched some variables around so that I began the load process with int values and then went to the char* values. It seemed to handle the ints fine, but when I got to the char*s, it crashed as always, giving me the following error: "unhandled exception in Song.exe (MSVCP60D.dll): 0xC0000005: Access violation"


XML is a way of saving data in a readable way. it's a markup language very similar to HTML.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
Quote:
Original post by Shakedown
Be sure to .close() your files when you're finished writing/reading to them

Meh. The ifstream destructor will take care of that.


It ain't C++ for no reason!

Share this post


Link to post
Share on other sites
Quote:
Original post by Lastmerlin
you can use a different character than whitespace to seperate your strings.


Or, presuming that you are determined to use this method of data storage and retrieval, you could either (1) replace spaces with an underscore during the write phase and then revert them during the read phase, or (2) you could verify that you are reading until EOL, or (3) you could use a properly formatted fscanf().

Share this post


Link to post
Share on other sites
Quote:
Original post by dylanwinn
Stupid question, but why not use shortSword, ShortSword, shortsword, etc. instead of Short Sword?


I thought of doing that, but the problem is the saved variable is 'Player1.weapon', and there are places in the code where Player1.weapon is called to be displayed, such as when I call up inventory for equipment, look at inventory, etc. For instance, an excerpt:


if (itemtarget.Weapon=="Short Sword")
{
shortsword.owned++;
currentequip=shortsword;
}



This is a segment for unequipping. I set the chosen players equipment to a player called 'itemtarget' and check what ends up being unequipped so that unequipped items can be returned to the inventory. Once the equipment has been switched out and itemtarget's equipment set to the new value ("Long Sword" for instance) I set the chosen player's equipment to itemtarget's equipment. This means that the saved value will constantly be used in places where it's important to have spaces and the like.

Share this post


Link to post
Share on other sites
You cant avoid rewriting some code. If your present savegame reads
"long sword short sword next_string" there is simply no possibility for the ifstream to determine where one string ends and where the space should be part of the string.

If you have some save routines cluttered all over your programm, so changing them all is really to much work consider the following:

Derive your an own classes from ofstream and ifstream and overload the << (repectively the >> ) operator for std::string 's to add an addition character automatically when writing and using this character to determine the end of the string when reading. Then use these classes for your fin, fout.

Share this post


Link to post
Share on other sites
Quote:
Original post by Lastmerlin
You cant avoid rewriting some code. If your present savegame reads
"long sword short sword next_string" there is simply no possibility for the ifstream to determine where one string ends and where the space should be part of the string.

If you have some save routines cluttered all over your programm, so changing them all is really to much work consider the following:

Derive your an own classes from ofstream and ifstream and overload the << (repectively the >> ) operator for std::string 's to add an addition character automatically when writing and using this character to determine the end of the string when reading. Then use these classes for your fin, fout.


I'm not worried about re-writing code, it's just that I'd rather solve it in a more simple manner if possible. The save file has line breaks, but if I use strings, the program goes to the next variable when it sees a space. The suggestion was made to remove the spaces, but that would involve creating new variable sets for storing the values without spaces and then turning those into values with spaces after loading, which would take a long time and a lot of code. You also had the suggestion of using the | character to tell the computer to go to the next value, which is a better solution than the previously proposed one, however it also requires a great deal of extra code (unless I use a clever 'for' loop... I'll think about that tomorrow as I do my schoolwork). If I use char*, the program crashes the second I try to load in any of my char* values.

I'm not dismissing the idea of editing my code. I've been trying a lot of things. I'd just rather figure out why the program is crashing as it tries to load character arrays when it's fine if I use strings (minus, of course, the problem with spaces). If I can fix the problem with just a line or two of code, I'd rather do that than add in hundreds of lines.

Share this post


Link to post
Share on other sites
Unfortunately, sometimes it's easier to rewrite a piece of code than it is to fix problems with it, especially if the problem is down to a design error.

If you repeatedly add clauses to your code to handle specific cases, you end up with so many special cases that it's difficult to maintain and easy to introduce more problems.

I would recommend creating a new save class with a well defined interface. That way, you can modify your actual saving/loading without having to make large changes to your main code base.

I would also second the opinion of using an XML schema for your file formats. This works really well if your data is stored internally as an STL map for example.

To help you out, an example XML format could look something like this -


<Player name = "fred" age ="21" >
<main_weapon name = "short sword" />
<armour name = "plate mail" />
</Player>
<

Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnimasave code:

*** Source Snippet Removed ***
etc.

and the start of the loading code:

*** Source Snippet Removed ***


What is the type of Player1.name? A char array? Did you allocate space for it? Is it big enough? Does it perhaps try to read the entire file rather than just one line and so overflowing you array?

Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnima

Oh, I've heard of XML as a language, but I was assuming that I couldn't mix and match C++ and XML.


I don't even think of XML as a language really, or at least not the way I use it. I just think of it as a nice, standard format for storing a bunch of data. TinyXML, which is the library I linked you to, is a C++ library that makes it very easy for you work with XML. Basically your app just specifies what data should be stored in which fields or attributes, and TinyXML writes out the file for you in XML format.

Share this post


Link to post
Share on other sites
And not just that, Xml is perfectly designed to store hierarchies. For our game, I decided to store the entire Scenegraph in the savegame in xml format. This way I can have objects in the players inventory, or in the world itself, I don't need to worry about that.


<File>
<Player>
...blaaa....
<Inventory>
<Weapon> // this weapon is inside the player's inventory
</Inventor>

<Weapon> // this weapon is on the ground
</File>

Share this post


Link to post
Share on other sites
Quote:
Original post by OldProgie2
Unfortunately, sometimes it's easier to rewrite a piece of code than it is to fix problems with it, especially if the problem is down to a design error.

If you repeatedly add clauses to your code to handle specific cases, you end up with so many special cases that it's difficult to maintain and easy to introduce more problems.

I would recommend creating a new save class with a well defined interface. That way, you can modify your actual saving/loading without having to make large changes to your main code base.

I would also second the opinion of using an XML schema for your file formats. This works really well if your data is stored internally as an STL map for example.


This save class doesn't have any clauses added to it for specific cases, and is, in fact, as bare bones simple as possible. A new save class would be identical to the one I have now:


void save( )
{
cout << "\nwhat name should the save file have? ";
cin >> filename;
ofstream fout(filename);
ifstream fin(filename);
if (fin)
{
cout << "\nthis saved game already exists, do you want to replace it?";
cout << "\n1 - yes";
cout << "\n2 - no\n";
cin >> choice;
if (choice=!1) goto donesave;
}
else if (!fin)
{
cout << "create new saved game file?";
cout << "\n1 - yes";
cout << "\n2 - no";
cin >> choice;
if (choice=!1) goto donesave;
}
fout << Player1.name;
fout << "\n" << Player2.name;
fout << "\n" << Player3.name;
//etc
fout << "\n" << web.owned;
fout << flush;
fout.close();
donesave:
;
}

void load()
{
cout << "enter the name of the file you wish to load.\n";
cin >> filename;
ifstream fin(filename);
std::string line;
if(fin.is_open())
{
while(!fin.eof())
{
fin >> Player1.name;
fin >> Player2.name;
fin >> Player3.name;
//etc
fin >> web.owned;
fin.close();
}
}
else cout << "Error: Chances are that file doesn't exist, or there was a funky bug. Try again ^_^";
//since I am not storing the characters' stats, I have a code here that determines level and sets the stats here
}



Yes, I know there's some sloppiness in the save section, but right now I'm trying to get things functional. What I'm writing now is more or less a sketch/rough draft of what I want it to be. As long as it does what it's supposed to, I'm happy.

I'll give TinyXML a look and see if I can get it to run with my program.

Quote:
Original post by Structural
What is the type of Player1.name? A char array? Did you allocate space for it? Is it big enough? Does it perhaps try to read the entire file rather than just one line and so overflowing you array?


I'll post some of the relevant source code. As I'm used to using strings and not char*, I might have overlooked something that I've forgotten.


class character
{
public:
char* name;
bool defend;
int level;
//more initialization of the character class
}

//There's a lot of code defining all the classes and specific members. I define several characters including Player1, Player2, etc.

//after a lot of definitions (many of which I hope to someday move to header files, but I had difficulties last time I tried) I get to my 'Define( )' function, which sets up all the stats necessary for a new game.

void define()
{
//define starting stats
Player1.name = "Lucent";
Player1.defend = false;
Player1.level=1;
//etc.
}



After that, it goes into the main body of the game.

As I said before, I'll look into TinyXML and see if that will help (although I need to finish my paper for Philosophy class, first)

Another thing I was looking into and just thought of again, was getting definitions for the monsters and items put into a header file or something I can #include at the start of the main game file so that I can keep my code separate from everything else. I tried doing it with my monsters, but at first it told me that I hadn't defined the variables, and when I returned the variable initiation to my code (just the definition of the monster class, really) it told me that I was redefining the variables. After spending a couple hours trying to figure out what was going wrong, I gave up, but since there's going to be ~300 items and ~150 monsters I really would like to store all that data away from the program to keep things less cluttered. I'm sure I'm just forgetting something silly, or my inexperience is showing.

Thanks again for the suggestions and help ^_^.

Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnima
The suggestion was made to remove the spaces, but that would involve creating new variable sets for storing the values without spaces and then turning those into values with spaces after loading, which would take a long time and a lot of code.


What?


string s; // So our routine doesn't affect state
s = Player1.name;
replace(s.begin(),s.end(),' ','_');
fout << s << endl;


s = Player2.name;
replace(s.begin(),s.end(),' ','_');
fout << s << endl;

s = Player3.name;
replace(s.begin(),s.end(),' ','_');
fout << s << endl;

// etc




// No need for intermediates here
fin >> Player1.name;
replace(Player1.name.begin(),Player1,name.end(),'_',' ');

fin >> Player2.name;
replace(Player2.name.begin(),Player2,name.end(),'_',' ');

fin >> Player3.name;
replace(Player3.name.begin(),Player3,name.end(),'_',' ');

// etc


Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnima

if (choice=!1) goto donesave;
}
fout << Player1.name;
fout << "\n" << Player2.name;
fout << "\n" << Player3.name;
//etc
fout << "\n" << web.owned;
fout << flush;
fout.close();
donesave:
;



Oh no you didn't!

Share this post


Link to post
Share on other sites
Oh, I like that, Erissian! I hadn't thought of that. It will add a lot to my code (I don't know exactly how many char*/string values there are, but it's at least 72... Plus, unless my players decide to be clever and give characters names with spaces, the names themselves will be one word) but it's a very functional idea.

Share this post


Link to post
Share on other sites
Quote:
Original post by erissian
Quote:
Original post by DarkAnima

if (choice=!1) goto donesave;
}
fout << Player1.name;
fout << "\n" << Player2.name;
fout << "\n" << Player3.name;
//etc
fout << "\n" << web.owned;
fout << flush;
fout.close();
donesave:
;



Oh no you didn't!


lol, goto gets a lot of criticism. I guess since I'm so used to it from my years programming in basic, I still use it now and then in my code when I'm trying to think of quick, sloppy ways of fixing potential problems (in this case, if I call the save routine again if the player says that they don't want to save, once the player exits, it will go back to where it left off in the save routine and save without asking the player). I'll eventually do something similar to what's going on in the load section now, but goto is working at the moment.

Share this post


Link to post
Share on other sites
Quote:
Original post by DarkAnima
Another thing I was looking into and just thought of again, was getting definitions for the monsters and items put into a header file or something I can #include at the start of the main game file so that I can keep my code separate from everything else. I tried doing it with my monsters, but at first it told me that I hadn't defined the variables, and when I returned the variable initiation to my code (just the definition of the monster class, really) it told me that I was redefining the variables. After spending a couple hours trying to figure out what was going wrong, I gave up, but since there's going to be ~300 items and ~150 monsters I really would like to store all that data away from the program to keep things less cluttered. I'm sure I'm just forgetting something silly, or my inexperience is showing.


I solved this problem. I was including my definition files in the workspace in Microsoft Visual C++, so it was putting all those values into the project, then trying to #include them, which is why it told me that I had already defined them. I'm happy that I got that working, it really unclutters and simplifies a lot of code.

I still would like to know why the cin function is unhappy with char*. I have plenty of good workarounds, so if I can't figure out how to make cin and char* be friends, I'll go back to strings and use one of the workarounds, but I'd rather fix the problem than find a complicated way around it, if possible.

[Edited by - DarkAnima on May 8, 2008 11:16:57 PM]

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this