Jump to content
  • Advertisement
Sign in to follow this  
Koobazaur

Loading Files - theory-behind-it questions....

This topic is 4889 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Alright, so I made this simple data file for my program:
Count: 3

1.
Name: Cookie_Gun
Accuracy: 5
Range: 40
SpriteID: 1
OrbX: 10
OrbY: 10
Cost: 40

2.
Name: Snarl_Snarl
Accuracy: 10
Range: 80
SpriteID: 2
OrbX: 30
OrbY: 30
Cost: 120

3.
Name: W00t_Cannon
Accuracy: 15
Range: 8845
SpriteID: 2
OrbX: 30
OrbY: 30


I looked into my big bad book of C++ programming to check up on loading files (never remember that, heh) and found out the good ole
SomeFile >> SomeVariable;
I am not sure about the exact name of this way of loading files, but it basically is about loading chunks of data at a time. Well, considering that I simply opened the notepad and started typing in, I didn't really think this method would work as I am not really creating any "chunks." To my surprise, it worked perfectly well, loading EXACTLY what I wanted. So can somebody tell me what on what basis does this way load the files? I would believe it just perceives different chunks as separated by empty space (" ") am I righT? Then, can I load in more than 1 word at a time ("Cookie Dough") this way w/o going into a loop that load each word seperatly until end of line is reached? BTW, for better understanding, I'll go ahead and attatch the full code I used.
#include <iostream>
#include <stdlib.h>
#include <fstream>

using namespace std;


//I put all the stuff dealing with files in a file namespace for better 
//organisation and understanding
namespace file
{
 int GetNumberValue(fstream * File, char * Name);
 char * GetStringValue(fstream * File, char * Name);
 bool GotoNo(fstream * File, int Number, bool bStratFromBOF=1);
}


int main(int argc, char *argv[])
{
  fstream file("weapons.txt", ios::in);  //load file...
  
  if( !file )  //if broken
    {
     cerr << "File not opened. STFU N00b!\n"; //scream
     system("PAUSE");	    //stop
     return -1;     //and die
    }

//some values we'll be loading in
 char Name[20];
 int Accuracy;
 int Range;
 int SpriteID;
 int OrbX;
 int OrbY;
 int Cost;
 
 //how many of such values are there?
 int Count=0;
 Count = file::GetNumberValue(&file, "Count:"); //find that out!
 
 //your casual loop
 for(int x = 1; x<=Count; x++)
  {
   //go to number, if it fails
   if(!file::GotoNo(&file, x))
      {
       cerr << "Could not find Number " << x << "\n"; //report the error
       system("PAUSE");	 //let the admin come back from a coffe break
       return -1;       //and quit on him
      }
   //load the values   
   strcpy(Name, file::GetStringValue(&file, "Name:"));  
   Accuracy = file::GetNumberValue(&file, "Accuracy:");
   Range = file::GetNumberValue(&file, "Range:");
   SpriteID = file::GetNumberValue(&file, "SpriteID:");
   OrbX = file::GetNumberValue(&file, "OrbX:");
   OrbY = file::GetNumberValue(&file, "OrbY:");
   Cost = file::GetNumberValue(&file, "Cost:");  
   
   //and display them
   cout << "\n\n  " << Name;
   cout.width(10);
   cout << "\nAccuracy: " << Accuracy;
   cout << "\nRange: " << Range;
   cout << "\nSpriteID: " << SpriteID;
   cout << "\nOrbX: " << OrbX;
   cout << "\nOrbY: " << OrbY;            
   cout << "\nCost: " << Cost;
   cout << "\n=============================\n";
   system("PAUSE");
   //rinse and repeat
  }
 
 
 
  file.close();   //close the file
  cout << endl;   //end the line
  system("PAUSE");//wait a sec	
  
  return 0;       // format c: anyone?
}


//Gets a number value next to "Name"
int file::GetNumberValue(fstream * File, char * Name)
{
 int NameLen = strlen(Name);  //get length of Name
 char NameLoaded[20];         //this will be the Name we will load in
 int Number=0;                //this will be the number we load
 
//while the loaded name isn't equal to the name we want...
do{
   *File >> NameLoaded; //... keep loading the name!
   if(File->eof()) //if we have reached the end of file
      {
       return -1; //return -1, value not found
      }
   }while( strcmp(Name, NameLoaded) != 0 );
  
 *File >> Number; //now load the number next to the name

return Number; //and return it
}

//Gets a string value next to "Name"
char * file::GetStringValue(fstream * File, char * Name)
{
 int NameLen = strlen(Name);  //get length of name
 char NameLoaded[20];         //this will be the Name we will load in
 char String[100];            //this will be the string we load
 
 //while the loaded name isn't equal to the name we want...
 do{
   *File >> NameLoaded; //... keep loading the name!
   if(File->eof())//if we have reached the end of file
      {
       return 0;  //return 0, value not found
      }
  } while( strcmp(Name, NameLoaded) != 0 );
  
 *File >> String; //now load the string next to the name

return String; //and return it
}

//this basically goes to the specified section, marked by a number
bool file::GotoNo(fstream * File, int iNumber, bool bStratFromBOF)
{
 char cNumber[10];          //this is the character string of the number we're going to
 char cNumberLoaded[10];    //this is the character string of the number we will load
 
 itoa(iNumber, cNumber, 10);//first, change the integer number to character string
 strcat(cNumber, ".");      //and add a dot -> 3. (marks section instead of data)
  
 if(bStratFromBOF)          //if we should start from beggining of file...
  File->seekg(0);           //go to beggining of file!
 
 //while the loaded number isn't the same as the number we want to go to...
 do{
   *File >> cNumberLoaded;  //...keep loading!
   if(File->eof())          //if we reach end of file
      {
       return 0;            //return 0, section not found
      }
  } while( strcmp(cNumber, cNumberLoaded) != 0 );
  
 return 1;                  //we found the section, return 1!
}


If you're still here - would you advise me to leave it as is, or change char to string? I work with C++ but I just like the warm and fuzzy feel of chars :) Thanks for feedback!

Share this post


Link to post
Share on other sites
Advertisement
When using >> and << operators with files you can get into some trouble. I have seen it a few times.
A better option is to use fin.getline(char *buf, size_t len, char termchar) and then parse the string buf.

Share this post


Link to post
Share on other sites
First off, std::string is easier to use, but if you're comfortable with char * and know all the pitfalls, then feel free to keep using it.

The extraction operator (operator >>) on a std::istream breaks by whitespace. The way data is extracted and the amount extracted depend on the type of the variable passed to it. To read strings containing whitespace, use either std::istream::getline() or std::getline(std::istream &, std::string & [, char delim]).

Share this post


Link to post
Share on other sites
Quote:
Original post by auLucifer
When using >> and << operators with files you can get into some trouble. I have seen it a few times.


Would you be kind enough to specify that? I would really like to know what kind of trouble I might face.

Share this post


Link to post
Share on other sites
Quote:
Original post by Koobazaur
Quote:
Original post by auLucifer
When using >> and << operators with files you can get into some trouble. I have seen it a few times.


Would you be kind enough to specify that? I would really like to know what kind of trouble I might face.


This is rubbish its just lack of knowledge, the insertion/extraction operators do formatted I/O they do basic parsing to and from the correct type.

Any ways back to the topic when people talk about "chunks" in the context of file loading they are generally referring to binary files not text files which is what your dealing with currently. To be honset your file format is abit complicated than it really needs to be, if you want to keep it that way then aleast get rid of the numbering.

Also your code is overly complicated when there are much more elegant & simpler solutions here is 1 example:

test.txt

Count: 3

Name: Cookie_Gun
Accuracy: 5
Range: 40
SpriteID: 1
OrbX: 10
OrbY: 10
Cost: 40

Name: Snarl_Snarl
Accuracy: 10
Range: 80
SpriteID: 2
OrbX: 30
OrbY: 30
Cost: 120

Name: W00t_Cannon
Accuracy: 15
Range: 8845
SpriteID: 2
OrbX: 30
OrbY: 30
Cost: 70




#include <istream>
#include <ostream>
#include <sstream>
#include <string>

template < typename T >
struct point2 {
T x, y;
};

struct sprite {

typedef point2<int> point2i;

std::string name;
point2i pos;
int id;
int accuracy;
int range;
int cost;

};

//extraction operator, stream out
std::ostream&
operator<<(std::ostream& os, const sprite& x) {
std::ostringstream s;
s.flags(os.flags());
s.imbue(os.getloc());
s.precision(os.precision());

s << "Name: " << x.name << '\n';
s << "Accuracy: " << x.accuracy << '\n';
s << "Range: " << x.range << '\n';
s << "SpriteID: " << x.id << '\n';
s << "OrbX: " << x.pos.x << '\n';
s << "OrbY: " << x.pos.y << '\n';
s << "Cost: " << x.cost << '\n';

return os << s.str();
}

//insertion operator, stream in
std::istream&
operator>>(std::istream& is, sprite& x) {

std::string buf;
sprite temp_sprite;

is >> buf;

if(buf == "Name:") {
is >> temp_sprite.name >> buf;
if(buf == "Accuracy:") {
is >> temp_sprite.accuracy >> buf;
if(buf == "Range:") {
is >> temp_sprite.range >> buf;
if(buf == "SpriteID:") {
is >> temp_sprite.id >> buf;
if(buf == "OrbX:") {
is >> temp_sprite.pos.x >> buf;
if(buf == "OrbY:") {
is >> temp_sprite.pos.y >> buf;
if(buf == "Cost:") {
is >> temp_sprite.cost;
x = temp_sprite;
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);
} else
is.setstate(std::ios_base::failbit);

return is;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <cstdlib>
#include <algorithm>
#include <iterator>
#include <vector>
#include <fstream>
#include <iostream>

int main() {

typedef std::vector<sprite> sprites;

std::ifstream ifs("test.txt");

if(!ifs) {
std::cerr << "could not open file\n";
return EXIT_FAILURE;
}


std::string temp;

ifs >> temp;

if(temp != "Count:") {
std::cerr << "messed up file\n";
return EXIT_FAILURE;
}

sprites ss;

sprites::size_type n = 0;

ifs >> n;

ss.reserve(n);

std::copy(std::istream_iterator<sprite>(ifs),
std::istream_iterator<sprite>(),
std::back_inserter(ss));

std::copy(ss.begin(), ss.end(),
std::ostream_iterator<sprite>(std::cout, "\n"));

}

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
Any ways back to the topic when people talk about "chunks" in the context of file loading they are generally referring to binary files not text files which is what your dealing with currently.


Right. I think the OP was looking for the term "tokens" instead. As Oluseyi was saying, the input is 'tokenized' by being broken on whitespace.

Quote:
To be honset your file format is abit complicated than it really needs to be, if you want to keep it that way then aleast get rid of the numbering.


Also right. You should try to avoid putting in redundant things, especially for a text format, except to the extent that they're needed to detect file corruption. Numbering items is quite redundant since the numbers won't be used by the objects in memory, and it's already quite clear where one entry ends and the next begins. And what are you going to do if the numbering is out of order?

And yes, there are indeed much simpler ways to go about doing the actual parsing. Although I feel compelled to rewrite that >> operator which is suffering from a nasty case of arrow anti-pattern:


template <typename T>
std::istream& get_item(std::istream& is, const std::string& expected_label, T& field) {
std::string actual_label;
is >> actual_label;
if (actual_label != expected_label) {
is.setstate(std::ios_base::failbit);
} else {
is >> field;
}
return is;
}

//insertion operator, stream in
std::istream& operator>>(std::istream& is, sprite& x) {
sprite temp_sprite;
// This could perhaps be refined further by some exception logic
// or by using an object that wraps istream and provides get_item as a member
// (or just making a sprite constructor that accepts an istream, instead)
// The parallelism in logic could be avoided by representing the sprite as
// a std::map instead, although type information would be lost. If you want
// to do things that way then a dynamically typed language may be more
// appropriate :)
is = get_item(is, "Name:", temp_sprite.name); if (is.fail()) return is;
is = get_item(is, "Accuracy:", temp_sprite.accuracy); if (is.fail()) return is;
is = get_item(is, "Range:", temp_sprite.range); if (is.fail()) return is;
is = get_item(is, "SpriteID:", temp_sprite.id); if (is.fail()) return is;
is = get_item(is, "OrbX:", temp_sprite.pos.x); if (is.fail()) return is;
is = get_item(is, "OrbY:", temp_sprite.pos.y); if (is.fail()) return is;
is = get_item(is, "Cost:", temp_sprite.cost); if (is.fail()) return is;
x = temp_sprite; return is;
}

Share this post


Link to post
Share on other sites
Let me suggest another small change for your file:

weapons.txt
------------------------------------------
weapon
Name: Cookie_Gun
Accuracy: 5
Range: 40
SpriteID: 1
OrbX: 10
OrbY: 10
Cost: 40
endweapon

weapon
Name: Snarl_Snarl
Accuracy: 10
Range: 80
SpriteID: 2
OrbX: 30
OrbY: 30
Cost: 120
endweapon

weapon
Name: W00t_Cannon
Accuracy: 15
Range: 8845
SpriteID: 2
OrbX: 30
OrbY: 30
Cost: 70
endweapon

END
--------------------------------------

That way you keep reading until you find the END keyword. No need to be counting the number of elements inside the files. The weapon/endweapon may help you.

Also, a simpler format, easier to parse but a little harder to read:

weapons.txt
------------------------------------------
weapon Cookie_Gun, 5, 40, 1, 10, 10, 40
weapon Snarl_Snarl, 10, 80, 2, 30, 30, 120
weapon W00t_Cannon, 15, 8845, 2, 30, 30, 70,
END
--------------------------------------

So, in a single line you have all the infor for a weapon.

Luck!
Guimo

Share this post


Link to post
Share on other sites
Thanks for the tips snk_kid! But I don't quite know what the last two lines mean


std::copy(std::istream_iterator<sprite>(ifs),
std::istream_iterator<sprite>(),
std::back_inserter(ss));

std::copy(ss.begin(), ss.end(),
std::ostream_iterator<sprite>(std::cout, "\n"));



Haven't seen std:copy or istream_iterator before and google isn't being very helpful :/

Share this post


Link to post
Share on other sites
Quote:
Original post by Oluseyi
Quote:
Original post by Koobazaur
Haven't seen std:copy or istream_iterator before...
std::copy
std::istream_iterator.



Lol, those are the exact sites I am browsing right now...
The istream_iterator seems kinda fuzzy, but I'm probably making it more complicated than it really is...


Just one more question snk_kid:

How can you call ss.reserve(n), ss.begin() and ss.end() when the sprites struct doesn't have these classes and it isn't a vector / list ?

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!