Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Tile Map System Out of Whack

This thread is locked; no one can reply to it. rss feed Print
Tile Map System Out of Whack
Eric Johnson
Member #14,841
January 2013
avatar

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

#SelectExpand
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

#SelectExpand
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

#SelectExpand
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
avatar

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
avatar

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*)':|
C:\Users\Eric II\Desktop\games\mld-40\new\map.cpp|38|error: invalid conversion from 'const char*' to 'std::basic_istream<char>::char_type* {aka char*}' [-fpermissive]|
c:\mingw\bin\..\lib\gcc\mingw32\4.6.2\include\c++\istream|599|error: initializing argument 1 of 'std::basic_istream<_CharT, _Traits>& std::basic_istream<_CharT, _Traits>::getline(std::basic_istream<_CharT, _Traits>::char_type*, std::streamsize, std::basic_istream<_CharT, _Traits>::char_type) [with _CharT = char, _Traits = std::char_traits<char>, std::basic_istream<_CharT, _Traits>::char_type = char, std::streamsize = int]' [-fpermissive]|
||=== Build finished: 2 errors, 0 warnings (0 minutes, 0 seconds) ===|

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
avatar

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.
01, 02, 03 etc etc and read two numbers at a time. But then you'd face the same problem if you ever had over 99 different tiles. This solution is a lot easier than looking for a delimiting character.

Cassio Renan
Member #14,189
April 2012
avatar

You don't need to remove the spaces. Get the integers one by one from the ifstream and it will work. Like this:

#SelectExpand
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.

map.txt#SelectExpand
11 22 333 4444 55555 2666666 7777777 88888888 999999999

Eric Johnson
Member #14,841
January 2013
avatar

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
avatar

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 ;D

Eric Johnson
Member #14,841
January 2013
avatar

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
avatar

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

#SelectExpand
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.
Note that there's a LOT of room for improvement here. For instance, it will get the number of columns from the last row, i.e.: It doesn't check for any(incorrect) larger or smaller rows. Said that, that map1.txt file I posted will fail(since the first line has 4 ints, and the second has 5). Use this one instead:

map1.txt#SelectExpand
11 22 333 4444 255555 666666 7777777 88888888

Eric Johnson
Member #14,841
January 2013
avatar

I appreciate your willingness to help me out here. :)

Edit
So here's what I understand from your example. Correct me if I am wrong...

#SelectExpand
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
avatar

yup, that's it.
Try to look at the reference for istream to get a better understanding on input streams(ifstream and istringstream inherit from them). I know that reading trough the reference may be a bit overkill for a beginner, but you should get an idea of what is what by looking at the info there.

EDIT: looking again:

#SelectExpand
29// Send map file's data into the buffer string 30std::getline(openfile, buffer);

you should rephrase that to:

#SelectExpand
29// Send current line's data from openfile into the buffer string 30std::getline(openfile, buffer);

Eric Johnson
Member #14,841
January 2013
avatar

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
avatar

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
avatar

Oh I see. Also, why use a string AND istringstream? Wouldn't a string alone be sufficient?

Cassio Renan
Member #14,189
April 2012
avatar

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:

#SelectExpand
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
avatar

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) {
//...
}

#SelectExpand
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.
http://cplusplus.com/reference/fstream/ifstream/

Cassio Renan
Member #14,189
April 2012
avatar

I know that.

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
avatar

The sscanf() function works fine for me.

#include <stdio.h>

char manynums[] = "1 2 3 9999";

int nums[4];

int main(void)
{
  sscanf(manynums,"%d %d %d %d",&nums[0],&nums[1],&nums[2],&nums[3]);
  printf("%d %d %d %d\n",nums[0],nums[1],nums[2],nums[3]);
  return 0;
}

They all watch too much MSNBC... they get ideas.

Eric Johnson
Member #14,841
January 2013
avatar

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. ;D

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
avatar

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.

--
I thought I was wrong once, but I was mistaken.

Eric Johnson
Member #14,841
January 2013
avatar

Well, technically I suppose they do (array lists are essentially vectors), but they are somewhat different than C++'s; that's what I meant.

Go to: