Sign in to follow this  
thallish

[SOLVED]problem with file I/O in C++ (i think)

Recommended Posts

thallish    202
hi i have started to write a small handler for my levels in the game im working on currently, but i have run into some problems and i dont know what they are. I have the relevant information put in a text file which i'm reading from one line after another, and depending on what the tag is the program should do different stuff. And then i use that collected data to present my level. Ok let me start out by showing the structure of the file:
LEVEL:	
1
NUMBEROFTEXTURES:
3
TEXTURES:
marble.jpg
yellowtech.bmp
panel2.jpg
MAPWIDTH:
5
MAPHEIGHT:
5
MAP:
0,0,0,0,0,
1,0,0,1,0,
0,0,0,0,2,
0,0,0,0,0,
1,1,2,0,0


And here is the routine i'm running
HRESULT cLevel::ReadFromFile(char* filename){

	ifstream fin(filename);

	char buffer[50];

	while(fin.getline(buffer,50)){

		if (buffer == "LEVEL:")
		{
			fin.getline(buffer, 2);
			m_levelNumber = (int)buffer;		// TODO: Find correct way to extract integer value from char
		}

		if (buffer == "NUMBEROFTEXTURES:")
		{
			fin.getline(buffer, 2);
			m_numberOfTextures = (int)buffer;		// TODO: Find correct way to extract integer value from char
		}

		if (buffer == "TEXTURES:")
		{
			for (int i = 0; i < m_numberOfTextures; i++)
			{
				fin.getline(buffer, 50);
				m_textures.push_back((string)buffer); // is this a problem
			}
		}

		if (buffer == "MAPWIDTH:")
		{
			fin.getline(buffer, 2);
			m_TileMapWidth = (int)buffer;		// TODO: Find correct way to extract integer value from char
		}

		if (buffer == "MAPHEIGHT:")
		{
			fin.getline(buffer, 2);
			m_TileMapHeight = (int)buffer;		// TODO: Find correct way to extract integer value from char
		}

		if (buffer == "MAP:")
		{
			for (int i = 0; i < m_TileMapWidth; i++)
			{
				for (int j = 0; j < m_TileMapHeight; j++)
				{
					fin.get(buffer,1);
					m_pTileMap[i][j] = (int)buffer;	// TODO: Find correct way to extract integer value from char
					fin.get(buffer,1);
				}
			}
		}
	}
	fin.close();
	return S_OK;
}


now this routine i've written in a console application first, made sure it worked and adjusted it. But i believe something has happened so it is not working correct. Now i have some private members in my class i'm using to store the necessary level info
int m_levelNumber;
int m_numberOfTextures;
	
vector<string> m_textures;
	
int m_TileMapHeight, m_TileMapWidth;
int m_NumberOfLayers;
	
int **m_pTileMap;

cTile m_tiles;


And i'm also gonna show the functions im using to display the sprites on screen
// this function loads all textures used on level
HRESULT cLevel::InitLevel(LPDIRECT3DDEVICE9 device)
{

if (FAILED(m_tiles.CreateSprite(device)))
	{
		MessageBox(NULL, "Couldn't create sprite!!", "ERROR", MB_ICONERROR);
		return E_FAIL;
	}


        // load all textures
        for (int i = 0; i < m_numberOfTextures; i++)
	{
		if (FAILED(m_tiles.LoadTexture(m_textures[i].c_str(), device, 64, 64)))
		{
			MessageBox(NULL, "Couldn't load texture!!", "ERROR", MB_ICONERROR);
			return E_FAIL;
		}
	}
	return S_OK;
}


// the preliminary render function
HRESULT cLevel::RenderLevel(LPDIRECT3DDEVICE9 device){

	// draw map
	for (int i = 0; i < m_TileMapWidth; i++)
	{
		for (int j = 0; j < m_TileMapHeight; j++)
		{
			if (FAILED(m_tiles.DrawTile(device,(float)i,(float)j, m_pTileMap[i][j],64.0f,64.0f,D3DCOLOR_RGBA(255,255,255,255))))
			{
				MessageBox(NULL, "Couldn't draw tile!!", "ERROR", MB_ICONERROR);
				return E_FAIL;
			}
		}
	}
	return S_OK;
}


/////// the load and draw functions in cTile ///////

// loads a texture into vector associated with the cTile object
HRESULT cTile::LoadTexture(const char* filename, LPDIRECT3DDEVICE9 device, UINT width, UINT height){

	char * tmp;
	strcpy(tmp,filename);

	if (FAILED(CreateTexture(tmp, device,  width, height)))
	{
		MessageBox(NULL, "Couldn't create texture", "ERROR", MB_ICONERROR);
		return E_FAIL;
	}

	m_TextureVector.push_back(m_pTexture);

	return S_OK;
}

// draws tile
HRESULT cTile::DrawTile(LPDIRECT3DDEVICE9 device, float xPosition, float yPosition, 
						int textureNumber, float tileWidth, float tileHeight, D3DCOLOR blendColor)
{
	if (SUCCEEDED(m_pSprite->Begin(D3DXSPRITE_ALPHABLEND)))
	{
		if(FAILED(m_pSprite->Draw(GetPointerToTextureInVector(textureNumber), 
			NULL, NULL, 
			&D3DXVECTOR3(xPosition*64.0f,yPosition*64.0f, 0.0f), 
			blendColor)))
		{
			MessageBox(NULL, "DrawSprite failed", "ERROR", MB_ICONERROR);
			return E_FAIL;
		}
		m_pSprite->End();
	}
	return S_OK;
}



Now i think the problem is either a string-char[] conversion problem or something along that line (maybe the (int) buffer loses some data and is that really the best way to do it?) or maybe it's a problem with function arguments? Any help will be appreciated /regards thallish [Edited by - thallish on August 4, 2005 6:55:49 AM]

Share this post


Link to post
Share on other sites
HellRiZZer    308
That's a bit problematic. The way I would've gone is save map tiles as letters for each tile (e.g a for 0, b for 1, etc if you have less than 5 types of tiles) and then scan character by offset and translate of from 'a'. For example, if the tile value is 2(c), the value would be 'c' - 'a' = 2

Share this post


Link to post
Share on other sites
grekster    640
havent done this for a while but IIRc you cant compare char arrays like this

buffer == "LEVEL:"

you need to use strcmp

if(strcmp(buffer,"LEVEL:") != 0)

Is that correct?

Share this post


Link to post
Share on other sites
twanvl    512
Since you are already using strings and iostream you should get rid of the char arrays completely:

istream& in;
while (in.good()) {
std::string line;
std::getline(in, line); // use getline(stream, string) to read std::strings
if (line == "SOMETHING") {
// ...
} else if (line == "SOMETHING ELSE") {
// ...
} else {
// It is often better to have an indication of an error,
// instead of just ignoring it. Otherwise typos for example
// will lead to debugging.
throw std::exception("There is an error in the file");
}
}

Share this post


Link to post
Share on other sites
thallish    202
thanks for the replies

when i went to bed after posting i started to thinking that

if (buffer == "LEVEL:") might be a problem. I'll just check.
I didn't know about the string way but ill also look into that,.

Now as i'm asking, the way i'm converting my string object to char* is by first using the member method c_str(), but as this returns a const char* i'm using strcpy(char*, const char*). Is this a proper way of doing it?

EDIT: And also is there a function that parses the integer value from a string? i have looked but cant seem to find one associated with the string object

regards
thallish

[Edited by - thallish on August 4, 2005 1:37:44 AM]

Share this post


Link to post
Share on other sites
Zahlman    1682
Quote:
Original post by thallish
if (buffer == "LEVEL:") might be a problem. I'll just check.
I didn't know about the string way but ill also look into that,.


Please, use std::string when you can, char* when you need to (an exception can be made for char*'s that will be used as "symbols"; i.e. that are const; and if you need to concatenate two of them, for example, you would do it by copying one to a std::string and appending the other).

Quote:
Now as i'm asking, the way i'm converting my string object to char* is by first using the member method c_str(), but as this returns a const char* i'm using strcpy(char*, const char*). Is this a proper way of doing it?


Yes, as long as you can be sure that the dest char* points at a memory allocation that is big enough to hold the data (result of the c_str() call). Also note that std::string is capable of holding a string with embedded \0 characters in the middle, but strcpy (and "char* strings" in general) will stop at the first one of those. And again, please only do this conversion where you have to (e.g. for passing a string to an API that was originally intended for C usage). And in these cases, if you *know* that the function will not change the passed-in data, you may consider a const_cast as well, instead of making a copy.

The basic idea is that outside code is not supposed to directly change the data that is "owned" by the std::string, because it could cause inconsistencies - for example, it may change the length of the string, and then the std::string's internal length count would be corrupted. Thus the std::string only provides a pointer to "const" data, so that the compiler will stop you if you try to make changes via that pointer. If you make your own copy of the data (and dispose of that copy properly later), though, you are of course free to do what you will with the copy.

Share this post


Link to post
Share on other sites
thallish    202
ok now i've gotten it to do what it should do. I'll just post the code here so you guys can see how it's done and maybe come with some pointers(no pun intended) on what can be improved.


//// the file !!!! ////

LEVEL:
1
NUMBEROFTEXTURES:
3
TEXTURES:
panel2.jpg
marble.jpg
yellowtech.bmp
MAPWIDTH:
5
MAPHEIGHT:
6
MAP:
00000
10010
00002
00000
11200
12201

//// the method !!!! ////
HRESULT cLevel::ReadFromFile(char* filename){

ifstream fin(filename);

while(fin.good()){

string line;
getline(fin,line);

if (line == "LEVEL:")
{
getline(fin, line);
m_levelNumber = atoi(line.c_str());

}
if (line == "NUMBEROFTEXTURES:")
{
getline(fin, line);
m_numberOfTextures = atoi(line.c_str());

}
if (line == "TEXTURES:")
{
for (int i = 0; i < m_numberOfTextures; i++)
{
getline(fin, line);
m_textures.push_back(line);
}

}
if (line == "MAPWIDTH:")
{
getline(fin, line);
m_TileMapWidth = atoi(line.c_str());

}
if (line == "MAPHEIGHT:")
{
getline(fin, line);
m_TileMapHeight = atoi(line.c_str());

}
if (line == "MAP:")
{
// creating tile map on runtime
m_pTileMap = new int*[m_TileMapHeight];

if (m_pTileMap != NULL)
{
for (int i = 0; i < m_TileMapHeight; i++)
{
m_pTileMap[i] = new int[m_TileMapWidth];
}
}

char c = '0';

for (int i = 0; i < m_TileMapHeight; i++)
{
for (int j = 0; j < m_TileMapWidth; j++)
{
c = fin.get();
const char tmp = c;
m_pTileMap[j][i] = atoi(&tmp);
}
fin.ignore();
}
}
}
fin.close();
return S_OK;
}



thx for the help

regards
thallish

Share this post


Link to post
Share on other sites
Enigma    1410
Danger, Will Robinson!
c = fin.get();
const char tmp = c;
m_pTileMap[j][i] = atoi(&tmp);

You're passing a pointer to a single character to a function that expects a non-numeric character terminated character array (i.e. a character array which contains a number followed by at least one character which cannot be part of the number). This means that the result of atoi is undefined. Since you're only dealing with single characters you could use:
c = fin.get();
m_pTileMap[j][i] = c - '0';

Or, you could use the extraction operators (plus a vector instead of all that dynamic allocation which will need deleting):
std::vector< std::vector< int > > m_pTileMap;

if (line == "MAP:")
{
// creating tile map on runtime
m_pTileMap.resize(m_TileMapHeight);
for (int i = 0; i < m_TileMapHeight; i++)
{
m_pTileMap[i].resize(m_TileMapWidth);
}
for (int i = 0; i < m_TileMapHeight; i++)
{
for (int j = 0; j < m_TileMapWidth; j++)
{
// read an integer from the stream
if (!(fin >> m_pTileMap[j][i]))
{
// not an integer
// ill-formed input
// error out somehow
}
// extract everything up-to and including the next comma
if (i != m_TileMapHeight && j != m_TileMapWidth)
{
fin.ignore(std::numeric_limits< std::streamsize >::max(), ',');
}
}
}
}


Enigma

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