Reputation: 641
I'm programming a level editor for a new game. The problem is, I am not sure what structure to use for storing my data.
It is a tile-based map engine, using x- and y coordinates and an id for the tile at that position.
I've got multiple layers, the map is resizeable, so an array may cause me some troubles, that's why I chose a std::vector for that case. To prevent a lot of overload I only add a tile, when somebody placed it, so the vector size is zero if there are no tiles and increases the more tiles are placed.
struct tile {
unsigned short tile_id;
unsigned short tile_x;
unsigned short tile_y;
};
And my vector:
std::vector<tile> tiles;
The thing is, before adding a new tile I need to check if there's already a tile at that x- and y-position.
// Returns true/false if there is a tile at given position or not
bool Layer::has_tile_at(unsigned short x, unsigned short y) {
unsigned int i;
for (i = 0; i < tiles.size(); i++) {
if (tiles[i].tile_x == x && tiles[i].tile_y == y)
return true;
}
return false;
}
My problem is, that for every placed tile, I must loop through the whole vector, which is fast at the beginning, but really gets a pain in the ass after some tiles have been placed.
Do you think my approach is okay so far, or is there something smarter and more performant?
Upvotes: 1
Views: 2219
Reputation: 4668
Data structure to use should depend mostly on the use cases: if you're doing mostly (x,y) reads, then perhaps you need a matrix (be it via a vector of vectors, or just array of arrays).
If you need indexed access AND easy iteration over tiles, perhaps keep the data within two data structures. You should be able to easily implement a 2d map with pointers to tiles within vector - initially empty, lazily loaded upon (x,y) access (remember about data safety!).
Consider for example:
class Map
{
public:
void addTile(std::shared_ptr<Tile> tile)
{
m_tiles.push_back(tile); // should probably remove old elements at (x,y) from the vector
m_map[{tile->x, tile->y}] = tile; // overwrites whatever was stored in there!
}
std::shared_ptr<Tile> getTile(int x, int y)
{
return m_tilesMap[{x, y}]; // if no tile there, returns default ctor-ed shared_ptr: nullptr
}
private:
std::vector<std::shared_ptr<Tile>> m_tiles;
using Index2D = std::pair<int, int>;
std::map<Index2D, std::shared_ptr<Tile>> m_tilesMap;
};
(extended comment with a brief code example: data is kept on the heap, while both vector and map keep copies of it - perhaps could be improved for easier removal)
Upvotes: 3
Reputation: 561
Why not use multiple vectors? You could essentially create a growable 2-D vector by having a vector of vectors, then overload the [] operator to first check if the vector size could contain that element (returning false if not), and if it can, check if that element isn't a constructed value (whatever your default "tile" may be). This would allow nearly O(1) lookup like in regular vectors. Otherwise you can use a formula for your max col/row distance and do a O(1) look-up that way with some 2-D to 1-D conversions such as in 2-D arrays.
This is what I'm thinking:
vector< vector<tile> > tiles;
bool::Layer has_tile_at(unsigned short x, unsigned short y) {
if (tiles.size() <= x) {
return false;
} else if (tiles[x].size() > y) {
if (tiles[x][y] != tile()) {
return true;
}
}
return false;
}
Edit:
As another user pointed out, you could also use pointers and check if tiles[x][y] == nullptr; instead!
Upvotes: 1