|
Tile Map System Out of Whack |
Eric Johnson
Member #14,841
January 2013
|
I threw together a simple application to test out drawing tiles to the screen. This is nothing new, but I had never drawn more than three or four different tiles at once, so I wanted to test out using sixteen different tiles (just for fun). Anyway, each tile's data is stored in a text file and uses an integer to represent a particular tile. For example, "10" would represent a water tile. However, I've run into an issue with tiles with a value greater than nine. It seems to treat "10" as both "1" and "0", thus scrambling the screen. main.cpp 1#include <iostream>
2
3#include <allegro5/allegro.h>
4
5#include <allegro5/allegro_image.h>
6
7#include "map.h"
8
9
10
11using namespace std;
12
13
14
15int main() {
16
17
18
19 al_init();
20
21
22 // May map object, of course
23
24 Map Map;
25
26
27
28 int screenW = 480;
29
30 int screenH = 360;
31
32
33
34 bool done, redraw = false;
35
36
37
38 al_set_new_display_flags(ALLEGRO_RESIZABLE);
39
40
41
42 ALLEGRO_DISPLAY *display = al_create_display(screenW, screenH);
43
44 ALLEGRO_EVENT_QUEUE *event_queue = al_create_event_queue();
45
46 ALLEGRO_TIMER *timer = al_create_timer(1.0 / 60.0);
47
48
49
50 al_set_window_title(display, "Just Testing");
51
52
53
54 al_init_image_addon();
55
56 al_install_keyboard();
57
58
59
60 // Change the current working directory to the assets folder
61
62 ALLEGRO_PATH *path = al_get_standard_path(ALLEGRO_RESOURCES_PATH);
63
64 al_append_path_component(path, "assets");
65
66 al_change_directory(al_path_cstr(path, '/'));
67
68 al_destroy_path(path);
69
70
71
72 ALLEGRO_BITMAP *tiles = al_load_bitmap("map.png");
73
74
75
76 // Validate resources
77
78 if (!tiles) done = true;
79
80 if (!Map.load("map.txt")) done = true;
81
82
83
84 al_register_event_source(event_queue, al_get_display_event_source(display));
85
86 al_register_event_source(event_queue, al_get_timer_event_source(timer));
87
88 al_register_event_source(event_queue, al_get_keyboard_event_source());
89
90
91
92 al_start_timer(timer);
93
94
95
96 while (!done) {
97
98
99
100 ALLEGRO_EVENT ev;
101
102
103
104 al_wait_for_event(event_queue, &ev);
105
106
107
108 if (ev.type == ALLEGRO_EVENT_TIMER) {
109
110
111
112 // Update
113
114
115
116 redraw = true;
117
118 }
119
120 else if (ev.type == ALLEGRO_EVENT_DISPLAY_CLOSE) {
121
122
123
124 // Player quit the game
125
126 done = true;
127
128 }
129
130
131
132 if (redraw && al_is_event_queue_empty(event_queue)) {
133
134
135
136 // Draw
137
138
139
140 redraw = false;
141
142
143
144 Map.draw(tiles);
145
146
147
148 al_flip_display();
149
150 al_clear_to_color(al_map_rgb(255, 255, 255));
151
152 }
153
154 }
155
156
157
158 al_destroy_display(display);
159
160 al_destroy_event_queue(event_queue);
161
162 al_destroy_timer(timer);
163
164 al_destroy_bitmap(tiles);
165
166
167
168 return 0;
169
170}
map.h 1#include <iostream>
2
3#include <fstream>
4
5#include <string>
6
7#include <algorithm>
8
9#include <allegro5/allegro.h>
10
11
12
13using namespace std;
14
15
16
17class Map {
18
19
20
21 public:
22
23 int loadCounterX, loadCounterY, sizeX, sizeY, tile[100][100];
24
25
26
27 bool load(const char* filename);
28
29 void draw(ALLEGRO_BITMAP *bitmap);
30
31};
map.cpp 1#include "map.h"
2
3
4
5bool Map::load(const char* filename) {
6
7
8
9 loadCounterX = 0;
10
11 loadCounterY = 0;
12
13
14 // Attempt to open map file
15
16 std::ifstream openfile(filename);
17
18
19 // Validate map file
20
21 if (!openfile.is_open()) {
22
23
24 // Failed to open map
25
26 return false;
27
28 }
29
30 else {
31
32
33 // Extract data from map file and throw it into a string
34
35 std::string line;
36
37 std::getline(openfile, line);
38
39
40 // Remove white spaces
41
42 line.erase(std::remove(line.begin(), line.end(), ' '), line.end());
43
44
45 // Our map will be this long across
46
47 sizeX = line.length();
48
49
50 // Start reading at the beginning of the file
51
52 openfile.seekg(0, std::ios::beg);
53
54
55
56 while (!openfile.eof()) {
57
58
59 // Assign tile data into array
60
61 openfile >> tile[loadCounterX][loadCounterY];
62
63
64 // Jump to the next tile in map file
65
66 loadCounterX++;
67
68
69
70 if (loadCounterX >= sizeX) {
71
72
73 // Hit end of map across, so jump to the next line
74
75 loadCounterX = 0;
76
77 loadCounterY++;
78
79 }
80
81 }
82
83
84 // Our map will be this tall up and down
85
86 sizeY = loadCounterY;
87
88
89
90 return true;
91
92 }
93
94}
95
96
97
98void Map::draw(ALLEGRO_BITMAP *bitmap) {
99
100
101 // Throw everything into a buffer
102
103 al_hold_bitmap_drawing(true);
104
105
106
107 for (int x = 0; x < sizeX; x++) {
108
109
110
111 for (int y = 0; y < sizeY; y++) {
112
113
114
115 switch (tile[x][y]) {
116
117
118
119 case 1:
120
121
122
123 al_draw_bitmap_region(bitmap, 0, 0, 16, 16, 16 * x, 16 * y, 0);
124
125 break;
126
127
128
129 case 2:
130
131
132
133 al_draw_bitmap_region(bitmap, 16 + 1, 0, 16, 16, 16 * x, 16 * y, 0);
134
135 break;
136
137
138
139 case 3:
140
141
142
143 al_draw_bitmap_region(bitmap, 16 * 2 + 2, 0, 16, 16, 16 * x, 16 * y, 0);
144
145 break;
146
147
148
149 case 4:
150
151
152
153 al_draw_bitmap_region(bitmap, 16 * 3 + 3, 0, 16, 16, 16 * x, 16 * y, 0);
154
155 break;
156
157
158
159 case 5:
160
161
162
163 al_draw_bitmap_region(bitmap, 0, 16 + 1, 16, 16, 16 * x, 16 * y, 0);
164
165 break;
166
167
168
169 case 6:
170
171
172
173 al_draw_bitmap_region(bitmap, 16 + 1, 16 + 1, 16, 16, 16 * x, 16 * y, 0);
174
175 break;
176
177
178
179 case 7:
180
181
182
183 al_draw_bitmap_region(bitmap, 16 * 2 + 2, 16 + 1, 16, 16, 16 * x, 16 * y, 0);
184
185 break;
186
187
188
189 case 8:
190
191
192
193 al_draw_bitmap_region(bitmap, 16 * 3 + 3, 16 + 1, 16, 16, 16 * x, 16 * y, 0);
194
195 break;
196
197
198
199 case 9:
200
201
202
203 al_draw_bitmap_region(bitmap, 0, 16 * 2 + 2, 16, 16, 16 * x, 16 * y, 0);
204
205 break;
206
207
208
209 case 10:
210
211
212
213 al_draw_bitmap_region(bitmap, 16 + 1, 16 * 2 + 2, 16, 16, 16 * x, 16 * y, 0);
214
215 break;
216
217
218
219 case 11:
220
221
222
223 al_draw_bitmap_region(bitmap, 16 * 2 + 2, 16 * 2 + 2, 16, 16, 16 * x, 16 * y, 0);
224
225 break;
226
227
228
229 case 12:
230
231
232
233 //
234
235 break;
236
237
238
239 case 13:
240
241
242
243 al_draw_bitmap_region(bitmap, 0, 16 * 3 + 3, 16, 16, 16 * x, 16 * y, 0);
244
245 break;
246
247
248
249 case 14:
250
251
252
253 al_draw_bitmap_region(bitmap, 16 + 1, 16 * 3 + 3, 16, 16, 16 * x, 16 * y, 0);
254
255 break;
256
257
258
259
260
261
262
263 case 16:
264
265
266
267 al_draw_bitmap_region(bitmap, 16 * 3 + 3, 16 * 3 + 3, 16, 16, 16 * x, 16 * y, 0);
268
269 break;
270
271 }
272
273 }
274
275 }
276
277
278 // Release the buffer and draw to the screen
279
280 al_hold_bitmap_drawing(false);
281
282}
map.txt 10 10 10 10 10 10 10 10 3 5 5 5 4 10 10 7 9 9 9 8 10 10 7 9 9 9 8 10 10 7 9 9 9 8 10 10 1 6 6 6 2 10 10 10 10 10 10 10 10 I've attached an image of what it results in. Any ideas why it's screwing my map up? Should I not use any tile above nine, it works just fine... But that would limit me to 10 (0-9) tiles.
|
Gnamra
Member #12,503
January 2011
|
What I usually do when I encounter problems like these is to use a symbol or letter to let my program know when to stop and add whatever it read up to that symbol or letter. I quickly looked over your code, what you're basically doing is just checking each number and adding it to the array. This wont work as you've already witnessed, the reason for this is when you're checking against 10, a double digit number. you add 1 to the array then you read again and add 0. One possible fix would be what I described above, example: std::string buffer; while (!openfile.eof()) { // Assign tile data into array buffer.clear(); openfile.getline(buffer.c_str(), 3, '.'); tile[loadCounterX][loadCounterY] = atoi(buffer.c_str()); // rest of the stuff you wrote here } map.txt would look like this: 10.10.10.10.10.10.10 10.3.5.5.5.4.10 10.7.9.9.9.8.10 10.7.9.9.9.8.10 10.7.9.9.8.10 Note: I didn't try any of this code so I don't think it'll be copy and paste-able. You can try though.
|
Eric Johnson
Member #14,841
January 2013
|
Thank you for the reply. I see where you're going with that, but your code did not work. I read up on the getline function too, but haven't managed to get it working just yet. It seems to work if I use a char* instead of a string for buffer, but then I can't clear it. Here's what your suggestion returns me: C:\Users\Eric II\Desktop\games\mld-40\new\map.cpp||In member function 'bool Map::load(const char*)':| It looks to me as though it wants a const char* instead of a string, but like I said earlier, I wouldn't be able to clear a const char*.
|
Gnamra
Member #12,503
January 2011
|
I thought it wouldn't work, but that wasn't the point of the post. You need to figure out a way to make sure that you're always reading the correct amount of integers. Another way to solve your problem would be to use double digits for all your tiles.
|
Cassio Renan
Member #14,189
April 2012
|
You don't need to remove the spaces. Get the integers one by one from the ifstream and it will work. Like this: 1#include <iostream>
2#include <fstream>
3
4int main(){
5 int map[100];
6 int n, i = 0, j;
7 std::ifstream openfile("map.txt");
8 if(!openfile.is_open()){
9 std::cout << "error 1: could not open map.txt";
10 return 1;
11 }
12
13 while(openfile>>n){
14 map[i] = n;
15 i++;
16 }
17
18 for(j=0;j<i;j++){
19 std::cout << map[j] << "\n";
20 }
21}
EDIT: Try compiling this code and testing it with the following input file. You'll see. 11 22 333 4444 55555
2666666 7777777 88888888 999999999
|
Eric Johnson
Member #14,841
January 2013
|
Thanks for the input, Cassio Renan. I am using a two-dimensional array for my x and y tiles. How would I incorporate your suggestion into a 2D array?
|
Cassio Renan
Member #14,189
April 2012
|
knowing the number of columns, it's easy: for(i=0;i<num_tiles;i++){ x = i%columns; y = i/columns; map[x][y] = tile[i]; } You may want to make the two first integers of your map file to be the map size. That way you can make it easier for you to get the entire map later. On a side note: This is only an example. You should get the integers directly into your matrix, instead of passing them to an array first, witch is a not very smart redundancy. But I guess you know that |
Eric Johnson
Member #14,841
January 2013
|
That would make sense I suppose, but my previous setup allowed me to dynamically get the rows and columns. I think it'd be better to implement Gnamra's suggestion about separating each value on the map with a dot or something, then to remove the dot and compare them. I've been reading up on getline and whatnot, but haven't had any luck yet being able to get his suggestion working. What do you think?
|
Cassio Renan
Member #14,189
April 2012
|
I see. Dumping them directly from the ifstream will ignore any spaces and newlines, so it's not a nice sollution. Instead, get the lines using getline(the global one, not istream's), and then dump them using a stringstream, just like if you used the ifstream. I'll code an example right now(and this one's more complicated ), so give me a minute. EDIT: Finally done 1#include <iostream>
2
3#include <istream>
4#include <fstream>
5
6#include <string>
7#include <sstream>
8
9int main(){
10 std::string buffer;
11 int map[100][100];
12 int i = 0, j = 0;
13 int w, h;
14 std::ifstream openfile("map1.txt");
15 if(!openfile.is_open()){
16 std::cout << "error 1: could not open map1.txt";
17 return 1;
18 }
19
20 while(true){
21 std::getline(openfile, buffer);
22 std::istringstream streambuffer;
23 streambuffer.clear();
24 streambuffer.str(buffer);
25 while(streambuffer >> map[i][j]){
26 j++;
27 }
28 i++;
29 if(openfile.eof()) break;
30 j = 0;
31 }
32 // i and j now hold the map's w and h.
33 w = i;
34 h = j;
35 for(i=0;i<w;i++){
36 for(j=0;j<h;j++){
37 std::cout << map[i][j] << " ";
38 }
39 std::cout << std::endl;
40 }
41}
Spaces delimit columns, newlines delimit rows. 11 22 333 4444
255555 666666 7777777 88888888
|
Eric Johnson
Member #14,841
January 2013
|
I appreciate your willingness to help me out here. Edit 1#include <iostream>
2#include <istream>
3#include <fstream>
4#include <string>
5#include <sstream>
6
7using namespace std;
8
9int main() {
10
11 std::string buffer;
12 int map[100][100];
13 int i = 0, j =0;
14 int w, h;
15
16 // Open map file
17 std::ifstream openfile("assets/map.txt");
18
19 if (!openfile.is_open()) {
20
21 // Failed to open map file
22 std::cout << "Erorr 1: could not open map.txt.";
23
24 return 1;
25 }
26
27 while (true) {
28
29 // Send map file's data into the buffer string
30 std::getline(openfile, buffer);
31
32 std::istringstream streambuffer;
33
34 // Pretty self-explanatory here
35 streambuffer.clear();
36
37 // Send contents of buffer into streambuffer
38 streambuffer.str(buffer);
39
40 // Throw streambuffer contents into map array
41 while (streambuffer >> map[i][j]) {
42
43 // Increase maps height
44 j++;
45 }
46
47 // Increase map's width
48 i++;
49
50 // Stop the while loop upon hitting end of file
51 if (openfile.eof()) break;
52
53 // Height reset
54 j = 0;
55 }
56
57 // Map's width and height
58 w = i;
59 h = j;
60
61 // Cycle through the width
62 for (i = 0; i < w; i++) {
63
64 // Cycle through the height
65 for (j = 0; j < h; j++) {
66
67 // Output tiles
68 std::cout << map[i][j] << " ";
69 }
70
71 // New line break
72 std::cout << std::endl;
73 }
74}
I am fairly new to C++.
|
Cassio Renan
Member #14,189
April 2012
|
yup, that's it. EDIT: looking again: 29// Send map file's data into the buffer string
30std::getline(openfile, buffer);
you should rephrase that to: 29// Send current line's data from openfile into the buffer string
30std::getline(openfile, buffer);
|
Eric Johnson
Member #14,841
January 2013
|
I'll be sure to read it over. Quick question: why do you reset j? If I remove the reset, it displays the file's contents, but if the reset is present, it does not display the file's contents.
|
Cassio Renan
Member #14,189
April 2012
|
if j is not reset, it will contain the number equivalent to width*height. Your map file probably has a newline at the end of it(another problem that the code doesn't check for), and that is making j reset just before reaching the EOF. In case you're still stuck, a sollution for that is to check if the streamed line is empty. That's easy: It will be empty if j == 0. Add these lines, right after the second while loop: if(j) h = j; else break; and remove the line: h = j; down bellow. Note that this fix will make any empty line simbolize "stop reading". That means, anything after an empty line will be ignored. |
Eric Johnson
Member #14,841
January 2013
|
Oh I see. Also, why use a string AND istringstream? Wouldn't a string alone be sufficient?
|
Cassio Renan
Member #14,189
April 2012
|
Actually, you could use a string, no prob. Using a stream just makes your job easier(You're not doubling the memory usage, per se: istringstream just holds a pointer to the string, instead of copying the data). One way of getting data directly from the string(altough really old) is to use g' old C's sscanf. It will also ignore spaces. EDIT: The reason to use an istringstream is to be able to use the overloaded extracion operator("<<") to get the data from the string. EDIT2: Correcting myself, sscanf will not ignore spaces, unless told to do so. A working example: 1#include <cstdio>
2#include <cstring>
3#include <string>
4
5int main(){
6 std::string example = "1 22 333 4444";
7 int numbers[4];
8 char buffer[1000]; // You should really use dynamic allocation instead of a fixed size for best results :)
9
10 // Get c++ string into a c string buffer.
11 strcpy(buffer, example.c_str());
12
13 for(int i=0;i<4;i++){
14 // Get int from string, and remove it from buffer. It will read the buffer until a newline or null terminator is found.
15 sscanf(buffer, "%d%[^\n]s", numbers + i, buffer);
16
17 // check if it worked
18 fprintf(stderr, "%d ", numbers[i], buffer);
19 }
20}
|
Edgar Reynaldo
Major Reynaldo
May 2007
|
WTF are you guys doing? Just use an ifstream and be done with it. Stop dicking around with stringstreams and buffers already. You can read directly into an integer from an ifstream. Read the width and height, create your map, and then read the values. ifstream >> int& skips leading whitespace, so you can keep using spaces in your text file. And as many as you need to maintain nice rows and columns. I do not know what number the stream extractor will return if a number has leading zeroes though. You could also write your own extractor function and call it directly. void store_next_int(ifstream input , int& store) { //... }
1#include <iostream>
2#include <fstream>
3#include <vector>
4#include <cstdlib>
5
6using namespace std;
7
8void abort_retry_fail() {
9 // Mwuah hahahahaha haaaaa
10 cout << "Abort, Retry, or Fail?" << endl;
11 abort();
12}
13
14int main(int argc , char** argv) {
15
16 if (argc < 2) {abort_retry_fail();}
17
18 ifstream file_reader(argv[1]);
19 if (!file_reader) {abort_retry_fail();}
20
21 vector< vector<int> > map;
22
23 int tiles_wide;
24 int tiles_tall;
25
26 /// TODO : ERROR CHECKING, sorry do this yourself
27
28 // first two values are width and height of map
29 file_reader >> tiles_wide;
30 file_reader >> tiles_tall;
31
32 // make map
33 map.resize(tiles_tall , vector<int>());
34 for (int i = 0 ; i < (int)map.size() ; ++i) {
35 map[i].resize(tiles_wide , 0);
36 }
37
38 // read map
39 for (int tile_y = 0 ; tile_y < tiles_tall ; ++tile_y) {
40 for (int tile_x = 0 ; tile_x < tiles_wide ; ++tile_x) {
41 file_reader >> map[tile_y][tile_x];
42 }
43 }
44
45 // Now output what we just read.
46 for (int tile_y = 0 ; tile_y < tiles_tall ; ++tile_y) {
47 for (int tile_x = 0 ; tile_x < tiles_wide ; ++tile_x) {
48 cout << map[tile_y][tile_x] << " ";
49 }
50 cout << endl;
51 }
52 cout << endl;
53
54 return 0;
55}
Here's Map.txt : 3 4 0 1 2 3 4 5 6 7 8 9 10 11 Here's the output : c:\ctwoplus\progcode\allegro5\test>MapReader Map.txt 0 1 2 3 4 5 6 7 8 9 10 11
Everything you need to look at is here on http://cplusplus.com. My Website! | EAGLE GUI Library Demos | My Deviant Art Gallery | Spiraloid Preview | A4 FontMaker | Skyline! (Missile Defense) Eagle and Allegro 5 binaries | Older Allegro 4 and 5 binaries | Allegro 5 compile guide |
Cassio Renan
Member #14,189
April 2012
|
I know that. Cassio Renan said: You may want to make the two first integers of your map file to be the map size. That way you can make it easier for you to get the entire map later.
Sheegoth said: That would make sense I suppose, but my previous setup allowed me to dynamically get the rows and columns
|
Arthur Kalliokoski
Second in Command
February 2005
|
The sscanf() function works fine for me.
They all watch too much MSNBC... they get ideas. |
Eric Johnson
Member #14,841
January 2013
|
Hi again. I appreciate the replies guys, truly. I went out and learned all about vectors and whatnot, and threw together another tile system, which worked much better than my last one... Then I came back here and saw Edgar Reynaldo's post; you knocked it out of the park man. Your approach is so much smaller and easier than I had going. Ha. I appreciate it! That's pretty damn nice. I come from a PHP background, so I'm still adjusting to C++... Vectors are so nice guys.
|
Jeff Bernard
Member #6,698
December 2005
|
Sheegoth said: I come from a PHP background, so I'm still adjusting to C++... Vectors are so nice guys. PHP's got vectors (essentially). Well, arrays have all the functionality of vectors... and a bunch of other data types. -- |
Eric Johnson
Member #14,841
January 2013
|
Well, technically I suppose they do (array lists are essentially vectors), but they are somewhat different than C++'s; that's what I meant.
|
|