Chain Design Pattern


When I first started making Flash games years ago, I found myself facing the same problem in every game and could not find a solution online.

How do I get game state from anywhere in the code base without using global variables?

Unless your making a game entirely inside one file, you will eventually need a way to get information about the level and things in it, like has the game started or finished? What about how big is the level?

Or event at a lower level, you need to check which entities got hit in a grenade blast, or figure out if this unit is on my team.

Initially a Singleton is a Good Solution

A good example is handling victory conditions. In RTS games the game checks every frame wether you won or lost.

This method checks if you won.

bool HasPlayerWonGame() {

  foreach(Entity entity in Entity.Instance.entities) {
    if (entity.teamName == "Enemy") {
      if (entity.health > 0) {
        return false;
      }
    }
  }

  return true;
}

Holy bonkers Batman, there are so many things wrong with this.

  • Entity.Instance.entities A singleton leaks global state, prevents testing and proper encapsulation
  • entity.health > 0 Just write this like what is is, entity.IsAlive
  • entity.teamName == “Enemy” Why loop through all entities? Just hold a separate array for each team

As time goes on this type of code will become widespread and headaches will come with it. I never use singletons in my code except for a few select classes, like my Event Manager.

Singletons are the most overused and misused design pattern in programming. Why? Having the ability to access the game state from a global variable leads to Spaghetti code.

Classes should not depend on global variables, or else when that one global variable changes, so does every other class that depends on it. Often leading to runtime crashes or worse subtle bugs. Modular code is good code.

So what is the better solution? This whole problem occurs because a specific thing about game state – in this case entities – is needed in a low-level place in code.

Using the Chain Pattern

My solution is to contain all game state in a parent-child relationship.

                 +----------+
                 |          |
                 |  Level   |
                 |          |
                 +----------+
               +---^     ^---+
               |             |
         +-----+----+   +----+------+
         |          |   |           |
         | Team List|   | Game Mode |
         |          |   |           |
         +----------+   +-----------+
   +--------^    ^-----+
   |                   | +------+-------+    +------+-------+ |              |    |              | | Player Team  |    | Enemy Team   | |              |    |              | +--------------+    +--------------+   ^---+
  |    +--+-------+      Etc...    | Entity 1 |    +----------+

Etc…

At the very highest position we have level. This contains attributes like levelWidth, levelHeight but more importantly references to teamList and gameMode. And teamList contains a reference to playerTeam and enemyTeam. And so on.

So with this structure, with just an instance of level you can drill down to get anything you are looking for. This entirely removes globals and makes it easier to write well encapsulated unit tests.

Same Example But Using The Chain Pattern

bool HasPlayerWonGame() {
  return level.teamList.enemyTeam.IsAllDead();
}

Much cleaner.

This means this instance only needs a reference to the level to get all the information it needs.

How Did Game Mode get a Reference to Level?

This method, HasPlayerWonGame(), is from the gameMode class. Take a look at the diagram again.

This is the great thing about the chain pattern, it can go up the chain and down the chain. The gameMode object went up to level and then drilled down to teamList.

                 +----------+
        Go down  |          | And up the chain
          +----+ |  Level   | <---+
          |      |          |     |
          |      +----------+     |
          v    +---^     ^---+    +
               |             |
         +-----+----+   +----+------+
         |          |   |           |
         | Team List|   | Game Mode |
         |          |   |           |
         +----------+   +-----------+

         Etc...

This means will just one entity you can get a reference to any thing in the game.

entity.team // Return current team
entity.oppositeTeam // Returns opposing team

Implementation

It’s important to note, these classes should never contain game logic, these are strictly data objects. They only hold data about an entity or level but not logic of how it works. That code should be pushed to controllers.

Level

public class Level {

    public TeamList teamList { get; private set; }

    public Teams playerId { get; private set; }
    public Teams enemyId { get; private set; }

    private GameMode gameMode;

    public LevelData levelData { get; private set; }

    public string name { get; private set; }

    public int width;

    public int length;

    public Level() {
    }

    public Level LoadData(LevelData levelData, Teams playerTeam, Teams enemyTeam) {
        this.teamList = new TeamList(this);
        this.levelData = levelData;
        this.name = levelData.name;

        playerId = playerTeam;
        enemyId = enemyTeam;

        this.gameMode = GameMode.createCondition(levelData.victoryType, this);

        return this;
    }

    public Team playerTeam {
        get { return teamList.Find(playerId); }
    }

    public Team enemyTeam {
        get { return teamList.Find(enemyId); }
    }
}

On level creation teamList and gameMode are also created. Notice this is passed into both, that is for going up the chain.

Note: levelData is a value-object for storing meta data about a level.

public enum Teams {
    Misc = 0,
    One,
    Two
};

public class TeamList {

    public Level level { get; private set; }

    public Team misc { get; private set; }
    public Team teamOne { get; private set; }
    public Team teamTwo { get; private set; }

    private Team[] _teams;

    public TeamList(Level level){
        this.level = level;

        misc = new Team(Teams.Misc, "TeamMisc", this);
        teamOne = new Team(Teams.One, "TeamOne", this);
        teamTwo = new Team(Teams.Two, "TeamTwo", this);

        _teams = new Team[3];
        _teams[0] = misc;
        _teams[1] = teamOne;
        _teams[2] = teamTwo;
    }

    public Team Find(Teams id){
        return _teams[(int)id];
    }

    public Team OpposingTeam(Teams id){
        if(id == Teams.One){
            return _teams[2];
        } else if(id == Teams.Two){
            return _teams[1];
        }

        return _teams[0];
    }

    public Team OpposingTeam(Team team){
        return OpposingTeam(team.id);
    }

}

A team list contains all teams in the game. The level reference is saved on the teamList instance for chaining up. Also notice this is passed to each instance of team.

Entity

public class Entity {

    public Entity(Team team) {
        this.team = team;
    }

    public Team team;

    public Level level { get { return team.teamList.level; } }

    public TeamList teamList { get { return team.teamList; } }

    public bool IsTeammate(Entity entity){
        return team == entity.team;
    }

}

You get the idea. I define a getter for level and teamList for convenience. Here are some more examples.

entity.IsTeammate(otherEntity) // true or false

entity.team.oppositeTeam.IsAllDead() // true or false

Conclusion

The Chain pattern is a great way of structuring games, lightly couples, no global instances and completely testable. I have keep using it for years and it has never failed me.

This also ties well into unit testing but that is a post for an other time.

Related Posts

Simple Explanation of the Pinyin Sounds

Failed Attempt at Creating a Video Search Engine

Test Your Chinese Using This Quiz

Using Sidekiq Iteration and Unique Jobs

Using Radicale with Gnome Calendar

Why I Regret Switching from Jekyll to Middleman for My Blog

Pick Random Item Based on Probability

Quickest Way to Incorporate in Ontario

Creating Chinese Study Decks

Generating Better Random Numbers