Roguelike game in C++ - Adding a map to the game

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 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 () { 2 3 // Initialize ncurses 4 Screen scr ; 5 6 // Print a welcome message on screen 7 scr . add ( "Welcome to the RR game.

Press any key to start.

If you want to quit press \" q \" or \" Q \" " ); 8 9 // Wait until the user press a key 10 int ch = getch (); 11 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 ); 15 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 ); 19 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 ); 23 24 // Fill the game map with numbers 25 game_map . fill_window (); 26 27 // Start the game loop 28 game_loop ( game_map , viewport , main_char , ch ); 29 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 ; 4 5 // Show the main character on the screen 6 game_map . add ( main_char ); 7 viewport . center ( main_char ); 8 viewport . refresh (); 9 10 while ( 1 ) { 11 ch = getch (); 12 13 // Main character movements 14 if ( ch == KEY_LEFT ) { 15 game_map . add ( main_char , main_char . row (), main_char . col () - 1 ); 16 viewport . center ( main_char ); 17 viewport . refresh (); 18 } 19 else if ( ch == KEY_RIGHT ) { 20 game_map . add ( main_char , main_char . row (), main_char . col () + 1 ); 21 viewport . center ( main_char ); 22 viewport . refresh (); 23 } 24 else if ( ch == KEY_UP ) { 25 game_map . add ( main_char , main_char . row () - 1 , main_char . col ()); 26 viewport . center ( main_char ); 27 viewport . refresh (); 28 } 29 else if ( ch == KEY_DOWN ) { 30 game_map . add ( main_char , main_char . row () + 1 , main_char . col ()); 31 viewport . center ( main_char ); 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:

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

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

@ is near the down 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 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: