Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
109 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Finite State Machines

Finite State Machines (FSMs) are frequently and most complexly used in artificial intelligence (AI) programming. States are used for different tasks for the AI, such as the "attack" state, the "defend" state, or the "find ammo" state.

FSMs are also used in a much more simplistic manner to handle different states of other things. Just as a light switch has an on state and an off state, a game may have a "menu" state, a "play" state, a "demo" state, etc.

Finite state machines are usually event based to some degree or another. The most common events are the "leave state" and the "enter state" events. Our FSM will be simplistic enough to not have to specify enter and leave events specifically, the actions normally performed by these events will be handled in our function, Execute. The Execute function can be described as an event handler, as it is called in response to an incoming line event.

For our FSM, we need to enumerate following states:

enum UserState
{
  US_NONE,
  US_TALKING,         // User talking
  US_ENTERUSERNAME,   // User entering a user name
  US_ENTERPASSWORD,   // User entering a password
  US_CREATEPASSWORD1, // User creating password 
  US_CREATEPASSWORD2  // User confirming password
};

Then add a variable, UserState state to CUser to keep track of the current state for that user.

Now we have our states, we can make the following test to see if a user is logged in:

inline bool LoggedIn() {return !(state == US_ENTERUSERNAME ||
                                 state == US_ENTERPASSWORD ||
                                 state == US_CREATEPASSWORD1 ||
                                 state == US_CREATEPASSWORD2 ||
                                 state == US_NONE); }

You'll want to call this function and check if a user is logged in before you go and broadcast text to them – add this check to CServer::Broadcast().

So we want to do a different thing when we receive an incoming-line event depending on what state we are in, so add a switch statement in place of that call to CServer::Broadcast(), which is dependent on the value of state, and add a case statement for each possible value.

TinyXML Crash Course

I'm sorry for anyone looking forward to me going retro and making some sort of hacked-together file format. After much mucking around and testing of different ways of doing files, XML was considered the best (because it needs to be easy to explain and can have extra values added later in the series without making the older files unreadable). Writing an actual XML parser is out of the question for this article series so TinyXML was chosen, as it is nice and free and easy to use. You are welcome to use any other data storage method. Perhaps you want to get really tricky and have your MUD use SQL?

I am going to assume you know how to write XML – it should be a breeze if you have had experience with any other ML (eg: HTML). If you have no idea, Richard Fine has written an excellent article on XML in games here.

You can download TinyXML from here.

You will need to have the documentation on hand if you don't know what a function call does (like you needed the Win32 SDK to look up details in the last tutorial).

First job, add TinyXML to your project by adding the following files: "tinystr.cpp" "tinyxml.cpp" "tinyxmlerror.cpp" "tinyxmlparaser.cpp". Check it compiles (it should) and fix any errors (I know, famous last words): It returns 0 in some places where it should return false, which Borland's compiler doesn't like.

Here is a quick look at what our XML file should look like:

<muddata>
    <userlist>
        <user name="AndrewRussell" pass="test" />
        <user name="test" pass="test" />
    </userlist>
</muddata>

I've added the class, CDataFile, which will handle TinyXML stuff. It contains a member of the type TiXmlDocument, which is what TinyXML represents an XML file as.

I've also added

TiXmlElement* UserListElement;

to quickly get a pointer to the user list. The constructor of CDataFile will load the XML file, and the deconstructor will save it back to disk again.

I've added the following function as well:

TiXmlElement* CDataFile::GrabUserByName(AString& name)
{
  // Grab the first user node
  // retval will be returned as and if we
  // find the user with the correct name
  TiXmlNode* retval = UserListElement->FirstChildElement("user");

  while( retval != NULL )
  {
    // is it an element?
    // does it have the same name as we want?
    if(retval->ToElement() &&
       (AString(((TiXmlElement*)retval)->Attribute("name", NULL)) == name))
    {
      return (TiXmlElement*)retval;
    }

    // Grab the next return value
    retval = UserListElement->IterateChildren("user", retval);
  }

  return NULL;
}

As you can see, this will go through the XML file, and search out a user with the user name we want.

Finally, I've added

TiXmlElement* userdata;

to CUser, so the user can keep track of its XML element.

Now that you've inserted XML functionality into your application, now make sure it still compiles.





Putting it all Together

Contents
  Introduction
  The Receiving Buffer
  Finite State Machines
  Putting it all Together

  Source code
  Printable version
  Discuss this article

The Series
  Part I
  Part II