Stop struggling with conditional logic: meet the State Pattern

State Pattern is like a transforming robot

Finite state machines are a common way to structure a program. But do you know how to build an efficient one while benefiting plainly from Object Oriented Programming best practices?

What advantages could you get from using State Pattern in your code?

  • State Pattern let you change its behavior at runtime, as many times as you want
  • State Pattern let you define behaviors independently from each other
  • State Pattern reduces complexity in conditional structures

As an example, I will use a little turn based tactical video game I made in Javascript (akin to Advance Wars, or Chess): it is composed of a battle grid, on which stands units that belongs to two different players. Then each player move his units during his turn and attack the other players’ units.

How to recognize state machines?

First thing first, you need to recognize when State Pattern could be applied to give you its full advantages. With a little bit of training, you’ll be able to notice it from almost any code base!

Let’s pick a few of my game’s user stories to kick-start our development:

  • If no unit is selected, I can select a friendly unit
  • If a friendly unit is selected, I can move it
  • If a friendly unit has moved closed to an enemy unit, I can make the friendly unit attack the enemy unit

What happen if I try to implement all of these user stories in a procedural manner? I would probably write some algorithm like the following one:

// Reference to the Player of which it is the turn to play
let activePlayer

// Reference to a Unit instance
let selectedUnit

/**
 * Triggers on a mouse click event.
 *
 * @param Tile tileUnderPointer Reference the Tile instance which is under the mouse pointer at the moment the click event is issued.
 */
function pointerDown(tileUnderPointer) {

	if (selectedUnit === null) {
		if (tileUnderPointer.unit !== null &&
                tileUnderPointer.unit.player === activePlayer &&
                (tileUnderPointer.unit.hasMoved === false || tileUnderPointer.unit.hasAttacked === false)) {
			selectedUnit = tileUnderPointer.unit
		}
	} else if (selectedUnit.hasMoved === false) {
	 	if (tileUnderPointer.unit === null &&
                calculateDistance(selectedUnit, tileUnderPointer) <= selectedUnit.moveRange) {
	 		moveUnit(selectedUnit, tileUnderPointer)
 		} else {
 			selectedUnit = null
		}	
	} else if (selectedUnit.hasAttacked === false) {
	 	if (tileUnderPointer.unit !== false &&
                tileUnderPointer.unit.player !== activePlayer &&
                calculateDistance(selectedUnit, tileUnderPointer) <= selectedUnit.attackRange) {
	 		resolveCombat(selectedUnit, tileUnderPointer.unit)
	 	}
		selectedUnit = null
	}
}

You have probably noticed that I called several functions like calculateDistance() and moveUnit() in the above code. I will hide their implementation details from you because it’s not the point of this post (or that’s just my excuse for being lazy). But if you are interested in the full source code, you’ll find a link to it at the end of this post¹.

What is important here is that I implemented two separate responsibilities in the same function:

  • Checking the actual state the program is in (unit selected or not, has moved or not, has attacked or not)
  • Computing the outcome of the player’s action (a click on a given tile of the grid)

That global variable controlling how the algorithm behaves is how you recognize a finite state machine in your program. The next step will be to identify the states, as well as the transitions from one state to another. Let’s do it.

States and transitions

Before I try to give you my personal explanation on state machines, let’s read what our source of universal truth (Wikipedia) says about them²:

The behavior of state machines can be observed in many devices in modern society that perform a predetermined sequence of actions depending on a sequence of events with which they are presented.

Wikipedia – Finite-state machine

That’s a very formal definition, but here are the important takeaways in my opinion:

  • The available actions depends on which state the program is in (e.g.: I have moved a unit close to an enemy, so I can attack).
  • The events transition a state into another (e.g.: selecting a friendly unit give the player the ability to move it).

Although it makes me wonder what a sequence in my game could be.

That might do it for a specific sequence, but there might be many sequences possible.

In order to represent all the different states and transitions in a state machine, we usually draw schemata like the following one:

The State Pattern only manages the transitions that lead from one state to another. States proceed their own business logic internally. We will then take advantage of Object Oriented Programming to handle every of the different behaviors that can happen from the same event (the user’s click).

Encapsulating and delegating

After all this setup, this might be the time to give a definition of the State Pattern himself. Here’s one from the book Head First: Design Patterns³:

The State Pattern allows an object to alter its behavior when its internal state changes. The object will appear to change its class.

Head First: Design Patterns, 410.

So we now have an object, which we’ll refer to as the context. The context holds a state as an internal property. But that state is an object as well (we call this approach ‘object oriented’ for a reason).

Here’s my implementation for both the context and the state interface: this interface will only be used to define the structure of each of our state objects.

class GameScene extends Phaser.Scene {
    create() {
        
        //...
        
        this.state = new SelectState({activePlayer: this.players[0], cursor: cursor}, this)

        this.input.on('pointerdown', () => {
           this.state.pointerDown(this.input.activePointer.position)
        })
    }
}

class GameState {
    constructor(previousState, scene) {
        this.activePlayer = previousState.activePlayer
        this.scene = scene
        this.cursor = previousState.cursor
    }

    pointerDown(position) {
        // This method needs to be overridden by child classes.
    }
}

GameScene is our context, that needs its behavior to be altered. It then delegates each call of the pointerdown event to be handled by its current state’s pointerDown() method. Here is the code in my state objects:

class SelectState extends GameState {

    // ...

    pointerDown(targettedTile) {
        let hoveredUnit = targettedTile.getUnit();

        if (hoveredUnit && hoveredUnit.player === this.activePlayer && false === hoveredUnit.hasMoved) {
            this.scene.state = new MoveState(Object.assign(this, { startPosition: targettedTile, selectedUnit: hoveredUnit }), this.scene);
        }
        // else we stay on SelectState
    }
}
class MoveState extends GameState {

    // ...

    pointerDown(targettedTile) {

        if (this.canMoveSelectedUnitTo(targettedTile)) {
            this.moveSelectedUnit(targettedTile);

            if (this.getEnemiesInRangeFrom(targettedTile).length > 0) {
                this.scene.state = new AttackState(this, this.scene);
            } else {
                this.scene.state = new SelectState(this, this.scene);
            }
        } else {
            this.scene.state = new SelectState(this, this.scene);
        }
    }
}
class AttackState extends GameState {
    
    // ...

    pointerDown(targettedTile) {

        if (this.potentialTargets.find(potentialTarget => potentialTarget === targettedTile)) {
            this.attackUnitOn(targettedTile);
        }

        this.scene.state = new SelectState(this, this.scene);
    }
}

Because the states classes all have the same pointerDown() method available, we can call this method regardless of which state is the current one. This is the principle of polymorphism.

Let’s put our classes into an UML class diagram:

UML class diagram of the State Pattern

Resistance to code changes

From the code shown above, you probably have noticed that I wrote the transition from one state to another inside the pointerDown() method of each state. This is not mandatory, as there are two possibilities:

If the states handle their transitions, this means that each time that I add a new state, I will need to modify each state that has a transition towards this new state.

If the context handle all the transitions, this means that each time that I add a new state, I will need to modify the context.

There is no better way, as in each scenario, I will have to modify some more classes than those I wanted to add or modify in the first place. The choice will be mostly dictated by the amount of conditional logic that needs to be performed to handle these transitions.

If a state always transitions to a same other state, I can easily set the transitions in the context. On the opposite, if a state need to verify conditions before choosing the state it will transition into, it will be easier to have these conditions coded in each states, otherwise it might clutter the context to much.

Other usages of the State Pattern

In this post, I demonstrated how the State Pattern could be applied to a video game. But this isn’t the only case where you could apply it!

For example, an image manipulation software like Photoshop would also need to handle different behaviors depending on the tool that the user has selected (the bursh, the eraser, the lasso, etc.).

Also, the State Pattern isn’t limited to handle only user clicks! Take for instance a video player, here are some potential states it could be in: LoadingState, PlayingState, PausedState.

Does this gives you some ideas about where you could apply State Pattern? Don’t hesitate to share them in the comments!


Sources

  1. “Siege Wars,” GitHub, last modified on 29 July 2020, https://github.com/raaaahman/siege-wars
  2. Finite-state machine,” Wikipedia, last edited on 11 June 2020, https://en.wikipedia.org/wiki/Finite-state_machine
  3. Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra, “The State of Things: the State Pattern,” in Head First: Design Patterns, (O’Reilly Media, Inc., 2004), 397-440.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.