I see.
That doesn’t mean you can’t also use an enumeration, just that you’d have to be more clever about it.
The main benefits of using an enumeration are:
- You can use named values like
TileType::OpenDoor
instead of unexplained numbers.
- A (scoped) enumeration is type safe, so you can’t accidentally mix up an enumeration with an integer value.
For things like damage and solidity, it won’t make much difference whether you use uint8_t
or an enumeration, but which values you assign to which tiles could make a difference in terms of how much code you have to write, and whether you have to handle damage specifically for different kinds of tiles or can use the same rule(s) (and thus the same code) for all tiles.
There’s two ways of doing that:
- Make the map unchangeable, and represent the doors and chests as objects
- This can use less RAM if you only have a few objects because only the objects need to be in RAM
- But it’s a bit more awkward to work with because you need to store those items in lists separate to the actual map
- Make the map modifiable, and represent the doors and chests as tiles
- This is generally easier to work with because you can focus all your logic on tiles
- But it potentially uses more RAM because the map has to be stored in RAM instead of progmem
If you were to go for a modifiable map then you might end up with something like this:
(I had to modify it to work for classic Arduboy because my FX unit doesn’t work with the default uploader and I haven’t got around to fixing it.)
A less structured example (untested):
constexpr uint8_t mapWidth = 16;
constexpr uint8_t mapHeight = 8;
constexpr uint8_t map_0[mapHeight][mapWidth] PROGMEM
{
{ 0, 1, 1, 1, 1, 1, 1, 14, 1, 1, 1, 1, 1, 1, 1, 2 },
{ 5, 19, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 19, 7 },
{ 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7 },
{ 5, 6, 6, 6, 16, 6, 6, 6, 6, 6, 6, 6, 6, 15, 15, 7 },
{ 5, 6, 18, 6, 6, 6, 6, 6, 6, 6, 6, 15, 15, 15, 6, 7 },
{ 5, 6, 6, 6, 6, 6, 6, 6, 6, 15, 15, 15, 6, 6, 6, 7 },
{ 5, 19, 6, 6, 6, 6, 6, 6, 6, 15, 6, 6, 6, 6, 19, 7 },
{ 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12 }
};
constexpr uint8_t map_1[mapHeight][mapWidth] PROGMEM
{
{ 0, 1, 1, 14, 1, 1, 1, 1, 2, 17, 17, 17, 17, 17, 17, 17 },
{ 5, 19, 6, 6, 6, 6, 6, 15, 7, 17, 17, 17, 17, 17, 17, 17 },
{ 5, 6, 6, 6, 6, 6, 15, 15, 7, 17, 17, 17, 17, 17, 17, 17 },
{ 5, 6, 6, 6, 6, 6, 15, 15, 8, 1, 1, 1, 1, 1, 1, 2 },
{ 5, 6, 6, 6, 6, 6, 15, 19, 15, 6, 6, 6, 6, 6, 18, 7 },
{ 5, 6, 6, 6, 6, 6, 6, 15, 6, 6, 6, 6, 6, 6, 6, 7 },
{ 5, 19, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 19, 7 },
{ 10, 11, 11, 11, 11, 11, 11, 13, 11, 11, 11, 11, 11, 11, 11, 12 }
};
constexpr uint8_t mapCount = 2;
constexpr uint8_t firstMapIndex = 0;
constexpr uint8_t lastMapIndex = (mapCount - 1);
// An array of all stored maps
constexpr uint8_t * maps[mapCount] PROGMEM
{
&map_0[0][0],
&map_1[0][0],
};
// Get a pointer to the specified map
const uint8_t * getMap(uint8_t index)
{
return static_cast<const uint8_t *>(pgm_read_ptr(&maps[index]));
}
enum class TileType : uint8_t
{
Invalid,
Ground,
Wall,
ClosedDoor,
OpenDoor,
ClosedChest,
OpenChest,
// ... et cetera
};
TileType room[mapHeight][mapWidth];
TileType getTile(uint8_t x, uint8_t y)
{
if((x > mapWidth) || (y > mapHeight))
return TileType::Invalid;
return room[y][x];
}
// Overwrites a tile in the map
void setTile(uint8_t x, uint8_t y, TileType tile)
{
if((x > mapWidth) || (y > mapHeight))
return;
room[y][x] = tile;
}
// Loads a map
void load(const uint8_t * data)
{
// Copies a map from progmem into RAM
memcpy_P(room, data, sizeof(room));
}
void loadIndexedMap(uint8_t index)
{
roomMap.load(getMap(index));
}
The most important parts being that:
- The
maps
array stores pointers to the maps
- The
memcpy_P
function copies the maps from progmem into the room
array
(I couldn’t use the word map
on its own because Arduino already defines a map
macro, which is really annoying! If it had been a function I could have at least used a namespace
to avoid the problem, but nope, it had to be a macro!)