C++ console RPG save/load game functions

Hello all,

First I'd like to say that yes, there may be better/easier ways to make a game and better types of games to make for a beginner, however I am on my second c++ class, and this isn't to make a great playable game. This is simply an exercise I have given myself in order to force myself to learn more C++in a "real world" scenario. That said:

I am making a linux/windows console rpg, all text based, and I have managed to create the new game/save feature by dropping all of the characters stats/name etc into a text file, one line for each in a very particular order. My problem is when I want to load the game. I can read each line as a string, but doing math on a string is a pain in the butt. Also, my idea at the moment is a struct called PlayerInformation with string playerName, string playerHP etc in order to keep track of that info while playing. Is there an easier way to do that? Should I use a series of arrays so that I don't have to deal with sending the struct parts to different functions? What is the best way to read info from a text file and convert it into an int that I can work with?

Function.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//function.h
/*****************************************
Billy Colley
*****************************************/
#ifndef FUNCTION_H
#define FUNCTION_H

//DEBUGGING DEFINITIONS (1 for true, 0 for false)
#define DEBUG_MAIN 0
#define DEBUG_newGame 0


using namespace std;

struct playerInformation

{

	string firstName;
	string lastName;
	string race;
	string playerClass;
	int playerHP;
	int playerMP;
	int playerDefense;
	int playerAttack;

};

bool newGame(bool);
bool loadGame(bool);


#endif 


loadGame.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <iostream>
#include "function.h"
#include <fstream>
#include <sstream>

using namespace std;

bool loadGame(bool finished)
{
	string inputChoice;
	string saveNames, playerInfoTemp[10], tempStuff;
	stringstream infoTemp;
	string saveNameString[10];
	playerInformation playerChar;
	int i=0;
	int HPTemp;
	bool success=0;
	cout << "You chose to load a game!" << endl;
	ifstream ifile;
	ifile.open(string("saveGameFile.txt").c_str());


	do{
		cout << "Which save would you like to load?" << endl << endl;
		while(!ifile.eof() and i<=9)
		{
			getline(ifile, saveNames);
			cout << saveNames << endl;
			saveNameString[i]=saveNames;
			i++;
		}
		i=0;
		while(i<=9 and success==0)
		{
			(cin >> inputChoice).get();
			if((saveNameString[i])==inputChoice)
			{
				cout << "Found the save file!" << endl << endl;
				success=1;
			}
			else
				cout << "save file not found! \nTry again:" << endl << endl;

		}
	}while(success==0);
	ifile.close();
	ifile.open(string(inputChoice+".txt").c_str());
	i=0;
/*	while(!ifile.eof() and i<=9)
	{
		getline(ifile, infoTemp);
		playerInfoTemp[i]=infoTemp;
		i++;
	}*/
	getline(ifile, playerChar.firstName);

	getline(ifile, playerChar.lastName);
//	playerChar.lastName=infoTemp;
	getline(ifile, playerChar.race);
//	playerChar.race=infoTemp;
	getline(ifile, playerChar.playerClass);
	cout << "playerClass= " << playerChar.playerClass << endl;
//	playerChar.playerClass=infoTemp;
	getline(ifile, tempStuff);
	infoTemp << tempStuff;
	infoTemp >> playerChar.playerHP;
	cout << "playerHP= " << playerChar.playerHP << endl;
//	infoTemp >> HPTemp;
//	istringstream (infoTemp) >> playerChar.playerHP;
/*	getline(ifile, infoTemp);
	istringstream (infoTemp) >> playerChar.playerMP;
//	getline(ifile, playerChar.playerMP);
//	playerChar.playerMP=infoTemp;
	getline(ifile, infoTemp);
	istringstream (infoTemp) >> playerChar.playerDefense;
//	getline(ifile, playerChar.playerDefense);
//	playerChar.playerDefense=infoTemp;
	getline(ifile, infoTemp);
	istringstream (infoTemp) >> playerChar.playerHP;
//	getline(ifile, playerChar.playerAttack);
*/
	ifile.close();
}


You can see where I've attempted several styles of grabbing the information, none of them seem to work. Some grab everything as integers, even the player name, others grab everything as a strng.

Anyway, thanks for any advice/copies of code I could use for reference.
If you really want to challenge yourself, you might consider designing a specific protocol for storing all the information in some raw binary format, and then learn to read and write the data this way. Example:

First byte of file: Header for the player's name, a byte containing an unsigned integer n (from 0 to 255, since it's only one byte) specifying the length of the player's name, X
Next X bytes of file: The player's name, stored plainly
Next byte, some more information....etc. etc. you get the idea, this is just a rough example.

This is only a suggestion. You certainly *can* use string streaming, but I find that it can get very "messy." :)
Ok some of this is a little confusing for me, as I'm still pretty new to C++. Here's what I took out of it.

File:
1010Borvandoin1001010
That would say the next ten digits are the player name, then the next four digits are the players HP, which is 10.
If that is what you meant, then I'm still going to have the same problem I currently am. Sure I'll be able to have a search parameter find the first byte, see how long the name is, grab the name as a string, then find the next byte, see that it's length is 4, then grab the 1010 as a string. Not sure how it's going to help me with that, but I do see the overall value of using this in general.

If that isn't what you mean, could you provide a more specific example, as I said I'm still kinda new and often the C++ help I get, I need help understanding.

Thanks for the quick reply,

Bill
You're welcome. :)


File:
1010Borvandoin1001010


Close, but not quite. What you've written here is 1's and 0's in plain text, defeating the purpose of using raw binary representation at all. Every "character" in a file takes one full byte of data. So your textual "1010" is going to consume 4 bytes -- ASCII values 49, 48, 49, 48. Take a look at this chart to see what I'm talking about, and ignore everything except the "Chr" and "Dec" columns:

http://www.asciitable.com/

This is what I am proposing, given the example name/length you've provided:

Byte 1 of file: 10, to signify the length of the name. Just 10 (in decimal, of course). The value of the BYTE (single "character") is ten. We don't care if that looks like anything textually -- incidentally, it represents a line-feed, but like I said, we don't care what it would mean in a file that was pure human-readable text, we just care that we stored a value of 10 in that byte. You are correct in that its binary representation is 1010, but the point is that we stored it in a single byte.

Byte 2 of file: 66, representing 'B'.
Byte 3 of file: 111, representing 'o'.
Byte 2 of file: 114, representing 'r'. (this part, you wrote correctly, I just wanted to point out what is happening behind the scenes)

And so on.

To flesh this out into some code, let's say we're saving a file, we know that the user's name is Borvandoin, and its length is 10, so we want to write a byte value of 10 and then the text itself to the file. We would do something like (I'm not actively testing this, so can't guarantee if I have the syntax perfect):

1
2
3
ofstream outfile ("save.dat");
outfile.put (10);
outfile.write (string_containing_name, 10);


Thus we first write a *single byte value* of 10 for the first byte, then follow it up with the 10 bytes containing the name. Then when the time comes along that the user wants to load, and we want to read it back in, we do something like:

1
2
3
4
ifstream infile ("save.dat");
char name_length;
infile.get (name_length);
infile.read (string_for_name, name_length);


Making any sense yet? Don't feel bad if it takes a bit of time, this isn't easy stuff and I find myself hoping I didn't go overboard with my suggestion. But do you see how clean the method is once you know how to do it?
It does make sense, to a degree. Does outfile.put(#) automatically convert that number into it's 1 byte form, or am I going to need to declare anything before that. Also, lets say I have this struct:


1
2
3
4
5
6
7
8
struct playerInformation
{
     string playerName;
     string playerClass;
     int playerHP;
};

playerInformation playerCharacter;

How would I pull that information from the saved file?

1
2
3
4
ifstream infile ("save.dat");
char name_length;
infile.get (name_length);
infile.read (playerCharacter.playerName, name_length);


if that is correct, will that work the same once it gets to the int for HP, or will it read it as a string?

Thanks again,

Billy

Does outfile.put(#) automatically convert that number into it's 1 byte form, or am I going to need to declare anything before that.


The put function is accepting that parameter in its "1-byte form" already, there is no conversion to be done. If you look at the function definition:

http://www.cplusplus.com/reference/iostream/ostream/put/

You will see that the parameter is a char. Important: whenever you see "char", you may alternatively think of it as a "byte", because a char is a single-byte data type (just watch out for "signed chars" vs "unsigned chars," but I won't lay that nonsense on your shoulders right now ;) as it shouldn't be an issue here). Thus you are passing in a "byte" value of ten, at the get-go.


if that is correct, will that work the same once it gets to the int for HP, or will it read it as a string?


It may be helpful if I point out here that what you are doing by this method is essentially writing the information to the file in the *same format* that it gets stored in *memory* during the execution of your program, byte-for-byte! If you want to read or write an int, make enough space and write the data rawly, like so:

For writing:
 
outfile.write ((char *)(&playerCharacter.playerHP), sizeof(int));


For reading:
 
infile.read ((char *)(&playerCharacter.playerHP), sizeof(int));


There is a lot going on here, so here is an explanation of what's happening; again, don't feel bad if it takes some time and studying to understand it.

First, notice the & in front of the variable name each time. Why? Because the read() and write() functions accept POINTERS to the location in memory where we want to read to / write from, so we supply their addresses instead of the values themselves (putting the & symbol at the beginning supplies the variable's address in memory instead of its contained value). Why didn't we need to do this with the strings? Because the name of a string is actually a pointer to where the string begins. Thus that variable was already a pointer and didn't need to be changed over. Actually, I believe you will need to apply the c_str function to your strings, a la:

 
infile.read (playerCharacter.playerName.c_str(), name_length);


In order to pass the actual pointer to the beginning of the character data (and not a C++ "string" type variable).

Next, after we've used the & sign to get the address of the HP variable, we cast the whole thing to a char* (a char pointer), with (char*)(&variable_name). Why? Simply because a char* is what the read and write functions expect. You, the programmer, know that it's actually an int, but you are pretending it's a char* temporarily to make the compiler happy, because that's what the read and write functions want.
I was wrong about the c_str. That gives you a const char *, you can't modify it that way, so reading that from the file is not going to work. I apologize for the incorrect information. You'll probably need to set up a buffer in order to do it this way, like so:

1
2
3
4
5
6
7
ifstream infile ("save.dat");
char name_length;
infile.get (name_length);
char buffer[name_length + 1]
infile.read (buffer, name_length);
buffer[name_length] = '\0';  // Manually null-terminate the old-style char array
playerCharacter.playerName = buffer;  //  C++-style string knows how to assign data to a string from an old-style char array 


This might (or might not!) be getting to be more trouble than what you want to go to / more advanced than you want to learn....let me know whether or not that's the case. :)

Also, can anyone tell me if there's an easier way to go about that ^ that I'm not thinking of?
Topic archived. No new replies allowed.