Chapter 3 – A Text-Based Console World

First, let’s focus on the game logic. For now I won’t start using pixel graphics and just use the console. Let’s create a little ASCII adventure to stretch our legs. Now keep in mind that the logic remains the same regardless of the interface I’m using. Hence the logic code I write for an ASCII Console Game can be pretty easily adapted to work with a Windows Graphics Game.

Here’s a rough working sketch of the game:


/////////////////////////////////////
// Simple ASCII Console World
// Author: Muhammad Ahmad Tirmazi
// Date: 12 July 2012
// License: BSD 4-Clause
/////////////////////////////////////

#include <cstdlib>
#include <windows.h>

#include <iostream>
#include <string>
#include <vector>

// Our Custom Namespace
namespace pro {

///////////////////////////////
// Just a typedef for 'char'
///////////////////////////////
typedef char GameTile;

///////////////////////////////
// Simple Point Class
// Holds the X and Y values
// of a 2D Object
//////////////////////////////
template< typename T > struct Point
{
    Point(void) : X(0), Y(0) {}
    Point(T x, T y) : X(x), Y(y) {}
    T X;
    T Y;
};

/////////////////////////////
// Simple GamePlayer Class
// For controlling the
// Main Character
/////////////////////////////
struct GamePlayer
{
    Point< int > Position;
    GameTile Tile;

    GamePlayer(std::vector< std::string >& map);

    void MoveLeft(std::vector< std::string >& map);
    void MoveRight(std::vector< std::string >& map);
    void MoveUp(std::vector< std::string >& map);
    void MoveDown(std::vector< std::string >& map);

    void Draw(void);
};

//////////////////////////////////////////
// Constructor
// Sets the Player's original position
// 1 is the code for a happy face
/////////////////////////////////////////
GamePlayer::GamePlayer(std::vector< std::string >& map)
    : Position(2, 2), Tile(1)
{
    if (map[Position.X][Position.Y] == ' ')
    {
        map[Position.X][Position.Y] = Tile;
    }
    else
    {
        std::cerr << "Player's Tile is Occupied" << std::endl;
    }
}

//////////////////////////////////////////
// Moves the Player 1 Tile To the Left
/////////////////////////////////////////
void GamePlayer::MoveLeft(std::vector< std::string >& map)
{
    // If the tile is empty
    if (map[Position.Y][Position.X - 1] == ' ')
    {
        // Remove our player's tile from it's original position
        map[Position.Y][Position.X] = ' ';

        Position.X --;
        // Fill it with our player's tile
        map[Position.Y][Position.X] = Tile;
    }
}

//////////////////////////////////////////
// Moves the Player 1 Tile To the Right
/////////////////////////////////////////
void GamePlayer::MoveRight(std::vector< std::string >& map)
{
    // If the tile is empty
    if (map[Position.Y][Position.X + 1] == ' ')
    {
        // Remove our player's tile from it's original position
        map[Position.Y][Position.X] = ' ';

        Position.X ++;
        // Fill it with our player's tile
        map[Position.Y][Position.X] = Tile;
    }
}

//////////////////////////////////////////
// Moves the Player 1 Tile Upwards
/////////////////////////////////////////
void GamePlayer::MoveUp(std::vector< std::string >& map)
{
    // If the tile is empty
    if (map[Position.Y - 1][Position.X] == ' ')
    {
        // Remove our player's tile from it's original position
        map[Position.Y][Position.X] = ' ';

        Position.Y --;
        // Fill it with our player's tile
        map[Position.Y][Position.X] = Tile;
    }
}

//////////////////////////////////////////
// Moves the Player 1 Tile Downwards
/////////////////////////////////////////
void GamePlayer::MoveDown(std::vector< std::string >& map)
{
    // If the tile is empty
    if (map[Position.Y + 1][Position.X] == ' ')
    {
        // Remove our player's tile from it's original position
        map[Position.Y][Position.X] = ' ';

        Position.Y ++;
        // Fill it with our player's tile
        map[Position.Y][Position.X] = Tile;
    }
}

} // namespace pro

////////////////////////////////
// A Character Representation
// Of Our Tile Map
///////////////////////////////
static std::string tileMap[] = {

"==========",
"=        =",
"=        =",
"=        =",
"=        =",
"=        =",
"=        =",
"=========="
};

/** Not recommended, but works for simple stuff... **/

///////////////////////////////////////////////////
// Calculates the vertical length of the tile map
//////////////////////////////////////////////////
static int mapLenght = sizeof(tileMap) / sizeof(tileMap[0]);

///////////////////////////////////////////////////
// Calculates the horizontal width of the tile map
///////////////////////////////////////////////////
static int mapWidth = tileMap[0].size();

///////////////////////////////////////////////////////////////
// Vector, to prevent us from dealing with the lousy pointer
// arithmetic that comes with arrays
///////////////////////////////////////////////////////////////
static std::vector< std::string > tileVector(tileMap, tileMap + mapLenght);

//////////////
// The player
//////////////
static pro::GamePlayer player(tileVector);

////////////////////////
// Draws the Tile Map
///////////////////////
void Draw(void)
{
    std::system("cls");

    for (int y = 0; y < mapLenght; y++)
    {
        for (int x = 0; x < mapWidth; x++)
        {
            std::cout << tileVector[y][x];
        }

        std::cout << "\n";
    }
}

//////////////////////////////////////
// Gets and Responds to user input
// from the keyboard
/////////////////////////////////////
bool GetInput(void)
{
    if (GetAsyncKeyState(VK_UP))
    {
        player.MoveUp(tileVector);
    }
    else if (GetAsyncKeyState(VK_DOWN))
    {
        player.MoveDown(tileVector);
    }
    else if (GetAsyncKeyState(VK_LEFT))
    {
        player.MoveLeft(tileVector);
    }
    else if (GetAsyncKeyState(VK_RIGHT))
    {
        player.MoveRight(tileVector);
    }
    else
    {
        return false;
    }
    return true;
}

//////////////////////////////////////////
// Exit when the player presses 'Escape'
/////////////////////////////////////////
bool CheckExit()
{
    if (GetAsyncKeyState(VK_ESCAPE))
    {
        return true;
    }

    return false;
}

//////////////////////////////////
// The main entrypoint function
/////////////////////////////////
int main()
{
    bool open = true;

    Draw();

    /////////////////////////
    // The Main Game Loop
    ////////////////////////
    while (open)
    {
        ////////////////////////////////////////////
        // Redraw if the player's position changes
        ////////////////////////////////////////////
        if (GetInput()) Draw();

        if (CheckExit()) open = false;
    }

    return 0;
}

Some Issues

  • GetASyncKeyState() and system(“cls”) are not cross-platform, we may need to use something else
  • We may need to make the code more comprehensive, object-oriented and improve the design
  • We still need to code the player’s interaction with other tiles

Writing Cross-platform Code

Although the above code will probably compile without any errors on most versions of Windows, there are a few things that may not work well on other operating systems such as Ubuntu etc. Firstly, I use a system call for clearing the screen. One solution to making the call cross-platform is to use pre-processor definitions to find out what platform we’re compiling on and use that system’s specific command for clearing the screen. For example:

void clear_screen(void)
{
    #if defined (WIN32) || defined (_WIN32)
    
    system("cls"); // use the "cls" command on windows
   
    #else
   
    system("clear"); // otherwise use the unix "clear" command

    #endif
}

Secondly, I use GetASyncKeyState() for input, which is a windows api function and hence is only supported on windows.

The best thing for writing cross-platform code is to use a third-party cross-platform library.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s