Finite State MachinesFinite 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 CourseI'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. |
|