Advertisement
  1. Code
  2. ActionScript
Code

The Power of Finite State Machines: Application and Extension

by
Difficulty:IntermediateLength:LongLanguages:

Here's the concluding part of our tutorial about creating a multi-state car using a finite state machine. In this part, you will see how easy it is to add more states as well as new features to the car. When finished, you'll know exactly how to structure control for your FSM Object, which will be demonstrated when we add animation and sound.


Final Result Preview

Check out the final result we'll be working towards:


Step 1: Continuing Where We Left Off - Additional States

Here's the State Transition Table (STT) we drew in the first part of the tutorial:

Finite state machine design pattern in AS3

When you need to add more states, always start with your STT. Here are the new states we'll be working towards, with room to add the new actions required:

Finite state machine design pattern in AS3

Try putting in the right actions for the newly added states.

I assume it's been a while since you read the first part of this tutorial, so take your time. Once you finish, compare your STT with the image shown below.

Finite state machine design pattern in AS3

Step 2: Retouching the IState Interface

The very next thing you need to do is add the two new state actions to your IState interface.

Open IState.as in FD and include the code below. I would add it just after the driveForward() method.

Now run the application and see what happens next.

Finite state machine design pattern in AS3

Any modification to the IState inteface needs to be reflected in all the classes that implement it. This is the great advantage of using an interface; I really like how it reminds you to add them for each existing state class.

Add the following code to all the existing state classes. Put it right below the driveForward() method. Let's have them call print(this + "apply action here") to remind us that we need to get back to them after creating the other two state classes.

You shouldn't get anymore errors when you run the application again.


Step 3: Creating the Two State Classes

Open the Car class, navigate to the initializeStates() method and add the code below.

Neither the variables nor the classes exist but we'll fix that. First, get inside the word "EngineDriveBackward", then press "CTRL + SHIFT + 1"; highlight "Create new class" then press "ENTER".

Finite state machine design pattern in AS3

Make sure to match all the info above before you click "OK". You should have something similar to the code below. Don't forget to add the _car variable before the constructor and add that "$" symbol for the parameter. Then do the assignment inside the constructor.

Okay. Now go back to the Car class where we left off and put your cursor inside the _engineDriveBackwardState variable and press "CTRL + SHIFT + 1"; choose "Declare private variable" then hit "ENTER". This will add it as the last variable before the constructor. Change its type to "IState". I would move it to where the other state variables are but that's optional.

Follow the same procedure to create the EngineDriveReallyFast class.

You'll get an error if you run the application about the toString() method for the two new classes not returning a value.

The Car will also need to provide external access to control for the two new actions. Add the code below after the driveForward() method.

Lastly, add the explicit getters at the lower section of your Car class just above the changeState() method.


Step 4: The EngineDriveBackward State

Refer back to your STT and start adding code from the top of the IState method implementations. You'll run into a snag somewhere but try to do it anyway. You may also use your own words for the print() method. Once you finish, compare your code with the class listing below.

The update() method requires an additional property for the Car. Place your cursor inside the REVERSE_FUEL_CONSUMPTION, press "CTRL + SHIFT + 1", choose "Declare constant", then hit "ENTER". This adds the constant at the top of the Car class with the default type of String. Change its type to Number and assign it a value of .0066. This gives 25 seconds of driving in reverse with a full tank of one gallon. You may move it to be with the other fuel consumption constants.

While you're there, you might as well add the TURBO_FUEL_CONSUMPTION constant with the value of .016 (10 seconds on turbo for one gallon). We'll use that later for the EngineDriveReallyFast state class.

It's that simple. Go ahead and finish up the EngineDriveReallyFast class. If you get stuck, go back and check with your STT. Or you can just open the completed class inside the "StatePatternPartial2" folder included with the source download and see what you missed.


Step 5: More Testing

Add the new state actions (_car.driveBackward() and _car.driveReallyFast() into the test sections in your "Main" class then run the app.

Check your print out against the test actions if they match.

Notice where it says "off apply action here"? That tells us we forgot to get back and add behavior for the newly added Car functions of the other state classes. Go back to each of them and apply the behavior based on your STT.

The app should run perfectly now. If you like, you can still match your work with the classes included with the source download. As mentioned earlier, they're inside the "StatePatternPartial2" folder.


Step 6: Adding Media Assets

With the source download is a folder named "media", drag that into the "bin" folder of your CarFSM project. It contains all the sound effects and graphics.

Next, also with the source download, you'll find a folder named "code", get inside that and into the "StatePatternComplete" folder. Don't touch anything except the "Media.as" class. Drag it into your "com.activeTuts.fsm" folder together with the rest of the classes you've made.

It's important you know how this class works so you can add to it later should you choose to try the homework of adding one other car functionality of returning back to park. So go over it at least once; I'll also explain its responsibilities:

It takes care of pretty much everything media-wise, from embedding media assets to allowing control over them. The first thing it does is embed all graphic and sound files inside the "media" folder. Animation, visual output, and sound are all controlled by the different state classes through the car instance which in turn passes the request to the media instance. When instantiated, this class does five things - It adds itself as a child of the car, it creates the car's visual assets, sounds, buttons, and then visual output.

Sound manipulation is usually handled with two functions; for example, when the car is running, if you select the "drive" radio button, the car calls playParkToDrive() which plays the sound of the car increasing power, then playPeakDrive() when the sound finishes for constant speed driving sound.


Step 7: Extending the Car's Responsibilities - Variables

Starting at the top of the Car class, add the following code after the _currentState variable declaration. These properties came up as features were added to the Car. For example, I wanted to bring back the gear to park after one second when the car is either off or out of fuel so there it is. Another example would be cleaning up the visual output display after 10 seconds of printing a statement.

Be sure to add _media = new Media (this); inside the init() method right after the initializeStates() method call.


Step 8: Extending the Car's Responsibilities - Car Functions

Now get inside the update() method and change it to match the code that follows.

Remember - the changes in the methods, however complex, only manipulate sound and animation. If you plan on doing the homework later (allowing the car to return to park from any of the drive states), here's the method you need to work on. Paste it before the turnKeyOn() method. This method gets called when the "park" radio button is clicked (if enabled). See addButtons() in the Media class.

From turnKeyOn() all the way down to reFuel() add _cleanUpTimer = 0 before the current state delegation. And that's how the visual output panel gets cleared after 10 seconds.

consumeFuel() and refillWithFuel() also need to be updated, as shown here:

I just kept adding whatever feature I could think of to show the State Pattern's flexibility.

Replace what's inside the print() method with _media.print ($text);.


Step 9: Extending the Car's Responsibilities - Sound Control

Go one line after the toString() method at the bottom of the class and paste the list below.

Since properties don't change when playing sound, we just delegate the work from car to media. For animation, the properties are manipulated inside the state classes which is then applied via access of the media through the car.

It might start to get a little confusing but just think of it this way - the Car is your central control hub with its logic contained inside the state classes. The current state has its own specific behavior for the called action, in response, it then calls specific functions in the Car to accomplish the required result.

Next comes control for the "park" radio button. We need these methods to automatically return the gear to park if the engine is either off or out of fuel (after one second). Don't get it confused with the returnToPark() method which is triggered by the radio button (see Step 29). Paste the code next. You'll see how these methods were conceived when we start adding to the state classes.


Step 10: Extending the Car's Responsibilities - Animation Control

The code below completes the class. Add it at the end and save your work.

That was a lot of added code. Compare your classes with the ones included with the source download if you run into errors when testing the app later.


Step 11: Controlling Media From EngineDriveReallyFast

For adding effects (in this case, handling visual manipulation), I've learned to always start where things change most. We could start working on the default state EngineOff - but then how do we know what animation should happen there unless we know what animation it might be coming back from? If we start there, we only know that the wheels shouldn't be rolling at all. But the Car may be coming from turbo.

Review and compare the IState method implementations for your current EngineDriveReallyFast version with the code below.

I've excluded the constructor as well as the toString() method since they're not doing anything with the media.

We're not making any changes to the state classes, we're only adding features for controlling sound and animation. You can copy the listing and replace all those methods in your EngineDriveReallyFast state or you can just add the missing pieces. It's up to you. The important part is we've abstracted the logic away from the Car object. Now we can make it as complex as we want. The focus is sharp. We know what we're working on and there's no mix-up.

Animation is handled through the update() method. The rest just takes care of playing sound.


Step 12: Animation for EngineDriveBackward

Now for the opposite end.

Review, compare then make changes just like we did for the EngineDriveReallyFast state.

Nothing fancy, we're just making it seem like the car's going in reverse. Since the previous state could be EngineDriveReallyFast, we have to make sure that the turbo blur effect is taken away (lines 51-52).


Step 13: The EngineDriveForward State

Here, we check whether the Car came from the reverse, park, or turbo state and adjust the rolling of the wheels appropriately. The rest of the changes are similar to the other states.


Step 14: The EngineOn State

A pattern's starting to show. Get to know the changes then apply them to your class.


Step 15: Out of Fuel Animation

One more State... =)


Step 16: Animation When the Car Is Turned Off

The last two state classes have the same response when you try to switch gears. Also, you know what possible animation it could be coming from and apply adjustment for it.


Step 17: Final Changes to Main

Replace the contents of your Main.as class with the code below.

All we do is add the car and call its update() method at every enter frame event. You should see the car and the visual output when you run the application.

You'll notice the Car runs out of gas too soon. Change the value of _fuelCapacity to 3 inside Car (variable declaration), and then test the Car thoroughly.

Congratulations! You finished the tutorial.


Summary

Let's now go over the whole procedure of creating a full featured FSM object from scratch.

  1. Sketch up the State Transition Table for your object.
  2. Create your Procedural FSM object.
  3. Once Procedural FSM works, if you need to add a lot more features and/or states, convert it to the State Pattern.
  4. Build your IState Interface first.
  5. Create the first/default state class (consult State Transition Table and Procedural FSM actions).
  6. Duplicate a copy of your Procedural FSM object, then allow public access to all properties the state classes need to control.
  7. Create the rest of the state classes.
  8. Add features/states as per your requirements. These usually present themselves while you're working on your State actions.
  9. When adding new state/s, start with your State Transition Table.
  10. Program the state/s action/s into the IState interface first, then create the state/s.
  11. Add the new action/s to the already existing states manually.
  12. When adding effects (logic) start with the state with biggest change. Then work on the opposite end so you can expect what could happen in the states between.
  13. Polish until logic for all the states meet your specifications.

Care to do the homework? Hint: You won't need to create another state.

Until next time! Thank you so much for reading!! =)

Comments are always welcome. Please post them below.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.