How To Build a Finite State Machine (FSM) with Swift
Yes, I admit, I ♥ Swift.
Currently, I’m working on a game. For the AI of this game, I decided to use Finite State Machines (FSMs). This is a technique I like and know very well, but rather from hardware design languages like VHDL/Verilog. Making FSMs in e.g. C++ feels sometimes a bit awkward, because you have to use if-else-if
constructs. This is because one usually wants to make a decision on both, the old state and the next one. Yes, one could use switch
and for instance a union or otherwise combined numbers, but to me this feels to much like hacking it together. Gladly, Swift makes provided us a cool enum
with which I’m going to show you how much fun FSMs in Swift can be.
First, we need two protocols. We could do this in one delegate but IMO two separate ones provide more control. So, first there has to be a protocol which enables us retrieving the next state and comparing states, e.g.:
protocol FSMStatesProtocol
{
func getNextState() -> FSMStatesProtocol
func equalsState(state: FSMStatesProtocol) -> Bool
}
The other protocol will form a simple delegate which is later used for actually making the switch:
protocol FSMDelegateProtocol
{
func switchState(oldState oldState: FSMStatesProtocol, newState: FSMStatesProtocol) -> Bool
}
Now comes the definition of the state machine itself. Code first, explanations will follow:
class StateMachine : NSObject
{
private var state : FSMStatesProtocol
var delegate : FSMDelegateProtocol?
init(initialState: FSMStatesProtocol)
{
state = initialState
super.init()
}
func updateFSM()
{
let newState = state.getNextState()
if state.equalsState(newState) { return }
if let switcher = delegate
{
if switcher.switchState(oldState: state, newState: newState)
{
state = newState
}
}
}
}
As one can see, the state machine class contains variables for both protocols defined before. Besides initializing state
with some initial state there is nothing special in here. However, the fancyness comes with updateFSM
which does the following:
- Receive a possible new state to switch to by calling the respective method from the protocol.
- Checking whether the states are equal, in which case nothing more needs to be done.
- Calling the delegate to actually make the switch, and
- if the switch was successful, save the new state.
Easy and straightforward, isn’t it? Now we come to the actual use of the state machine. I’m going to demonstrate this with the FigureAI
class.
class FigureAI : FSMDelegateProtocol
{
enum FigureStates : FSMStatesProtocol
{
case Sitting, Walking, Standing
func equalsState(state: FSMStatesProtocol) -> Bool
{
return self == (state as! FigureStates)
}
func getNextState() -> FSMStatesProtocol
{
switch self
{
case Sitting : return Standing
case Walking : return Standing
case Standing : return Sitting
}
}
}
func switchState(oldState oldState: FSMStatesProtocol, newState: FSMStatesProtocol) -> Bool
{
let oldState = oldState as! FigureStates
let newState = newState as! FigureStates
switch (oldState, newState)
{
case (.Sitting, .Standing):
makeFigureStandUp()
return true
case (.Standing, .Sitting):
makeFigureSitDown()
return true
// More cases
default: return false
}
}
}
Again, straightforward implemention, no black magic™. The class in detail:
FigureStates
implements an enum
which conforms to the previously defined FSMStatesProtocol
. For compliance, it has to implement equalsState
and getNextState
. Of course, in equalsState
, we have to use a cast from FSMStatesProtocol
to FigureStates
. Since this case won’t fail, we used as!
instead of as?
for casting. getNextState
is fairly easy, it simply returns a new state. In a more advanced implementation, one could use e.g. statistics or the like to make it more AI-like.
The best comes for last! switchState
is the function which does the magic. Here, we use switch (oldState, newState)
and decide what to do. E.g. if the figure is sitting and the new state is standing, we have to make it stand up. The case (.Sitting, .Standing)
covers that part. The same goes for standing to sitting. You get the idea.
This particular last part is, what would lead you to many if-else-if
statements in C or C++ or any other language which does not provide this enum
fancy.
Happy finite switching!
Note: this actually has become a part 2 with an improved version of the state machine: How To Build a Finite State Machine (FSM) with Swift (Pt. 2)