Roguelike game in C++ - Adding a rudimentary monster to the game

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

Last time we’ve added a real map to the game, generated with a Perlin noise function. But where is the fun in a roguelike game without a monster ? In this post, we are going to add a rudimentary monster to our game, that will chase the main character. The player task will be to dodge the monster and stay alive …

Our monster will be dumb, all he knows is to chase the main character on the map. Because I’m more interested in testing an idea for a chasing algorithm, we are not going to create a separate class for the monster, we’ll refactor the code and add some AI to our monster in a future tutorial. For now we could use the Character class to initialize our monster:

1 int main () { 2 3 ... 4 5 // Initialize the main character. We are going to put this in the middle of 6 // the game map (for now) 7 Character main_char ( '@' , game_map . height () / 2 , game_map . width () / 2 ); 8 9 // Add a dummy monster to the game (we start with a hard coded monster position defined 10 // relatively to our main character position) 11 Character monster ( 'M' , main_char . row () + 7 , main_char . col () + 22 ); 12 13 ... 14 15 // Start the game loop 16 game_loop ( game_map , viewport , main_char , ch , monster ); 17 18 return 0 ; 19 }

Now, that we have a monster in our game, we could add it to the map in the game_loop function:

1 void game_loop ( Frame & game_map , Frame & viewport , Character & main_char , int ch , Character & monster ) { 2 // Check if the user wishes to play the game 3 if ( ch == 'q' || ch == 'Q' ) return ; 4 5 // Add the monsters to the game_map 6 game_map . add ( monster ); 7 8 .... 9 }

What about the chasing algorithm ? We could use a simple idea based on the Euclidean distance for moving the monster:

Check the up, down, left and right neighbor positions for our monster.

If a cell (neighbor) is free, calculate the distance between this cell and the main character position.

Move in the cell that minimize the distance between the monster and the main character.

We’ll start by creating a new member function in Frame, that will check if the target position is free to move in or not:

1 // Check if the target position is free 2 bool Frame :: target_position ( int row_0 , int col_0 ) { 3 // Get the element at the target position: 4 char target = mvwinch ( _w , row_0 , col_0 ); 5 // If the target position is watter, wall or snow stop the character to go through it 6 if ( target == '~' || target == '#' || target == 'S' ) { 7 return FALSE ; 8 } 9 // If the target position is a monster don't let the character to go through it (for now) 10 if ( target == 'M' ) { 11 return FALSE ; 12 } 13 return TRUE ; 14 }

For now, we could add the tracking (chasing) algorithm in the game_loop function just after the main character movements, this code will be moved to a Monster class in our next article from this series:

1 ... 2 // Other characters movements 3 // Try to move left 4 int pos = - 1 , score = game_map . height () * game_map . height () + game_map . width () * game_map . width (), dist = - 1 ; 5 if ( game_map . target_position ( monster . row (), monster . col () - 1 )) { 6 dist = std :: pow ( main_char . row () - monster . row (), 2 ) + std :: pow ( main_char . col () - ( monster . col () - 1 ), 2 ); 7 if ( score > dist ) { 8 score = dist ; 9 pos = 0 ; 10 } 11 } 12 // Try to move right 13 if ( game_map . target_position ( monster . row (), monster . col () + 1 )) { 14 dist = std :: pow ( main_char . row () - monster . row (), 2 ) + std :: pow ( main_char . col () - ( monster . col () + 1 ), 2 ); 15 if ( score > dist ) { 16 score = dist ; 17 pos = 1 ; 18 } 19 } 20 // Try to move up 21 ... 22 // Try to move down 23 ... 24 25 switch ( pos ) { 26 case 0 : 27 game_map . add ( monster , monster . row (), monster . col () - 1 ); 28 break ; 29 case 1 : 30 game_map . add ( monster , monster . row (), monster . col () + 1 ); 31 break ; 32 case 2 : 33 game_map . add ( monster , monster . row () - 1 , monster . col ()); 34 break ; 35 case 3 : 36 game_map . add ( monster , monster . row () + 1 , monster . col ()); 37 break ; 38 } 39 ...

Let’s see the new code in action. When the game starts, we have the main character, @, and the monster, M, in their initial positions:

A screenshot of the game after a few movements, note how the monster follows the player character:

I’ve also made a small movie for showing how you can play the game:

In the next article, we are going to refactor the code for the monster, in order to be able to add more than one monster to the screen. We could also define areas on which a monster can see the main character. When you have ten monsters on your game, it won’t be much fun if every monster from the game can see the position of the player character and start chasing it. We could create a few kind of monsters with different characteristics. Also the main character should have a sword or some other weapon to be able to kill or stop a monster …

All posts from this series:

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: