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:

Putting it all Together (FSM part 2)

Now we can load and save files, we can finish off our Finite State Machine.

Here is a diagram of how our Finite State Machine will operate:

Here is a list of the different states:

US_ENTERUSERNAME
All input is the user attempting to enter a user name. Here I only check it's not longer than 64 characters. You may want to check for spaces, symbols, etc. Once a valid user name is entered, then search for it in the user database. If it's found, let them try to enter a password. If it cannot be found, then get them to create a user.

US_ENTERPASSWORD
If they entered an existing user name, ask them to enter the password for that user, and match it up with the database. This is fairly straightforward.

US_CREATEPASSWORD1
Get them to enter some text for their password. Again, you may want to add checks to make sure they're not trying to send you symbols, colour codes, and other such garbage.

US_CREATEPASSWORD2
Here we check if the password they entered matches their first attempt to enter a password. If it does, log them in, and also add them to the XML database.

US_TALKING
Simply tag their name onto, and then broadcast whatever they type, to the rest of the users.

Here is the code for all of that:

void CUser::Execute(AString& str)
{
  // check for 0 length string size of 1 for nul terminator
  if(str.GetLength() <= 1)
  {
    return;
  }

  switch(state)
  {
    case US_NONE:
      break;

    case US_TALKING:
      CmdSpeak(str);
      break;

    case US_ENTERUSERNAME:
      // Check that length is small enough (don't want it too big)
      // An exercise - make length configurable in mud.xml
      // (remember to then make length only affect new user names,
      // we don't want making it smaller stopping people logging in)
      if(str.GetLength() < 64)
      {
        userdata = GETSERVER->datafile.GrabUserByName(str);
        if(userdata != NULL) // does the user exit
        {
          SendText(A_STR("Welcome Back ") + str +
                   A_STR("!\r\nEnter Password:\r\n"));
          state = US_ENTERPASSWORD;
        }
        else // userdoesn'tn't exit - create and ask for password
        {
          SendText(A_STR("User ") + str +
            A_STR(" Does Not Exist - Creating\r\nEnter Password:\r\n"));

          userdata = new TiXmlElement("user");
          userdata->SetAttribute("name", str.GetString());
          state = US_CREATEPASSWORD1;
        }
      }
      else
      {
        SendText(A_STR("Too Long! Try again...\r\n"));
      }
      break;

    case US_ENTERPASSWORD:
      // Check the password matches
      if(str == AString(userdata->Attribute("pass")) )
      {
        // Now we can start talking
        state = US_TALKING;
        username = AString(userdata->Attribute("name"));
        SendText(A_STR("Logged In!\r\n\r\n"));
        GETSERVER->Broadcast(A_STR("Please welcome ") +
           username + A_STR(" to the server!\r\n\r\n"));
      }
      else
      {
        SendText(A_STR("Password does not match - try again...\r\n"));
      }
      break;

    case US_CREATEPASSWORD1:
      if(str.GetLength() < 64)
      {
        userdata->SetAttribute("pass", str.GetString());
        state = US_CREATEPASSWORD2;
        SendText(A_STR("Confirm Password:\r\n"));
      }
      else
      {
        SendText(A_STR("Too Long! Try again...\r\n"));
      }
      break;

    case US_CREATEPASSWORD2:
      if(str == AString(userdata->Attribute("pass")) )
      {
        // User account created successfuly - add to database
        // This transfers the object to tinyxml
        GETSERVER->datafile.UserListElement->LinkEndChild(userdata);

        // Now we can start talking
        state = US_TALKING;
        username = AString(userdata->Attribute("name"));
        SendText(A_STR("Account Created! Logged In!\r\n"));
        GETSERVER->Broadcast(A_STR("Please welcome the new user, ") +
                       username + A_STR(", to the server!\r\n\r\n"));
      }
      else // No Match
      {
        SendText(A_STR("Password does not match - try again...\r\n"));
      }
      break;
  }
}

Before we finish up, I'll describe what owns the XML element where, as this could be somewhat confusing.

In US_ENTERUSERNAME, the XML variable is set to NULL, and it knows nothing about the XML element. If it goes into US_ENTERPASSWORD, then it gets a pointer to an XML element that is owned by, and constructed and deconstructed by TinyXML. However if it is a new user account being created, the XML element we create is owned by us, and not by TinyXML. We are responsible for deleting that element. That is – unless we register it with TinyXML, which will make TinyXML in charge of deleting it when it's done, and also, for saving it to the XML file. You can see it being registered in the code that handles US_CREATEPASSWORD2. If the user is in the talking state, we are guaranteed that TinyXML owns the XML element (unless we made some sort of coding mistake).

Final Notes

It has been a long time since I started writing this article, and even longer since I released the first one. Real life makes it quite hard to have time to do these things. Apologies to all those who have sat around waiting for it, and thank you to the many people who emailed me telling me to get on with it.

Most importantly, in this time, I have come to use the STL (standard template library) almost religiously (whereas previously I used it rarely, if ever). I highly recommend learning how to use it.

Anyway, you now have a functional chat server. It is now possible to continue programming your MUD server on your own, and I highly recommend doing so. Later tutorials are not going to be based on the code as much, so are unlikely to break anything.

When I say "functional", I do not recommend using it for any sort of public server. There are some huge security holes. Most importantly, we don't check for things like quote marks in user names, which, when saved directly to TinyXML could very easily muck up the XML file.

I will address these security problems in a later article.

Until next time,

Happy coding,

Andrew Russell.




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