Archived

This topic is now archived and is closed to further replies.

Joker2000

file i/o error and more

Recommended Posts

Joker2000    122
For some reason I get an application-defined exception when this line of my code executes. while(!strcmp(playerName.c_str(), loadName.c_str())) The purpose of the routine is to search a file for the name the user enters, then load the CPlayer class with that data. Here's a little more of the routine.
CPlayer * CPlayer::LoadSavedPlayer(string loadName)
{
    ifstream loadPlayer("Players.dat", ios::in | ios::binary);

    CPlayer * cmpPlayer;
    loadPlayer.read((char *)&cmpPlayer, sizeof(CPlayer));
    while(!strcmp(playerName.c_str(), loadName.c_str()))
    {
        if (loadPlayer.eof())
        {
            cout << endl << "That player was not found.";
            getch();
            loadPlayer.close();
            return 0;
        }
        loadPlayer.read((char *)&cmpPlayer, sizeof(CPlayer));
    }
    // load the CPlayer class if match found
    loadPlayer.close();
    return cmpPlayer;
}

CPlayer * PromptNewPlayer()
{
    CPlayer * pTempPlayer;
    cout << endl << "Enter the name of the player you would like to load.";
    cout << endl;
    cin >> loadPlayer;
    pTempPlayer = pTempPlayer->LoadSavedPlayer(loadPlayer);
    return pTempPlayer;
}
 
Now, I don't think I have the file search/load class routine written correctly. How should I be doing this? --- Joker2000 Stevie Ray Vaughan - The Legend Edited by - Joker2000 on 7/24/00 10:08:56 PM

Share this post


Link to post
Share on other sites
a error,

CPlayer * PromptNewPlayer()
{
CPlayer * pTempPlayer;

.....

pTempPlayer = pTempPlayer->LoadSavedPlayer(loadPlayer); return pTempPlayer;
}

you dont allocate a object, you are using a uninitialized pointer.

CPlayer * pTempPlayer;

must (or can be)

CPlayer * pTempPlayer = new CPlayer;

Share this post


Link to post
Share on other sites
one more, string compare returns zero when they are equal

while(!strcmp(playerName.c_str(), loadName.c_str()))

must be(can be)

while( strcmp(playerName.c_str(), loadName.c_str()) != 0 )

or simply

while( strcmp(playerName.c_str(), loadName.c_str()) )

Share this post


Link to post
Share on other sites
Cyberdrek    100
Claus Hansen Ries:
    
while(!strcmp(playerName.c_str(), loadName.c_str()))
[/source]

this is a valid way of doing it also. It''s the exact same thing as doing

[source]
while( strcmp(playerName.c_str(), loadName.c_str()) != 0 )
[/source]

both methods are good but that not the problem.

Joker2000:

Here is where I get confused in reading your code:
[source]
CPlayer * PromptNewPlayer(){
CPlayer * pTempPlayer;
cout << endl << "Enter the name of the player you would like to load.";
cout << endl;
cin >> loadPlayer;
pTempPlayer = pTempPlayer->LoadSavedPlayer(loadPlayer);

return pTempPlayer;
}


Now in here, you try to write to loadPlayer which is openned for read and not write second, as Claus Hansen Ries was saying, your trying to write to an uninitialized pointers. Anyhow, this is what I can see at first hand. If that is not it, let us know...


Cyberdrek
Headhunter Soft
DLC Multimedia

Share this post


Link to post
Share on other sites
You got me for a moment =), BUT

Yes it is a vaild way, BUT THEY ARE NOT EQUAL METHODS =)

while(!strcmp(playerName.c_str(), loadName.c_str()))
Is the same as saying "while strcmp returns zero" (!ZERO -> TRUE)

however
while( strcmp(playerName.c_str(), loadName.c_str()) != 0 )
Is saying "while strcmp is not returning zero" (NOT EQUAL ZERO -> TRUE)

strcmp return zero when "playerName.c_str() == loadName.c_str()"

So he jump out of the "while" when the names are not equal, but he want to continue in the loop when the names aren't equal.




Edited by - Claus Hansen Ries on July 25, 2000 6:30:37 AM

Share this post


Link to post
Share on other sites
Grib    122
Problem 1 (though not really a problem) you are using stl::string
so why strcmp? while(PlayerName != LoadName) looks better, ain't confusing and *might* even be faster. Plus, it's safe string classes know thier own size, so they don't have to look for '\0'.

That being said, that ain't the problem. What happens when you do this?

        

class Dynamic {
int *Data;
int size;
public:
Dynamic(int s) : size(s), Data(new int[size]) {}
};

Dynamic a(20),b(30);

out.write((char *)&a,sizeof(Dynamic));
in.read((char *)&b, sizeof(Dynamic));

[/source]

Answer, <strong>pain</strong>. The first line writes, not the contents of the memory Data points to, but the address stored in
Data. When you read b.Data points to the memory that a allocated
for it's data. Chaging b changes a and vice versa.

Now, what do you suppose string does after all the handwaving and char_traits and so forth. Yep, a pointer to the heap, just like
Dyanamic above.

The easyest way around this is to read/write your records as ascii and overload << and >>. This does get slow and wastefull eventually, so you want a file record structure.

[source]

struct PlayerFileRec {
char playerName[80];
float x,y,x;
int health;
etc...
bool LoadSavedPlayer(istream &in,const string &loadName);
};
bool PlayerFileRec::LoadSavedPlayer(istream &in,const string &loadname) {
do {
in.read((char *)this,sizeof(PlayerFileRec));
if(loadName == playerName) return true;
} while(in.good());
return false;
}

// Add this method to CPlayer

CPlayer & CPlayer::Copy_From_FileRec(const PlayerFileRec &r) {
PlayerName = r.PlayerName;
x = r.x;
y = r.y;
etc..
return *this;
}

// Your new CPlayer::LoadSavedPlayer now looks like


bool CPlayer::LoadSavedPlayer(const string &loadName) {
ifstream in("Players.dat",ios::in | ios::binary);
PlayerFileRec rec;
bool status = rec.LoadFromFile(in,loadName);
if(status) {
Copy_From_FileRec(rec);
} else {
cerr << "Player " << loadName << " Not Found" << endl;
}
in.close();
return status;
}



The only tricky bit is you must now remeber to create a Copy_To_FileRec(PlayerFileRec &rec) that uses
strncpy and c_str as apropriate before writeing (using write(*,size)) to Player.dat.

Note also that the use of read((char *)this,sizeof(PlayerFileRec) might be a bit too hackish for some, and come to think of it, may not compile. The point is never use read and write on anything but pod (plain old data).

Edited by - Grib on July 25, 2000 1:54:27 PM

Edited by - Grib on July 25, 2000 1:56:10 PM

stupid tags, this time It'll work!

Edited by - Grib on July 25, 2000 1:58:58 PM

Share this post


Link to post
Share on other sites
Joker2000    122
Ok, I''ve slightly edited my code to fix some of the minor issues, so below I have made a few changes. I am very interested in Grib''s solution, but I''m not exactly sure how to do it. I know he pretty much listed the exact code I need to use, but I''m still looking over it, trying to understand everything. So I''ll probably be sending Grib an email or two to help me straighten things out. In the meantime, please feel free to post any and all solutions. I need the help!


    
CPlayer * CPlayer::LoadSavedPlayer(string loadName)
{
ifstream loadPlayer("Players.dat", ios::in | ios::binary);

CPlayer * cmpPlayer = new CPlayer("Empty", false, 0, 0, 0, 0, 0, 0);
loadPlayer.read((char *)&cmpPlayer, sizeof(CPlayer));
while(playerName != loadName)
{
if (loadPlayer.eof())
{
cout << endl << "That player was not found.";
getch();
loadPlayer.close();
return 0;
}
loadPlayer.read((char *)&cmpPlayer, sizeof(CPlayer));
}
// match found...load CPlayer data class

loadPlayer.close();
return cmpPlayer;
}

void CPlayer::SavePlayer(CPlayer * thePlayer, CWeapon * theWeapon)
{
ofstream savePlayer("Players.dat", ios::out | ios::app | ios::binary);

savePlayer.write((char *)&thePlayer, sizeof(CPlayer));
savePlayer.close();
}

CPlayer * PromptNewPlayer(void)
{
string loadPlayer;
CPlayer * pTempPlayer;

cout << endl << "Enter the name of the player you would like to load.";
cout << endl;
cin >> loadPlayer;
pTempPlayer = pTempPlayer->LoadSavedPlayer(loadPlayer);
break;
}



---
Joker2000
Stevie Ray Vaughan - The Legend

Share this post


Link to post
Share on other sites
Grib    122
quote:
Original post by Joker2000

Ok, I''ve slightly edited my code to fix some of the minor issues, so below I have made a few changes. I am very interested in Grib''s solution, but I''m not exactly sure how to do it. I know he pretty much listed the exact code I need to use, but I''m still looking over it, trying to understand everything. So I''ll probably be sending Grib an email or two to help me straighten things out. In the meantime, please feel free to post any and all solutions. I need the help!

---
Joker2000
Stevie Ray Vaughan - The Legend



Ok you still have the problem that you are using read/write on
classes that have pointers. read/write will save the value of the pointer, not what that pointer points to.
    
string astr;
ifstream in("filename.dat",ios::binary);
in.read((char *)&astr,sizeof(string));



will NEVER work, even if string is a member of a
class. It only works for classes have no pointers/references/static members,
have no virtual functions, and all member classes are the
same way.

I strongly recomend that the first version of your players.dat
file should be simple ascii text. load each member with >>
save each member with << just put whitespace between each member that you output, newlines after each record/class.

You can now edit your data files with a text editor, and spot errors that much more easily.

Share this post


Link to post
Share on other sites
Joker2000    122
You do realize that the whole point is to write the file in a format the user CANNOT read to prevent him from changing values and such, right? Will >> and << still "encrypt" them if I'm using ios::binary?

Well, I think I can pretty much say that the ios::binary won't do what I want as far as making the player data impossible to edit. I just tried it out using << and >> with straight ASCII text and it shows up normally. I know this is what Grib said I should do for my first version of Players.dat. So then I ask...what next?


---
Joker2000
Stevie Ray Vaughan - The Legend


Edited by - Joker2000 on July 25, 2000 5:58:51 PM

Share this post


Link to post
Share on other sites
Grib    122
quote:
Original post by Joker2000

You do realize that the whole point is to write the file in a format the user CANNOT read to prevent him from changing values and such, right? Will >> and << still "encrypt" them if I'm using ios::binary?


---
Joker2000
Stevie Ray Vaughan - The Legend




The user, for the time being, is you. Thus being able to edit,
and more importantly read save games is a good thing. Simply
writing your save games as binaries provides no difficulty
whatsoever to those who wish to cheat. It saves disk space and
is often faster, but provides no security. If you want to
prevent people from editing your files, use a checksum that's
difficult to guess, or, even better, use some form of
error-correcting code. This will confuse most kids with hex
editors and even add safety. Then, if you are still paranoid use
legal strength DES/TwoFish and obsfucate the key.

It might take a month or two before some determined kid has a
save game editor up. The thing is, get it working first, and
make it easy to work with.


Edited by - Grib on July 25, 2000 5:56:50 PM

Share this post


Link to post
Share on other sites
Joker2000    122
Ok, let's say I had the following code written for SavePlayer and LoadPlayer. SavePlayer works, but I'm having trouble writing a search algorithm for LoadPlayer. I've gone ahead and used Grib's ASCII method to see exactly what I'm doing.


void CPlayer::SavePlayer()
{
ofstream savePlayer("Players.dat", ios::out | ios::app);

savePlayer << "\"" << playerName << "\"" << " ";
savePlayer << hasWeapon << " ";
savePlayer << health << " ";
savePlayer << armor << " ";
savePlayer << pocketMoney << " ";
savePlayer << bankMoney << " ";
savePlayer << experience << " ";
savePlayer << skillLevel << endl << endl;
savePlayer.close();
}
CPlayer * CPlayer::LoadSavedPlayer(string loadName)
{
string pName;
bool hWeapon;
unsigned short health, armor, pMoney, bMoney, experience, skill;
ifstream loadPlayer("Players.dat");

while(loadName != playerName)
{
if (loadPlayer.eof())
{
cout << endl << "That player name could not be found.";
getch();
return 0;
}
// THIS IS WHERE I NEED THE FILE SEARCH COMMAND
}
THIS IS WHERE I LOAD THE DATA IF MATCH IS FOUND
}
CPlayer * PromptNewPlayer(void)
{
string loadPlayer;
CPlayer * pTempPlayer;

cout << "Name of player you would like to load.";
cout << endl;
cin >> loadPlayer;
pTempPlayer = pTempPlayer->LoadSavedPlayer(loadPlayer);
}

Notice on SavePlayer, the field delimiter is a blank space and quotes are around the player name.


---
Joker2000
Stevie Ray Vaughan - The Legend


Edited by - Joker2000 on July 25, 2000 12:49:50 AM

Share this post


Link to post
Share on other sites
Cyberdrek    100
quote:

Notice on SavePlayer, the field delimiter is a blank space and quotes are around the player name.



Only one little thing that I can see: you don''t need to put the spaces yourself. When I tried and then edited my Text File, the space delimiter was there. I didn''t have to do it manually...



Cyberdrek
Headhunter Soft
DLC Multimedia

Share this post


Link to post
Share on other sites