Writing a Simple Role Playing Game with C# .NET and the State Pattern

modified

Introduction

The State Pattern is an interesting design pattern in that it allows us to separate out portions of code into individual related modules, or states. This pattern is particularly useful for applications which need to retain state information, such as the current phase a program is in. While you can typically maintain this information using a basic integer variable, the State pattern helps abstract the specific state logic, reduces code complexity, and greatly increases code readability. This can make the difference between an application maintenance nightmare and a work of art.

This article describes how to use the State Pattern with C# to create a simple, console-based RPG role playing game. You’ll be able to see exactly how the State pattern fits into the flow of the rpg game and how it easily cleans up the code, in what might typically be a confusion of integer values and if-then statements.

C# .NET State Pattern RPG Role Playing Game

If Then Else What?

The core problem that the State pattern solves is reducing the complexity of if-then statements, which we ultimately need when managing state information. Since we’re going to be creating a role playing game, knowing if the character is in an exploratory or battle state is important.

For our simple role playing game, we’ll consider 2 states that our character may be in and 2 commands that he can perform. He can be Exploring or he can be in Battle and he can choose to Look Around or to Attack. Since we have 2 states and 2 command possibilities, we’ll naturally have 4 situations to check for. They are:

1
2
3
4
5
6
7
State: Exploring
Look
Attack
State: Battle
Look
Attack

Without design patterns, you might normally design a role playing game similar to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void Main()
{
   int state = 0;
   string command = "";
   while (command != "Quit")
   {
      if (command == "Look" && state == 0)
      {
         // Explore the dungeon.
         DoExploreDungeon();
      }
      else if (command == "Attack" && state == 0)
      {
         // There's nothing to attack when you're just exploring!
         DoNothingToAttack();
      }
      else if (command == "Look" && state == 1)
      {
         // You can't explore when a monster is attacking you!
         DoMonsterFreeHit();
      }
      else if (command == "Attack" && state == 1)
      {
         // Attack the monster.
         DoAttackEvilMonster();
      }
   }
}

You would then define the function for each situation listed above. While this would certainly work and might not look like much code, think about what would happen if you added another command, such as “Inventory”. You would have to add an additional two if-then statements. If you wanted to add another state, such as “InStore”, you would need to create even more if-then statements. You can see how the above code could end up becoming complex and more difficult to maintain. This is where the State Pattern comes in!

Cleaning House with the State Pattern

Let’s clean up the above code by implementing the State Pattern. Since we have two possible states, we’ll have two state classes which manage the details of each state, but the main loop will be reduced to the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void Main()
{
   RPGController rpg = new RPGController();
   string command = "";
   while (command != "Quit")
   {
      if (command == "Look")
      {
         rpg.Explore();
      }
      else if (command == "Attack")
      {
         rpg.Battle();
      }
   }
}

In the above C# code, using the State Pattern, we’ve already cut in half the number of if-then statements. In fact, we only need to check which command the user selected and pass call the proper function for that command. The State Pattern itself takes care of determining which specific state should act upon the command. As you can see, this greatly reduces the amount of code complexity. Let’s take a look at how we put the State Pattern together.

There’s Always an Interface

We start by constructing an interface for the State Pattern controller to use. All states will implement this interface so that they have the same list of functions which the controller class can call. Depending on which state is currently active, the controller will execute one of the interface functions. The functions in the interface can be considered the possible commands the user can choose. The concrete states we construct from this interface can be considered the possible states the character may be in.

As you can tell, we’ll need to define the function bodies for each possible function in the state interface. In the case of impossible states, such as being in the Battle state and trying to Explore, we can either throw an exception and leave the function empty, or display an entertaining message to the user. In either case, the state pattern still helps us manage the complexity.

Since we only have two commands, our interface for the State Pattern is defined as follows:

1
2
3
4
5
public interface IState
{
    int Explore();
    int Battle(int level);
}

Exploring the State of the Explore State

We have two states to create implementations for: Explore and Battle. We’ll start with the Explore state. This will also be the initial state that the character will reside in. Since the character can select two commands (Look and Attack), and since we defined two functions in the interface (Explore and Battle), we can fill in the body for the user commands as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ExploreState : IState
{
    private RPGController context;
    public ExploreState(RPGController context)
    {
        this.context = context;
    }
    #region IState Members
    public int Explore()
    {
        Console.WriteLine("You look around for something to kill.");
        int ran = RandomGenerator.GetRandomNumber(5);
        if (ran == 0)
        {
            Console.WriteLine("A monster approaches! Prepare for battle!");
            context.SetState(context.GetBattleState());
        }
        else if (ran == 1)
        {
            Console.WriteLine("You find a golden jewel behind a tree!");
            return 2;
        }
        return 0;
    }
    public int Battle(int level)
    {
        Console.WriteLine("You find nothing to attack.");
        return 0;
    }
    #endregion
}

First, notice that the state implements IState. This means it has an Explore and Battle function, which are the two commands the user can do. When in the Explore state, upon exploring we’ll let the user either find a magical item which increases his experience, or he’ll find a monster that he must kill. For the code, we simply pick a random number, and depending on the value, let the user know he found a monster or magical item. If he found a monster, the user’s state will be changed to the Battle state. This allows us to call the same interface functions (Explore and Battle) but from a new state, the Battle state. On the other hand, if he found a magical item, his state remains in the Explore state. Notice that we also define the Battle function even though this is within the Explore state. Since the user can’t attack a tree, we’ll simply tell him there is nothing to attack. You could also throw an exception in the Battle() function if you wish, as long as the user interface prevents the user from choosing the Attack command while in the Explore state. In our case, the commands never change for the user, so we’ll define the bodies for all functions. At this point, you could easily draw a simple state diagram, that illustrates our game, but we’ll move on with the code.

Battling the Battle State

Just as we did with the Explore state, the Battle state will define the same two interface functions Explore and Battle. The difference is that since we’re now in a battle state, executing the Explore command should throw an exception (or display a message), and the Battle command will actually perform an attack. We define this state as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class BattleState : IState
{
    private RPGController context;
    private int rounds = 0;
    public BattleState(RPGController context)
    {
        this.context = context;
    }
    #region IState Members
    public int Explore()
    {
        Console.WriteLine("You'd love to, but see, there's this big ugly monster in front of you!");
        return 0;
    }
    public int Battle(int level)
    {
        Console.Write("You try to slay the monster.. ");
        rounds++;
        System.Threading.Thread.Sleep(1000);
        int maxRan = 10 - level;
        if (maxRan < 1)
        {
            maxRan = 1;
        }
        int ran = RandomGenerator.GetRandomNumber(maxRan);
        if (ran == 0)
        {
            Console.WriteLine("he's dead!");
            context.SetState(context.GetExploreState());
            int tempRounds = rounds;
            rounds = 0;
            return tempRounds;
        }
        else
        {
            Console.WriteLine("but fail.");
        }
        if (rounds >= 9)
        {
            Console.WriteLine("You panic and run away in fear.");
            context.SetState(context.GetExploreState());
            rounds = 0;
        }
        return 0;
    }
    #endregion
}

The beauty of the State Pattern is in how easy it is to add new states and commands to our application. Since the class implements the same interface (ie. the same commands), we can easily define this new Battle state. The only interesting part to this class is the contents of the Battle function, which simply picks a random number to tell if the user killed the monster. For each round the user fails to kill the monster (in one blow), the user loses potential experience. The faster a monster is killed, the more experience he gains.

It’s also important to note that in the Battle function we include a transition back to the Explore state (just as we did in the Explore state’s Explore() function if the user found a monster). Notice that we include a reference to the State controller’s context. This is just a pointer to the state controller, as the controller will ultimately handle which state we’re in and calling the desired functions. Let’s move on to describing how the controller works.

The Master Pattern Controller

As you noticed in the state definitions above, we have a reference to a “context” variable, which points to the state controller. The controller manages the current state and maipulates the state. Our main program will actually call the controller, not the individual states.

The controller can be defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class RPGController
{
    private IState exploreState;
    private IState battleState;
    private IState state;
    private int level = 1;
    public RPGController()
    {
        exploreState = new ExploreState(this);
        battleState = new BattleState(this);
        state = exploreState;
    }
    public int Explore()
    {
        return state.Explore();
    }
    public int Battle()
    {
        return state.Battle(level);
    }
    public void SetState(IState state)
    {
        this.state = state;
    }
    public void SetLevel(int level)
    {
        this.level = level;
    }
    public int GetLevel()
    {
        return level;
    }
    public IState GetExploreState()
    {
        return exploreState;
    }
    public IState GetBattleState()
    {
        return battleState;
    }
}

The first point to note is that we define an instance of each state within the controller. We define exploreState and battleState. We instantiate these variables to their designated concrete state (Explore and Battle). We also define a state pointer, which holds our current state. This is similar to defining the integer variable in the original example above.

Next, we define functions for each command that the user can select (or just copy the functions we used in the interface which coorespond to the same thing): Explore and Battle. For the body to these functions, we simply execute the current state’s method for that command. So for Explore(), we simply call state.Explore() and for Battle, we simply call state.Battle(). Remember, state is our current state and will point to either exploreState or battleState. Regardless of which concrete state it points to, the functions it can call are the same.

We also define a few helper functions, such as the SetState() function, which lets us change our active state, and the GetExploreState() and GetBattleState() functions, which return a concrete instance of the particular state. These are used during the state transitions when we reference the “context” variable to change states.

Putting it All Together, I Think We’ve Got a Game

You might be surprised at this point to realize we pretty much have a fully functional state-aware game. The state pattern hides and abstracts much of the complexity that we normally have to manage in the main code. Since the states are tucked away in individual modules managed by the controller, we only have to use the controller to handle our game states. We can define the main program loop as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Program
{
    static RPGController rpg = new RPGController();
    static int score = 0;
    static int nextLevel = 10;
    static void Main(string[] args)
    {
        ConsoleKeyInfo key;
        Console.WriteLine("-=-=- Simple Battle Adventure v1.0 -=-=-");
        do
        {
            Console.WriteLine();
            Console.WriteLine("L = Look Around, A = Attack, Q = Quit");
            Console.Write("Score [" + score + "] Level [" + rpg.GetLevel() + "] Action [L,A,Q]: ");
            key = Console.ReadKey();
            Console.WriteLine();
            DoAction(key.Key);
        }
        while (key.Key != ConsoleKey.Q && rpg.GetLevel() < 10);
        if (rpg.GetLevel() >= 10)
        {
            Console.WriteLine();
            Console.WriteLine("You WIN! Final score [" + score + "]");
        }
        Console.ReadKey();
    }
    static void DoAction(ConsoleKey key)
    {
        if (key == ConsoleKey.L)
        {
            int points = rpg.Explore();
            if (points > 0)
            {
                Console.WriteLine("You gain " + points + " heroic points!");
                score += points;
            }
        }
        else if (key == ConsoleKey.A)
        {
            int rounds = rpg.Battle();
            if (rounds > 0)
            {
                int points = 10 - rounds;
                if (points < 0)
                {
                    points = 0;
                }
                score += points;
                Console.WriteLine("You gain " + points + " heroic points!");
            }
        }
        if (score >= nextLevel)
        {
            rpg.SetLevel(rpg.GetLevel() + 1);
            nextLevel = rpg.GetLevel() * 10;
            Console.WriteLine("Your wonderous experience has gained you a level! Level " + rpg.GetLevel());
        }
    }
}

The first thing we do is include a variable for the RPG controller. This is our key to using the state pattern controller. Next, we display a basic menu to the user and allow him to select a command on the keyboard. Our state pattern comes into play when we process the command in the DoAction() function. Depending on the command selected, we call the controller’s designated function for that command (Explore or Battle). The controller (and its concrete states) handle the actual details about what to do based upon the user’s input. So, when the user chooses to Look, we just tell the controller to execute a Look and it tells the current state to execute a “Look”.

There is one additional utility class left out from the above code, which manages selecting random numbers. This class is defined as follows:

1
2
3
4
5
6
7
8
9
public static class RandomGenerator
{
    private static Random random = new Random();
    public static int GetRandomNumber(int maxValue)
    {
        return random.Next(maxValue);
    }
}

Output

If you’d like to try the game yourself, you can download a copy of the compiled game to try it out. Of course, you’ll need to have the .NET framework on your PC. Once you put together the above code and run the program, the output of our rpg game using the State Pattern, will look like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
-=-=- Simple Battle Adventure v1.0 -=-=-
L = Look Around, A = Attack, Q = Quit
Score [0] Level [1] Action [L,A,Q]: L
You look around for something to kill.
You find a golden jewel behind a tree!
You gain 2 heroic points!
L = Look Around, A = Attack, Q = Quit
Score [2] Level [1] Action [L,A,Q]: L
You look around for something to kill.
A monster approaches! Prepare for battle!
L = Look Around, A = Attack, Q = Quit
Score [2] Level [1] Action [L,A,Q]: A
You try to slay the monster.. but fail.
L = Look Around, A = Attack, Q = Quit
Score [2] Level [1] Action [L,A,Q]: A
You try to slay the monster.. but fail.
L = Look Around, A = Attack, Q = Quit
Score [2] Level [1] Action [L,A,Q]: A
You try to slay the monster.. he's dead!
You gain 5 heroic points!
L = Look Around, A = Attack, Q = Quit
Score [7] Level [1] Action [L,A,Q]: A
You find nothing to attack.
L = Look Around, A = Attack, Q = Quit
Score [7] Level [1] Action [L,A,Q]: L
You look around for something to kill.
You find a golden jewel behind a tree!
You gain 2 heroic points!
L = Look Around, A = Attack, Q = Quit
Score [9] Level [1] Action [L,A,Q]: L
You look around for something to kill.
L = Look Around, A = Attack, Q = Quit
Score [9] Level [1] Action [L,A,Q]: L
You look around for something to kill.
A monster approaches! Prepare for battle!

In the above output, you can guess which state the user was in when he performed each command and which action was called. For example, after slaying the monster, the user was returned to the Explore state, but selected the Attack command anyway. The response was “You find nothing to attack”, indicating the state transitioned from Battle to Explore successfully.

Conclusion

Managing state in an application typically requires utilizing an integer index variable with a series of if-then or case statements, which can often add to code complexity and maintenance issues. The State Pattern in C# helps us organize our states into individual modules with a single controller and helps us abstract away the details behind the state implementation. This allows us to reduce code complexity, increase readability, and ultimately reduce maintenance in the future. The State Pattern is an easy addition to your design patterns toolkit and can be used in any stateful object oriented application.

About the Author

This article was written by Kory Becker, software developer and architect, skilled in a range of technologies, including web application development, machine learning, artificial intelligence, and data science.

Share