Jump to content

  • Log In with Google      Sign In   
  • Create Account

Squared'D's Journal

A Complete Graphics-less Game #5 - World Map Part 2

Posted by , in A Complete Graphics-less Game 16 June 2013 - - - - - - · 698 views
text, text based, game and 2 more...
It's been a while since I've had a chance to update this. I've started working on another project with an old friend of mine and 3 others so I haven't had the time to really focus on this project, but I'll try to keep it up. I think this is a good project for me. As I dive deeper into it, I feel that I'll be able to explore some interesting concepts. So what have I been working on?

World Map Part 2
You don't have to be a genius to see that the game is very limited. I thought this was as good a time as any to try to load the world map from a file. In all of my recent programs, I haven't been using C++ streams to do any file loading. It's been years since I've done anything with it. Honestly, even using "std::cout" feel like weird to me. I grew up on printf and all it's derivatives, but I will try my best to use streams now. Remember, I'm making this program as a learning experience. I want to show that programmers don't have to jump into graphics programming from the beginning. They can instead test basic concepts and refine their skills using console applications.

Long story short, I'm going to build a loader to load the world map. As I said before, towns and the world map will use the same basic system as everything is really just locations connected by links. That's why do code uses the name AreaMap and not WorldMap. Here's a sample of the world map. I'm using text files for now as they are easy by hand.
"Washington"   1 3
"Frederick"    0 2 3 
"Pittsburgh"   1 4
"Baltimore"    0 1 4 5
"Philadelphia" 2 3 5
"Camden"       3 4 6
"Newark"       5 7
"New York"     6 8
"Boston"       7
I think this format is simple enough. It's merely the name of the location followed by a list of the links. The loader will perform minimal error checking so the file should be in the proper format.

Input File Streams in C++
Input file streams in C++ are quite simple. Just include the <fstream> header. I'll use the std::ifstream type because this is only for input. There are two ways to open the file: specifying the file in the object constructor or by using the open() method. Here's an example:
std::ifstream m_file_stream;
m_file_stream.open("test.txt", std::ios::in);

// or

std::ifstream m_file_stream("test.txt", std::ios::in);
After opening a basic text file, you can use it just like std::cin.

Loading the Area Map
For this loader, I decided to create a loader class that will handle loading the file. I decided to make it a class because to load the file in a clean way, I needed to use multiple function calls. Putting it in a single class made everything fit nicely. I also had to make some changes to the CAreaMap class so I can add links to a location after the location in the map has been created.

Here's the class definition:
// forward declaration of for the CAreaMap class
class CAreaMap;

/// \brief return values for the loader
enum LoaderResult
    LOAD_OK		= 0,
    LOAD_EOF	= 1,
    LOAD_FAIL	= 2

/// \brief Class for loading an area map
/// An Area map can represent all the places in a town or it can be used as a
/// higher level world map.
class CAreaMapLoader
        /// \brief Loads the Area Map from a file
        /// @param szFile the file name
        /// @param the_area_map the area map to load the data to
        /// @return the result of the load
        LoaderResult LoadFromFile(const char *szFile, CAreaMap &the_area_map);

        // Helper function to parse a line
        LoaderResult ReadLine(CAreaMap &the_area_map);

        // Helper function to search a string for a quoted sub-string
        LoaderResult GetNextStringToken(const std::string &inputline, size_t start, std::string &out, size_t &next);

        // Helper funtion to search a string for an int
        LoaderResult GetNextIntToken(const std::string &inputline, size_t start, int &out, size_t &next);

        // Helper funtion to search for the next numeric character
        size_t findNum(const std::string &in, size_t start);

        std::ifstream m_file_stream;
I made some helper functions to help me parse the file. Here's the code from LoadFromFile() and ReadLine():
LoaderResult CAreaMapLoader::LoadFromFile(const char *szFile, CAreaMap &the_area_map)
    // create the stream and open the file
    m_file_stream.open(szFile, std::ios::in);

    // check for success
    if(m_file_stream.fail()) return LOAD_FAIL;

    // read the lines
    LoaderResult result;
        result = ReadLine(the_area_map);
    } while(result == LOAD_OK);

    if(result == LOAD_EOF)
        // this isn't failue, we just processed all of the data
        result = LOAD_OK;

    // close the file

    return result;

LoaderResult CAreaMapLoader::ReadLine(CAreaMap &the_area_map)
    // read the first line, it should be the number of places
    std::string file_line;

    // check for eof before getting the line otherwise if the last line ends with eof, it won't be processed
    if(m_file_stream.eof()) return LOAD_EOF;

    // get the next line
    std::getline(m_file_stream, file_line);

    // check for blank lines
    if(file_line.size() < 1) return LOAD_OK;

    if(m_file_stream.fail()) return LOAD_FAIL;
    if(m_file_stream.bad()) return LOAD_FAIL;

    size_t cursor = 0;
    std::string location_name;

    // get the location name
    GetNextStringToken(file_line, cursor, location_name, cursor);

    // create the location without any links
    LocationIndex loc_index = the_area_map.AddLocation(location_name, 0);

    // get the links
    LoaderResult link_result = LOAD_OK;
    int link;
        link_result = GetNextIntToken(file_line, cursor, link, cursor);
        if(link_result == LOAD_FAIL) return LOAD_FAIL;
        else if (link_result == LOAD_OK)
            // add the links
            the_area_map.AddLocationLink(loc_index, link);
    } while(link_result == LOAD_OK);

    return LOAD_OK;
The code is very simple. I read an entire line from the string and then scan the line for the information that I need. Since I broke the code up into smaller sections, it's not some big convoluted mess.

Loading the map is as simple as this:
CAreaMapLoader map_loader;
map_loader.LoadFromFile("testmap.txt", m_WorldMap);
That's it. You can check the full source to see it working and as always, let me know if you have any problems compiling it.

Attached Files

A Complete Graphics-less Game #4 - World Map Test

Posted by , in A Complete Graphics-less Game 02 June 2013 - - - - - - · 962 views

Beginning on the World Map

I had hoped to release this earlier, but other projects have held me up a bit. Also when I had first envisioned writing this, I thought that I would be able to write about my thought process while I was actively making decisions. That turned out to be too difficult as I change my mind a lot. So I decided that it would be better to make all of the decisions, code something up, and then document my findings here.

Lessons Learned from the last prototype
I learned a lot working on that prototype. I know, it wasn't anything special, but at least I have something working that I can test out. As I said in my last entry, the prototype was written using a more C-style and not C++. Why? I don't know. I guess even though I've been using C++ for years, I'm still more comfortable using C-style functions. So what did I learn from the prototype? If you take a look at the code, I'm sure you'll see a lot of redundancy. Also, as you move from place to place in the game, a new menu is always drawn. To me, I feel like I'm moving between pages. Each page has the following task:
  • draw a menu
  • wait for input
  • process input
Now, how will I implement this into a more robust world map system? In this game, the world map will be a collection of places connected by links. When I think about other aspects of the game, when the user enters towns, a similar system will be used. Because of this, I'll use more generic-style names to describe things.

The Area Map
First, I need to develop a way to story the area information. For this, I'll start by defining a location.
typedef unsigned int LocationIndex;

/// \brief Defines one location in an area and it's links
struct AreaLocation
	std::string name;			//!< The name of the location

	std::vector <LocationIndex> links;	//!< The links

	/// \brief Helper function to return the number of links in this location
	inline unsigned int GetNumLinks() const { return links.size(); }

I've defined the type LocationIndex because I feel it's clearer than sticking a bunch of "unsigned int"s everywhere. The AreaLocation struct is fairly simple. I contains the area name and a vector of the links. The helper function GetNumLinks() isn't needed, but it will help the code be clearer.

To hold all of this together, I made the CAreaMap class.
/// \brief Class for an area map
/// An Area map can represent all the places in a town or it can be used as a
/// higher level world map.
class CAreaMap
		/// \brief Constructor for CAreaMap
		/// This constructs a new CAreaMap taking the name as the argument
		/// param name[in] The name of the map
		CAreaMap(const std::string &name);

		// ~CAreaMap(void); // we don't need a destructor yet

		/// \brief Returns the name of the map
		const std::string &GetName() const { return m_area_name; }

		/// \brief Returns a location by index
		const AreaLocation &GetLocationByIndex(LocationIndex index) const
			return m_locations[index]; // will crash on invalid index

		/// \brief Returns the number of locations
		inline unsigned int GetNumLocations() const { return m_locations.size(); }

		/// \brief Adds a location to the map
		/// This will add a location to the map. The variable list is a list of ints,
		/// and are the indices for each location that this location links to.
		void AddLocation(std::string name, int num_links, ...);

		std::string m_area_name;
		std::vector <AreaLocation> m_locations;

So far, all of the type are simple so I don't feel that I have use for pointers at the moment. In general, I've been learning to avoid pointers and to use them only when necessary. I'm still in the beginning stages of this project so there's a big possibility for change later. Sometimes when working on projects alone, we want to code everything right the first time. But this isn't always possible. Without a strong framework, you may not even be able to test things properly. And if you make some code and wait for its dependencies to get coded or vice versa before testing, when you run into bugs, you may have a hard time figuring out where the bug is because you've waited so long to do any testing. That's why I'm a huge fan of making temporary code for testing.

Processing and distinguishing between user commands is very important. This will be a text based game and all commands will come in via a single keystroke. To do this, _getch() is a good function, but it has a problem. _getch() is a blocking function and won't return until the user presses a key. I never want to block the code waiting for a keystroke. Other things may need to be processes such as AI and AI agent should be able to move in and out of an area while you're thinking about your next action. The function _kbhit() can be used to check to see if a key is waiting in the queue. If _kbhit() returns true, then I can get the key using _getch().

I don't want to feed keystrokes directly into the system to be processed. I want them to be translated first. These are the enums and structs that I'll use to define commands.
/// \brief Enum of the possible user commands
enum UserCommandType
    Command_Null		= 0,
    Command_Invalid		= 1,
    Command_Move		= 2,
    Command_MoveLink		= 3,
    Command_Cancel		= 4,
    Command_Quit		= 5

/// \brief Enum detailing what kind of commands we're listening for
enum SubCommandMode
    Mode_Base	= 0,
    Mode_Moving	= 1

struct UserCommand
    UserCommandType cmd_type;
    int		    cmd_param;
The UserCommandType enum details the type of command the user has entered. I've also made the UserCommand struct because some commands need an extra parameter. But why the SubCommandMode enum? Remember before I said that I never want to block waiting for input. This enum will allow me to have sub-menus with different controls. For example, the user should press 'M' to move, but after that, the user should enter the link number or C to cancel. I don't want to block the program while the user thinks about his or her next location and I don't want to introduce another look so I'll use this enum so the program will know what kind of input the user is entering.

The World Map State
Even though this test project doesn't support multiple states yet, I'm going to start using this terminology now. Remember, I'm still in the early phases and don't want to introduce inheritance yet.
/// \brief Class for the world state
/// This class allows a user to be able to explore a world map as
/// defined in the CAreaMap class.
class CWorldMapState

        /// \brief Runs the main loop for this state
        void DoMainLoop();

        /// \brief Sets the world map to the default value
        void SetUpWorldMap();

        /// \brief Gets the next user command
        UserCommand GetNextUserCommand();

        /// \brief Show an error message on an invalid command
        void OnInvalidCommand();

        /// \brief process the current message
        /// \return true to redraw the current location
        bool OnProcessCommand(const UserCommand &user_cmd);

        /// \brief shows an area display on std::cout
        void DisplayAreaLocation(const AreaLocation &) const;

        /// \brief returns true if the world map is looking at a valid location
        bool isCurrentLocationValid() const;

        /// \brief returns the current location
        const AreaLocation &GetCurrentLocation() const;

        CAreaMap m_WorldMap;
        unsigned int m_CurLocation;

        SubCommandMode m_CommandMode;

Most of the methods are private, but may end up being protected after I add a state base class. The really class only has one public method and that is to run the main loop. The SetUpWorldMap() method is temporary and will be removed or changed drastically after I add loading the map from a file.

Here's the main loop. Sorry for the lack of comments:
void CWorldMapState::DoMainLoop()
    UserCommand user_cmd;

        // dislay the current location

            // query for the next user command
            user_cmd = GetNextUserCommand();

            if(user_cmd.cmd_type != Command_Null)
                // check for an invalid input message
                if(user_cmd.cmd_type == Command_Invalid)
                else if (user_cmd.cmd_type != Command_Quit)
        } while (user_cmd.cmd_type != Command_Quit);
Every time through the loop the program queries for new input. If there's new input, it is sent to be processed.

The GetNextUserCommand() method checks to see if a key has been pressed and then turns that keystroke into a command.
UserCommand CWorldMapState::GetNextUserCommand()
    UserCommand new_command;
    new_command.cmd_type = Command_Invalid;

        int key = _getch();
        if(m_CommandMode == Mode_Base)
                case 'q':
                case 'Q':
                    new_command.cmd_type = Command_Quit;
                case 'm':
                case 'M':
                    new_command.cmd_type = Command_Move;
        else if(m_CommandMode == Mode_Moving)
            if((key == 'c') || (key == 'C'))
                // the user has pressed cancel
                new_command.cmd_type = Command_Cancel;
            int max_link = (int)GetCurrentLocation().GetNumLinks();
            int link = key - '0'; // convert to an int
            if((link >= 0) && (link < max_link))
                new_command.cmd_type = Command_MoveLink;
                // set the index as the param
                new_command.cmd_param = (int)GetCurrentLocation().links[link];
        new_command.cmd_type = Command_Null;

    return new_command;

I'll just show the two other important methods here just so you can take a look. You can download the source to see the complete project.
bool CWorldMapState::OnProcessCommand(const UserCommand &user_cmd)
    if(user_cmd.cmd_type == Command_Move)
        std::cout << "Enter link (0 - " << GetCurrentLocation().GetNumLinks() - 1 << ") (C - Cancel)" << std::endl;

        // change the command mode so we'll process user commands as links
        m_CommandMode = Mode_Moving;
    else if(user_cmd.cmd_type == Command_MoveLink)
        m_CommandMode = Mode_Base;
        m_CurLocation = (unsigned int)user_cmd.cmd_param;
        std::cout << "Moving to " << GetCurrentLocation().name << std::endl;
        return true; // redraw the current location
    else if(user_cmd.cmd_type == Command_Cancel)
        m_CommandMode = Mode_Base;
        return true; // redraw the current location

    // do not redraw the current location
    return false;

void CWorldMapState::DisplayAreaLocation(const AreaLocation &location) const
    std::cout << std::endl << "==========================================" << std::endl;
    std::cout << "==========================================" << std::endl;
    // write the name
    std::cout << "Now in " << location.name << std::endl;

    // write the links
    std::cout << std::endl << "Links:------------------------------------" << std::endl;
    for(unsigned int i = 0; i < location.GetNumLinks(); ++i)
        unsigned int link_index = location.links[i];

        if(link_index < m_WorldMap.GetNumLocations()) // make sure the location is valid
        std::cout << i << ") " << m_WorldMap.GetLocationByIndex(link_index).name << std::endl;

    // write the menu
    std::cout << std::endl << "Commands:---------------------------------" << std::endl;
    std::cout << "M) Move" << std::endl;
    std::cout << "Q) Quit" << std::endl;
    std::cout << "==========================================" << std::endl;

If you have any questions about this entry you can leave a comment.

Note about the source
I develop using Microsoft Visual Studio 2010 Professional so if you have any problems compiling the code on your system let me know.

Attached Files

A Complete Graphics-less Game #2

Posted by , in A Complete Graphics-less Game 22 May 2013 - - - - - - · 630 views
design, beginner, programming and 2 more...
Part 2 - The Preliminary Design / Concept
I'm going to start this project by doing a little design work first. When I used to work at the USDA, I would review proposals from companies bidding on IT projects. Their concepts and specs had to be good because they wanted get the contract and get money. What I'm doing now is nothing like that. This isn't going to be a major project and any docs that I make will only be used by me so I don't need a complete design document. I do feel that I'll need something to help my stay on task. I need a goal and a defined scope.

My goal is to make a game not a text-based game engine; though I will try to organize things into reusable parts. I will make an adventure rpg game. Adventure games work will with just text and that's basically the type of game we used to make in middle school.

Before I can go any further, I need to define the game a little bit more. I should decide on what the game will be about.

Game Name: The Next Tomorrow (the working title)

Setting: This game will be in a steam punk setting on a post-apocalyptic Earth.

Story: After a war that nearly destroyed the planet, humans have started to rebuild. Over the past 75 years after the apocalypse humans have cities again, but the former countries no longer exist. City-states are the only types of governments that still exist.

Goal of the player:
The player by his actions can achieve these goals
  • The player can remain a mercenary and perform quest for money.
  • The player can attempt to build his own city-state.
  • The player can attempt to rise up through the ranks and become the leader of a city-state.
My game will have these features:
  • A pregame menu system
  • A World Map
  • Towns
  • Quest
  • A Battle System
  • multiple character classes
  • an inventory system
  • dialogue
Internally, the game will have the following:
  • Load the world from disk
  • save and load game features game
  • scripted AI (using Angel Script)
This is it for the design. It's not complicated right? Are all the details there? No, not yet. Next time I'll build a quick prototype of some of the features that I want to implement. After I make the first prototype, I'll be able to understand what I need to build better and I may be able to think of some new features.

A Complete Graphics-less Game #1

Posted by , in A Complete Graphics-less Game 22 May 2013 - - - - - - · 709 views
beginner, text based, game and 1 more...
The Idea
What's the first thing that the we often notice when playing video games? Most of us see the graphics first and games that look good and have a lot of eye-candy make people immediately interested. Because of this, many (myself included) have been focusing on making good looking games. While I think good graphics are necessary, focusing on graphics too much can cause new programmers especially to not take the time to develop other needed skills. We can't all be graphics programmers right? How about organizing entities? How about loading data or integrating with a scripting language? These are some issues that I will tackle in this journal.

How will I do it without worrying about graphics? When I was in middles school, we used to have old Apple II computers in the computer lab. Some of us used to make simple text games which were just a really long nest of if statements. So that gave me the idea to make a text based from start to finish and I will document my progress using this journal. I will not use any of my old code. I will start from scratch and try to use only standard C++.

I've used console applications before to test code before, but this will be my first full-scale text-based game. I'll try to post at least once a week. I'll start with a quick a dirty game design. Then I'll do some prototyping, then I'll design a little more. This will not be a tutorial per se, as I don't want to show people how to make a text-based game. I want to show how it can be useful to step away from the graphics and concentrate on other things.

Recent Comments

January 2017 »

22 232425262728