Solarian Programmer

My programming ramblings

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:

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

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

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

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 int main() {
 3 	// Initialize ncurses
 4 	Screen scr;
 6 	// Print a welcome message on screen
 7 	scr.add("Welcome to the RR game.\nPress any key to start.\nIf you want to quit press \"q\" or \"Q\"");
 9 	// Wait until the user press a key
10 	int ch = getch();
12 	// Create an ncurses window to store the game map. This will be twice the size
13 	// of the screen and it will be positioned at (0,0) in screen coordinates
14 	Frame game_map(2*scr.height(), 2*scr.width(), 0, 0);
16 	// Create an ncurses subwindow of the game map. This will have the size 
17 	// of the user screen and it will be initially postioned at (0, 0)
18 	Frame viewport(game_map, scr.height(), scr.width(), 0, 0);
20 	// Initialize the main character. We are going to put this in the middle of 
21 	// the game map (for now)
22 	Character main_char('@', game_map.height()/2, game_map.width()/2);
24 	// Fill the game map with numbers
25 	game_map.fill_window();
27 	// Start the game loop
28 	game_loop(game_map, viewport, main_char, ch);
30 	return 0;
31 }

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

 1 void game_loop(Frame &game_map, Frame &viewport, Character &main_char, int ch) {
 2 	// Check if the user wishes to play the game
 3 	if(ch == 'q' || ch =='Q') return;
 5 	// Show the main character on the screen
 6 	game_map.add(main_char);
 8 	viewport.refresh();
10 	while(1) {
11 		ch = getch();
13 		// Main character movements
14 		if(ch == KEY_LEFT) {
15 			game_map.add(main_char, main_char.row(), main_char.col() - 1);
17 			viewport.refresh();
18 		}
19 		else if(ch == KEY_RIGHT) {
20 			game_map.add(main_char, main_char.row(), main_char.col() + 1);
22 			viewport.refresh();
23 		}
24 		else if(ch == KEY_UP) {
25 			game_map.add(main_char, main_char.row() - 1, main_char.col());
27 			viewport.refresh();
28 		}
29 		else if(ch == KEY_DOWN) {
30 			game_map.add(main_char, main_char.row() + 1, main_char.col());
32 			viewport.refresh();
33 		}
34 		else if(ch == 'q' || ch == 'Q') {
35 			break;
36 		}
37 	}
38 }

Let’s try the new version of the game:

Initial screen:

Initial game screen

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

Main character

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

Viewport follow follow the main character

@ is near the down left corner of the map:

Left corner of the map

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:

comments powered by Disqus