Beginner's Guide to Roguelikes in C/C++  Updated January 2013
Article One: Program Flow

The Unstated Obvious

Every program that has ever been created can be reduced to three key phases: input, processing and output.

Sometimes, it seems so obvious that we miss it. Take for example the following program:

#include <iostream.h>
int main( void )
{
int a = 0, b = 1;
int c = a + b;
cout << c;
return 0;
}

When you compile and run the code, your inputs are the numbers you've assigned to the variables a and b when you were entering the code. Your processing is the arithmetic - adding the two variables a and b and storing the value in variable c. Your output is drawing the value of variable c to the screen.

You may be asking: what does this have to do with writing a game? Well, a game is based on exactly the same principles - only that the input, output and (especially) the processing phase are much more complicated.

Normally the program flow in any game can be broken down into the following sequence of events:

Can you see the sequence of events occurring here?

As you can see, there are only two real differences:

To word it the differently: you present a situation to the player, the player evaluates it and decides how to act and the game reacts to the player's action. The trouble comes in the details but that is essentially the framework of any game.


Implementation


To actually show how this works, let's create a very simple "game" in which the user controls a character on the screen and can move it around. One of the first (and frankly, one of the most difficult) obstacles you'll run into is how to draw text to the screen and to get keyboard input from the player.

Most roguelike developers do this through the use of a console interface library, something like libtcod or 'Curses. For the guide, I've written a (hopefully) turnkey and easy-to-use console library that we'll be using for all of our examples.

Note
On the main page you can find detailed information about the console library and how to use it. Feel free to take a look there if you have any questions. But just to cover the basics, all examples require the following files to be present in your project when you build:
  • Static library for the Console interface:
    • if you're using a GCC-based compiler (like MinGW), use libConsole.a
    • if you're using Microsoft Visual Studio, use Console.lib
  • Header file to tell your compiler what's available in the console interface library. This is cleverly named Console.h.
When building on a GCC-based compiler, remember that you'll need to add "-L. -lConsole" to your command line, for example, to build the example project for this article, the command line should look something like "gcc -Wall article1.cpp -L -lConsole -o Article1.exe"

The first thing we should always do when the program runs is to clear the screen of any junk that was there before. If you don't know what state a system is in, assert it. It's good practice to get into. The code for this is here:

console.Clear();

Much like how iostream.h would give you access to the cout object, the console library gives you access to an object called console, which behaves like a very simple version of cout. Don't worry, it's not as complicated as it seems.

Right, now that's out of the way, we can move on. Remember how the program flow for games repeats? We're going to need a loop somewhere in there, so we might as well add it now. Let's make it an infinite loop since we don't know how we will terminate it.

while( true )
{
// Output
// Input
// Processing
}

So putting it all together, the code should look something like this now:

#include <conio.h>
#include "Console.h"
int main( void )
{
console.Clear();
while( true )
{
// Output phase goes here
// @@@ TODO @@@
// Input phase goes here
// @@@ TODO @@@
// Processing phase goes here
// @@@ TODO @@@
}
return 0;
}

Alright, it's starting to take shape!


Output Phase


The first and most important thing is to show the player what's going on. It's not much of a game if all you see is a blank screen.

To do this, we need to draw the player's character on the screen. In homage to Rogue, we'll use the "AT" symbol, @, to represent the player. But it's not enough to simply throw it at the screen, we need to make sure that it's in the right spot.

Screen1_1.gif
Coordinates!

Regardless of the operating system involved, in console mode the screen is divided up into a grid, much like graph paper or a chess board. Only one character can occupy one tile at a time. This is grid perfectly suited to console-based games as it gives a sense of space and distance in your game.

What we need to do now is to determine on what square tile on the console the player is at. To do this, we need to make sure we record the player's position in some sort of variable. Since the screen is two-dimensional, we'll use an (X, Y) coordinate system to indicate position.

int nPlayerX, nPlayerY;

Where the nPlayerX variable determines how many tiles from the top-left of the console the player is at horizontally and the nPlayerY variable determines the vertical offset from the same corner.

We need to initialize these variables this so that the player starts at a definite location (rather than whatever garbage data was residing at their addresses). A console screen by default usually is 80x25 characters big, so let's try for something in the middle.

int nPlayerX=40, nPlayerY=12;

Now all we need to do is get the computer to draw the ' @ ' character at that precise location on the console. To do that we need the following code.

// Output phase
console.Position( nPlayerX, nPlayerY );
console << '@';

This makes our total code look like this.

#include <conio.h>
#include "Console.h"
int main( void )
{
console.Clear();
int nPlayerX=40, nPlayerY=12;
while( true )
{
// Output phase
console.Position( nPlayerX, nPlayerY );
console << '@';
// Input phase goes here
// @@@ TODO @@@
// Processing phase goes here
// @@@ TODO @@@
}
return 0;
}

Pretty straightforward, right?


Input Phase


Now we have drawn the player's character to the screen, we can now let the player tell us what they want to do.

So how do we get this input? The easiest way to do this is with the getch() function, which does two things:

  1. Waits for the user to press a key
  2. Returns an ASCII character representing the key the user pressed

These two things do exactly everything we need to do for this phase. All we need to do is save the key that the user pressed so that we can process it later.

char nKey = getch();

The idea here is that we can check for specific keys and perform different operations depending on the keys. This would look like:

if( nKey == '2' )
{
// Move down
}
else if( nKey == '4' )
{
// Move left
}
else if( ... )
{
// Do something
}

There are a few problems with getch() though:

So to counter this, most console libraries like libtcod or 'curses have their own special routines to read input from the keyboard. In addition, these libraries are often designed to behave the same regardless of system platform or compiler. Our console library tries to do the same thing, through a method called WaitForKeypress().

KEYPRESS sKeyPress = console.WaitForKeypress();

The WaitForKeypress() method returns a structure containing information regarding a key press that your game experienced from the user. The most important thing about this structure is the "eCode" property which tells you what key was pressed (the other properties tell you if control keys were pressed at the same time).

This is everything we need for our input phase, so the final product looks like this:

#include <conio.h>
#include "Console.h"
int main( void )
{
console.Clear();
int nPlayerX=40, nPlayerY=12;
while( true )
{
// Output phase
console.Position( nPlayerX, nPlayerY );
console << '@';
// Input phase
KEYPRESS sKeyPress = console.WaitForKeypress();
// Processing phase goes here
// @@@ TODO @@@
}
return 0;
}


Processing Phase


With input and output done, all that's left is to actually do something when a key is pressed. In this case, we're going to make that the arrows keys move the player around. If we keep NUMLOCK on, then the only key presses we need to worry about are:

Which Way Is Up Again?

Movement occurs by incrementing or decrementing the coordinate variables nPlayerX and nPlayerY. The question is, which way is up? How do we move left? To figure this out, take a look at the following picture. It contains two characters, each put at a different coordinate.

Screen1_3.gif
Coordinates!

The second character is down and to the left of the first, but according to the coordinates, it is (-10, 5) away. Using this, we can determine the following:

This can be implemented using either if or switch statements. We'll use a switch statement as it's a little cleaner to look at. Pay close attention to the increments and decrements here.

// Processing phase - Implement the player's command
switch( sKeyPress.eCode )
{
// Move down
case CONSOLE_KEY_DOWN:
nPlayerY++;
break;
// Move left
case CONSOLE_KEY_LEFT:
nPlayerX--;
break;
// Move right
case CONSOLE_KEY_RIGHT:
nPlayerX++;
break;
// Move up
case CONSOLE_KEY_UP:
nPlayerY--;
break;
// Quit
case CONSOLE_KEY_ESCAPE:
return 0;
// Ignore any other key
default:
break;
}

Putting it all together we get the following:

#include "Console.h"
int main( void )
{
console.SetTitle( "Article One Demo" );
// Initialize the player's on-screen location
int nPlayerX=40, nPlayerY=12;
// Main program loop
while( true )
{
// Wipe the console clean
console.Clear();
// Output phase - Draw the player to the screen
console.Position( nPlayerX, nPlayerY );
console << '@';
// Input phase - Wait for the player to do something
KEYPRESS sKeyPress = console.WaitForKeypress();
// Processing phase - Implement the player's command
switch( sKeyPress.eCode )
{
// Move down
case CONSOLE_KEY_DOWN:
nPlayerY++;
break;
// Move left
case CONSOLE_KEY_LEFT:
nPlayerX--;
break;
// Move right
case CONSOLE_KEY_RIGHT:
nPlayerX++;
break;
// Move up
case CONSOLE_KEY_UP:
nPlayerY--;
break;
// Quit
case CONSOLE_KEY_ESCAPE:
return 0;
// Ignore any other key
default:
break;
}
}
return 0;
}


Compile, Build and ... what?


That's pretty much it. Compile and run the code and see what we've got. The first thing you notice is the following effect:

Screen1_2.gif
WTF?

It keeps on drawing the same character all over the place!

This is actually to be expected. We're drawing things to the screen, and moving around, without ever once erasing anything. This can be fixed by adding a simple, one line command at the beginning of the output phase, resulting in an output phase that looks like this:

// Output Phase
console.Clear(); // <-- The fix is here
console.Position( nPlayerX, nPlayerY );
console << '@';

Basically, we wipe the entire screen clear and draw the player in the new position. Save, compile and run the fix and see how things work now.


Summary


If I have done my job, you should now be getting comfortable with structuring your code, and have begun to get your feet wet with the whole concept of drawing to the console and receiving user input.

There are a few ignored problems with this example, the most blatant to the experienced developer is that there is no code to prevent the user from moving off the screen which can lead to data corruption, program crashes and other major errors. Before you move on, I would recommend playing around with this application to make it limit the user to only moving within the screen itself.

If you can do this you are getting the hang of things quite well. Also, it may help to play around a little and try to intentionally mess up the code to get used to how the compiler reports errors.


You can download the example source code here