Can you read data from a file on which data was written manually?

Hello!

I'm Currently coding a game and am running into a small issue.

In order to render levels, maps or whatever you want to call it, I use a Map class. The data is written directly in the class constructor in such a manner:

First I created a private structure Level which holds data about the level:
1
2
3
4
5
6
7
8
9
10
11
 struct Level
		{
			int level;
			string backgroundPath;
			string foregroundPath;
			SDL_Rect sourceRec;
			SDL_Rect playerSpawn;
			SDL_Rect walkable;
			vector<SDL_Rect> obstacles;
			vector<Tp> teleporters;
		};


Where Tp is another privvate structure:
1
2
3
4
5
6
7
 struct Tp
		{
			int tpnum;
			SDL_Rect teleporter;
			int leadsToLvlNum;
			SDL_Rect tpCoordinates;
		};


So What I do to make the data available to the game is to put an array of a Level structure object in the Map class constructor.
Here is what I mean:
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
 Map::Map()
{
	currentLevel = 0;	//Changes the first level that's loaded
	SDL_Rect *obs = new SDL_Rect;
	SDL_Rect *teleporter = new SDL_Rect;
	Tp *tp = new Tp;


	// LEVEL 0 DATA_TEST LEVEL;
	level[0].level = 0;
	level[0].backgroundPath = "textures/lvl0.png";
	level[0].foregroundPath = "textures/lvl0obst.png";
	level[0].sourceRec.x = 0;
	level[0].sourceRec.y = 0;
	level[0].sourceRec.h = 640;
	level[0].sourceRec.w = 640;
	level[0].playerSpawn.x = 50;
	level[0].playerSpawn.y = 10;
	level[0].playerSpawn.h = 160;
	level[0].playerSpawn.w = 160;
	level[0].walkable.x = 80;
	level[0].walkable.y = 80;
	level[0].walkable.h = 560;
	level[0].walkable.w = 560;
	level[0].teleporters.clear();

	obs->x = 325;
	obs->y = 241;
	obs->h = 75;
	obs->w = 75;

	level[0].obstacles.push_back(*obs);

	tp->tpnum = 0;
	tp->teleporter.x = 513;
	tp->teleporter.y = 353;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 150, 150, 160, 160 };
	tp->leadsToLvlNum = 1;

	level[0].teleporters.push_back(*tp);


	//LEVEL 1 DATA_HOUSE MAP;
	level[1].level = 1;
	level[1].backgroundPath = "textures/lvl1.png";
	level[1].foregroundPath = "textures/lvl1obst.png";
	level[1].sourceRec.x = 0;
	level[1].sourceRec.y = 0;
	level[1].sourceRec.h = 640;
	level[1].sourceRec.w = 640;
	level[1].playerSpawn.x = 380;
	level[1].playerSpawn.y = 200;
	level[1].playerSpawn.h = 160;
	level[1].playerSpawn.w = 160;
	level[1].walkable.x = 45;
	level[1].walkable.y = 240;
	level[1].walkable.h = 400;
	level[1].walkable.w = 550;

	obs->x = 60;
	obs->y = 245;
	obs->h = 35;
	obs->w = 105;

	level[1].obstacles.push_back(*obs);

	obs->x = 60;
	obs->y = 355;
	obs->h = 45;
	obs->w = 110;

	level[1].obstacles.push_back(*obs);

	obs->x = 480;
	obs->y = 255;
	obs->h = 100;
	obs->w = 95;

	level[1].obstacles.push_back(*obs);

	obs->x = 540;
	obs->y = 455;
	obs->h = 95;
	obs->w = 55;

	level[1].obstacles.push_back(*obs);

	tp->tpnum = 0;
	tp->teleporter.x = 320;
	tp->teleporter.y = 620;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 250, 480, 160, 160 };
	tp->leadsToLvlNum = 2;

	level[1].teleporters.push_back(*tp);

	//OUTSIDE HOUSE MAP DATA
	level[2].level = 2;
	level[2].backgroundPath = "textures/lvl2.png";
	level[2].foregroundPath = "textures/lvl2obst.png";
	level[2].sourceRec.x = 0;
	level[2].sourceRec.y = 0;
	level[2].sourceRec.h = 640;
	level[2].sourceRec.w = 640;
	level[2].playerSpawn.x = 130;
	level[2].playerSpawn.y = 70;
	level[2].playerSpawn.h = 160;
	level[2].playerSpawn.w = 160;
	level[2].walkable.x = 0;
	level[2].walkable.y = 0;
	level[2].walkable.h = 640;
	level[2].walkable.w = 640;

	obs->x = 0;
	obs->y = 0;
	obs->h = 160;
	obs->w = 295;

	level[2].obstacles.push_back(*obs);

	tp->tpnum = 0;
	tp->teleporter.x = 125;
	tp->teleporter.y = 185;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 130, 70, 160, 160 };
	tp->leadsToLvlNum = 1;

	level[2].teleporters.push_back(*tp);


	delete obs;
	delete teleporter;
	delete tp;

	loadMap(currentLevel, level);
};


MY QUESTION:
Is it possible to have the program read data, in this case all the data stored in the level array, BUT having manually written that data on the file?

Or do I have to create a small program to store data on a file, and then use a function in game that would read it?

Thanks in advance,

Regards,

Hugo.
Last edited on
Hello hoogo,

Basically yes. When you manually create such a file you first have to know what information will be stored in the file. If there are strings should they go first or last? Do the strings contain a space or considered one word?

A string like "oneword" can be separated by a space and easily worked with. But if the string would be "two words". Formatted input of "inFile >> name;" would read until the first white space only reading the "two" and stopping leaving "words" for the next input and that is not what you want.

In the input file it works better as two words,(next input). No space needed around the comma. This way you can use std::getline(inFile, name, ','); and it will extract everything up to and including the comma then discard the comma.

It all comes down to what information you need and how you will need to read it.

I am thing that you could start with the strings, ending each string with a comma followed by numeric values separated by a space. You could either put all this on one line or each piece of info on a separate line. To start with a separate line might make it easier to keep track of what you have entered.

Hope that helps,

Andy
Thank you Andy for your answer, Actually yes the only strings I need to have are those you can see above, and they are paths so they will never contain spaces e.g. "textures/lvl1obst.png".

I'm wondering though, as you can see above,
1
2
3
4
5
6
        obs->x = 60;
	obs->y = 245;
	obs->h = 35;
	obs->w = 105;

	level[1].obstacles.push_back(*obs);


This is the data for one obstacle/hitbox on the map and teleporters work the same.
If I don't have a fixed number of them PER LEVEL, how do I manage reading data from the file without having to scan the file for certain markers that would indicate when one level's data has been fully loaded?

So this would Mean I can't use a for loop to load data, correct?
e.g.
1
2
3
4
5
6
for (int count = 0; count < maxLevels; count ++)
{
     level[count].XXXX = data from file;
     level[count].YYYY= data from file;
     ...
}



Last edited on
Hello hoogo,

I wanted to mention this before I was interrupted. "structs" by default are "public" by default unless you use the key word "private" or "protected". The opposite is a class which is "private" by default until you use the key word "public".

In the first bit of code I have concerns the way it is written. First I do not see how "level[1].obstacles" is defined which makes me wonder if "*obs" is the correct use.

The for loop can work, but the question is there more than one record to be read or just one? If there is only one the for loop works. If there is more than one a while loop would work better as:
1
2
3
4
5
6
while (inFile >> firstPath)
{
    inFile >> secondPath;
    inFile >> number;
    inFile >> number;
}

Should what is on the rhs of ">>" require a subscript you could do something like:
1
2
3
4
5
6
7
8
std::size_t index{};

while (inFile >> something[index].firstPath)
{
    inFile >> something[index].secondPath;
    inFile >> something[index].number;
    inFile >> something[index++].number;
}

In the last line you add 1 to "index" when it is finished being used.

For the for loop the lines that read the file are done in the same format as in the above while loop.

I do have some questions based on what I do not see and I am not sure yet what to ask for. One I do know is what is "SDL_Rect"? Where is it defined or comes from and how is it defined?

Another one where is "level[?]" defined and how? At the moment all I see is in the struct "Level" there is "int level", which should be "int level{};". the exceptions are "std::strings", "std::vector", "std::map" and other containers which are empty when defined until you add to them. All other variables should be initialized when defined.

I will look into "Map::Map" deeper and see what else I might find.

Hope that helps,

Andy
Let me post the whole Map class for you, if that makes things easier
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
#ifndef MAP_H
#define MAP_H

#include "Game.h"

class Map
{
	private:

		struct Tp
		{
			int tpnum;
			SDL_Rect teleporter;
			int leadsToLvlNum;
			SDL_Rect tpCoordinates;
		};

		struct Level
		{
			int level;
			string backgroundPath;
			string foregroundPath;
			SDL_Rect sourceRec;
			SDL_Rect playerSpawn;
			SDL_Rect walkable;
			vector<SDL_Rect> obstacles;
			vector<Tp> teleporters;
		};
	

		int currentLevel;
		Level *level = new Level[3];

		SDL_Rect sourceR;

		SDL_Rect destinationR;
		SDL_Rect walkableArea;

		SDL_Texture* backgroundTexture;
		SDL_Texture* foregroundTexture;

	public:
		Map();
		~Map();

		void loadMap(int, Level* lvl);
		void drawBackgroundMap();
		void drawForegroundMap();
		void updateMapPosition(int x, int y);
		bool isWalkable(SDL_Rect rect);
		bool steppedOnTeleporter(SDL_Rect);
		Teleport getTeleporter(SDL_Rect);
		SDL_Rect getPlayerSpawn();
		SDL_Rect getPlayerSpawnAfterTeleport(int);

		void loadLevel(int);
};

#endif 


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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
Map::Map()
{
	currentLevel = 0;	//Changes the first level that's loaded
	SDL_Rect *obs = new SDL_Rect;
	SDL_Rect *teleporter = new SDL_Rect;
	Tp *tp = new Tp;


	// LEVEL 0 DATA_TEST LEVEL;
	level[0].level = 0;                                                     //Number of the level
	level[0].backgroundPath = "textures/lvl0.png";           //Path to level texture
	level[0].foregroundPath = "textures/lvl0obst.png";      //Path to obstacle texture for that level
	level[0].sourceRec.x = 0;                                          //sourceRec is the rendering rectangle for the level
	level[0].sourceRec.y = 0;
	level[0].sourceRec.h = 640;
	level[0].sourceRec.w = 640;
	level[0].playerSpawn.x = 50;                                    //rendering rectangle for player
	level[0].playerSpawn.y = 10;
	level[0].playerSpawn.h = 160;
	level[0].playerSpawn.w = 160;
	level[0].walkable.x = 80;                                          //rectangle holding info on where player can walk
	level[0].walkable.y = 80;
	level[0].walkable.h = 560;
	level[0].walkable.w = 560;
	level[0].teleporters.clear();                                       

	obs->x = 325;                                                        //this is one hitbox
	obs->y = 241;
	obs->h = 75;
	obs->w = 75;

	level[0].obstacles.push_back(*obs);                        //hitbox 1 is pushed back into vector for level 0

	tp->tpnum = 0;                                                     //This the teleporter's number
	tp->teleporter.x = 513;                                          //Rectangle holding info about teleporter rendering
	tp->teleporter.y = 353;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 150, 150, 160, 160 };           //rectangle holding info on where to teleport the player
	tp->leadsToLvlNum = 1;                                         //which level the teleporter gets you to

	level[0].teleporters.push_back(*tp);                        //pushes back this teleporter to vector for level 0


	//LEVEL 1 DATA_HOUSE MAP;                                 //NEXT LEVEL
	level[1].level = 1;
	level[1].backgroundPath = "textures/lvl1.png";
	level[1].foregroundPath = "textures/lvl1obst.png";
	level[1].sourceRec.x = 0;
	level[1].sourceRec.y = 0;
	level[1].sourceRec.h = 640;
	level[1].sourceRec.w = 640;
	level[1].playerSpawn.x = 380;
	level[1].playerSpawn.y = 200;
	level[1].playerSpawn.h = 160;
	level[1].playerSpawn.w = 160;
	level[1].walkable.x = 45;
	level[1].walkable.y = 240;
	level[1].walkable.h = 400;
	level[1].walkable.w = 550;

	obs->x = 60;
	obs->y = 245;
	obs->h = 35;
	obs->w = 105;

	level[1].obstacles.push_back(*obs);

	obs->x = 60;
	obs->y = 355;
	obs->h = 45;
	obs->w = 110;

	level[1].obstacles.push_back(*obs);

	obs->x = 480;
	obs->y = 255;
	obs->h = 100;
	obs->w = 95;

	level[1].obstacles.push_back(*obs);

	obs->x = 540;
	obs->y = 455;
	obs->h = 95;
	obs->w = 55;

	level[1].obstacles.push_back(*obs);

	tp->tpnum = 0;
	tp->teleporter.x = 320;
	tp->teleporter.y = 620;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 250, 480, 160, 160 };
	tp->leadsToLvlNum = 2;

	level[1].teleporters.push_back(*tp);

	//OUTSIDE HOUSE MAP DATA
	level[2].level = 2;
	level[2].backgroundPath = "textures/lvl2.png";
	level[2].foregroundPath = "textures/lvl2obst.png";
	level[2].sourceRec.x = 0;
	level[2].sourceRec.y = 0;
	level[2].sourceRec.h = 640;
	level[2].sourceRec.w = 640;
	level[2].playerSpawn.x = 130;
	level[2].playerSpawn.y = 70;
	level[2].playerSpawn.h = 160;
	level[2].playerSpawn.w = 160;
	level[2].walkable.x = 0;
	level[2].walkable.y = 0;
	level[2].walkable.h = 640;
	level[2].walkable.w = 640;

	obs->x = 0;
	obs->y = 0;
	obs->h = 160;
	obs->w = 295;

	level[2].obstacles.push_back(*obs);

	tp->tpnum = 0;
	tp->teleporter.x = 125;
	tp->teleporter.y = 185;
	tp->teleporter.w = 5;
	tp->teleporter.h = 5;
	tp->tpCoordinates = { 130, 70, 160, 160 };
	tp->leadsToLvlNum = 1;

	level[2].teleporters.push_back(*tp);


	delete obs;
	delete teleporter;
	delete tp;

	loadMap(currentLevel, level);
};


And SDL_Rect is part of the SDL2 library, it's a rectangle.
Assume you declare SDL_Rect rectangle a rectangle, rectangle can store 4 values, respectively x, y, w, and h, being x and y coordinates, height and width.
you can access them that way:
1
2
3
4
rectangle.x = ...;
rectangle.y = ...;
rectangle.h = ...;
rectangle.w = ...;


Regarding your question about the array of Level structures, you can see it defined in the private part of the Map class : Level *level = new Level[3];

So if you're trying to understand what my constructor does. I manually set the level data within the array of levels, so that i can access any member of the array while running the game, to change and render different levels etc.


EDIT: This might look messy, I do this as a hobby so I'd never say I'm an expert at C++. But I can confirm that the code is correct, the game runs just fine!
Last edited on
Hello hoogo,

Thank you for the class that helps.

I figure thet the "SDL_Rect" came from a header file I do not believe I have, but not a problem.

I will see what I can fine.

Andy
https://wiki.libsdl.org/SDL_Rect

This is ressource for SDL_Rect.

I'm going to try making it work with files on my side too.

Thank you a lot for your time Andy!
Okay Andy, I think I've found a way. It seems a bit wonkey but well it works. I tested it in a small program to not mess up my game first. This is all without the SDL_2 library since I couldn't really see an interest in including libraries for a small thing like this.

So here is my levelDataFile.txt:

____Level 0____
#0
_number: &0
_Background path: &textures/lvl0.png
_Foreground path: &textures/lvl0obst.png
_Render SDL_Rect:
x: &0
y: &0
h: &640
w: &640
_Player spawn position:
x: &50
y: &10
h: &160
w: &160
_Walkable area:
x: &80
y: &80
h: &560
w: &560

#1
*Obstacle 1:
x: &325
y: &241
h: &75
w: &75

#1
*Teleporter 1:
_Number: &0
_Coordinates:
x: &513
y: &353
h: &5
w: &5
_Teleports the player to:
x: &150
y: &150
h: &160
w: &160
_Leads to level number: &1


____Level 1____
#1
_number: &1
_Background path: &textures/lvl1.png
_Foreground path: &textures/lvl1obst.png
_Render SDL_Rect:
x: &0
y: &0
h: &640
w: &640
_Player spawn position:
x: &380
y: &200
h: &160
w: &160
_Walkable area:
x: &45
y: &240
h: &400
w: &550

#4
*Obstacle 1:
x: &60
y: &245
h: &35
w: &105

*Obstacle 2:
x: &60
y: &355
h: &45
w: &110

*Obstacle 3:
x: &480
y: &255
h: &100
w: &95

*Obstacle 4:
x: &540
y: &455
h: &95
w: &55

#1
Teleporter 1:
_Number: &0
_Coordinates:
x: &320
y: &620
h: &5
w: &5
_Teleports the player to:
x: &250
y: &480
h: &160
w: &160
_Leads to level number: &2



END OF FILE
#-1


You will notice what I did here. For each block (LEVELS/OBSTACLES/TELEPORTERS) you find "#" followed by the amount of said thing that's after it.
E.g. after level 0 you find #1, in level 1 there are 4 obstacles, hence you find #4 in front of the obstacles, and same for the one teleporter #.

What I do is run for loops with a counter which I get from reading the file after those # signs.
When the program reads -1 from the file, it means all the data has been read.


Here is my driver program to illustrate it:
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <stdlib.h>

using namespace std;


void pause();


	struct object
	{
		int x;
		int y;
		int h;
		int w;
	};
	
	struct teleporter
	{
		int number;
		int x;
		int y;
		int h;
		int w;
		int spawnPlayerX;
		int spawnPlayerY;
		int spawnPlayerH;
		int spawnPlayerW;
		int leadsToLevel;
	};

struct Level
		{
			int level;
			string backgroundPath;
			string foregroundPath;
			int sourceRectX;
			int sourceRectY;
			int sourceRectH;
			int sourceRectW;			
			int playerSpawnX;
			int playerSpawnY;
			int playerSpawnH;
			int playerSpawnW;
			int walkableX;
			int walkableY;
			int walkableH;
			int walkableW;
			vector<object> obstacles;
			vector<teleporter> teleporters;
		};

int main()
{
	const int MAX_LVL = 3;
		
	string temp;
	int tempNum;
	
	object *obst = new object;
	teleporter *tp = new teleporter;
	
	bool endOfFile = false;
		   
	Level* level = new Level[MAX_LVL];	   
		   
	
	ifstream levelDataFile;
	levelDataFile.open("level.data.txt", ios::in);
	
	if (levelDataFile.fail())
	{
		cout << "\n Failed to open level.data.txt!\n";
		pause();
		return(0);
	}
	else
	{
		cout << "\n File successfully opened!\n";
	}
	
	
	for (int count = 0; !endOfFile; count ++)
	{
		getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		tempNum = atoi(temp.c_str());
		
		if (tempNum == -1)
		{
			endOfFile = true;
			break;
		}
		
		cout << "\n\nLevel " << count << "...";
		
		//read number
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].level;
		
		//read paths
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].backgroundPath;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].foregroundPath;
		
		//read rendre rectangle
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].sourceRectX;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].sourceRectY;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].sourceRectH;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].sourceRectW;
		
		//read player spawn
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].playerSpawnX;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].playerSpawnY;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].playerSpawnH;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].playerSpawnW;
		
		//read walkable area
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].walkableX;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].walkableY;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].walkableH;
		getline(levelDataFile, temp, '&');
		levelDataFile >> level[count].walkableW;
		
		//read obstacles
		getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		tempNum = atoi(temp.c_str());
		
		if (tempNum > 0)
		{
			for (int index = 0; index < tempNum; index ++)
			{
				getline(levelDataFile, temp, '&');
				levelDataFile >> obst->x;
				getline(levelDataFile, temp, '&');
				levelDataFile >> obst->y;
				getline(levelDataFile, temp, '&');
				levelDataFile >> obst->h;
				getline(levelDataFile, temp, '&');
				levelDataFile >> obst->w;
				
				level[count].obstacles.push_back(*obst);
				cout << "\nLoaded one obstacle";
			}
		}
		
		//read teleporters
		getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		tempNum = atoi(temp.c_str());
		
		if (tempNum > 0)
		{
			for (int index = 0; index < tempNum; index ++)
			{
				//teleporter number
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->number;
				
				//coordinates of teleporter
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->x;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->y;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->h;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->w;
				
				//where player spawns
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->spawnPlayerX;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->spawnPlayerY;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->spawnPlayerH;
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->spawnPlayerW;
				
				//leads to level number
				getline(levelDataFile, temp, '&');
				levelDataFile >> tp->leadsToLevel;
				
				level[count].teleporters.push_back(*tp);
				cout << "\nLoaded one teleporter";
			}		
		}
		
		cout << "\nLevel " << count << " Successfully loaded!";
	}
	
	
	
	delete obst;
	delete tp;
	

	pause();
	
	return(0);
}


void pause()
{
	cout << "\n\nPress enter to continue...\n";
	cin.sync();
	cin.get();
	return;
}



You might be wondering why I bother looking around the file for "&" signs. In face, since it's manually written I want to keep as readeable as possible, if I'm looking to change a level's characteristic I want to be able to do it efficiently and fast.

However I don't know if that's the fastest way to do it, but regardless, this function would run only when the game initialises so it's ok I think.

Do you think this is a decent workaround, what are your thoughts?

Thanks you in advance!
Hello hoogo,

I will work on your new code. Most of it looks OK for now.

For your input file this is not quite what I had in mind. I was just thinking of the actual information that you need not all the extra that you have. To use what you have this is what I would do with the file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#0
number:0
Background path:textures/lvl0.png
Foreground path:textures/lvl0obst.png
Render SDL_Rect:
x:0
y:0
h:640
w:640
Player spawn position:
x:50
y:10
h:160
w:160
Walkable area:
x:80
y:80
h:560
w:560

Since it is there just use the colon as the delimiter. The " &" is just extra work that you do not need.

In main starting at line 101 change all the '&'s to ':'s. And just to be safe I would read the paths using "std::getline()".

My first thought is the use of (getline, formatted input, getline) may not work, but I have seen a case or two where it did work without any problems. That is something I will have to check into further.


Sorry I meant to send this earlier and got side tracked.

Since then I have been working on your program. Most was a good start, but needed a little extra.

Inside the for loop the file was being read, but not all of the code was reading it properly
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//read number
getline(levelDataFile, temp, ':');
levelDataFile >> level[count].level;

//read paths
getline(levelDataFile, temp, ':');
std::getline(levelDataFile, level[count].backgroundPath);
getline(levelDataFile, temp, ':');
std::getline(levelDataFile, level[count].foregroundPath);

//read render rectangle
std::getline(levelDataFile, temp);
getline(levelDataFile, temp, ':');
levelDataFile >> level[count].sourceRectX;
getline(levelDataFile, temp, ':');
levelDataFile >> level[count].sourceRectY;
getline(levelDataFile, temp, ':');
levelDataFile >> level[count].sourceRectH;
getline(levelDataFile, temp, ':');
levelDataFile >> level[count].sourceRectW;
levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>. 

The "//read render rectangle" section is what I have done for the rest of the sections. I am up to the part that deals with the vectors. Your extra headings means that some lines have to be dealt with separately. Easy to tell what I added, the lines start with "std::". The other is the "ignore" at the end of the section. This clears the file read buffer before the next "getline" otherwise it reads the "\n" left in the buffer.

I will have to finish it up tomorrow when I have more light to see with.

Hope that helps,

Andy
Hello hoogo,

This has taken awhile to work out and test, but I believe I have it working.

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <stdlib.h>
#include <limits>  // <--- For the ".ignore" lines
#include <cctype>  // <--- For std::stoi().

//using namespace std;  // <--- Best not to use.


void pause();


struct object
{
	int x;
	int y;
	int h;
	int w;
};

struct teleporter
{
	int number;
	int x;
	int y;
	int h;
	int w;
	int spawnPlayerX;
	int spawnPlayerY;
	int spawnPlayerH;
	int spawnPlayerW;
	int leadsToLevel;
};

struct Level
{
	int level;
	std::string backgroundPath;
	std::string foregroundPath;
	int sourceRectX;
	int sourceRectY;
	int sourceRectH;
	int sourceRectW;
	int playerSpawnX;
	int playerSpawnY;
	int playerSpawnH;
	int playerSpawnW;
	int walkableX;
	int walkableY;
	int walkableH;
	int walkableW;
	std::vector<object> obstacles;
	std::vector<teleporter> teleporters;
};

int main()
{
	const int MAX_LVL = 3;

	std::string temp;
	int tempNum;

	object *obst = new object;  // <--- Pointers OK, but not needed.
	teleporter *tp = new teleporter;

	bool endOfFile = false;

	//Level* level = new Level[MAX_LVL];
	Level level[MAX_LVL];  // <--- Changed to a regular array for testing. The pointer is not really needed.

	std::ifstream levelDataFile;
	levelDataFile.open("level.data.txt", std::ios::in);  // <--- It is an "ifstream" the "ios::in" is not needed.

	if (levelDataFile.fail())
	{
		std::cout << "\n Failed to open \"level.data.txt\"!\n";
		pause();
		// <--- An alternative
		//std::this_thread::sleep_for(std::chrono::seconds(5));  // Requires header files "chrono" and "thread"

		return(0);  // <--- Gives the wrong idea.
		//exit(1);  // <--- I like "exit" here. Thee 1, or any number > 0, means that something is wrong.
	}
	else
	{
		std::cout << "\n File successfully opened!\n";
	}


	for (int count = 0; !endOfFile; count++)
	{
		std::getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		tempNum = std::atoi(temp.c_str());

		if (tempNum == -1)
		{
			endOfFile = true;
			break;
		}

		std::cout << "\n\nLevel " << count << "...";

		//read number
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].level;

// <--- Down to here worked.

		//read paths
		std::getline(levelDataFile, temp, ':');
		std::getline(levelDataFile, level[count].backgroundPath);
		std::getline(levelDataFile, temp, ':');
		std::getline(levelDataFile, level[count].foregroundPath);
		//levelDataFile.clear();

		//read rendre rectangle
		std::getline(levelDataFile, temp);  // <--- Used to read a heading.
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].sourceRectX;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].sourceRectY;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].sourceRectH;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].sourceRectW;
		levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>. Used to clear the "\n" from the stream buffer.

		//read player spawn
		std::getline(levelDataFile, temp); // <--- Used to read a heading.
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].playerSpawnX;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].playerSpawnY;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].playerSpawnH;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].playerSpawnW;
		levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

		//read walkable area
		std::getline(levelDataFile, temp); // <--- Used to read a heading.
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].walkableX;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].walkableY;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].walkableH;
		std::getline(levelDataFile, temp, ':');
		levelDataFile >> level[count].walkableW;
		levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

		//read obstacles
		std::getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		//tempNum = atoi(temp.c_str());
		tempNum = std::stoi(temp);

		if (tempNum > 0)
		{
			levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

			for (int index = 0; index < tempNum; index++)
			{
				std::getline(levelDataFile, temp);
				std::getline(levelDataFile, temp, ':');
				//std::getline(levelDataFile, temp, ':');

				levelDataFile >> obst->x;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> obst->y;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> obst->h;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> obst->w;
				levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

				level[count].obstacles.push_back(*obst);
				std::cout << "\nLoaded one obstacle";
			}
		}

		//read teleporters
		std::getline(levelDataFile, temp, '#');
		levelDataFile >> temp;
		tempNum = atoi(temp.c_str());

		if (tempNum > 0)
		{
			levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

			for (int index = 0; index < tempNum; index++)
			{
				//teleporter number
				std::getline(levelDataFile, temp); // <--- Used to read a heading.
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->number;
				std::getline(levelDataFile, temp);

				//coordinates of teleporter
				std::getline(levelDataFile, temp);
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->x;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->y;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->h;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->w;
				levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // <--- Requires header file <limits>.

				//where player spawns
				std::getline(levelDataFile, temp); // <--- Used to read a heading.
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->spawnPlayerX;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->spawnPlayerY;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->spawnPlayerH;
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->spawnPlayerW;

				//leads to level number
				std::getline(levelDataFile, temp, ':');
				levelDataFile >> tp->leadsToLevel;

				level[count].teleporters.push_back(*tp);
				std::cout << "\nLoaded one teleporter";
			}
		}

		std::cout << "\nLevel " << count << " Successfully loaded!";
	}



	delete obst;
	delete tp;


	pause();

	return(0);
}


void pause()
{
	std::cout << "\n\nPress enter to continue...\n";
	std::cin.sync();
	std::cin.get();
	return;
}


Continued:
Last edited on
Hello hoogo,

Part 2:

And I changed the input file to :

____Level 0____
#0
_number:0
_Background path:textures/lvl0.png
_Foreground path:textures/lvl0obst.png
_Render SDL_Rect:
x:0
y:0
h:640
w:640
_Player spawn position:
x:50
y:10
h:160
w:160
_Walkable area:
x:80
y:80
h:560
w:560
#1
*Obstacle 1:
x:325
y:241
h:75
w:75
#1
*Teleporter 1:
_Number:0
_Coordinates:
x:513
y:353
h:5
w:5
_Teleports the player to:
x:150
y:150
h:160
w:160
_Leads to level number:1
____Level 1____
#1
_number:1
_Background path:textures/lvl1.png
_Foreground path:textures/lvl1obst.png
_Render SDL_Rect:
x:0
y:0
h:640
w:640
_Player spawn position:
x:380
y:200
h:160
w:160
_Walkable area:
x:45
y:240
h:400
w:550
#4
*Obstacle 1:
x:60
y:245
h:35
w:105
*Obstacle 2:
x:60
y:355
h:45
w:110
*Obstacle 3:
x:480
y:255
h:100
w:95
*Obstacle 4:
x:540
y:455
h:95
w:55
#1
Teleporter 1:
_Number:0
_Coordinates:
x:320
y:620
h:5
w:5
_Teleports the player to:
x:250
y:480
h:160
w:160
_Leads to level number:2
END OF FILE
#-1


You should find that the input file makes it easier to work with. Otherwise with what you had there are blank lines that would need to be addressed in the program. You could use the blank lines to help make the file, but they need removed before you use the file.

Hope that helps,

Andy
Last edited on
Hi,

Just to reinforce what Andy has said about the pointers. To me it looks like the classic case of using new just to obtain a pointer, which is bad news. I have looked at some SDL tutorials and none of them seem to do this. I am aware that SDL is written in C, I am pretty sure it would internally allocate memory with malloc or something similar, so it is already on the heap.

The problem with new is that if anything throws an exception the neither the destructor nor the delete is reached, so memory is leaked.

Apparently it is possible to use smart pointers but that might be more complex right now. I read about that on StackOverflow.

It seems that you are creating your own data structure which will then be used to create the actual SDL object. I can understand this for the Teleporters (they have data of where to be teleported to), but not for object and obstacle - these seem like they will become a SDL_Rect or surface. So why not have a data structure containing these SDL objects directly? The STL has emplace_back which constructs an item in place in a container like std::vector.


Thank you so much Andy! I see what you did and it indeed makes more sense and seems more efficient.
I’m currently away from home for a few days but I’ll come back to you as soon as I’m working on my game again. In the meantime I’ll read documentation on the limits library since I’m not familiar with it at all.

Thanks a lot!


Thanks for pointing that out TheIdeasMan I actually didn’t think about that. I’ll look into what you suggested for pointers! About the obstacle structure : you pointed out that it did not need to be a struc. In my game it’s merely a SDL_Rect. But since this is just a driver program to test this ´idea’ i couldn’t be bothered to include all the libs ans linker stuff.

Thanks a lot for your time, Same as Andy, I’ll get back to you when I get home!
TheIdeasMan wrote:
So why not have a data structure containing these SDL objects directly?


My bad, you did actually have them in you OP.


Sorry about mentioning emplace_back , SDL is C so there are no constructors for the API objects.

Btw, try to come up with good meaningful identifiers for your variables. I am not keen on these:

1
2
3
4
5
6
struct Level
{
	int level;
// .........
Level level[MAX_LVL];


Maybe level could be level_num or level_id ? This sounds a bit pedantic, but it can cause problems - maybe when one is tired :+) One might wonder why this doesn't compile : levelDataFile >> level[count].Level;

Consider that they should be an unsigned type, because they are used as array or vector subscript. It's better to use std::size_t , that is what the STL does.

Try not to abbreviate identifiers too much - the length of a variable name should be proportional to it's scope. But you have :

Tp *tp = new Tp;
Which is used throughout the program. There is no clue as to what Tp means until 30 lines after that assignment, not even in the declaration of Tp.

Well written code with good identifiers tells a story of what the code does without lots of comments.

https://isocpp.org/wiki/faq/style-and-techniques#hungarian
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-name-length

There is lots of other good stuff in those links.

Good Luck !!

Hello hoogo,

The "limits" library is very useful, but in the beginning you do not need to understand everything just how to use it. One of the nice things about C++ is you do not have to know everything at first just how to use it. In time, like me, a better understanding will come.

I agree with the TheIdeasMan you do not need all those pointers at the beginning of main. That is the reason I changed the "level" array so I could see what is happening. As a pointer all I could see at first is the first element and that did not work well.

Andy
@Handy Andy

I glad you agree with me reinforcing what you said :+D Cheers!

Edit:

I can see where hoogo is coming from though, a lot of the other SDL objects are declared as pointers or their address is taken for arguments, so they aren't passed about by value - some of them can be large. It's just that I was objecting to the new
Last edited on
@Handy Andy
Hey Andy, sorry It took me so long to respond. First of, thanks a lot for the time and effort you put into my code I appreciate it a lot.

I see what you did with it and it does look more organised and logical that way indeed.

I have a few questions though:
1
2
3
4
5
6
7
8
if (levelDataFile.fail())
	{
		cout << "\n Failed to open level.data.txt!\n";
		//pause();
		// <--- An alternative
		//std::this_thread::sleep_for(std::chrono::seconds(5));  // Requires header files "chrono" and "thread"
		exit(1);
	}
Would that just freeze the command prompt or would that display something on the latter?


 
levelDataFile.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
If I understand well, with the little research I did, the limit library is used to determine the maximum amount of data that can be held by a variable. (correct me if I'm wrong). So for that statement, you're deleting the file stream -- however big it is -- until a "\n" is encountered? I don't understand well, would you please mind explaining step by step what that statement does and means?

And finally, you pointed out not to use using namespace std;, I've seen that seemingly controversial topic before. Could you elaborate on what is to be gained by putting std:: in front of everything instead of including the namespace?

Thank you a lot again,

regards,

Hugo.


Topic archived. No new replies allowed.