Roguelike game in C++ - Adding a map to the game
Posted on July 16, 2012 by Sol

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

The last post of this series has laid the ground for a small roguelike game - the main character @ was added on the screen and the user was able to change his position using the arrow keys. Now it is time to add more interactivity to our game by creating a test map on which the character is allowed to move freely.

We’ll start by refactoring a bit the code for the screen initialization part implemented last time, we could put all this code in a Screen class, we show here only the definition of the Screen class:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Screen {
	int _height, _width;
public:
	// Initialize the ncurses library
	Screen();
	// Clear ncurses
	~Screen();
	// Print a message on the screen
	void add(const char *message);
	// Get the screen height and width
	int height();
	int width();
};

A game character is currently defined as having an ASCII symbol and a position, in the future we could add more properties for a character like color, speed etc … Having all this in a class, that can be extended in the future, sounds like a good idea:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Character {
	int _row, _col;
	char _symbol;
public:
	// Create a character
	Character(char symbol, int row_0, int col_0);
	// Change the character position
	void pos(int row_0, int col_0);
	// Get character's row (y) position
	int row();
	// Get character's col (x) position
	int col();
	// Get the symbol of the character
	char symbol();
};

A game map is usually bigger than the player’s screen and at a particular moment only a part of this map should be visible to the player for a bit of frill. We could use an ncurses WINDOW for storing the game’s map and a subwindow of this for what is actually shown on the screen, in other words we’ll need a window and a viewport.

We could create a Frame class to define our game map (a window) and the viewport (a subwindow). A Frame object could be initialize by specifying his width, height and his position, for a viewport we’ll also need to specify his parent window:

 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
32
33
34
class Frame {
	// Frame dimensions
	int _height, _width;
	// Frame position
	int _row, _col;
	// Boolean - FALSE for a window and TRUE for a subwindow (viewport)
	bool _has_super;
	// Pointer to an ncurses WINDOW
	WINDOW *_w;
	// Pointer to an ncurses WINDOW, this will be NULL for a window and will point to the parent window
	// for a subwindow
	WINDOW *_super;
public:
	...
	// Initialize a main window (no parent)
	Frame(int nr_rows, int nr_cols, int row_0, int col_0);
	// Initialize a subwindow (viewport) with a parent window
	Frame(Frame &sw, int nr_rows, int nr_cols, int row_0, int col_0);
	~Frame();
	...
	// Fill a window with numbers - the window is split in four equal regions,
	// each region is filled with a single number, so 4 regions and 4 numbers.
	// This is a suggestion of how this will look:
	//         0 | 1
	//         -----
	//         2 | 3 
	// This function is used only for debugging purposes.	
	void fill_window();
	...		
	// Add a character to the window
	void add(Character &x);
	// Center the viewport around a character
	void center(Character &x);	
};

The fill_window method from above is useful in the development phase of the game, we’ll need a dummy game map on which we could move our main character. The viewport will follow the main character movements with the aid of the center method from Frame.

Using the above elements we can define the main function or our game in a much cleaner way than before:

 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
int main() {

	// Initialize ncurses
	Screen scr;

	// Print a welcome message on screen
	scr.add("Welcome to the RR game.\nPress any key to start.\nIf you want to quit press \"q\" or \"Q\"");

	// Wait until the user press a key
	int ch = getch();

	// Create an ncurses window to store the game map. This will be twice the size
	// of the screen and it will be positioned at (0,0) in screen coordinates
	Frame game_map(2*scr.height(), 2*scr.width(), 0, 0);

	// Create an ncurses subwindow of the game map. This will have the size 
	// of the user screen and it will be initially postioned at (0, 0)
	Frame viewport(game_map, scr.height(), scr.width(), 0, 0);

	// Initialize the main character. We are going to put this in the middle of 
	// the game map (for now)
	Character main_char('@', game_map.height()/2, game_map.width()/2);

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

	// Start the game loop
	game_loop(game_map, viewport, main_char, ch);

	return 0;
}

Currently the game loop contains code for moving the character on the screen and centering the viewport:

 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
32
33
34
35
36
37
38
void game_loop(Frame &game_map, Frame &viewport, Character &main_char, int ch) {
	// Check if the user wishes to play the game
	if(ch == 'q' || ch =='Q') return;

	// Show the main character on the screen
	game_map.add(main_char);
	viewport.center(main_char);
	viewport.refresh();

	while(1) {
		ch = getch();

		// Main character movements
		if(ch == KEY_LEFT) {
			game_map.add(main_char, main_char.row(), main_char.col() - 1);
			viewport.center(main_char);
			viewport.refresh();
		}
		else if(ch == KEY_RIGHT) {
			game_map.add(main_char, main_char.row(), main_char.col() + 1);
			viewport.center(main_char);
			viewport.refresh();
		}
		else if(ch == KEY_UP) {
			game_map.add(main_char, main_char.row() - 1, main_char.col());
			viewport.center(main_char);
			viewport.refresh();
		}
		else if(ch == KEY_DOWN) {
			game_map.add(main_char, main_char.row() + 1, main_char.col());
			viewport.center(main_char);
			viewport.refresh();
		}
		else if(ch == 'q' || ch == 'Q') {
			break;
		}
	}
}

Let’s try the new version of the game:

Initial screen:

Figure_1

Game starts and the main character, @, is shown in the middle of the game map:

Figure_4

Moving @ down, left, down we could see the viewport moving with the main character:

Figure_5

@ is near the down left corner of the map:

Figure_6

As a side note, the character is forced for stay on the map versus the previous version of the game were it was possible to move the character outside the screen.

Next time we are going to test a few algorithms for terrain generation.

If you have any improvements in mind feel free to drop me a comment.

All posts from this series:

If you are interested to learning more about ncurses I would recommend reading Programmer’s Guide to NCurses by Dan Gookin.

If you are interested to 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