A C# .NET Adventure Game Sim and the Strategy Pattern

modified

Introduction

The strategy design pattern is a useful pattern for pulling out frequently changing pieces of code and encapsulating them within individual classes. The strategy pattern allows us to reference these new classes in a loosely-coupled fashion, without directly referencing the concrete implementations. This gives us the powerful ability of choosing concrete classes dynamically at runtime, instead of hard-wiring them during code design. This article will show an example usage of the strategy design pattern in creating a basic RPG-style adventure game simulator. The idea for the simulator comes from the popular book Head First Design Patterns by O’Reilly.

C# .NET Strategy Design Pattern in an Adventure Game Simulator

Game Design

In this particular game design, we will have specific types of characters and weapons. This directly leads to two basic classes: Character and Weapon. All characters will inherit from an abstract Character class, which will contain the basic functions that all characters can perform, such as displaying themselves and attacking. The weapon class will contain information about the weapon and how it attacks. Since each weapon will attack differently, we will encapsulate the weapon algorithms into separate classes, through the use of the strategy design pattern.

Our First Interface IWeaponBehavior

We’re going to start the design with our first interface, which represents the design of our weapon class. This is the core piece to the strategy pattern, in that it allows us to reference the interface, rather than the concrete implementations of each weapon’s algorithm class. While you could certainly create an individual class for each weapon and include a concrete reference to that weapon in each character class, by pulling out the details of the weapon and referencing them with an interface, you keep the game design flexible, and easily modifable.

1
2
3
4
5
public interface IWeaponBehavior
{
    void Display();
    void UseWeapon();
}

Our interface contains two methods. One displays details about the weapon and the other executes an attack. We’ll be defining the concrete implementations of each weapon shortly. However, everything we need to begin defining the character types is here in the interface.

We Can’t Attack Without a Body

Moving on, we’ll focus on the creation of the Character class, which is the base class for all character types. Our characters need to hold a weapon and display themselves. Since we’re going to have several different types of characters, we want to utilize a base class and optimize code-reuse as much as possible.

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
public abstract class Character
{
    private IWeaponBehavior weapon;

    public IWeaponBehavior Weapon
    {
        get
        {
            return weapon;
        }
        set
        {
            weapon = value;
        }
    }

    public Character(IWeaponBehavior _weapon)
    {
        Weapon = _weapon;
    }

    public abstract void Display();

    public void Fight()
    {
        Weapon.UseWeapon();
    }
}

What we have here is a character class. Notice that the class contains an instance member of the IWeaponBehavior interface. This, in effect, gives the character a weapon to hold. The details of the type of weapon or how it attacks are of no concern to the character class. The character class just knows that it needs to call Weapon.UseWeapon() when it wants to attack. The strategy design pattern will, in turn, call the appropriate concrete class which contains the details of the weapon.

Our character class also contains a public member of Weapon, to allow us to dynamically change the weapon at runtime. That is, when a character is instantiated, the constructor sets up the weapon type. This works fine, except that it sets in stone the type of weapon that this character can use. By including a means to change the weapon’s concrete class (ie. the public Weapon object), we can change the weapon whenever we wish, at run-time.

Finally, our character class contains an abstract Display() method so we can look at our character. The concrete characters will fill in the body for this method.

As far as code-reuse, our character base class contains all code for handling the weapon, which will be shared amongst all concrete character classes.

Let the Army Come Forth

Now that we have a character base class setup, using the strategy design pattern, it’s time to create some real characters. At the very least, we need a Barbarian and a Goblin. And well, what’s a kingdom without a king? Also for good measure, throw in a Paladin.

1
2
3
4
5
6
7
8
9
10
11
12
public class Barbarian : Character
{
    public Barbarian(IWeaponBehavior _weapon)
        : base(_weapon)
    {           
    }

    public override void Display()
    {
        Console.WriteLine("You are a strong, hulky barbarian.");
    }
}

Our first concrete character, Barbarian, inherits from the Character class, bringing in all the strategy design pattern code for handling a weapon. The only real meat to this class that remains to be filled in is the Display() method. Our constructor takes a generic weapon as a parameter and passes that to the base class. This is what allows us to create any type of weapon for the Barbarian. The King, Paladin, and Goblin follow the same pattern.

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 King : Character
{
    public King(IWeaponBehavior weapon)
        : base(weapon)
    {
    }

    public override void Display()
    {
        Console.WriteLine("You are a rightous proud king.");
    }
}

public class Paladin : Character
{
    public Paladin(IWeaponBehavior weapon)
        : base(weapon)
    {
    }

    public override void Display()
    {
        Console.WriteLine("You are a holy paladin, slayer of evil.");
    }
}

public class Goblin : Character
{
    public Goblin(IWeaponBehavior weapon)
        : base(weapon)
    {
    }

    public override void Display()
    {
        Console.WriteLine("You are a nasty evil goblin.");
    }
}

While the concrete characters seem quite simple, the important item to note about them is the usage of the strategy design pattern in accepting an interface to a weapon as input in the constructor. This is the key to creating loosely-coupled code.

Enough Characters Already, I Want to Fight

We have enough characters to start our small kingdom, so now it’s time to give them some weapons. Since we already have an interface for our weapon, we can begin creating the concrete implementations. Each weapon will be different, but the Character class doesn’t mind. Let’s start with a Knife.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class KnifeBehavior : IWeaponBehavior
{
    #region IWeaponBehavior Members

    public void Display()
    {
        Console.WriteLine("a dull knife.");
    }

    public void UseWeapon()
    {
        Console.WriteLine("You stab wildly with your knife.");
    }

    #endregion
}

The KnifeBehavior class implements the IWeaponBehavior interface and defines a body for the Display() and UseWeapon() functions. The strategy design pattern will dictate, at run-time, which detailed weapon class gets executed. We can define a few more weapons using the same pattern.

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
public class SwordBehavior : IWeaponBehavior
{
    #region IWeaponBehavior Members

    public void Display()
    {
        Console.WriteLine("a long, sharp sword.");
    }

    public void UseWeapon()
    {
        Console.WriteLine("You slash with your sword.");
    }

    #endregion
}

public class CrossbowBehavior : IWeaponBehavior
{
    #region IWeaponBehavior Members

    public void Display()
    {
        Console.WriteLine("a finely made crossbow.");
    }

    public void UseWeapon()
    {
        Console.WriteLine("You fire a bolt from your crossbow.");
    }

    #endregion
}

Putting it All Together

Now that we have our characters and weapons, we can run a quick test to see how they work with the strategy design pattern.

1
2
3
4
5
6
static void Main()
{
    Character character = new Paladin(new SwordBehavior());
    character.Display();
    character.Fight();
}

Output

1
2
You are a holy paladin, slayer of evil.
You slash with your sword.

Notice in the above code, we start with a generic character variable and instantiate a Paladin from it. By passing in the SwordBehavior class, the strategy design pattern gives the Paladin a sword to use. We can now display and attack by calling the generic character’s Display() and Fight() methods. The Fight() method is particularly interesting, in that it calls the weapon interface, which in turn, calls the concrete weapon UseWeapon() method.

To demonstrate the power of the strategy design pattern in changing concrete classes at run-time, notice how we can change the Paladin’s weapon on-the-fly:

1
2
3
4
5
6
7
8
9
10
static void Main()
{
    Character character = new Paladin(new SwordBehavior());
    character.Display();
    character.Fight();

    // Change the Paladin's weapon at run-time.
    character.Weapon = new CrossbowBehavior();
    character.Fight();
}

Output

1
2
3
You are a holy paladin, slayer of evil.
You slash with your sword.
You fire a bolt from your crossbow.

In the above examples, you can see how the strategy design pattern makes it easy to add new characters and weapons with no impact to the other classes.

Making the Adventure Simulator More Interactive

To really get a feel for how the strategy pattern works at run-time, we’ll provide a few menus for the user to interact with to choose a character type, choose a weapon, look at himself, and attack. We’ll also provide menu options to change the character type and weapon. Since our design uses the strategy pattern to implement the weapon behavior, we can easily interchange any type of weapon with our character class.

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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
class Program
{
    private static Character character;

    static void Main(string[] args)
    {           
        Console.WriteLine("AdventureSimulator With the Strategy Design Pattern");
        Console.WriteLine("Press Q to quit.");
        Console.WriteLine("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-");

        Console.WriteLine("");

        // Initialize the character.
        GetCharacter();
        GetWeapon();

        while (!ShowActions())
        {               
        }
    }

    static bool ShowActions()
    {
        Console.WriteLine("Choose an action:");
        Console.WriteLine("L - Look");
        Console.WriteLine("A - Attack");
        Console.WriteLine("C - Change Character");
        Console.WriteLine("W - Change Weapon");
        Console.WriteLine("Q - Quit");
        Console.Write("Your selection> ");

        bool done;
        bool quit = false;

        do
        {
            done = true;

            switch (Console.ReadKey().Key)
            {
                case ConsoleKey.L: Console.WriteLine(""); character.Display(); break;
                case ConsoleKey.A: Console.WriteLine(""); character.Fight(); break;
                case ConsoleKey.C:
                    {
                        // Remember our weapon.
                        IWeaponBehavior weapon = character.Weapon;

                        Console.WriteLine("");
                        GetCharacter();

                        // Set our weapon.
                        character.Weapon = weapon;
                    }
                    break;
                case ConsoleKey.W: Console.WriteLine(""); GetWeapon(); break;
                case ConsoleKey.Q: quit = true; break;
                default: done = false; break;
            }
        }
        while (!done);

        return quit;
    }

    static void GetCharacter()
    {
        Console.WriteLine("Choose a character:");
        Console.WriteLine("A - King");
        Console.WriteLine("B - Paladin");
        Console.WriteLine("C - Goblin");
        Console.WriteLine("D - Barbarian");
        Console.Write("Your selection> ");

        bool done;

        do
        {
            done = true;   

            switch (Console.ReadKey().Key)
            {
                case ConsoleKey.A: character = new King(new KnifeBehavior()); break;
                case ConsoleKey.B: character = new Paladin(new SwordBehavior()); break;
                case ConsoleKey.C: character = new Goblin(new CrossbowBehavior()); break;
                case ConsoleKey.D: character = new Barbarian(new KnifeBehavior()); break;
                default: done = false; break;
            }
        }
        while (!done);

        Console.WriteLine("");
        character.Display();
    }

    static void GetWeapon()
    {
        Console.WriteLine("Choose a weapon:");
        Console.WriteLine("A - Knife");
        Console.WriteLine("B - Sword");
        Console.WriteLine("C - Crossbow");
        Console.Write("Your selection> ");

        bool done;

        do
        {
            done = true;

            switch (Console.ReadKey().Key)
            {
                case ConsoleKey.A: character.Weapon = new KnifeBehavior(); break;
                case ConsoleKey.B: character.Weapon = new SwordBehavior(); break;
                case ConsoleKey.C: character.Weapon = new CrossbowBehavior(); break;
                default: done = false; break;
            }
        }
        while (!done);

        Console.WriteLine("");
        Console.Write("You pick up ");
        character.Weapon.Display();
    }
}

Output

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
AdventureTest With the Strategy Design Pattern
Press Q to quit.
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Choose a character:
A - King
B - Paladin
C - Goblin
D - Barbarian
Your selection> C
You are a nasty evil goblin.
Choose a weapon:
A - Knife
B - Sword
C - Crossbow
Your selection> A
You pick up a dull knife.
Choose an action:
L - Look
A - Attack
C - Change Character
W - Change Weapon
Q - Quit
Your selection> A
You stab wildly with your knife.
Choose an action:
L - Look
A - Attack
C - Change Character
W - Change Weapon
Q - Quit
Your selection>Q

Conclusion

The strategy design pattern helps us remove frequently changing pieces of code and encapsulate them within individual classes (algorithms). By defering the decision on which type of concrete class to use, we can maintain loosely-coupled code that can dynamically change at run-time. The strategy design pattern also helps to foster code-reuse and maintainability of future code.

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