Managing Game State in Breakout

Sun, Jun 12, 2011

Switching from Pong to Breakout introduces a little more complexity in the game state, with scores, highscores, lives, levels and more.

So it becomes worthwhile to invest a little time in some better coding patterns.

Breakout uses the same basic Game.Runner that was abstracted from my earlier pong game here, with some minor changes:

  • Some additional DOM manipulation methods.
  • A Math helper module for move, accelerate and intercept methods.
  • A StateMachine helper class.

The biggest change is the introduction of the state machine which helps keep the game code from degenerating into a complex spaghetti mess.

State vs State

First, we need to clarify some terminology. Depending on the context, when we say game state we could mean one of 2 things:

  • game values - e.g. positions, lives, scores, level, etc.
  • game mode - e.g. playing, paused, menu etc

A finite state machine is used to manage the second case, the game mode. It automatically generates methods to transition between the game modes and provides events for custom code for when those transitions occur. It is during these events that the game values can be cleared or set.

Typically, when I talk about the game state, I’m refering to the second case, that which is controlled by the finite state machine, e.g. the game mode. If I want to refer to the state of some internal game variable I will be specific and say something like number of lives, or current position.

For example. The play() method might be used to transition from the menu state into the game state, during which time the current values for score, lives, level etc can be set.

Confused yet ? Great, then lets move on…

Breakout State Machine

For a simple game like breakout I can start off with 2 simple states:

  • menu - waiting for the user to start the game
  • game - the user is playing the game.

There is a single event play to get from menu to game state, but there are 2 different events that can be used to transition in the other direction, abandon and lose. The FSM is constructed with the following simple configuration:

  state: {
    initial: 'menu',
    events: [
      { name: 'play',    from: 'menu', to: 'game' },
      { name: 'abandon', from: 'game', to: 'menu' },
      { name: 'lose',    from: 'game', to: 'menu' }
  ]},

The state machine takes care of some infrastructure and provides us with:

  • play() - transition from ‘menu’ to ‘game’
  • abandon() - transition from ‘game’ to ‘menu’
  • lose() - transition from ‘game’ to ‘menu’
  • is(s) - return true if state s is the current state

This allows us to simply hook into the state machines events in order to set the internal breakout game state values:

  onmenu: function() {
    this.resetLevel();
    this.paddle.reset();
    this.ball.reset();
  },

  ongame: function(delay) {
    this.score.reset();
    this.ball.reset();
  },

  onleavegame: function() {
    this.score.save();
    this.score.resetLives();
  },

  onbeforeabandon: function() {
    return this.runner.confirm("Abandon game?")
  },

This kind of infrastructure allows our game to stay a clean, event driven application instead of devolving into a complex procedural set of if/then/else statements.

Object State Machines

In addition to the high level game state (menu vs play), any individual object within a game can also benefit from being a finite state machine. For example, in a game like gauntlet, the individual baddies might have ‘idle’, ‘searching’, ‘chasing’, ‘dying’ states.

However, in a simple game like Breakout, this wasn’t necessary, so thats a strategy I’ll have to wait until I tackle a more complex example….

You can find the game here and the code is here