The Vector equivalent of your array would be this:
std::vector< std::vector<int> > level;
(Space on the right is mandatory because the compiler likes to confuse it with the right shift bitwise operator: ">>")
You then would use the fstream header to read a .txt file and load it into the vector.
This is pseudocode for loading the map from the .txt file (written in browser, haven't tested):
std::vector< std::vector<int> > LoadLevel(const std::string& filename)
{
std::vector< std::vector<int> > level;
std::ifstream in(filename); // Open an input stream for the file 'filename'.
if (!in || in.fail())
{
std::cerr << "Couldn't load level " << filename << "!\n";
} else
{
std::string height;
getline(in, height);
int mapHeight = atoi(height.c_str());
for (int y = 0; y < mapHeight; ++y)
{
std::vector<int> row;
std::string line;
getline(in, line); // Get the next line in the stream.
// Iterate over every character in the read line and put its numeric
// value in our row.
for (int x = 0; x < line.length(); ++x)
{
row.push_back(atoi(line[x].c_str())); // Unless you know nobody's going to fool around,
// you should check whether the character is actually
// a number you should do some error checking.
}
level.push_back(row); // Put the parsed row into our level.
}
in.close(); // Free resources. This is important! We don't want any memory leaks.
}
return level;
}
Usage:
std::vector< std::vector<int> > level = LoadLevel("level.txt");
if (level.is_empty())
{
// Level didn't load.
}
// Getting a tile at the coordinates (3, 2).
// Note that the coordinates are swapped.
int tileAt3_2 = level[2][3];
Example .txt file (6x6 level):
6 <-- This is the level's height.
333333
303003
303003
333003
300003
333333
in.close(); // Free resources. This is important! We don't want any memory leaks.
Do I need to manually close a ifstream? (https://stackoverflow.com/questions/748014/do-i-need-to-manually-close-a-ifstream)
Given a text file such as this:
0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1
0 1 1 1 1 1 1 0 0 0 0 2 0 0 0 0
1 1 0 0 0 0 0 0 3 3 3 3 3 3 3 3
0 1 0 0 2 0 3 3 3 0 1 1 1 0 0 0
0 1 1 0 3 3 3 0 0 0 1 1 1 2 0 0
0 0 1 0 3 0 2 2 0 0 1 1 1 1 2 0
2 0 1 0 3 0 2 2 2 0 1 1 1 1 1 1
0 0 1 0 3 2 2 2 0 0 0 0 1 1 1 1
The following will read from it (it is the same as the tutorial example, except that it reads from a file instead of an array):
#include <SFML/Graphics.hpp>
#include <fstream>
#include <iterator>
#include <sstream>
class TileMap : public sf::Drawable, public sf::Transformable
{
public:
bool load(const std::string& tileset, sf::Vector2u tileSize)
{
// load the tileset texture
if (!m_tileset.loadFromFile(tileset))
return false;
std::vector<std::vector<int> > tiles;
std::ifstream map_stream("map.txt");
for (std::string line; std::getline(map_stream, line);){
tiles.push_back(std::vector<int>());
std::stringstream line_stream(line);
std::copy(std::istream_iterator<int>(line_stream), std::istream_iterator<int>(), std::back_inserter(tiles.back()));
}
unsigned int height = tiles.size();
m_vertices.setPrimitiveType(sf::Quads);
// populate the vertex array, with one quad per tile
for (unsigned int j = 0; j < height; ++j){
unsigned int width = tiles[j].size();
for (unsigned int i = 0; i < width; ++i)
{
// get the current tile number
int tileNumber = tiles[j][i];
// find its position in the tileset texture
int tu = tileNumber % (m_tileset.getSize().x / tileSize.x);
int tv = tileNumber / (m_tileset.getSize().x / tileSize.x);
// get a pointer to the current tile's quad
sf::Vertex quad[4];
// define its 4 corners
quad[0].position = sf::Vector2f(i * tileSize.x, j * tileSize.y);
quad[1].position = sf::Vector2f((i + 1) * tileSize.x, j * tileSize.y);
quad[2].position = sf::Vector2f((i + 1) * tileSize.x, (j + 1) * tileSize.y);
quad[3].position = sf::Vector2f(i * tileSize.x, (j + 1) * tileSize.y);
// define its 4 texture coordinates
quad[0].texCoords = sf::Vector2f(tu * tileSize.x, tv * tileSize.y);
quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y);
quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize.x, (tv + 1) * tileSize.y);
quad[3].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y);
for (int k=0; k<4; ++k)
m_vertices.append(quad[k]);
}
}
return true;
}
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
{
// apply the transform
states.transform *= getTransform();
// apply the tileset texture
states.texture = &m_tileset;
// draw the vertex array
target.draw(m_vertices, states);
}
sf::VertexArray m_vertices;
sf::Texture m_tileset;
};
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(512, 256), "Tilemap");
// create the tilemap from the level definition
TileMap map;
if (!map.load("tileset.png", sf::Vector2u(32, 32)))
return -1;
// run the main loop
while (window.isOpen())
{
// handle events
sf::Event event;
while (window.pollEvent(event))
{
if(event.type == sf::Event::Closed)
window.close();
}
// draw the map
window.clear();
window.draw(map);
window.display();
}
return 0;
}
No information about the map's dimensions is hardcoded. In fact, it is possible for some lines to have fewer elements and it will still work.
I just need to find a way to store the values in a text file into a vector array (in this case vector<int>)
This does the trick:
std::vector<int> tiles;
std::ifstream map_stream("map.txt");
std::copy(std::istream_iterator<int>(map_stream), std::istream_iterator<int>(), std::back_inserter(tiles));
However, it means the number of columns will still be hardcoded.
std::copy is an algorithm copying the given range (defined by the first two parameters, which are two iterators) to the one supplied by the third parameter.
The istream_iterators specify a range covering the input stream of the text file
std::back_inserter(tiles) will append elements at the end of tiles - effectively the same as invoking push_back in a hand-written loop.
Yes it's fancy; you still have to help yourself.
i) if Lignum's function return a vector and you want one returning a boolean, write both and have the latter call the former
ii) The map's location doesn't got anything to do with the other parameters. If you wish to pass it, pass it.
This isn't a diary to blabber on about wants and needs nor is it a place to lay out requirements, unless you mean to hire us.