Table of Contents Source Code Article One Article Three
The purpose of this article is to describe and present an implementation of a simple mapping system. I would like to stress that there are many ways to do this and the implementation I'm presenting is probably the simplest available. These techniques originate back in the early days of game development, in the ancient times before 3D graphics when people were still boasting about how many different colors their games could support.
But don't let simplicity and age fool you: there's a reason why it's been around forever. It can handle most gameplay features and crazy ideas you can throw at it. It has stood the test of time, and is repeatedly relied on by beginner and expert alike.
So what this article contains is:
a very simple discussion of the concept
an implementation that creates a map and draws it to the screen
adds what was discussed in Article One to let the user walk around
But enough talk. Let's get started, shall we?
Actually, the word theory is overkill. If you've played a roguelike game before, you have an intuitive grasp of the concept already. Just ignore how to implement NPC's and items for now and focus only on creating a background for the game to be played on. All that other stuff is always added after the map system has been established.
The whole idea can be broken down into three key points:
1) Divide the world into discrete chunks called tiles.
2) Tiles are defined a specific type - a wall, a floor, a tree, water, a door, etc.
-and the most important-
3) One tile = one character on the screen
And that's about it. Like everything else in life, the trick isn't so much in the idea as it is in the implementation.
If you look at games like NetHack, ADOM and others (including Rogue itself), you will notice that they divide their world into roughly screen-sized levels. Normally they go a little smaller, in order to leave room on the screen to show equipment, HP and other character status as well as talking/combat messages. Not once do you see the levels in these games spanning a horizontal region larger than the screen size. By doing this, they keep the map implementation *very* simple. And trust me, simple is good: the fastest and most reliable code out there is the code that doesn't need to exist.
The simplest trick is to define a two-dimensional array of integers as your map. The number you assign to a particular array element determines what occupies the space in the game world the tile represents. This sounds a little complicated, but I assure you it is really simple.
I think this is one of those things that is easier to grasp by doing, so let's just get started. The includes you need are:
ExtendedWin32Console.h - for console output with color and explicit text positioning
conio.h for reading input from the keyboard
The first thing that you need to do is add the main function.
int main( void )
{
// Initialization
// Game loop
return 0;
}
Now we're ready to get cracking.
First thing we need to do is declare and define the two-dimensional array we're going to be using as the map. Before we can do that, we need to figure out what size we want it to be. I heavily advocate the use of constant variables for this, because these are important values that are referred to often in a game and it's hell if you have to change the dimensions later on if you didn't do this. I've spent too many hours running through pages and pages of code searching for every calculation that might involve these numbers, and I've learned my lesson. Learn from my lazy mistakes and save yourself a ton of headache when it comes to programming, a little bit of thought goes a long way.
I'm going to keep the map small so that it can be readable in this document, you can go as big as you like.
So, add this just after your #includes:
// Map dimensions
#define MAP_WIDTH 20
#define MAP_HEIGHT 15
Okay, so now we can declare the array we'll be using as a map. I'd recommend this to be a global variable. When your game really starts to come along, more than one function will need to access this data.
int nMapArray[MAP_HEIGHT][MAP_WIDTH];
Putting it all together, it looks like this:
#include "conio.h"
#include "ExtendedWin32Console.h"
// Map Dimensions
#define MAP_WIDTH 20
#define MAP_HEIGHT 15
// Map declaration
int nMapArray[MAP_HEIGHT][MAP_WIDTH];
int main( void )
{
// Initialization
// Game loop
return 0;
}
Makes sense so far?
Now that we have the map declared we need to take a step back for a second. This is an array of numbers, so how does this become our map? Well, imagine that every individual element in the array is one tile. So we can say that if the element = 0 it could represent a floor tile, if it was a 1 it could represent a wall tile.
This makes sense, so let's declare a few constants to help us remember:
// Tile Types
#define TILE_FLOOR 0
#define TILE_WALL 1
Cool, so let's see about creating a simple map!
Because this is a two-dimensional array of numbers, you can initialize it in C++ right in the declaration. So, we can use this to create our first map.
int nMapArray[MAP_HEIGHT][MAP_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
This is a big blank room with no walls whatsoever. It's kind of boring, so let's add two rooms to it.
int nMapArray[MAP_HEIGHT][MAP_WIDTH] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};
Can you see the 1's in there? They represent walls, remember?
This doesn't mean much if you can't see it on the screen. So the first thing we need to do is translate the numbers in the array into characters that appear on the screen. This seems like a good place for a function, so we'll go that way. Add this just after your constant declarations:
// Function Prototypes
void DrawMap( void );
It might seem wierd to people that there'd be no parameters to this function, but remember that the map is a global variable.
The function needs to draw every tile present in the map, so we're going to need to access every element in the array. That sounds like a for loop to me.
void DrawMap( void )
{
for( int y = 0; y < MAP_HEIGHT; y++ )
{
for( int x = 0; x < MAP_WIDTH; x++ )
{
// Draw the tile
}
}
}
Now to draw the tiles. This fits nicely into a switch statement, which makes the function look like this.
void DrawMap( void )
{
for( int y = 0; y < MAP_HEIGHT; y++ )
{
for( int x = 0; x < MAP_WIDTH; x++ )
{
// Draw the tile
switch( nMapArray[y][x] )
{
case TILE_FLOOR:
console.SetColor( 7 );
console << '.';
break;
case TILE_WALL:
console.SetColor( 8 );
console << '#';
break;
}
} // end of for loop
} // end of for loop
}
So now, let's just invoke this from the main function and see what we get.
void main( void )
{
// Initialization
DrawMap();
// Game loop
return 0;
}
Running the program, we get this:

Something's really wrong!
What happened here? I left this in because this was something that happened to me tons. In fact, this happened while I was writing this tutorial. ;)
The function is faithfully drawing everything, but it's not going down a line. The trick is to move to the proper line line right before the for( int x ... ) command. You could do it after as well, but I prefer before because it asserts the text position before you work, rather than after. It's better to assert things rather than to assume.
To run the fix, a single line is inserted.
void DrawMap( void )
{
for( int y = 0; y < MAP_HEIGHT; y++ )
{
console.SetPosition( 0, y ); // THE FIX!!!
for( int x = 0; x < MAP_WIDTH; x++ )
{
// Draw the tile
switch( nMapArray[y][x] )
{
case TILE_FLOOR:
console.SetColor( 7 );
console << '.';
break;
case TILE_WALL:
console.SetColor( 8 );
console << '#';
break;
}
} // end of for loop
} // end of for loop
}
This one change results in the program looking like this.

It's not a game if all we do is show pictures of the dungeon. So the next challenge is to see about combining what we learned from Article One into what we have already.
The biggest point to remember is that you need to draw the player's character on top of the map. I want to stress this. For this type of map implementation I do not recommend having the player as a component of the map system itself; it's possible but complicated. There are other, more intricate map implementations that can support that intrinsically, but this is not one of them. It's not a bad thing, but something to keep in mind.
So the idea here is to draw the map to the screen, then draw the player's character on top of it. This two step cycle is very similar to Article One, with the exception that the map is a background instead of a blank screen.
Taking what we've learned from before, we're going to need a loop in the main() function. I'm not going to re-iterate all that has already been said, so I'll fill in the obvious. Remember to clear the screen at the beginning of each process loop.
int main( void )
{
// Initialization
int nPlayerX = 4;
int nPlayerY = 4;
// Main Game Loop
char nKey;
while( true )
{
// Draw the world map
DrawMap();
// Draw the player to the screen
console.SetColor( 14 );
console.SetPosition( nPlayerX, nPlayerY );
console << '@';
// Grab the user input
nKey = getch();
// Process the input
switch( nKey )
{
// Move up
case '8':
// Do stuff
break;
// Move left
case '4':
// Do stuff
break;
// Move right
case '6':
// Do stuff
break;
// Move down
case '2':
// Do stuff
break;
// Quit the program
case 27: // (ESC) key
return 0;
}
}
}
The trick is to peek ahead at the tile type that the player would be moving into. If the tile is a wall or some other impassible object, then deny the motion and keep the player exactly where they were. If the tile is passable then adjust the player's coordinates as you normally would.
To keep things clean, I'd recommend making a separate function to do the testing. On that takes a map coordinate and returns true or false - true if you can walk onto it, false if you can't.
bool IsPassable( int nMapX, int nMapY )
{
// Store the value of the tile specified
int nTileValue = nMapArray[nMapY][nMapX];
// Return true if it's passable
if( nTileValue == TILE_FLOOR || nTileValue == TILE_GRASS || nTileValue == TILE_OPENDOOR )
return true;
// If execution get's here, it's not passable
return false;
}
The value of constants really help make this easy to read. There's one more thing to add, to make sure that the player can't walk off the map itself and crash our program.
bool IsPassable( int nMapX, int nMapY )
{
// Before we do anything, make darn sure that the coordinates are valid
if( nMapX < 0 || nMapX >= MAP_WIDTH || nMapY < 0 || nMapY >= MAP_HEIGHT )
return false;
// Store the value of the tile specified
int nTileValue = nMapArray[nMapY][nMapX];
// Return true if it's passable
if( nTileValue == TILE_FLOOR || nTileValue == TILE_GRASS || nTileValue == TILE_OPENDOOR )
return true;
// If execution get's here, it's not passable
return false;
}
Now we've got the check completed, let's add it to the processing phase of our game loop. Taking the switch..case label for the 'move up' command:
case '8':
// Look ahead to see if we can enter this location
if( IsPassable( nPlayerX, nPlayerY-1 ) )
{
// Move the player
nPlayerY--;
}
break;
Now when you repeat that for left, right and down as well, you get a process phase that looks like this.
// Process the input
switch( nKey )
{
// Move up
case '8':
// Look ahead to see if we can enter this location
if( IsPassable( nPlayerX, nPlayerY-1 ) )
{
// Move the player
nPlayerY--;
}
break;
// Move left
case '4':
// Look ahead to see if we can enter this location
if( IsPassable( nPlayerX-1, nPlayerY ) )
{
// Move the player
nPlayerX--;
}
break;
// Move right
case '6':
// Look ahead to see if we can enter this location
if( IsPassable( nPlayerX+1, nPlayerY ) )
{
// Move the player
nPlayerX++;
}
break;
// Move down
case '2':
// Look ahead to see if we can enter this location
if( IsPassable( nPlayerX, nPlayerY+1 ) )
{
// Move the player
nPlayerY++;
}
break;
// Quit the program
case 27: // (ESC) key
return 0;
}
Running it you get this.

That's the basics of mapping. Try adding a couple of new tile types to expand what you can do with your game. For instance, add trees and grass outside of the buildings to create an outdoor level. For added practice, be sure to make the trees impassible.
Doing this, you can make a level that looks like this:

Table of Contents Source Code Article One Article Three