Sign in to follow this  
3lement

[C++] Saving, Loading & Reading .txt Files Into Arrays

Recommended Posts

3lement    100
Hey all,

Progress is coming along on my game. I'm currently trying to work out how to save/load files using fstream. The idea is pretty self-explanatory; store values for whether gear exists or not in the player's inventory, and give the player the ability to save these values to a profile, or load them from a different profile.

Here's what I have so far (pieced together from various tutorials online):

[code]int DoLoad()
{

ifstream Profile1;
Profile1.open("Profile1F.txt");
string Profile1V[6];
while (!Profile1.eof())
{

Profile1 >> Profile1V[5];
}

cout<<Profile1V[5];
Profile1.close();
return 0;
}[/code]


And the contents of my Profile1F.txt file:

[code]5
6
7
8
9
0[/code]


I don't understand arrays very well, so this is probably a bad method to be using. The cout is only to test whether my values were stored; what I'd like is to take the values from each individual line and store it into a new int.

For example, if line 1 == '1', then HavePickaxe = 1.

I think this would be a fairly effective patchwork save/load system. I'm not concerned with security measures; if someone [i]really[/i] wants to cheat at my game by modifying the .txt file, let them. This is more for the learning experience.

So, almighty devs, how would one go about accomplishing the text -> integer conversion?

*[b]disclaimer[/b]: As of yet, cout will only produce the value of '0'. I managed to get it to output 567890 with a couple changes, but I still have no way of separating those numbers and storing each individual one in an int.

Share this post


Link to post
Share on other sites
zacaj    667
Profile1V[6] is a list of 6 separate strings. To print them each out you would have to do this:
[code]
for(int i=0;i<6;i++)
cout<<Profile1V[i];[/code]

Similarly, for reading the values in, youre repeatedly storing the new number in the 6th string in Profile1V (starts at 0, not 1, so index 5 is the 6th one). You would need to keep track of how many lines youve read, and use that to index Profile1V:
[code]int numRead=0;
in loop:
Profile1>>Profile1V[numRead];
numRead++;[/code]
Im still not very clear on what the different numbers youre reading in are supposed to mean

Share this post


Link to post
Share on other sites
3lement    100
[quote][color=#1C2837][size=2]Im still not very clear on what the different numbers youre reading in are supposed to mean [/size][/color][/quote]


Basically, I'm going to have a header file where I store int definitions, such as HavePickaxe. If HavePickaxe = 1, line 1 of the .txt file will be "1", meaning the player currently owns the Pickaxe item. If the player does not have it, it will of course be 0.


This way, when they player's going along in his adventure and comes across a Pickaxe, my game will make HavePickaxe = 1, which will then be written to line 1 of the .txt file. This file can be written (Save) or read (Load), and by using #define I can change values such as Gold to P1Gold and go from there.


The values in the current .txt are just placeholders to see if I can assign (and read) 6 values to the array.


If it helps, just ignore the code I have above. It's very patchwork; I'd be willing to scrap it if I can find a more efficient way of doing what I've just explained.

Share this post


Link to post
Share on other sites
zacaj    667
So in the final version the file would just be 1s and 0s?

A more extensible way to do this would be to have an array of all the items the player could have
For example:
[code]int items[NUMBEROFDIFFERENTITEMS];
#define PICKAXE 0
#define SHOVEL 1
#define AXE 2
[/code]

then you would set each int in the array to 1 or 0 based on whether the player has the corresponding item. So, you could use items[SHOVEL]=1; to say the player has a shovel, for instance. To save the file, run through all the different item codes, and print out the code of any item which the player has. So, if the player had a PICKAXE and an AXE, then your saved file would contain
[code]0
2[/code]

You could print this like this:
[code]for(int i=0;i<NUMBEROFDIFFERENTITEMS;i++)
if(items[i]==1)
SAVE i TO THE FILE[/code]
To load the file, first set every number in items to 0, then read each line of the file, and set items[CURRENTNUMBER] to 1

Share this post


Link to post
Share on other sites
3lement    100
Thanks for the quick reply, but I still have a number of questions:

- By #defining outside of local scope, won't that change all instances of the aforementioned int values regardless of whether I loaded the game or not?

- How can you define int names to an array? As far as I know, you can only do something like array[ ] = {1, 2, 3}, which won't work if you use named values instead.

- How does one save/load the values of the array one by one, as opposed to saving to the whole array at once?

This is probably the most mind-boggling problem I've encountered thus far. Your help is very much appreciated.

Share this post


Link to post
Share on other sites
zacaj    667
using a #define replaces anywhere you put the first word with the second. So when you write
items[SHOVEL]=1;
youre really writing
items[2]=1;

I think youre not completally understanding arrays... Ill try to explain, but you might want to read some more tutorials on the subject. Also, sorry if any of this seems stupid-obvious, but its hard to get a feel for how much you get online.
Arrays are just a whole bunch of single numbers
int array[5]; is equivilant to
int array1;
int array2;
etc.
But, you can use a variable (like int i) to access a certain one of the variables in the array.
So you can do array[i]=1;
instead of having to say
[code]
switch(i)
case 1:
array1=1;
breakl
case 2:
array2=1;
[/code]
So, you can just use for loops to access each member(individual element) of an array based on the variable you use in the for loop (im assuming you know what for loops are)

Share this post


Link to post
Share on other sites
IggyT    104
[code]int DoLoad() //why output an int?
{

ifstream Profile1;
Profile1.open("Profile1F.txt");
string Profile1V[6]; //this is an array of strings
while (!Profile1.eof()) //this is a good practice
{

Profile1 >> Profile1V[5]; //with this you only access the last value of your array and therefore overwrite it every iteration
}

cout<<Profile1V[5]; //with this you only display the said last value of the array of strings
Profile1.close(); //good practice
return 0;
}[/code]

To make this simple, the only reason it's displaying 0 is the fact that you are only accessing the last memory section of the string array that you're making and you keep writing over it in each iteration of your loop so you end up with only the last value of your file.

As a short lesson on arrays, arrays are safe temporary pointers, they allocate the size of whatever you're making the array of in memory space (in the case of strings you're making it the size of the string class in 6 slots side-by-side in memory)

int array[6]; // is the same as declaring six separate integers.

To access the first value of an array you start with 0

first value would be accessed using :

array[0];

and the last is accessed using :

array[5]; //one less than the number you originally used to allocate its size because the memory space begins with 0

Strings have the capability to resize themselves when you give them a new value ([u]a string is a class that holds a resizeable const char array[/u]) so what you might want to do if you are only taking bool-type information in the file is just resize the string and add the new value in. (Or alternatively you can catenate like I did below.)

[code]string DoLoad()
{

ifstream Profile1;
Profile1.open("Profile1F.txt");
string Profile1V; //this is a class that holds an array value
for (int i = 0; !Profile1.eof(); i++)
{
Profile1V += " "; //this will resize your string by one space by telling your string to add in a blank space. THIS ONLY WORKS WITH STRINGS.
getline(Profile1,Profile1V[i]); //this will read to the newline symbol in each line of your file
cout<<"\n"<<Profile1V; //this needs to be erased after you see the proof because it's unnecessary
}


Profile1.close();
return Profile1V; //send a copy of the string out into your program where you should put it in a function or loop that sorts it
}[/code]

This is a way of doing this, but to be honest you need to look into how arrays work and learn it very well. It's crucial to programming (as are pointers and addresses.)

As a simple way of checking each of the values and putting them into a useable format do something like :

[code]

string info = DoLoad(); //sets the value to what's output in my function above

int size = info.length(); //sets to an easy to find and use variable holding amount of information you need

bool YesOrNo[size]; //dynamically allocated array based on your information needs

for(int i = 0; i<size; i++) //this checks each spot in your const char array (the string class called info I made above)
{
if(info[i] == '0')
YesOrNo[i] = false;
else if(info[i] == '1')
YesOrNo[i] = true;
else
cout<<"\nThere was an error in loading!"<<endl; //while debugging or even after you need things like this to know why you program isn't working as planned.
}

//set the value accordingly here or use the bool array and have the values held elsewhere

[/code]

If you want to use this, give me some credit. But I suggest you make your own because it's your game and you'll need to decide how to deal with data.

Share this post


Link to post
Share on other sites
fastcall22    10840
To answer your questions directly, you'd probably want to do something like this:
[code]
// in do_load():
int idx;
while ( Profile1 >> idx ) { // read: for all integers the file (more specifically: until an non-integer is encountered)
switch ( idx ) {
case 0: havePickaxe = true; break;
case 1: haveSword = true; break;
case 2: havePotion = true; break;
// .
// .
// .
default:
cout << "Unknown item index: " << idx << endl;
}
}

// in do_save():
if ( havePickaxe )
file << 1 << endl;
if ( haveSword )
file << 2 << endl;
if ( havePotion )
file << 3 << endl;
// .
// .
// .
[/code]

Unfortunately this entails a lot of work when you add a new item. The code is also very cumbersome. What happens if you accidentally mistook haveAwesomeItem as 4 instead of 7 in the save code?


Here are a few suggestions:

Familiarize and experiment with [url=http://cplusplus.com/doc/tutorial/arrays/]arrays[/url].
Next, do the same with [url=http://www.cplusplus.com/doc/tutorial/other_data_types/#enum]enum[/url]erations.

Then decide how you want to store your items. It seems that you're accessing your items via "havePickaxe" or "haveSword".

Why not represent this with an array, such as "bool haveItem[10]"? Then "havePickaxe" becomes "haveItem[0]" and "haveSword" becomes "haveItem[1]".
Can a player have more than one of a specific item type? If so, then it becomes "int items[10]", which tracks how many of each item type the player has. Why not use an enumeration instead of magic numbers to keep track of which slot in the array an item type belongs? (e.g.: haveItem[item_pickaxe] instead of haveItem[0]?)

From here, the design becomes simple:
[code]
// enumeration to keep track of the different item types
enum item_type {
item_pickaxe, // == 0
item_sword, // == 1
item_potion, // == 2
/* ... */ // == n
item_type_count // n + 1
};

// the actual inventory of the player
// an array of integers which keep track of the amount
// of each item_type the player has
int items[item_type_count] = {}; // <-- the curly brackets initialize the entire array with 0

bool do_save() {
// open the file
ofstream fs( "profile.txt" );
if ( !fs )
return false; // failed to open the file

// write how many of each item the player has in sequence, space-separated to the file
for ( int idx = 0; idx < item_type_count; ++idx )
fs << items[idx] << ' ';

return true;
} // (file is automatically closed)

bool do_load() {
// open the file
ifstream fs( "profile.txt" );
if ( !fs )
return false; // failed to open the file

// attempt to read a sequence of space separated integers,
// the number of items of each item type the player had
for ( int idx = 0; idx < item_type_count; ++idx ) {
fs >> items[idx]; // attempt to read an integer
if ( !fs )
return false; // read failed
}

return true;
} // (file automatically closed)


void test_function() {
items[item_pickaxe]++; // read: the player acquires one pickaxe
if ( items[item_potion] > 0 ) { // read: if the player has potions
items[item_potion]--; // read: consume a potion
player_hp += 13;
}
}
[/code]

Also, it seems you want support for different profiles. You can use [url=http://cplusplus.com/reference/iostream/stringstream/]stringstream[/url]s to build [url=http://cplusplus.com/reference/string/string/]string[/url]s to configure which file to load/save from:

[code]
bool do_load( int profile_number ) {
ostringstream ss;
ss << "profile" << profile_number << ".txt";
string filename = ss.str();
ifstream fs( filename.c_str() );
if ( !fs ) {
cout << "Unable to open file \"" << filename << "\" for reading." << endl;
return false;
}

// ...

return true;
}
[/code]

Share this post


Link to post
Share on other sites
3lement    100
A whole lot of very informative methods in here. Thank you all very much, I'm currently having a lot of fun trudging through it all with reference material side-by-side.

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