Delegates and Dice

IMG_5589_90_91.jpg

I’ve been marking the First Year “Space Cheese Battle” implementations and it has been great fun. The aim of the work is to implement the board game “Space Cheese Battle”, which is a bit like Snakes and Ladders crossed with Ludo. Using rockets. And cheese.

The program uses a dice to control movement and in the final implementation this must of course be completely random. However when the program is being tested a random dice is exactly what you don’t want. What you want is a way of getting particular numbers so that you can test the program more easily. I first hit this kind of problem when I wrote a pontoon (blackjack) playing program and I had to wait for ages for the game to deliver a King followed by an Ace, so I could test the part of the program that deals with that. Eventually I figured out that a good way to speed things up was to allow me to type in the card being dealt, rather than have it picked randomly. Later on I worked out that it is even easier to test if you have a “pre-set” list of cards that are dealt automatically.

We can do the same thing with the dice for Space Cheese Battle. I can make three dice methods:

static Random rand = new Random();

static int RandomThrow()
{
    return rand.Next(1, 7);
}

This is the “production” dice throw method. It just delivers a value between 1 and 6.

static int InputThrow()
{
    int result = 0;

    while (true)
    {
        Console.Write("Enter throw value: ");
        try
        {
            result = int.Parse(Console.ReadLine());
            break;
        }
        catch { }
    }
    return result;
}

This is the “manual entry” dice method. It returns whatever the user types in. Note that I’ve not limited the values at all. It is sometimes useful to put in very large dice throws so that you can move the player onto particular squares for testing.

static int[] diceValues = new int[] { 
    3,4,5,4       
};

static int throwPos = 0;

static int FixedThrow()
{
    int result = diceValues[throwPos];
    throwPos = throwPos + 1;
    if (throwPos == diceValues.Length) throwPos = 0;
    return result;
}

This is the “fixed sequence” dice. I can create a whole set of values and then have them replayed by the dice. This is great for automated testing.

Of course the difficultly comes when I have to write my program and I need to switch between these dice behaviours. The students had hit this problem too, and some had created tests that checked the dice mode and then called the appropriate method when the program ran. These work, but the best way to select the kind of dice that you want to use is to create a delegate which refers to a particular method.

delegate int DiceThrow();

A delegate is a type of variable that can refer to a method with a particular signature. The statement above creates a delegate type called DiceThrow which can refer to a method that accepts no parameters and returns an integer, just like the dice methods above.  I can create a variable of type DiceThrow.

static DiceThrow ActiveDice;

The variable ActiveDice can refer to any method that accepts no parameters and returns an integer. I use it like this:

ActiveDice = new DiceThrow(RandomThrow);

The variable ActiveDice now refers to a new delegate instance connected to the RandomThrow method. Which means that the statement:

Console.WriteLine(ActiveDice());

- will call the RandomThrow method and print out what it returns.  In other words I can use the ActiveDice method exactly as if it was a method that accepts no parameters and returns an integer. When the delegate is called it will follow the reference to the method it has been connected to and then run that method. The great thing about my game code is that I don’t need to change the code that uses the dice to make it pick up new dice behaviours. I just have to call the delegate.

Note: The C# implementation also incudes a Delegate class (note the capital D) which intellisense might try to get you to use. This does something different and confusing. Make sure you use the delegate with the lower case d.