The basic setup is as follows.
My game should be able to handle a variety of different tile map configurations, such as your typical euclidean 2D map, but also hexagonal maps, octagonal maps, trigonometrical maps, and if required some higher dimensions, too.
The catch: The "Level" class can't know of any of these configurations - it needs to work with abstraction in order to make a more general interface for working with all of these map configurations.
The problem.
I'm having trouble coming up with a clean enough design to implement this. Could someone criticize my code and give me some pointers on what could be done differently, how you would do it, etc.?
I've written a small example demonstrating my current design. There are comments inserted where I would like to get some feedback. I deliberately removed a whole bunch of stuff to simply demonstrate what this is about.
Specifically:
- Is the overall design acceptable? Could it be done better?
- Line 40-48: Is downcasting a base class like that clean? I have a guarantee that the derived class is in fact the class being targeted by the cast, but does this make it any more acceptable? Is there a cleaner way to do so?
- Line 64 and 80: This is how I'd instantiate a Level object. Is it OK to instantiate the appropriate map implementation in the constructor like that and have Level's destructor clean up? Is there a better way of doing this?
Here's the code.
#include <iostream>
// abstract class defining a position
class PositionBase
{
public:
PositionBase() {}
virtual ~PositionBase() {}
};
// abstract class describing a map
class TileMapBase
{
public:
TileMapBase() {}
virtual ~TileMapBase() {}
virtual void setTile( const PositionBase& pos, const char& tile ) = 0;
virtual const char& getTile( const PositionBase& pos ) const = 0;
};
// Euclidean coordinate implementation
class EuclideanPosition : public PositionBase
{
public:
EuclideanPosition() {}
EuclideanPosition( int x, int y ) : x(x), y(y) {}
~EuclideanPosition() {}
int x;
int y;
};
// Euclidean map impelentation
class EuclideanTileMap : public TileMapBase
{
public:
EuclideanTileMap() {}
~EuclideanTileMap() {}
// is downcasting "pos" like this clean?
void setTile( const PositionBase& pos, const char& tile )
{
m_Map[ ((EuclideanPosition*)&pos)->x ][ ((EuclideanPosition*)&pos)->y ] = tile;
}
const char& getTile( const PositionBase& pos ) const
{
return m_Map[ ((EuclideanPosition*)&pos)->x ][ ((EuclideanPosition*)&pos)->y ];
}
private:
char m_Map[20][20]; // for simplicity's sake
// I'm using a graph class for this
};
// contains various information about the level
// the catch: this class cannot know of what tile
// system is being implemented
class Level
{
public:
// constructor, requires a new map object to be passed in
// is this clean?
Level( TileMapBase* map ) : m_Map( map ) {}
// destructor, cleans up the map
// again, clean or not?
~Level() { delete m_Map; }
TileMapBase* getTileMap( void )
{
return m_Map;
}
private:
TileMapBase* m_Map;
};
int main()
{
Level myLevel( new EuclideanTileMap() );
myLevel.getTileMap()->setTile( EuclideanPosition(4,4), 'a' );
std::cout << "tile at position 4,4: " << myLevel.getTileMap()->getTile( EuclideanPosition(4,4) ) << std::endl;
return 0;
}