Roguelike game in C++ - Map generation with Perlin noise
Posted on July 25, 2012 by Sol

The code for this post is on GitHub: https://github.com/sol-prog/roguelike.

In the last post of this series we’ve added a dummy map to the game on which the main character was able to move freely. Now it is time to add a real map to our roguelike game, in order to be able to add elements on the map we are going to use a Perlin noise function. If you are interested in how I’ve implemented the Perlin noise function in C++ you could read my Perlin noise in C++11 article.

I’ll include here, for completeness, the PerlinNoise class definition. The complete code is on the Github repository posted at the beginning of this article:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class PerlinNoise {
	// The permutation vector
	std::vector<int> p;
public:
	// Initialize with the reference values for the permutation vector
	PerlinNoise();
	// Generate a new permutation vector based on the value of seed
	PerlinNoise(unsigned int seed);
	// Get a noise value, for 2D images z can have any value
	double noise(double x, double y, double z);
private:
	double fade(double t);
	double lerp(double t, double a, double b);
	double grad(int hash, double x, double y, double z);
};

Let’s start by adding a new function, gen_Perlin, to the Frame class created in the second part of this series, this function will use PerlinNoise to fill the map with lakes, planes, mountains and snow. For this, we’ll need to distribute the relief according to some arbitrary rule. Assuming the PerlinNoise will return values from 0 to 1, we could use the next rule for generating the terrain:

  • from 0 to 0.35 we’ll have water or lakes
  • from 0.35 to 0.6 we’ll have floor or planes
  • from 0.6 to 0.8 we’ll have walls or mountains
  • from 0.8 to 1.0 we’ll have snow

Using the above rule, we could implement the gen_Perlin member function:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Fill the map with lakes, planes, mountains and snow
void Frame::gen_Perlin(const unsigned int &seed) {
	// Create a PerlinNoise object with a random permutation vector generated with seed
	PerlinNoise pn(seed);

	for(int i = 0; i < _height; ++i) {     // y
		for(int j = 0; j < _width; ++j) {  // x
			double x = (double)j/((double) _width);
			double y = (double)i/((double) _height);

			double n = pn.noise(10 * x, 10 * y, 0.8);

			// Watter (or a Lakes)
			if(n < 0.35) {
				mvwaddch(_w, i, j, '~');
			}
			// Floors (or Planes)
			else if (n >= 0.35 && n < 0.6) {
				mvwaddch(_w, i, j, '.');
			}
			// Walls (or Mountains)
			else if (n >= 0.6 && n < 0.8) {
				mvwaddch(_w, i, j, '#');
			}
			// Ice (or Snow)
			else {
				mvwaddch(_w, i, j, 'S');
			}
		}
	}
}

For using gen_Perlin, we’ll need to change a single line in the main function of our game:

1
2
	// Fill the game map with numbers
	game_map.fill_window();

the highlighted line we’ll become:

1
2
	// Fill the game map with lakes, planes, mountains and snow using a Perlin noise function
	game_map.gen_Perlin(237);

The number 237 from above was chosen arbitrarily, it is an unsigned integer used to seed the random number engine that will be used to generate a permutation in the PerlinNoise object. We could change this number from game session to game session and we’ll have a large number of different maps.

Let’s start the game and see what we’ve got until now:

Figure_9

I think the terrain looks cool, but the fact that we can walk through walls and watter is not so realistic, we’ll need to add some rules to the game like for e.g.:

  • no walking through walls!
  • no walking trough watter unless the @ has a boat or some magical spell (to be defined later).

Currently, the character movements are simulated trough this function:

1
2
3
4
5
6
7
8
// Add a character at a specific position to the window
void Frame::add(Character &x, int row_0, int col_0) {
	if((row_0 >= 0 && row_0 < _height) && (col_0 >= 0 && col_0 < _width)) {
		erase(x);
		mvwaddch(_w, row_0, col_0, x.symbol());
		x.pos(row_0, col_0);	
	}
}

Let’s add a quick and dirty test that will block @ from going through watter, walls and snow:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void Frame::add(Character &x, int row_0, int col_0) {
	if((row_0 >= 0 && row_0 < _height) && (col_0 >= 0 && col_0 < _width)) {
		// Get the element at the target position:
		char target = mvwinch(_w, row_0, col_0);
		// If the target position is watter, wall or snow stop the character to go trough it
		if (target == '~' || target == '#' || target == 'S') {
			return;
		}
		erase(x);
		mvwaddch(_w, row_0, col_0, x.symbol());
		x.pos(row_0, col_0);	
	}
}

Let’s run the code:

Figure_10

No matter how many time the user will try, the main character won’t be able to go trough watter, walls and snow.

Next time, we are going to add objects on the map that will give the character special powers and maybe a cute little monster that needs to be slashed by the main character in order to win the game …

All posts from this series:

If you are interested in reading more about procedural texture generations and various noise functions, you could read Texturing and Modeling: A Procedural Approach by D. S. Ebert, F. K. Musgrave, D. Peachey, K. Perlin, S. Worley:

If you are interested in learning more about the new C++11 syntax I would recommend reading The C++ Programming Language by Bjarne Stroustrup.

or, Professional C++ by M. Gregoire, N. A. Solter, S. J. Kleper 2nd edition:

blog comments powered by Disqus