PERT Estimation 101

bear-to-do-list

Task estimates; the very real enemy of the developer. The numbers you provide for a task or piece of project work (whether this is in hours, days, story points, etc.), as an ‘estimate’, are often not being viewed as an approximation by all involved. This particular subject is covered, rather marvellously by Robert C.Martin, aka Uncle Bob, in his book The Clean Coder: A Code of Conduct for Professional Programmers; a book that I implore every developer to read. To quote Robert C.Martin directly, I believe this covers the underlying issue pretty well in a good number of cases:

The problem is that we view estimates in different ways. Business likes to view estimates as commitments. Developers like to view estimates as guesses. The difference is profound.

On a good number of occasions, I have found myself taking a good hiding, due to the estimates I have offered up on project work; normally where you find the estimate has been relayed to the customer as a concrete guarantee. In many cases, both parties should have shouldered more responsibility for this disparity; as the developer, I should have been more explicit as to what my estimates actually meant. In turn, those relaying information to the customer should have probably dug deeper into the heart of the matter, to extract further raw information as to the ‘likelihood’ that something could be achieved by a certain time.

Good dialogue and communication is a key factor, of course, as is making sure that you are always clear when you are providing an ‘estimate’ and making a ‘commitment’.

This is a subject that I always find myself circling back to and that I truly want to get better at, plain and simple. For that reason, this post will be geared towards looking at one potential strategy for improving the estimation process; the Program Evaluation and Review Technique (which is covered again pretty well in ‘The Clean Coder’).

What is PERT?

The Program Evaluation and Review Technique (PERT) encapsulates an analysis technique that is used for more accurately estimating large and complex projects. The basic idea is to reveal a distribution, or create a probabilistic structure, to reduce inaccuracies in estimates and attempt to account for uncertainty in a project’s schedule. It was created in 1957 for use by the U.S. Navy Special Projects Office to manage the Polaris nuclear submarine project.

In short, the process involves identifying the likely values for the minimum time and maximum time a task will take whilst pinpointing the ‘most likely’ estimate for a task’s completion, from observing a calculated distribution. Uncertainty is factored in using some standard deviation values.

How does it work?

The concept is as follows; first, when an estimate is required, you provide three numbers:

  • O: Optimistic Estimate (known as the ‘wow, how the hell did I do it that quick!’ estimate).
  • N: Nominal Estimate (the estimate with the greatest chance of being successful, or the correct estimate essentially).
  • P: Pessimistic Estimate (the doomsday estimate, where everything goes wrong except the Earths tectonic plates opening up to consume you, as you code the feature).

The first figure, ‘O’, involves thinking about the ‘pipe dream’ figure, i.e. if you could type the code at 1000 thousand lines a minute, making zero errors, and everything was to plug into place and work the first time. If Chuck Norris was coding the feature, this would be his estimate (this should have less than a one percent chance of being the actual time taken in order for the calculation to be meaningful). Next up, come up with the figure for N, which is defined as the estimate with the highest ‘chance’ of being the correct one. On a distribution bar chart of estimates, this would be the highest bar. Lastly, define P, which should be presented as the ‘worst case’ scenario estimate, stopping short of accounting for absolute catastrophes (again, this should have less than a one percent chance of being the ‘correct’ estimate, when all is said and done).

You’ve got three numbers, hoorah! A probability distribution can be gleaned for these figures simply enough (with ET being the expected time, I’ve changed the name of the variables to make more sense to me, when compared to ‘The Clean Coder’ or Wiki, but feel free to go with what fits for you):

ET = (O + 4N + P) / 6

The wiki definitions for all of these numbers are good, so I’ve included them below:

Wiki Definitions for PERT Calculation Facets

Wiki Definitions for PERT Calculation Facets.

The last part of this wonderful, little, puzzle is calculating the standard deviation (SD); which equates to getting your mitts on a value that indicates the degree of uncertainty that is associated with a given task:

SD = (P - O) / 6

Let’s observe a detailed example to get to grips with this fully, concentrating on how some extra elements come into play for a series of tasks.

A detailed example

In this example, Ryan (a name that came totally off the top of my head, milliseconds of thought went into that!) has a total of five items that he needs to estimate, that are part of a full project. Here are the specific tasks and Ryan’s initial estimates (expressed in days), before applying PERT calculations:

SIDE NOTE: Breaking a large project or piece of work into smaller tasks can often be a good strategy for mitigating risk up front and weeding out bugbears ahead of time.

Task Estimate
Write stored procedures for the customer export page 3
Write Web API components for the customer export page 2
Build the business logic layer for the customer export page 5
Build and style the customer export page (UI) 3
Assist Hayley with automated tests 1

That comes to a grand total of 14 days.

Ryan is now asked to apply the PERT approach to his estimates, and the mental cogs start to turn. Here’s a run-down of Ryan’s thoughts on each part of the project, in order:

  1. Stored Procedures: “Ok, I think I am fairly likely to complete this within 3 days, but there are quite a few elements of Dave’s existing helper stored procedures that I don’t understand. So, I’ll actually have to factor that in. Oh, and I have to do a data conversion!”
  2. Web API Components: “I’ve written most the of Web API components up to this point, so I’m well versed in this area at least and am confident with this figure. Even if everything went wrong it would only stretch to 3 days.”
  3. Business Logic Layer: “Jane wrote most of this layer and I’m not that well-versed in how it hangs together. The domain behind this is actually quite new to me. This could take significantly more time…”
  4. Building the page and styling: “The specification looks good and I’ve done a few similar pages in the past. It could take slightly longer if things did go wrong, as there are a few UI bells and whistles, including effects, that are required…”
  5. Assisting Hayley: “I was just kind of time boxing this in my mind, it’s almost certain she will need support beyond this, however – it really is an unknown!”

After a bit of head scratching and discussion with other team members, the figures below come out of the boiling pot. PERT has been applied, times are expressed in days and abbreviations used for the sake of space. Values are rounded to one decimal place:

Task O N P ET SD
Write stored procedures for the customer export page 3 5 11 5.7 1.3
Write Web API components for the customer export page 1 2 3 2 0.3
Build the business logic layer for the customer export page 2 6 13 6.5 1.8
Build and style the customer export page (UI) 1 4 9 4.3 1.3
Assist Hayley with automated tests 1 3 7 3.3 1

ET and SD values are calculated using the formulas previously discussed. Firstly, using the ET values, we can understand that Ryan will ‘likely’ be finished with these tasks (based on the sum of the ET values) in 21.8 days, let’s say 22 days then. To get a feel for the ‘uncertainty’ behind these tasks we have to look at the square root of the sum of the squares of the SD values for the tasks (a bit of a mouthful), as shown here:

(1.3 2 + 0.3 2 + 1.8 2 + 1.3 2 + 1 2) 1/2 =
(1.69 + 0.09 + 3.24 + 1.69 + 1) 1/2 =
7.71 1/2 = ~2.8 days

We can surmise from this that there could be 2.8 days, so let’s say 3 days, of uncertainty to factor in; therefore Ryan could well take somewhere in the region of 25 days to complete the series of tasks, or possibly even 28 days (if we double the time calculated for SD across the sequence). However, we know from this that anything over these values is an unlikely outcome. We are a far cry from the original estimate of 14 days to cover all of the tasks.

Summary

The general premise is to add some pragmatism and realism to the whole process of estimation. I have, on many occasions, estimated too optimistically and then have struggled to hit the mark; this is just one technique for attempting to mitigate that risk by being more methodical in the estimation process.

I hope you’ve enjoyed this primer and happy coding, estimating, etc. Until the next time!

Gordon Ramsay Home-made Fish Fingers with a Chip Butty Application…Just because

There was no need for this post to exist or for these four hundred and thirty-eight (with brackets and other nonsense) random lines of C# code to even see the light of day. For some unknown reason, I woke up over the weekend and spent an hour watching Gordon Ramsay YouTube videos when I came across this amazing looking take on fish fingers with a chip butty:

The video somehow leads me to write the following code. I wasn’t trying to write anything particularly good (i.e. a gaming engine or anything reusable, per say), it’s just an hour or two of me sitting on my tod mindlessly coding to no exacting standards (i.e. it’s all in one file, doesn’t conform to my usual commenting/ordering standards, is horrendously formed and pretty damn awful). Essentially, just enjoy the ‘random factor’ of all of this; you’ll notice I throw in a tonne of exceptions because, in my head, Gordon Ramsay wouldn’t use simple boolean logic to denote if your choice was correct or not, he would throw massive exceptions all over the place (pretty sure this is how he would write his code).

Here it is, very much un-sanitised and untested:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Media;

namespace GordonRamseyCookingSimulator
{
    class Program
    {
        private static List<string> gordonSoundBites = new List<string>()
        {
            "gordon-clip-one.wav",
            "gordon-clip-two.wav",
            "gordon-clip-three.wav"
        };

        private const string WELL_DONE_SOUND_BITE = "well-done.wav";

        private static SoundPlayer player = new SoundPlayer();

        private const string GOOD_JOB_TEXT = "Good job, moving on...";
        private const string GORDON_DEMANDS_TEXT = "Gordon demands that you choose a {0}";

        private static Dictionary<string, string> complexOptions = new Dictionary<string, string>();

        static void Main(string[] args)
        {
            Console.WindowHeight = 60;
            Console.WindowWidth = 185;
            Console.ForegroundColor = ConsoleColor.Green;
            bool playFailSound = false, continuePlaying = true;

            WriteToConsole($"Let's make Home-made Fish Fingers and a Chip Butty with Gordon Ramsay{ Environment.NewLine }================================================================================");

            do
            {
                try
                {
                    MakeFishFingersWithAChipButty();
                }
                catch (Exception ex)
                {
                    Console.ForegroundColor = ConsoleColor.Red;

                    Console.WriteLine();
                    playFailSound = true;
                    Console.WriteLine("Gordon Ramsay looks upon you with shame. You have failed because: ({0}) - {1}", ex.GetType().Name, ex.Message);
                }

                if (playFailSound)
                {
                    player.SoundLocation = gordonSoundBites[new Random().Next(0, 3)];
                    player.Play();

                    playFailSound = false;
                }

                WriteToConsole("Play again (Y/N followed by enter)");

                continuePlaying = RetrieveTerifiedCooksChoice().Equals("y", StringComparison.InvariantCultureIgnoreCase);

            } while (continuePlaying);
        }

        private static void MakeFishFingersWithAChipButty()
        {
            Console.WriteLine();

            // Cook with the big man
            NavigateThroughThePotatoSelectionForTheButtyTest();
            NavigateThroughThePeelingInstrumentTest();                  // This one should be easy ahem (peeling...peeler!)
            NavigateThroughTheChipPreparationTest();
            NavigateThroughTheChipCookingChoiceTest();
            NavigateThroughTheFishSelectionTest();
            NavigateThroughTheFishPreparationTest();
            NavigateThroughTheHerbSelectionTest();
            NavigateThroughTheCookingInstrumentForFishFingersTest();
            NavigateThroughTheFishFinalCookingStepTest();
            NavigateThroughTheBreadSelectionTest();

            // Freebie from Gordon - He takes pity, and dishes out an awesome nugget of knowledge
            Console.ForegroundColor = ConsoleColor.DarkMagenta;
            WriteToConsole("HEY IDIOT! This one is for free; remember to rub the bread in the chip oil on the tray (do a 'mop' up) and get all of those awesome flavours!");
            Console.ForegroundColor = ConsoleColor.Green;

            // The FINAL TEST!
            NavigateThroughTheFinalServingTest();

            // SUCCESS (woop woop)!!!
            GodJob();

            Console.ForegroundColor = ConsoleColor.DarkMagenta;
            WriteToConsole("You have survived the Home-made Fish Fingers and a Chip Butty challenge! Well done! Press enter to exit...");
            Console.ForegroundColor = ConsoleColor.Green;
        }

        private static void NavigateThroughTheFinalServingTest()
        {
            // Final choice, how does this thing get served?
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "sauce to serve this with and decide if you should share this with others or not:"));
            complexOptions = CreateDictionaryWithOptions("Serve with ketchup and devour everything yourself.",
                "Serve with brown sauce and share (there is enough for two).", "Serve with ketchup and hand the whole dish over to a loved one, you've already eaten.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "A");
        }

        private static void NavigateThroughTheBreadSelectionTest()
        {
            // What type of bread should you use!!!
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "bread type for the chip butty (buttered, of course!):"));
            WriteEnumValuesToConsole<BreadType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<BreadType>(RetrieveTerifiedCooksChoice(), "thickcrustybread");

            // SUCCESS (so far anyway, you're nearly there)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishFinalCookingStepTest()
        {
            // How long should they be cooked and what do you add near the end (when the heat is turned up)?
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "time for how long the fish fingers should be cooked on each side and what secret ingredient should be added near the end of this process:"));
            complexOptions = CreateDictionaryWithOptions("Cook for 3 minutes on each side adding some butter near the end.",
                "Cook for 2 minutes on each side adding some margarine near the end.", "cook for 5 minutes on each side adding some butter near the end.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "A");

            // SUCCESS (so far anyway, getting there)!!!
            GodJob();
        }

        private static void NavigateThroughTheCookingInstrumentForFishFingersTest()
        {
            // What cooking instrument should be used to cook said fish fingers
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "cooking instrument with which to cook your fish:"));
            WriteEnumValuesToConsole<ChefToolsForTheJob>();
            GordonRamsayProcessesYourToolChoiceForFoolishMistakes(RetrieveTerifiedCooksChoice(), ChefToolsForTheJob.Pan);

            // SUCCESS (so far anyway, getting there)!!!
            GodJob();
        }

        private static void NavigateThroughTheHerbSelectionTest()
        {
            // What herb should be used with the fish???
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "herb to chop and use with your breadcrumb mixture for the fish:"));
            WriteEnumValuesToConsole<HerbType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<HerbType>(RetrieveTerifiedCooksChoice(), "dill");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishPreparationTest()
        {
            // Ok, how should the fish be prepared...
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for preparing the fish:"));
            complexOptions = CreateDictionaryWithOptions("Cut into squares, pepper and cover in a little chill powder before coating in seasoned flour, then use egg-wash.",
                "Cut into battons, lightly salt before coating in seasoned flour and rolling, then use egg-wash.", "Cut at an angle, lightly salt and don't flour (just use egg-wash).");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "B");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheFishSelectionTest()
        {
            // Choose the correct fish type
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "fish type for your fish fingers:"));
            WriteEnumValuesToConsole<FishType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<FishType>(RetrieveTerifiedCooksChoice(), "pollock");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheChipCookingChoiceTest()
        {
            // Ok, what do we 'bang' the chips into in order to cook them
            complexOptions.Clear();
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for cooking the chips:"));
            complexOptions = CreateDictionaryWithOptions("Chuck the chips in a roasting hot pan with oil and cook for 10 minutes.",
                "Chuck the chips in the deep fat fryer until done.", "Chuck the chips on an oiled baking tray and cook in a pre-heated oven.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "C");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughTheChipPreparationTest()
        {
            // How do you choose to make the chips???
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "method for making preparing the chips for cooking:"));
            complexOptions = CreateDictionaryWithOptions("Chop, add oil, don't season and add chilli. Then shake.",
                "Chop, blanch, season and add paprika. Then shake.", "Chop, add oil, don't season and add pesto. Then shake.");

            WriteDictionaryValuesToConsole(complexOptions);
            GordonRamsayInspectsYourComplexChoice(RetrieveTerifiedCooksChoice(), "B");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughThePeelingInstrumentTest()
        {
            // Choose the correct peeling method, or you'll become a cropper
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "peeling instrument for your potatoes:"));
            WriteEnumValuesToConsole<ChefToolsForTheJob>();
            GordonRamsayProcessesYourToolChoiceForFoolishMistakes(RetrieveTerifiedCooksChoice(), ChefToolsForTheJob.Peeler);

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static void NavigateThroughThePotatoSelectionForTheButtyTest()
        {
            player.SoundLocation = WELL_DONE_SOUND_BITE;

            // Choose the correct potato type for the chips
            WriteToConsole(string.Format(GORDON_DEMANDS_TEXT, "potato type for your chips:"));
            WriteEnumValuesToConsole<PotatoType>();
            GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<PotatoType>(RetrieveTerifiedCooksChoice(), "desiree");

            // SUCCESS (so far anyway, there is a long way to go)!!!
            GodJob();
        }

        private static Dictionary<string, string> CreateDictionaryWithOptions(params string[] items)
        {
            Dictionary<string, string> complexItems = new Dictionary<string, string>();

            if (items == null || items.Count() == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            List<string> keys = new List<string>() { "A", "B", "C" };

            if (items.Count() > keys.Count())
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            for (int i = 0; i < items.Count(); i++)
            {
                complexItems.Add(keys[i], items[i]);
            }

            return complexItems;
        }

        private static void GodJob()
        {
            player.Play();
            WriteToConsole(Environment.NewLine + GOOD_JOB_TEXT);
        }

        private static void WriteDictionaryValuesToConsole(Dictionary<string, string> complexOptions)
        {
            if (complexOptions == null || complexOptions.Count == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            complexOptions.ToList().ForEach(item =>
            {
                WriteToConsole($"{ item.Value }: type = { item.Key }", false);
            });

            Console.WriteLine();
        }

        private static void GordonRamsayInspectsYourComplexChoice(string choiceString, string gordonsDesiredOutcome)
        {
            if (complexOptions == null || complexOptions.Count == 0)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            if (!choiceString.Trim().Equals(gordonsDesiredOutcome, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new GordonRamsayPoorChefSkillException("GOD DAMN IT, WHY DID YOU DO IT LIKE THAT. USELESS, SIMPLY USELESS - EPIC FAIL!");
            }
        }

        private static void GordonRamsayProcessesYourToolChoiceForFoolishMistakes(string choiceString, ChefToolsForTheJob gordonsDesiredOutcome)
        {
            if ((ChefToolsForTheJob)CheckForAStupidChoiceInGeneral(choiceString) != gordonsDesiredOutcome)
            {
                throw new GordonRamsayPoorChefSkillException("GOD DAMN IT, WHY DID YOU CHOOSE THAT BLOOMING (imagine swearing here) TOOL FOR THE JOB - EPIC FAIL!");
            }
        }

        private static void GordonRamsayProcessesYourIngredientChoiceForFoolishMistakes<T>(string choiceString, string gordonsDesiredOutcome) where T : struct, IConvertible
        {
            if (!typeof(T).IsEnum)
            {
                throw new GordonRamsayEnragedAtTheDeveloperException();
            }

            string choiceFromEnum = Enum.GetName(typeof(T), CheckForAStupidChoiceInGeneral(choiceString));
            if (string.IsNullOrWhiteSpace(choiceFromEnum) || !choiceFromEnum.Equals(gordonsDesiredOutcome, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new GordonRamsayWrongIngredientException("WRONG, WRONG, WRONG INGREDIENT!!! - EPIC FAIL");
            }
        }

        private static int CheckForAStupidChoiceInGeneral(string choiceString)
        {
            int choiceValue;
            if (!int.TryParse(choiceString, out choiceValue))
            {
                throw new GordonRamsayInvalidChoiceException("YOU DIDN'T GIVE ME A BLOOMING (imagine swearing here) NUMBER - EPIC FAIL!");
            }

            return choiceValue;
        }

        private static string RetrieveTerifiedCooksChoice()
        {
            Console.ForegroundColor = ConsoleColor.Blue;

            Console.Write("MAKE YOUR CHOICE: ");
            string choice = Console.ReadLine();
    
            Console.ForegroundColor = ConsoleColor.Green;

            return choice;
        }

        private static void WriteToConsole(string text, bool addAdditionalNewLine = true)
        {
            Console.WriteLine(text + (addAdditionalNewLine ? Environment.NewLine : string.Empty));
        }

        private static void WriteEnumValuesToConsole<T>() where T : struct, IConvertible 
        {
            Type typePassedIn = typeof(T);

            if (typePassedIn.IsEnum)
            {
                Enum.GetValues(typePassedIn).Cast<T>().ToList().ForEach(item =>
                {
                    Enum enumValue = Enum.Parse(typeof(T), item.ToString()) as Enum;
                    WriteToConsole($"{ item }: type = { Convert.ToUInt32(enumValue) }", false);
                });
            }

            Console.WriteLine();
        }
    }

    public enum BreadType
    {
        MediumSlicedGranary = 0,
        HovisHalfAndHalf = 1,
        Baguette = 2,
        ThickCrustyBread = 3
    }

    public enum HerbType
    {
        Basil = 0,
        Chives = 1,
        Dill = 3,
        Sage = 4,
        Tarragon = 5
    }

    public enum FishType
    {
        Haddock = 0,
        Pollock = 1,
        Tuna = 2,
        Salmon = 3
    }

    public enum PotatoType
    {
        Desiree = 0,
        KingEdward = 1,
        Laura = 2,
        MelodyPotato = 3
    }

    public enum ChefToolsForTheJob
    {
        RustySpoon = 0,
        Peeler = 1,
        Screwdriver = 2,
        Pan = 3,
        Griddle = 4
    }

    public class GordonRamsayInvalidChoiceException : Exception
    {
        public GordonRamsayInvalidChoiceException(string message)
            : base(message)
        {

        }
    }

    public class GordonRamsayWrongIngredientException : Exception
    {
        public GordonRamsayWrongIngredientException(string message)
            : base(message)
        {

        }
    }
    
    public class GordonRamsayPoorChefSkillException : Exception
    {
        public GordonRamsayPoorChefSkillException(string message)
            : base(message)
        {

        }
    }

    public class GordonRamsayEnragedAtTheDeveloperException : Exception
    {
        public GordonRamsayEnragedAtTheDeveloperException()
            : base("OMG, YOU ARE SO INCOMPETENT. THIS SOFTWARE DEVELOPER DOESN'T KNOW HOW HIS OWN CODE SHOULD WORK. SHOCKING!")
        {

        }
    }
}

It takes the form of a beautiful console application; here it is in full swing:

Gordon Ramsay Cooking Challenge.

Gordon Ramsay Cooking Challenge.

For extra kicks, I forced my wife to play it to…adding annoying sound bites (for success and failure, in each case, to cover all of my bases):

Claire Epic Failing.

Claire Epic Failing.

Claire Nailing It.

Claire Nailing It.

It felt pretty darn good to just rattle out a piece of random, not that well thought out code. I think I just needed to write ‘something’, so this is what we have ended up with for better or for worse. I urge you to write something awful (just for yourself, not for your day job, unless you quite like the idea of being tossed out onto the street that is!) and get liberated, just for once! There is quite a bit of pressure sometimes to conform to standards, practice patterns, etc. This is all good and well, but just remember that you can always just hammer out something ill-thought for yourself and enjoy doing so; sometimes it can be just the ticket! 😉

I seem to be, as it was pointed out to me today, late to the Gordon Ramsay game-based party anyway:

Gordon Ramsay Dash

There will be more golden, crispy, fish finger like coding posts coming soon; in the meantime have a great morning/afternoon (or evening/night) depending on when you read this, bye for now!

Christmas Wind-down

Hi everyone,

With the holidays almost upon us I just wanted to wish everyone a very Merry Christmas and all the best for the New Year!

If possible, I will do my best to crank out a further post between now and the New Year. Here’s a teaser for the first part of the year (likely approximately the first quarter), taken from my original ‘pot of things’ to cover with a few additions that are taking precedence, based on current interests:

  • Continued prodding around in F# (likely starting with charting as promised).
  • The Razor View Engine (basic overview).
  • An Arduino starter project.
  • Deeper dives into JavaScript (in the build up to some exams…more to follow here).
  • As an extension to this, I want to do some ES6 coverage.
  • A return to Unity (I’ll probably do a big hit around the start of Spring).
  • Plus other things from my original cooking pot, including Udemy courses and sneak peaks on other libraries/utilities as I can, etc.

I may well revise my posting frequency a little also. After planning on doing two posts a week I’ve become acutely aware that I’m not really hitting that mark, so I’ll either go down to one post a week or learn to produce posts faster (and better!). We’ll have to see how all of this pans out.

Anyway, have a wonderful holiday!

Future Decoded 2015 Play-by-play

Hello beautiful people!

It’s a fantastic, gorgeous Saturday morning (it’ll be Monday by the time I hit the publish button, such is the enormity of the post!); the birds are chirping, the sun is shining through the balcony windows (and there is a bloody wasp outside, STILL!!!) and my wife has left me…………to go on a girly weekend (that probably sounded more alarming than intended; hmmm, oh well, it stays!). Whilst she is away fighting the good fight, this gives me the opportunity to go over my thoughts on the recent Future Decoded 2015 event that took place at ExCel in London.

The links to outline this event have been posted before on my blog, but just in case, here are the goods again:

Future Decoded 2015
Future Decoded 2015: Technical Day Highlights

Before we begin, it’s worth pointing out that I attended this event a couple of weeks ago, so apologies if any inaccuracies pop up. I’ll do my best to stick to the facts of what I can remember and specific points that interested me; other commitments ended up preventing me from getting to this particular post sooner. You’ll all let me off, being the super gracious, awesome folks you are, I’m sure :-).

So, FIGHT!!!!!

Sorry, I had a dream about Mortal Kombat last night and upper-cutting people into the pit – What a great stage that was! Ah, the memories….Let’s begin/start/get on with it then.

Morning Key Notes

The morning Key Notes were varied and expansive in nature. I won’t discuss all of them here, only the takeaway points from the talks that struck a chord with me.

1) Scott Guthrie. EVP Cloud and Enterprise, Microsoft (Azure).

I was particularly looking forward to this talk being a keen follower of Scott Guthrie (include Scott Hanselman), and I normally try to catch up with Channel 9 features and Azure Fridays whenever possible (I’ve linked both, although I’m sure most of you, if not all, have come across Channel 9 before or heard of Azure Fridays).

The talk did have primer elements as you would expect, i.e. here’s the Azure Portal and what you can expect to find (in relation to resources, templates you can access for applications, services, Content Distribution Networks (CDN), etc). The next bit really caught me cold, who was expecting a giant image slide of a cow! I certainly wasn’t…

Estrus in Cows

What followed was a full example of real-time data recording and assessment surrounding the monitoring of cows in Asia. I’ve provided a link below that sums up the concept of Estrus (being in heat) nicely enough, but it laymen’s terms it relates to cows ‘being in the mooooooood’ (wife insisted I added that joke). Obviously, a farmers’ ability to accurately detect this, urm, state of being in a cow is an incredibly important factor in the ability to produce calves.

It turns out that a cow tends to move more when in the Estrus state; something that can certainly be measured. So, with pedometers attached to cows to measure steps taken and an Azure based service receiving and providing feedback in real-time, the farmer in question was able to take action to maximise calf production. Further to this, analysis of the data gathered was able to identify trends against how long cows have been in the Estrus state, and the gender of offspring. Crazy stuff, but all very interesting. Feel free to read further to your hearts content:

Cow Estrus Detection

The Internet of Things (IoT) was briefly touched on and another brief, live coding example ensued.

Scott produced a small, bog-standard heat sensor (apparently, just a few pounds, I was impressed he didn’t say dollars!) and proceeded to demonstrate a basic WinForms application passing a JSON payload to Azure in real-time (measurements taken a few times a second). This strikes me as exciting territory, and I have friends who do develop applications working in tandem with sensors already, backed up by technologies such as the Raspberry Pi and Arduino, for example. The talk closed with the conceptual idea that the majority of data, in the world today, is still largely unmeasured, and hoped that Azure would be an important platform in unlocking developers potential to measure previously untapped data.

2) Kevin Ashton. Inventor of the “Internet of Things”.

Kevin coined the term the Internet of Things (IoT), and gave a very good talk on what this means, as well as identifying certain ‘predictions’ for the future. For instance, that we, as a species, would survive climate change for one. He quickly noted that calling ‘BS’ on this particular one would be tricky should we suffer a doomsday style event at the hands of climate change (I don’t imagine the last thoughts of humanity to be, ‘oh, Kevin Ashton was so bloody wrong!’). Another interesting prediction; we would all own a self-driving car by 2030. Prototype examples already exist, such as Googles (and Apples) efforts, and the Tesla:

Google/Apple (Titan) Self Driving Cars
The Tesla

Self-driving cars being one of the cases in point, the IoT relates to how a whole new host of devices will now become ‘connected’. Besides cars rigged up to the internet, we are all aware of the hooking up of internal systems in our homes (heating, etc) and utility devices (the washing machine), as to always be online and accessible at a moments notice. This world isn’t coming per say, it’s essentially already here.

Pushing past this initial definition, Kevin was keen to stress that the IoT was not limited in its definition to just ‘the connecting of hardware to the internet’ only. Wiki sums this up quite nicely on this occasion, but software (services and analytics) that moves forward with hardware changes will ultimately change the way we live, work, shop and go about our daily lives. Whether this be data relayed from the fridge to google glasses (yes, you are out of milk!), or perhaps a self-driving car ordering ‘click and collect’ shopping and driving you to the collection point after work (not to mention triggering the heating x miles from home!). Software, and the analysis of the new kinds of data we can record from interconnected elements, will be a huge driving force in how our world changes:

Internet of Things (IoT)

Lastly, before I forget and move on, a key phrase voiced several times (although I cannot remember the exact speaker, so apologies for that, it was probably David Chappell) was to reset your defaults. Standard client/server architecture was discussed, and for those of us that are part of long running businesses this is what we are exclusively, or at least partially, dealing with on a daily basis still. However, the change to the use of mobile devices, tablets, etc, as clients and the cloud as the underpinning location for the services these clients communicate with is becoming the norm. For start-ups today, mobile first development and the cloud (Azure or Amazon Web Services (AWS)) are probably the initial go-to.

For some of us (speaking from a personal standpoint only), a major factor in our success as developers could simply be determined by understanding the cloud and getting the necessary experience to make the transition (for those who are not actively taking part in this world of course).

So, now we have the IoT, let’s talk security…

3) Graham Cluley. Security Analyst, grahamcluley.com.

Graham delivered a funny and insightful talk surrounding everyones’, ‘Oh my God, the horror, please kill me’ subject, the wonderful world of security.

In a nutshell, he argues (and certainly proves his point as you’ll read next) that the IoT will bring wonders to our world, but not without issues. We now have a scenario whereby a breadth of new devices have suddenly become internet connected. However, are the driving forces behind these changes the people who are used to dealing with the murky world of malware, viruses and hacking attempts (such as OS developers)? Probably not, is the initial answer. This is, of course, just a cultural divide between those used to trans-versing the security world and protecting devices from such attacks, and those tasked with bringing new devices to the interconnected world.

The hacking of self-driving cars (big topic it would seem) was discussed:

Fiat Chrysler Recalls

Also, the potential of hacking pacemakers was covered (bluetooth/wifi enabled), famously featured in the TV series Homeland and which actually lead to Vice President Dick Cheney’s cardiologist disabling the wireless functionality of his device:

Pacemaker Hacking
Could Pacemakers Be Hacked?

Although funny, the talk did indeed bring up a very serious issue. The ramifications could be catastrophic, depending on the types of devices that ultimately end up being exposed to the masses via the web. Essentially, as the IoT age develops, extra care must be taken to ensure that security is right on up there, in the hierarchy of priorities, when developing software for these devices.

4) Chris Bishop. Scientist and Lab Director, Microsoft Research.

The last talk I would personally like to discuss briefly was by Chris Bishop; there were a few great nuggets here that are well worth covering.

The idea of Machine Learning (not a topic I was overly familiar with for starters), Neural Networks and Pattern Recognition laid the foundation for a talk looking at the possibility of producing machines with human-level, or even super-human, intelligence.

The Microsoft Kinect was used to demonstrate hand-tracking software that, I have to admit, had an incredible amount of fidelity in recognising hand positions and shapes.

Lastly, a facial recognition demonstration that could estimate, with good accuracy, the emotional state of a person was kicked off for us all to see. Very, very impressive. There was most certainly an underlying feeling here (and as much was hinted at) that his kind of technology has many hurdles to jump. For instance, building something that can consume an image and accurately describe what is in that image is still a flaky concept, at best (and the difficulties of producing something capable of this are relatively vast).

Still, a greatly enjoyable talk! A book was touted, and I believe (please don’t shout at me if I’m wrong) this is the one:

Pattern Recognition and Machine Learning

After the morning Key Notes, a series of smaller talks and break-out sessions were available to us. Here’s how I spent my time…

Unity3D Grok Talk

Josh Taylor. Unity Technologies.

It’s my sincere hope that, on discovering this, my employer won’t decide to sack me! This was over lunch and was a self-indulgent decision I’m afraid! You’ll know from some of my historical posts that I have a keen interest in Unity3D (and have spent time making the odd modest prototype game here and there), and I was interested to see how Unity 5 was progressing, especially as a greater cohesive experience with Visual Studio had been promised.

In this short, 20 minute talk, we experienced how Visual Studio (finally) integrates nicely into the Unity3D content creation pipeline. Unity3D now defaults to using Visual Studio as the editor of choice, with Monodevelop being pushed aside. Apologies to anyone who likes Monodevelop, but I’ve never been able to get behind it. With wacky intellisense and with what I can only describe as a crash-tastic experience in past use, I haven’t seen anything yet to sway me from using Visual Studio. In fact, it was demonstrated that you can even use Visual Studio Code if you wish and, as it’s cross-platform, even Mac and Linux users can switch to this if they wish. More reasons to leave Monodevelop in the dust? It’s not for me to say really, go ahead and do what you’ve got to do at the end of the day!

In order to debug Unity projects in Visual Studio in the past a paid for plugin was required. This particular plugin has been purchased by Microsoft and is now available to all. Being able to easily debug code doesn’t sound like much, but trust me it’s like having a basic human right re-established – such good news!!!

The new licensing model was also commented on, a massive plus for everyone. The previous Free/Pro divide is no more; now everyone gets access to the lions share of the core features. You only need to start spending money as you make it (fair for Unity to ask for a piece of the pie if you start rolling in profit/expanding a team to meet the new demand). For me, this means I actually get to use the Unity Pro water effects, hoorah ;-).

Following this, I spent a bit of time last weekend watching the Unite 2015 Key Notes, discussing 2D game development enhancements, cloud based builds and Oculus support. Well worth a look if and when time allows:

Unite 2015 Key Notes

Plus, if Oculus technology interests you, then it’s definitely worth watching John Carmacks (formerly of ID Software, the mind behind Wolfenstein and Doom) Key Note from the Oculus Connect 2 event:

John Carmack Oculus Keynote

Very exciting times ahead for Unity3D I believe. Self-indulgence over, moving forward then…

Journey to the Intelligent Cloud

Corey Sanders. Director of Program Management, Azure.

Following the Unity3D talk, I made my way back to the ICC Auditorium (I missed a small section of this particular talk, but caught the bulk of it) to catch up on some basic examples of how the new Azure Portal can be used. This took the form of a brief overview of what’s available via the portal, essentially a primer session.

In my recent, personal work with Azure I’ve used the publishing capability within Visual Studio to great affect; it was very transparent and seamless to use by all accounts. A sample was provided within this particular session which demonstrated live coding changes, made in GitHub, being published back to a site hosted on Azure.

Going into a tangent….

Very much a personal opinion here, but I did find (and I wasn’t the only one) that a good portion of the content I wanted to see was a) on at the same time (the 1:15pm slot) and b) was during the core lunch period where everyone was ravenous, I’m a ‘hanger’ sufferer I’m afraid. C# with Mads Torgerson, ASP.NET 5, Nano Servers and Windows 10 (UWP) sessions all occupied this slot, which drove me a little nuts :-(. This felt like a scheduling issue if I’m honest. I’d be interested to hear from anyone who did (or didn’t) feel the same.

I was so disappointed to miss Mads Torgerson, I very much enjoyed the recent C# language features overview and would have loved to have made this breakout session! I did walk past him later in the day, and I hope he never reads this, but he seemed ridiculously tall (perhaps Godly C# skills made him appear several inches taller, who knows!). It doesn’t help that I’m on the shorter side either, I just wanted to be 5′ 11″, that’s all I ever wanted (break out the rack, I need to get stretching!). I should have said hello, but wimped out!

F# Language Breakout Session

Don Syme. Principal Researcher, Microsoft Research.

This was easily the part of the event that resonated the most with me, and strongly influenced the foray into F# that I undertook recently. Don Syme, the designer and architect of the F# language, took us through a quality primer of the syntax and how F# can be used (and scaled) for the cloud.

All of this aside, the most impressive part of the talk was a live demonstration of F# Type Providers. Again, this is fully covered in my previous post so I’ll just direct you to that, which in turn will aid me in cutting down what is now becoming a gargantuan post. In summary, the ability to draw information directly from web pages, rip data straight from files and databases, and combine and aggregate it all together using minimal code produces a terse, easy to understand and pretty darn good experience in my book. Even the code behind producing visual feedback, in the form of the charting API, is succinct; the bar really isn’t set too high for new starters to get involved.

If you decide to give anything a go in the near future, I would give F# the nod (followed closely, just a hair’s breadth away, by jQuery in my opinion). Certainly check it out if you get the chance.

Final Key Note

Professor Brian Cox. Physicist.
Krysta Svore. Senior Researcher, Microsoft Research.

The day proceeded in fast forward and, before we’d really had the chance to gather our thoughts, we were sitting in the main auditorium again faced by Professor Brian Cox, Krysta Svore and a menagerie of confused attendees staring at mathematical formulas outlining quantum theory.

Into the wonderful world of quantum computers we dance, and in my case, dragging my brain along from somewhere back yonder in a desperate attempt to keep up. Thankfully, I’m an avid TED talk fanatic and had, in the run up to the event, brushed up on a few quantum theory and quantum mechanics videos; lucky I did really. The content was dense but, for the most part, well put together and outlined the amazing (and potentially frightening) world of possibilities that quantum computers could unlock for us all.

Professor Brian Cox cruised through the theories we’d need to be intimate with in order to understand the onslaught of oncoming content surrounding quantum computers. In essence, a traditional ‘bit’, has a defined state (like a switch), on or off. However, and this is the simple essence of what they were trying to get to, traditional bits are reaching limitations that will prevent us from solving more complex problems, in a timely manner (you’ll see what I mean in a second). Therefore, qubits, born from quantum theory, are the answer.

Now, I’m not going to insult your intelligence and go into too much detail on a subject that I am clearly not an expert in. So, just in ‘laymen’s bullet points’, here is what I took from all that was said and done across the Key Note:

  • With bits, you are dealing with entities that can have a fixed state (0 or 1). A deterministic system if you will, that has limitations in its problem crunching power.
  • Qubits, however, take us into the realm of a probabilistic system. The qubit can be in a superposition of all of the allowed states, not just 0 or 1.
  • Therefore, the problem crunching powers of qubits are exponential in nature, but the probabilistic nature makes measuring them (and interactions involving them) difficult to get to grips with.

So is it worth fighting through the technical problems in order to harness qubits? What kind of gains are we talking about here?

Krystra Svore outlined an example that displayed that it would take roughly one billion years for a current super computer to crack (more complex than standard) RSA encryption. How long would it take a quantum computer you may ask? Significantly faster is the answer, estimated at around one hundred seconds in fact. This clearly defines for us the amazing problems we’ll be able to solve, whilst simultaneously illustrating the dangerous times that lay ahead from a security standpoint. Let’s just hope cryptography keeps up (I can see a few sniffs to suggest things are in the pipeline, so I will keep an eye out for news as it pops up).

So you want a quantum computer I hear you say! Hmmm, I wouldn’t put it on the Christmas list anytime soon. Due to the fact current quantum computers need to be super cooled (and from the pictures we got to see, didn’t look like you could hike around with it!), we’re not likely to get our hands directly on them in the near future.

Can you get your mitts on quantum simulators today? Apparently yes in the answer (completed untested links, just for you to peruse on your own, good luck):

QC Simulators
Project Liquid

Taking nothing away from the Key Note though, it was a concrete finish to an excellent event. Would I go again? You bet! Should we get the train next time instead of driving? Taking into account the mountains of free beer and wine on offer, of course! To finish up, before summarising the Expo itself, if you haven’t been and get the opportunity (in fact, actively seek the opportunity, enough said) then definitely book this in your calendar, thoroughly brilliant.

Expo

Very, very quickly, as I am acutely aware that your ability to focus on this post (if not already) must have completely diminished by this point, I wanted to describe what the Expo itself had to offer. If you’re still reading, give yourself a pat on the back!

One of the more compelling items we saw was the use of the new Lumia phone as a (kind of) desktop replacement attempt. Let’s get one thing straight, you’re not going to be doing hardcore software development using Visual Studio or any other intensive task on this device anytime soon. However, there was certainly enough evidence to suggest that basic productivity tasks would be possible using a mobile phone as a back bone to facilitate this.

The Lumia can be hooked up to a dock, akin to the Surface Pro 4 (the docks are subtly different apparently, so are not cross-compatible), and that allows it to be tied to a display device. You can also get a folding mouse and keyboard, for a very lightweight, on-the-go experience. Interesting certainly, but there is a definite horse-power issue that will prevent anyone working on anything remotely intensive from getting on board. Anyway, for those interested the link below will get you started:

Lumia Docking Station

I saw a few Surface Pros, and wondered whether we could potentially smuggle a few out of the Expo! Only kidding, no need to call the Police (or for anyone I work with thinking I am some kind of master criminal in the making) :-).

An Oculus demonstration booth was on the Expo floor, and displays were hooked up to show what the participants were experiencing. It was noted that a few of the people using the Oculus seemed to miss the point a bit, and kept their head completely still as they were transported through the experience. Once the heads started moving (to actually take in the world) you could visibly see people getting incredibly immersed. Alas, the queues were pretty darn large every time I made my way past, so I didn’t get a chance to experience it first-hand. One for the future.

There was also a programmable cocktail maker, an IoT masterpiece I think you’ll agree. A perfect union of hardware, software and alcohol, a visionary piece illustrating the future has arrived!

The next time an event like this comes around I will endeavour to get a post up in a timely fashion (which will vastly improve the content I hope).

Thanks for reading and a high five from me if you made it this far. Back to coding in the upcoming post I promise, until the next time, cheers from me (and would you believe it, it’s now Tuesday)!

Code Project: Epic Quest! Unit Testing

A staple of TDD (Test Driven Development), although not quite how I’ve implemented it here (as I already have too much code hanging around prior to writing any tests), the humble Unit Test is something I’ve wanted to dedicate a little bit of time to for a while; so here it is.

Inclusion of Unit Tests in a project, from my own personal experience, is something that rarely takes as much precedence as it should. Sometimes they aren’t included at all; other times they are incredibly slim-line to the point of only scratching the surface – This feels like a bit of a shame. I won’t get into my own feelings too much on the matter beyond saying that I’m of the belief that they certainly have a place and add value.

To this end I’ve gone ahead and created a few simple Unit Tests covering some of the foundation classes of the EpicQuest code base as follows:

  • DungeonTests test class – Covering the EpicDungeon class.
  • RoomTests test class – Covering the Room class (and creation of room content).
  • CombatTests test class – Covering a very basic combat scenario which will be fleshed out further in due course.
  • WeaponTests test class – Covering a basic Weapon object creation scenario.
  • ArmourTests test class – Covering a basic Armour object creation scenario.
  • TreasureTests test class – Currently unimplemented (the concept of ‘treasure’ still needs consideration).

As a bit of a visual aid, here’s a snippet from Visual Studio showing the Unit Test project structure and a Class Diagram illustrating the setup:

Unit Test Project Configuration.

Unit Test Project Configuration.

Unit Test Class Diagram.

Unit Test Class Diagram.

One of key things to notice here is that the EpicQuest project has been referenced in order for us to get hold of the types we need to test. A key benefit I’ve gained since inclusion of the tests is that I’ve been forced to compartmentalise my code further to make testing distinct pieces of functionality possible. Some code has also been re-homed fully (i.e. whereby I had methods sitting within classes whereby it didn’t actually make sense for them to live there from a code structure point of view and, therefore, a Unit Testing perspective). A few difficult questions have also been posed in relation to the accessibility of certain members during creation of the test code; issues I wouldn’t have had to consider without travelling down this path. All in all, pretty positive outcomes so far.

So what’s the anatomy of a basic Unit Test, how is it triggered and how can we glean valuable information from the results?

Within Visual Studio at least, the first requirement is a Unit Test project, as the previous screen shot illustrates (EpicQuest.Tests). From here, a great place to begin is by adding a Basic Unit Test template class:

Add New Unit Test Dialog.

Add New Unit Test Dialog.

This will provide the following stub class adorned with the TestClass attribute with a single test method, suitably adorned with the TestMethod attribute. These attributes identify the object/members as test entities for exposure in the Test Explorer window which we’ll see later.

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EpicQuest.Tests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {

        }
    }
}

Test methods, regardless of the functionality being tested, will normally follow a structure that looks like this:

/// <summary>
/// Test method that ensures that the EpicDungeon
/// instance level RemoveDungeonRoom method is working
/// as expected.
/// </summary>
[TestMethod]
public void EpicDungeonRoomRemovalTest()
{
	//Phase one - Arrange (essentially complete any configuration to actually perform the test - In this case 
	//create a dungeon and add a known room instance to it, taking note of the room count prior to removal of the room)
	Room roomToRemove = new Room();

	EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration()); //Opted to create a random room configuration to strengthen the test scenario
	dungeon.AddDungeonRoom(roomToRemove);

	int previousRoomCount = dungeon.DungeonRooms.Count;

	//Phase two - Test/Act (remove the room)
	dungeon.RemoveDungeonRoom(roomToRemove);

	//Phase three - Test/Assert (run tests to ensure that the correct end result, after removal of the room, has been achieved)
	Assert.IsFalse(dungeon.DungeonRooms.Contains(roomToRemove), "EpicDungeonRoomRemovalTest (DungeonTests) failed. Test room roomToRemove still found in dungeon.DungeonRooms.");
	Assert.AreEqual<int>((previousRoomCount - 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomRemovalTest (DungeonTests) failed. Room count (dungeon.DungeonRooms.Count) has an unexpected value.");
}

The structure factors in three distinct phases:

  • Arrange. This stage involves configuring any objects and setup as required by the test you plan to perform.
  • Act. Post configuration you actually need to call the functionality you wish to test.
  • Test. Lastly, assertions are run in order to see if the code under test worked as expected.

The MSDN documentation surrounding running Unit Tests through Visual Studio is well written and quite comprehensive. Further information can be found by accessing the following link:

Walkthrough: Creating and Running Unit Tests for Managed Code.

In order to see if the code under test is working as expected the static members of the Assert class come into play (of which there are a few, see the Assert Class MSDN documentation for a full listing).

In the above listing, I’ve opted to firstly use Assert.IsFalse to make a logical test to see if the Room instance I am expecting to be removed by the RemoveDungeonRoom method has actually be taken out of the EpicDungeon instance. Lastly, I’ve called on a generic version (which is awesome!) of Assert.AreEqual to make a secondary check to ensure the remaining count of Room objects in the EpicDungeon is as I would expect, following the previous Room object removal. The Assert static members allow for value types and objects to be passed in for inspection and, in the instances I’ve seen, for a string message to be specified; to be shown if the test fails. You’ll see other examples of Assert methods in action, such as the Assert.IsInstanceOfType, in later examples.

The Assert class and the members on offer are fairly intuitive right off the bat; you’ll probably only need a few minor peeks at the documentation to get underway if you’re familiar with C#.

NOTE: Some of the xml comments and variable names, etc have been tweaked throughout the code at varying times meaning some of the screen shots you see may fall out of line slightly with the code samples. If something is functionally different then I have gone back and altered the screen shots to align with the code.

As there’s not tonnes of code as of yet in the tests I’ve written here’s the full listing of what I have so far:

Dungeon Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models.Dungeon;
using System.Collections.Generic;

namespace EpicQuest.Tests.Dungeon
{
    /// <summary>
    /// Dungeon entity specific Unit Tests
    /// for the Epic Quest game.
    /// </summary>
    [TestClass]
    public class DungeonTests
    {
        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object (static) level GenerateRandomRoomConfiguration method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomGenerationTest()
        {
            //Generate a random room amount seed and get an actual number of rooms as generated by EpicDungeon.GenerateRandomRoomConfiguration. Obtain the difference
            int roomNumberSeed = new Random().Next(3, 10), roomNumber = EpicDungeon.GenerateRandomRoomConfiguration(roomNumberSeed).Count,
                difference = (roomNumber - roomNumberSeed);

            //Extra debugging
            System.Diagnostics.Trace.WriteLine(string.Format("EpicDungeonRoomGenerationTest roomNumberSeed {0}; roomNumber: {1}; difference: {2}.", roomNumberSeed, roomNumber, difference));

            //The test is successful if the difference is between 0 and 2 (these are the rules that I've specified)
            if (difference < 0 || difference > 2)
            {
                Assert.Fail("EpicDungeonRoomGenerationTest (DungeonTests) failed. Room difference between seed number and actual, generated number out of range. Difference: {0}", difference);
            }
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object constructor is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonCreationTestOne()
        {
            //Get a predefined list of rooms and pass them into an EpicDungeon object constructor
            List<Room> testRooms = new List<Room>();
            testRooms.AddRange(new Room[3] { new Room(), new Room(), new Room() });

            EpicDungeon dungeon = new EpicDungeon(testRooms);

            //Test to ensure that dungeon.DungeonRooms is based on our testRooms collection
            Assert.AreEqual(testRooms, dungeon.DungeonRooms, "EpicDungeonCreationTestOne (DungeonTests) failed. Test room array is not represented by dungeon.DungeonRooms (different refs).");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// object constructor is working
        /// as expected (very basic test).
        /// </summary>
        [TestMethod]
        public void EpicDungeonCreationTestTwoBasic()
        {
            //Test the constructor that takes an array of room objects
            Room[] roomArray = new Room[] { new Room(), new Room(), new Room() };

            EpicDungeon dungeon = new EpicDungeon(roomArray);

            //The room array is converted into a list within the Room class - Do a simple count check as a follow up to EpicDungeonCreationTestOne
            Assert.AreEqual<int>(roomArray.Length, dungeon.DungeonRooms.Count, "EpicDungeonCreationTestTwoBasic (DungeonTests) failed. Test array room count differs to dungeon.DungeonRooms.Count.");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// instance level AddDungeonRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomAdditionTest()
        {
            EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration());
            
            int previousRoomCount = dungeon.DungeonRooms.Count;

            Room roomToAdd = new Room();
            dungeon.AddDungeonRoom(roomToAdd);

            Assert.IsTrue(dungeon.DungeonRooms.Contains(roomToAdd), "EpicDungeonRoomAdditionTest (DungeonTests) failed. Test Room roomToAdd not found in dungeon.DungeonRooms.");
            Assert.AreEqual<int>((previousRoomCount + 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomAdditionTest (DungeonTests) failed. dungeon.DungeonRooms.Count has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the EpicDungeon
        /// instance level RemoveDungeonRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void EpicDungeonRoomRemovalTest()
        {
            //Phase one - Arrange (essentially complete any configuration to actually perform the test - In this case 
            //create a dungeon and add a known room instance to it, taking note of the room count prior to removal of the room)
            Room roomToRemove = new Room();

            EpicDungeon dungeon = new EpicDungeon(EpicDungeon.GenerateRandomRoomConfiguration()); //Opted to create a random room configuration to strengthen the test scenario
            dungeon.AddDungeonRoom(roomToRemove);

            int previousRoomCount = dungeon.DungeonRooms.Count;

            //Phase two - Test/Act (remove the room)
            dungeon.RemoveDungeonRoom(roomToRemove);

            //Phase three - Test/Assert (run tests to ensure that the correct end result, after removal of the room, has been achieved)
            Assert.IsFalse(dungeon.DungeonRooms.Contains(roomToRemove), "EpicDungeonRoomRemovalTest (DungeonTests) failed. Test room roomToRemove still found in dungeon.DungeonRooms.");
            Assert.AreEqual<int>((previousRoomCount - 1), dungeon.DungeonRooms.Count, "EpicDungeonRoomRemovalTest (DungeonTests) failed. Room count (dungeon.DungeonRooms.Count) has an unexpected value.");
        }
    }
}

Room Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models.Dungeon;
using EpicQuest.Interfaces;
using EpicQuest.Models;
using System.Collections.Generic;
using System.Linq;
using EpicQuest.Models.Items;

namespace EpicQuest.Tests.Rooms
{
    /// <summary>
    /// Room entity specific Unit Tests
    /// for the Epic Quest game.
    /// </summary>
    [TestClass]
    public class RoomTests
    {
        /// <summary>
        /// Test method that ensures that the Room
        /// object (static) level GenerateRandomRoomContent method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void RoomContentGenerationTest()
        {
            //Test that Room.GenerateRandomRoomContent is producing content based on the seed provided
            int roomContentSeed = 2, roomContentNumber = Room.GenerateRandomRoomContent(roomContentSeed).Count, difference = (roomContentNumber - roomContentSeed);

            //Produce some extra debugging output
            System.Diagnostics.Trace.WriteLine(string.Format("RoomContentGenerationTest (RoomTests) roomContentSeed {0}; roomContentNumber: {1}; difference: {2}.", 
                roomContentSeed, roomContentNumber, difference));

            //Ensure the room content number produced is within the expected range (based on the seed)
            if (difference < 0 || difference > 2)
            {
                Assert.Fail("RoomContentGenerationTest (RoomTests) failed. Room content difference between seed number and actual, generated number out of range. Difference: {0}", difference);
            }
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// object constructor is working as expected.
        /// </summary>
        [TestMethod]
        public void RoomContentCreationTestOne()
        {
            //Add some content to a list of type IRoomContent and pass this to the Room constructor
            List<IRoomContent> roomContentList = new List<IRoomContent>();
            roomContentList.AddRange(new IRoomContent[] { new Skeleton(), new Skeleton(), new Kobold(), new Kobold() });

            Room room = new Room(roomContentList);

            //Check to ensure that the content in the newly created Room object is based on the information passed to the constructor
            Assert.AreEqual(roomContentList, room.RoomFeatures, "RoomContentCreationTestOne (RoomTests) failed. Test room content array is not represented by room.RoomFeatures (different refs).");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// object constructor is working
        /// as expected (very basic test).
        /// </summary>
        [TestMethod]
        public void RoomContentCreationTestTwoBasic()
        {
            //Chuck an array of IRoomContent to the Room constructor
            IRoomContent[] roomContent = new IRoomContent[] { new Kobold(), new Skeleton() };

            Room room = new Room(roomContent);

            //Again, similar to the Dungeon test of a similiar nature, do a simple count of content items and ensure it's correct
            Assert.AreEqual<int>(roomContent.Length, room.RoomFeatures.Count, "RoomContentCreationTestTwoBasic (RoomTests) failed. Test array room Content count differs to room.RoomFeatures.Count.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level AddContentToRoom method is working
        /// as expected.
        /// </summary>
        [TestMethod]
        public void RoomContentAdditionTest()
        {
            //Create a new room and added random content (as base content). Take a note of the items of content currently in the room
            Room room = new Room(Room.GenerateRandomRoomContent());

            int currentRoomFeatureCount = room.RoomFeatures.Count;

            //Add a new monster to the room
            IRoomContent kobold = new Kobold(); 
            room.AddContentToRoom(kobold);

            //Ensure the monster has correctly been added to the room
            Assert.IsTrue(room.RoomFeatures.Contains(kobold), "RoomContentAdditionTest (RoomTests) failed. Test room feature kobold not found in room.RoomFeatures.");
            Assert.AreEqual<int>((currentRoomFeatureCount + 1), room.RoomFeatures.Count, "RoomContentAdditionTest (RoomTests) failed. Room feature count (room.RoomFeatures.Count) has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level RemoveContentFromRoom method is working
        /// as expected. 
        /// </summary>
        [TestMethod]
        public void RoomContentRemovalTest()
        {
            //Add a new skeleton to a new room (with some other random content to strengthen the test example). Take a note of the count of room features prior to calling RemoveContentFromRoom
            IRoomContent skeleton = new Skeleton();

            Room room = new Room(Room.GenerateRandomRoomContent());
            room.AddContentToRoom(skeleton);

            int previousRoomFeatureCount = room.RoomFeatures.Count;

            //Run the test, remove the skeleton from the room
            room.RemoveContentFromRoom(skeleton);

            //Ensure that the known skeleton instance has been removed from the room (and that the count of remaining pieces of room content is correct)
            Assert.IsFalse(room.RoomFeatures.Contains(skeleton), "RoomContentRemovalTest (RoomTests) failed. Test room feature skeleton still found in room.RoomFeatures.");
            Assert.AreEqual<int>((previousRoomFeatureCount - 1), room.RoomFeatures.Count, "RoomContentRemovalTest (RoomTests) failed. Room feature count (room.RoomFeatures.Count) has an unexpected value.");
        }

        /// <summary>
        /// Test method that ensures that the Room
        /// instance level RoomClearedOfMonstersTest method 
        /// is working as expected. 
        /// </summary>
        [TestMethod]
        public void RoomClearedOfMonstersTest()
        {
            //Configure some fixed, known pieces of room content and add it to a new room
            IRoomContent skeleton = new Skeleton(), kobold = new Kobold(), treasureItem = new TreasureItem();

            Room room = new Room(skeleton, kobold, treasureItem);

            //Test that the room contains monsters at this point
            Assert.IsFalse(room.RoomClearedOfMonsters, "RoomClearedOfMonstersTest (RoomTests) failed. No monsters detected in the room.");

            //Remove just the fixed, known monsters - Leaving the treasure item intact
            room.RemoveContentFromRoom(skeleton, kobold);

            //Final round of tests. Ensure all monsters have been removed and that only one piece of content remains (the treasure item)
            Assert.IsTrue(room.RoomClearedOfMonsters, "RoomClearedOfMonstersTest (RoomTests) failed. Monsters detected in the room.");
            Assert.AreEqual<int>(room.RoomFeatures.Count, 1, "RoomClearedOfMonstersTest (RoomTests) failed. An unexpected room feature count was detected (room.RoomFeatures.Count).");
            Assert.IsInstanceOfType(room.RoomFeatures.First(), typeof(TreasureItem), "RoomClearedOfMonstersTest (RoomTests) failed. Remaining room feature is not of the expected type. Type detected: {0}",
                room.RoomFeatures.First().GetType().Name);
        }
    }
}
 

Combat Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Interfaces;
using EpicQuest.Models;
using EpicQuest.Utility;

namespace EpicQuest.Tests.Combat
{
    /// <summary>
    /// Combat based entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class CombatTests
    {
        /// <summary>
        /// Test method that ensures that the StaticGameActions
        /// object (static) level HandleCombat method 
        /// is working as expected.  
        /// </summary>
        [TestMethod]
        public void CombatTestOne()
        {
            //Setup the aggressor and defender ICombatant supporting objects
            ICombatant aggressor = new Brawler(), defender = new Skeleton();

            //Handle the combat. Take the defenders previous health and obtain the damage done from StaticGameActions.HandleCombat call
            int defenderPreviousHealth = defender.Health, damageDone = StaticGameActions.HandleCombat(aggressor, defender);

            //Test that the defenders health has been subtracted properly (TODO - Handle dead defenders and make sure this works if a defender is overkilled)
            Assert.AreEqual<int>((defenderPreviousHealth - damageDone), defender.Health);
        }
    }
}

Weapon Tests

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models;
using EpicQuest.Models.Dice;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// Weapon entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class WeaponTests
    {
        /// <summary>
        /// Test method that ensures that the Weapon
        /// object constructor is working as expected.
        /// </summary>
        [TestMethod]
        public void WeaponTestBasic()
        {
            //Create a new Weapon
            Weapon weaponItem = new Weapon(GameEnums.HeroOffensiveItemType.LegendaryBastardSword);

            //Ensure that the die associated with the object are as expected
            Assert.IsTrue((weaponItem.Dice.Where(die => die is BlueDie).Count() == 1 && weaponItem.Dice.Where(die => die is RedDie).Count() == 1
                && weaponItem.Dice.Count == 2), "WeaponTestBasic (WeaponTests) failed. Incorrect type of die/die count detected.");
        }
    }
}

Armour Tests

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using EpicQuest.Models;
using EpicQuest.Models.Dice;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// Armour entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class ArmourTests
    {
        /// <summary>
        /// Test method that ensures that the Armour
        /// object constructor is working as expected. 
        /// </summary>
        [TestMethod]
        public void ArmourTestBasic()
        {
            //Die property is publically accessible??? TO CHECK; Either way create a new, known armour type
            Armour armourPiece = new Armour() { DefensiveItemType = GameEnums.HeroDefensiveItemType.LeatherChestPiece, DefensiveItemTypeSlot = GameEnums.HeroDefensiveItemSlotType.Chest };

            //Ensure the die for this piece of armour are correct
            Assert.IsTrue((armourPiece.Dice.Where(die => die is GreenDie).Count() == 2 && armourPiece.Dice.Count == 2), "ArmourTestBasic (ArmourTests) failed. Incorrect type of die/die count detected.");
        }
    }
}

Treasure Item Tests

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EpicQuest.Tests.Items
{
    /// <summary>
    /// TreasureItem entity specific Unit Tests
    /// for the Epic Quest game. 
    /// </summary>
    [TestClass]
    public class TreasureTests
    {
        /// <summary>
        /// TO DO.
        /// </summary>
        [TestMethod]
        public void TreasureTestBasic()
        {
            //TO DO, Treasure not fully implemented
        }     
    }
}

So how do you run these tests? It’s pretty damn easy – You just make use of the Test Explorer window in Visual Studio. A massive plus for me is that Unit Test projects, class templates and the Test Explorer are all available with the express edition of Visual Studio; big win! Either type ‘Test Explorer’ into the Quick Launch search bar in the top right hand corner of the Visual Studio UI or navigate to TEST > Windows > Test Explorer on the Visual Studio menu bar:

Accessing the Test Explorer Window.

Accessing the Test Explorer Window.

The Test Explorer window will automatically detect projects in scope that contain members marked with the TestMethod attribute (the classes of course need the TestClass attribute to). The Test Explorer window allows you to run all tests, previously failed tests, individual tests or a specific playlist of tests. The Test Explorer will appear like this in its initial state:

The Test Explorer Window (Initial State).

The Test Explorer Window (Initial State).

Here’s what the Test Explorer looks like after a successful test run. For starters, you get a run-down of how long each test took to run along with a link taking you to the location of the test method in the source code. The image shows an example of some additional debugging output:

Test Additional Output Example.

Test Additional Output Example.

In order to get hold of extra debugging output you can use the Trace.WriteLine static method as indicated in the following code snippet:

/// <summary>
/// Test method that ensures that the Room
/// object (static) level GenerateRandomRoomContent method 
/// is working as expected. 
/// </summary>
[TestMethod]
public void RoomContentGenerationTest()
{
	//Test that Room.GenerateRandomRoomContent is producing content based on the seed provided
	int roomContentSeed = 2, roomContentNumber = Room.GenerateRandomRoomContent(roomContentSeed).Count, difference = (roomContentNumber - roomContentSeed);

	//Produce some extra debugging output
	System.Diagnostics.Trace.WriteLine(string.Format("RoomContentGenerationTest (RoomTests) roomContentSeed {0}; roomContentNumber: {1}; difference: {2}.", 
		roomContentSeed, roomContentNumber, difference));

	//Ensure the room content number produced is within the expected range (based on the seed)
	if (difference < 0 || difference > 2)
	{
		Assert.Fail("RoomContentGenerationTest (RoomTests) failed. Room content difference between seed number and actual, generated number out of range. Difference: {0}", difference);
	}
}

Let’s rig a failure. I’ve simulated an underlying change to the code base that is going to trip up some of our Unit Tests. We receive the following output in the Test Explorer window after running all tests:

Test Explorer Failure Example.

Test Explorer Failure Example.

Two of our test methods fell over and a quick look at the output suggests that the Room class AddContentToRoom instance level method appears to be functioning unexpectedly (the room feature is not being added to the room). This is the state of the source code as it currently stands:

/// <summary>
/// Public method that allows a piece of content/multiple pieces
/// of content to be added to this particular Room.
/// </summary>
/// <param name="features">A single piece of content/array of content pieces to add to the Room.</param>
public void AddContentToRoom(params IRoomContent[] features)
{
	if (features != null && features.Count() > 0)
	{
		foreach (IRoomContent item in features)
		{
			roomFeatures.Add(new Kobold());
		}
	}
}

Looking through this code we can see that the method accepts an array of type IRoomContent named features. After a null check and a count to ensure the array is not empty we enter a simple foreach statement to add the IRoomContent supporting objects to the roomFeatures data field. The roomFeatures.Add statement is however simply adding a new Kobold object to the roomFeatures field list and ignoring the items in the array being passed in. Bad bug; slap on the wrists for introducing that!!! Let’s alter it to this:

/// <summary>
/// Public method that allows a piece of content/multiple pieces
/// of content to be added to this particular Room.
/// </summary>
/// <param name="features">A single piece of content/array of content pieces to add to the Room.</param>
public void AddContentToRoom(params IRoomContent[] features)
{
	if (features != null && features.Count() > 0)
	{
		foreach (IRoomContent item in features)
		{
			roomFeatures.Add(item);
		}
	}
}

Now we are using the foreach item variable correctly and adding this to the roomFeatures collection.

Back in the Test Explorer we are now able to explicitly re-run just the failed tests as shown:

Failed Tests in the Test Explorer Window.

Failed Tests in the Test Explorer Window.

The failed tests run up and we can see that these tests run without error, happy days!

Passed Tests in the Test Explorer Window.

Passed Tests in the Test Explorer Window.

As for Unit Testing, that’s about it for now. The only other small alteration I’ve made to the existing code base at this time is removal of the HandleCombat method from the GameManager class (to aid in Unit Testing/general code structure). This now resides in a new static class called StaticGameActions.

using System;
using EpicQuest.Interfaces;

namespace EpicQuest.Utility
{
    /// <summary>
    /// Public static utility class for the
    /// EpicDungeon game.
    /// </summary>
    public static class StaticGameActions
    {
        /// <summary>
        /// Private method that shows, in principle, how combat
        /// can be handled between two ICombatant based objects.
        /// </summary>
        /// <param name="aggressor">The instigator of the combat represented by a class implementing ICombatant.</param>
        /// <param name="target">The defender represented by a class implementing ICombatant.</param>
        public static int HandleCombat(ICombatant aggressor, ICombatant target)
        {
            //Prepare values representing the dice (random) and values to keep a tally of the offence, defence and difference totals
            int damageTotal = 0, defenceTotal = 0, difference = 0;
            Random rollDie = new Random();

            //Roll attack dice for the aggressor and get a damage total
            aggressor.AttackDice.ForEach(attackDie =>
            {
                damageTotal += (byte)attackDie.DieFaces[rollDie.Next(0, 5)];
            });

            //Roll defence dice for the defencder and get a defence total
            target.DefenceDice.ForEach(defenceDie =>
            {
                defenceTotal += (byte)defenceDie.DieFaces[rollDie.Next(0, 5)];
            });

            //Determine the difference between the damage/defence totals and deal damage to the target if required
            difference = damageTotal - defenceTotal;

            if (difference > 0)
            {
                target.Health -= difference;
            }

            return difference < 0 ? 0 : difference; //Only return non-negative results
        }
    }
}

That’s about it folks for the time being. Unit Testing is an interesting topic that requires some further investigation so I want to bring you another small piece looking at the TestInitialize and TestCleanup attributes and how more in depth tests can be constructed.

In other news, I’ve upgraded my blog account and got a new domain which I’m sure you’ll notice (splashed some cash as promised!). I’ll shortly be, as stated, making a few styling tweaks to the theme in addition to adding a dedicated Facebook page and Twitter feed, so watch this space.

Ciao for now.

Back Online: Normal Service Resumed

I’m back from my hiatus which encompassed getting married, eating far too much food and drinking wine and beer on the wonderful Adriatic coast. It’s time to get back to some serious coding and perhaps reconsider the longer term plans for this blog.

To start us off, I’ve been contemplating pushing a little money into this to sharpen up the experience a little and will most likely give the blog some dedicated presence on Facebook/Twitter. Why do it by halves; I’ll go balls deep and hope for the best!

There are numerous items that I previously wanted to, and still plan on, covering but other nuggets of technology have caught my eye in the interim. In addition to just writing code, I would also like to reflect on my own methodologies for learning subject matter and trying to improve comprehension as I progress on this journey. Anything I do to this end will get ‘air time’ within this blog and I’ll you all know if I come across anything that works particularly well (or falls flat on its face!) as and when it happens.

Lastly, although not strictly ‘code’ based, my wife (weird to say that!) plans on starting her own business this year so it provides us both with an opportunity to reimagine our workspace in the home. The plan is to turn our crap-hole of a box room into a useable work area; as we get stuck into this I’ll post updates to show how this evolves.

As we all know, putting something down on paper (or the internet!) is the first step on any journey. Here’s the redefined hubs of activity as I see them covering things you can expect to see on this blog in 2015/2016.

  • Reimagining of the Blog and some kind of dedicated presence on Facebook/Twitter.
  • Changes to our home workspace to show you how this progresses.
  • Updates covering any learning techniques as I study them. If these are useful to coding then expect them to get ‘air time’. For starters, look out for:
  • Coverage on the following topics (not sure on how basic/advanced this will be – Most likely this will comprise of feelers into a topic unless something really takes my fancy):
    • Finishing off the Epic Quest project.
    • F# Forays.
    • Coverage of Python.
    • Some further raw JavaScript coverage including jQuery.
    • Hello World level Raspberry Pi.
    • Coding against the Leap Motion Controller API.
    • Xamarin Tools.
    • ASP.NET MVC.
    • My friend Pete has written a superb object-orientated take on JavaScript – Picket.JS.
    • Further C# Unity game development (I still see myself covering a larger scale project encompassing the use of Blender, Gimp and Unity to make a standalone title).
    • Posts covering C# and TSQL (I’ve done some MySQL work recently so I will incorporate this into the proceedings if possible) as interesting topics catch my eye.
    • WPF (Rooting around in XAML) as time allows.

In and around this, I’m starting to sniff around the idea of completing full Microsoft Certifications in the next year to year and a half, so as I hop hurdles surrounding this I’ll give you all of the details.

This is not really designed to be a personal ransom note and I’m not going to outright hold myself to completing all of these things, but I do want to make a commitment to producing content as and when I can and keeping this fun (for myself and anyone reading along).

All that’s left to say is wish me luck and watch this space!

Code Project: Epic Quest! Basic Architecture

This project has been moving along at a nice, steady pace; here’s a run-down of the progress and first round of decisions I’ve made surrounding the games architecture.

I don’t plan on showing every single line of code. Instead, I’ll focus on the core structural work I’ve done and just list out details on a few of the classes that I’ve been working on. I’ll happily make the project available at the end of all this should anyone want to take a full look at the code.

Starting off with the very basics, you’ll remember me saying that I used to love Hero Quest as a kid (and to be honest I think I’d have a better appreciation of the game now; I just used to stand over all of the figures and wind my older brother up!). A core concept of Hero Quest was the attack and defence dice, with the amount of dice being rolled dependant on skills, weapons, augmentations and abilities. Here’s my first implementation of Green (weakest), Blue (middle-of-the-road) and Red (bad-ass) Die classes which hang off an abstract, base Die class.

Current Die Class Hierarchy.

Current Die Class Hierarchy.

As I stated before, I tend to create a fairly fleshed out class hierarchy before deciding whether any fat needs to get trimmed. In this case, I removed the AttackDie and DefenceDie classes and Attack/Defence Die class variants for each colour. Instead, I’ve opted for each dice to support ‘Miss’ (will be renamed to ‘Fail’), ‘SinglePoint’, ‘DoublePoint’, ‘TriplePoint’ and ‘CriticalPoint’ results. The ‘Point’ results will be utilised for determining both attack and defence roll values. The Green Die is the weakest of the three and contains the most ‘Miss’ results and only ‘SinglePoint’ attack/defence results. The Blue Die has only two ‘Miss’ results and supports ‘SinglePoint’ and ‘DoublePoint’ result values. Finally, the Red Die represents the strongest attack and defence clout, having (as per the Blue Die) two ‘Miss’ results and ‘DoublePoint’, ‘TriplePoint’ and ‘CriticalPoint’ results. The various weapons and armour will allow the players to roll various amounts of these dice (monsters, although not using items per say, will simply utilise a fixed amount of dice for attack and defence, unless I implement some kind of ability system for them in addition to the heroes).

Here, as an example, is the current code behind the Die base class and the Red Die (just for illustration purposes; the code for the Green Die/Blue Die is of course very similar due to its affiliation with the Die base class).

using EpicQuest.GameEnums;

namespace EpicQuest.Models
{
    /// <summary>
    /// Base class for all Epic Quest Die objects.
    /// </summary>
    internal abstract class Die
    {
        #region Abstract Members

        /// <summary>
        /// Each Die must implement its own array of
        /// DieFace enum values (i.e. the possible results
        /// supported by the Die in question).
        /// </summary>
        internal abstract DieFace[] DieFaces { get; }

        #endregion Abstract Members
    }
}
using EpicQuest.GameEnums;

namespace EpicQuest.Models.Dice
{
    /// <summary>
    /// Internal Sealed Class representing the strongest 
    /// dice type in Epic Dungeon. This will only be assigned
    /// to the strongest weapons/armour in the game.
    /// </summary>
    internal sealed class RedDie : Die
    {
        #region Private Read Only Data Fields

        //Represents the Red Die 'faces' (aka the possible results from 'rolling' the die)
        private readonly DieFace[] dieFaces;

        #endregion Private Read Only Fields

        #region Overriden Properties

        /// <summary>
        /// Allow access to the die faces (results)
        /// that make up this dice.
        /// </summary>
        internal override DieFace[] DieFaces
        {
            get 
            {
                return dieFaces;
            }
        }

        #endregion Overriden Properties

        #region Constructor

        /// <summary>
        /// RedDie Constructor that configures
        /// the results available on this Die.
        /// </summary>
        internal RedDie()
        {
            dieFaces = new DieFace[6] { DieFace.Miss, DieFace.DoublePoint, DieFace.TriplePoint, DieFace.Miss, DieFace.TriplePoint, DieFace.CriticalPoint };
        }

        #endregion Constructor
    }
}

Before I go any further….

Tangent alert!!! For anyone who doesn’t know what Hero Quest is, or just wants a refresher on how awesome that game was, check out the 1991 commercial (I was 7; imagine how insane this was!).

Shameless plug also for Board James (aka the Angry Video Game Nerd), here’s a link to his Hero Quest episode for kicks.

So, I could comfortably sit here and fill (I mean FILL!) this post with videos about board games, like this, that I’d happily sit and burn hours playing…but I’d quite like for everyone to continue reading these posts, so I’ll rein that urge right back in (for now)! I’m not promising anything though; I could well go full geek again with zero notice.

Dice are now in tow…Good start I hear you say! Well, we need something that will actually use the Dice – To that end I bring you a Weapon and Armour class.

Current Weapon/Armour Class Hierarchy.

Current Weapon/Armour Class Hierarchy.

In the current state, the Weapon class is a little less fleshed-out than Armour class. These classes implement a handful of very lightweight interfaces supporting the following concepts:

  • The object supports a List of Die objects (i.e. we have secured a contract stating any object using the IRollsDice interface will support this kind of member)
  • The object should be treated as a piece of ‘treasure’ in its own right. This is represented by supporting a GoldValue integer property.
  • The Weapon and Armour classes support independent interfaces enforcing support for Weapon/Armour Types (i.e. Item Types and which ‘Slots’ Armour fills).

Below is the full implementation, well the current implementation anyway, of both of these classes.

using EpicQuest.GameEnums;
using EpicQuest.Interfaces;
using EpicQuest.Models.Dice;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EpicQuest.Models
{
    /// <summary>
    /// Internal Class (not yet sealed, not sure of the plans here)
    /// representing a piece of Armour in Epic Quest.
    /// </summary>
    internal class Armour : IHeroArmour, ITreasure
    {
        #region Constant Members

        //Constants that keep a track of the level 'boundaries' for item types (based on the HeroDefensiveItemType underlying byte value).
        //This system does indeed involve some manual maintenance of the enum types/these values but the engine code found in the 
        //GetDefensiveItemMappings method should take care of the rest
        private const int levelOneMax = 6, levelTwoMax = 15, levelThreeMax = 24, levelFourMax = 26, levelFiveMax = 28, levelMax = 5;

        #endregion Constant Members

        #region Private Data Fields

        //A list of 'dice' that get rolled (in defence) for any hero 'wearing' this Armour
        private List<Die> dice = new List<Die>();

        //Enum values that represent the Armour Type and what 'Body Slot' (i.e. Head, Chest) the Armour takes up
        private HeroDefensiveItemType defensiveItemType = HeroDefensiveItemType.None;
        private HeroDefensiveItemSlotType defensiveItemSlotType = HeroDefensiveItemSlotType.None;

        #endregion Private Data Fields

        #region ITreasure Interface Support (Properties)

        /// <summary>
        /// Return the GoldValue of this particular
        /// piece of Armour (currently fixed at 5).
        /// </summary>
        public int GoldValue
        {
            get
            {
                return 5;
            }
        }

        #endregion ITreasure Interface Support (Properties)

        #region IHeroArmour Interface Support (Properties)

        /// <summary>
        /// Provides access to the Armour Type
        /// associated with this particular piece of Armour.
        /// </summary>
        public HeroDefensiveItemType DefensiveItemType
        {
            get
            {
                return defensiveItemType;
            }
            set
            {
                defensiveItemType = value;

                //Reset the Dice associated with this piece of Armour (based on Type)
                Dice = GetDiceBasedOnDefensiveItemType(value);
            }
        }

        /// <summary>
        /// Provides access to the Armour Type Slot
        /// associated with this particular piece of Armour.
        /// </summary>
        public HeroDefensiveItemSlotType DefensiveItemTypeSlot
        {
            get
            {
                return defensiveItemSlotType;
            }
            set
            {
                defensiveItemSlotType = value;
            }
        }

        #endregion IHeroArmour Interface Support (Properties)

        #region IRollsDice Interface Support (Properties)

        /// <summary>
        /// Provides access to the Defence Dice
        /// associated with this particular piece of Armour.
        /// </summary>
        public List<Die> Dice
        {
            get
            {
                return dice;
            }
            set
            {
                dice = value;
            }
        }

        #endregion IRollsDice Interface Support (Properties)

        #region Static Data Fields (Armour Mappings)

        //Private static backing dictionary (for all classes to use) that provides a mapping 'table' on Armour Types to Armour Slots (and the level of these Armour Types)
        private static Dictionary<KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>, int> armourMappings;

        #endregion Static Data Fields (Armour Mappings)

        #region Static Properties (Armour Mappings)

        /// <summary>
        /// Allows access to the static Armour Mapping
        /// dictionary so other classes can view Armour Type,
        /// Armour Slot to Level mappings (so Armour can be placed into
        /// the right slots on a Hero/to check the hero is of the
        /// appropriate level to wear a piece of Armour.
        /// </summary>
        internal static Dictionary<KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>, int> ArmourMappings
        {
            get
            {
                return armourMappings;
            }
            private set
            {
                armourMappings = value;
            }
        }

        #endregion Static Properties (Armour Mappings)

        #region Static Constructor

        /// <summary>
        /// Static constructor that fires on first use of this class
        /// to setup Armour Types/Slots and Level mappings.
        /// </summary>
        static Armour()
        {
            armourMappings = GetDefensiveItemMappings();
        }

        #endregion Static Constructor

        #region Internal Static Methods
        
        /// <summary>
        /// Internal static method that returns a 'Slot Type'
        /// based on an Armour Type.
        /// </summary>
        /// <param name="itemType">The Armour Type to inspect.</param>
        /// <returns>The Slot Type this piece of Armour belongs to.</returns>
        internal static HeroDefensiveItemSlotType GetSlotTypeBasedOnArmourType(HeroDefensiveItemType itemType)
        {
            switch (itemType)
            {
                case HeroDefensiveItemType.ClothTunic:
                case HeroDefensiveItemType.LeatherChestPiece:
                case HeroDefensiveItemType.NoviceMagicianRobe:
                case HeroDefensiveItemType.NoviceRobeOfSkulls:
                case HeroDefensiveItemType.AdeptMagicianDragonSkinRobe:
                case HeroDefensiveItemType.AdeptRobeOfDemonSkulls:
                case HeroDefensiveItemType.ChainMailChestPiece:
                case HeroDefensiveItemType.DragonSkinChestPiece:
                case HeroDefensiveItemType.MasterworkPlateChestPiece:
                case HeroDefensiveItemType.BloodStainedMithralArmour:
                case HeroDefensiveItemType.BloodStainedMithralRobe:
                    {
                        return HeroDefensiveItemSlotType.Chest;
                    }
                case HeroDefensiveItemType.LeatherHelm:
                case HeroDefensiveItemType.ChainMailHelm:
                case HeroDefensiveItemType.MasterworkPlateHelm:
                    {
                        return HeroDefensiveItemSlotType.Head;
                    }
                case HeroDefensiveItemType.LeatherGloves:
                case HeroDefensiveItemType.ChainMailGloves:
                case HeroDefensiveItemType.MasterworkPlateGloves:
                    {
                        return HeroDefensiveItemSlotType.Hands;
                    }
                case HeroDefensiveItemType.LeatherBoots:
                case HeroDefensiveItemType.ChainMailBoots:
                case HeroDefensiveItemType.MasterworkPlateBoots:
                    {
                        return HeroDefensiveItemSlotType.Feet;
                    }
                case HeroDefensiveItemType.LeatherPants:
                case HeroDefensiveItemType.ChainMailPants:
                case HeroDefensiveItemType.MasterworkPlatePants:
                    {
                        return HeroDefensiveItemSlotType.Legs;
                    }
                case HeroDefensiveItemType.SilverNecklace:
                case HeroDefensiveItemType.GoldNecklace:
                    {
                        return HeroDefensiveItemSlotType.Necklace;
                    }
                case HeroDefensiveItemType.SilverRing:
                case HeroDefensiveItemType.GoldRing:
                    {
                        return HeroDefensiveItemSlotType.Ring;
                    }
                case HeroDefensiveItemType.None:
                default:
                    {
                        return HeroDefensiveItemSlotType.None;
                    }
            }
        }

        /// <summary>
        /// Internal static method that returns the 'Dice' that
        /// can be used based on the type of Armour specified.
        /// </summary>
        /// <param name="itemType">The Armour Type to inspect.</param>
        /// <returns>A List of Dice (Die) objects that can be rolled based on the Armour Type specified.</returns>
        internal static List<Die> GetDiceBasedOnDefensiveItemType(HeroDefensiveItemType itemType)
        {
            List<Die> defensiveDice = new List<Die>();

            switch (itemType)
            {
                default:
                case HeroDefensiveItemType.ClothTunic:
                case HeroDefensiveItemType.LeatherHelm:
                case HeroDefensiveItemType.LeatherGloves:
                case HeroDefensiveItemType.LeatherBoots:
                case HeroDefensiveItemType.LeatherPants:
                case HeroDefensiveItemType.SilverNecklace:
                case HeroDefensiveItemType.SilverRing:
                    {
                        defensiveDice.Add(new GreenDie());
                    }
                    break;
                case HeroDefensiveItemType.LeatherChestPiece:
                case HeroDefensiveItemType.GoldNecklace:
                case HeroDefensiveItemType.GoldRing:
                    {
                        defensiveDice.AddRange(new Die[] { new GreenDie(), new GreenDie() });
                    }
                    break;
            }

            return defensiveDice;
        }

        #endregion Internal Static Methods

        #region Private Static Methods

        /// <summary>
        /// Private static method that configures (and returns a dictionary representing)
        /// Armour Type to Armour Slot mappings (with a value denoting what level a hero
        /// needs to be to wear a piece of Armour).
        /// </summary>
        /// <returns>A dictionary that represents Armour Type to Armour Slot mappings (with level information).</returns>
        private static Dictionary<KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>, int> GetDefensiveItemMappings()
        {
            Dictionary<KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>, int> mappings = new Dictionary<KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>, int>();

            //Temporary try/catch (put in during development as this piece of code evolves)
            try
            {
                //There are currently '5' (or whatever levelMax is set to) levels of Armour Items present in the game - Loop 5 times to capture all Armour Types
                for (int level = 1; level <= levelMax; level++)
                {
                    //Get values in the HeroDefensiveItemType enum and get the ones belong to the current level (based on for loop)
                    Enum.GetValues(typeof(HeroDefensiveItemType)).Cast<HeroDefensiveItemType>().Where(item => GetLevelConditionFilter(level, item)).ToList<HeroDefensiveItemType>().ForEach(item =>
                    {
                        //Store a reference, in the mappings dictionary, to the Armour Type, the Slot Type the Armour belongs to (via the GetSlotTypeBasedOnArmourType method) and the level this
                        //item belongs to (represented by the level variable)
                        HeroDefensiveItemSlotType slotType = GetSlotTypeBasedOnArmourType(item);
                        mappings.Add(new KeyValuePair<HeroDefensiveItemType, HeroDefensiveItemSlotType>(item, slotType), level);   
                    });
                }
            }
            catch (Exception ex)
            {
                //Write errors to the console (to be removed) - No need to interrupt the flow of the game for now (broken mappings = broken game, so the player would need to close anyway)
                Console.WriteLine(ex.Message);
            }

            return mappings;
        }

        /// <summary>
        /// Private static method that returns a boolean determining in the 
        /// Armour Type specified belongs to the current level value passed in.
        /// </summary>
        /// <param name="level">The level to check against (does the Armour specified belong to this level).</param>
        /// <param name="item">The Armour Type to check.</param>
        /// <returns>If the Armour Type belongs to the specified level then true, otherwise false. An Armour Type of HeroDefensiveItemType.None will always result in false.</returns>
        private static bool GetLevelConditionFilter(int level, HeroDefensiveItemType item)
        {
            //When using this filter an Armour Type of None should never be included, return false
            if (item == HeroDefensiveItemType.None)
            {
                return false;
            }

            //Only return true if an Armour Types underlying byte type falls between the level min/max limits specified (by constants in this class)
            switch (level)
            {
                default:
                case 1:
                    {
                        return ((byte)item > 0 && (byte)item <= levelOneMax);
                    }
                case 2:
                    {
                        return ((byte)item > levelOneMax && (byte)item <= levelTwoMax);
                    }
                case 3:
                    {
                        return ((byte)item > levelTwoMax && (byte)item <= levelThreeMax);
                    }
                case 4:
                    {
                        return ((byte)item > levelThreeMax && (byte)item <= levelFourMax);
                    }
                case 5:
                    {
                        return ((byte)item > levelFourMax && (byte)item <= levelFiveMax);
                    }
            }
        }

        #endregion Private Static Methods
    }
}
using EpicQuest.GameEnums;
using EpicQuest.Interfaces;
using EpicQuest.Models.Dice;
using System.Collections.Generic;

namespace EpicQuest.Models
{
    /// <summary>
    /// Internal Class (not yet sealed, not sure of the plans here)
    /// representing a Weapon in Epic Quest.
    /// </summary>
    internal class Weapon : ITreasure, IHeroWeapon
    {
        #region Private Data Fields

        //A list of 'dice' that get rolled (in attack) for any hero 'using' this Weapon
        private List<Die> dice = new List<Die>();

        //An Enum value that represent the Weapon Type being used
        private HeroOffensiveItemType offensiveItemType = HeroOffensiveItemType.Fists;

        #endregion Private Data Fields

        #region IRollsDice Interface Support (Properties)

        /// <summary>
        /// Provides access to the Attack Dice
        /// associated with this particular Weapon.
        /// </summary>
        public List<Die> Dice
        {
            get
            {
                return dice;
            }
            set
            {
                dice = value;
            }
        }

        #endregion IRollsDice Interface Support (Properties)

        #region ITreasure Interface Support (Properties)

        /// <summary>
        /// Return the GoldValue of this particular
        /// Weapon (currently fixed at 5). 
        /// </summary>
        public int GoldValue
        {
            get
            {
                return 5;
            }
        }

        #endregion ITreasure Interface Support (Properties)

        #region IHeroWeapon Interface Support (Properties)

        /// <summary>
        /// Provides access to the Weapon Type
        /// associated with this particular Weapon.
        /// </summary>
        public HeroOffensiveItemType OffensiveItemType
        {
            get 
            {
                return offensiveItemType; 
            }
	        set 
	        {
                offensiveItemType = value;

                //Reset the Dice associated with this Weapon (based on Type)
                Dice = GetDiceBasedOnOffensiveItemType(offensiveItemType);
	        }
        }

        #endregion IHeroWeapon Interface Support (Properties)

        #region Constructor

        /// <summary>
        /// Constructor for the Weapon Class
        /// that just sets the Weapon Type currently.
        /// </summary>
        /// <param name="itemType">The Weapon Type for this particular Weapon.</param>
        public Weapon(HeroOffensiveItemType itemType = HeroOffensiveItemType.Fists)
        {
            OffensiveItemType = itemType;
        }

        #endregion Constructor

        #region Internal Static Methods

        /// <summary>
        /// Internal static method that, based on the Weapon Type
        /// provided, returns the appropriate Attack Dice.
        /// </summary>
        /// <param name="itemType">The Weapon Type to inspect.</param>
        /// <returns>The dice that can be rolled based on the Weapon Type specified.</returns>
        internal static List<Die> GetDiceBasedOnOffensiveItemType(HeroOffensiveItemType itemType)
        {
            List<Die> offensiveDice = new List<Die>();

            switch (itemType)
            {
                default:
                case HeroOffensiveItemType.Fists:
                case HeroOffensiveItemType.RustyDagger:
                case HeroOffensiveItemType.RustyMace:
                case HeroOffensiveItemType.RustyShortSword:
                    {
                        offensiveDice.Add(new GreenDie());
                    }
                    break;
                case HeroOffensiveItemType.RustyBastardSword:
                    {
                        offensiveDice.AddRange(new Die[] { new GreenDie(), new GreenDie() });
                    }
                    break;
                case HeroOffensiveItemType.SilverHexDagger:
                case HeroOffensiveItemType.MagicMissile:
                    {
                        offensiveDice.AddRange(new Die[] { new GreenDie(), new BlueDie() });
                    }
                    break;
                case HeroOffensiveItemType.FlameTorch:
                case HeroOffensiveItemType.Freeze:
                case HeroOffensiveItemType.FineBarbedWireMace:
                case HeroOffensiveItemType.JaggedSword:
                    {
                        offensiveDice.AddRange(new Die[] { new GreenDie(), new GreenDie(), new BlueDie() });
                    }
                    break;
                case HeroOffensiveItemType.Disolve:
                case HeroOffensiveItemType.LegendaryBastardSword:
                    {
                        offensiveDice.AddRange(new Die[] { new BlueDie(), new RedDie() });
                    }
                    break;
            }

            return offensiveDice;
        }

        #endregion Internal Static Methods
    }
}

The key point that I want to focus in on at the moment is that each class implements an interface that states that they ‘support’ dice.


#region IRollsDice Interface Support (Properties)

/// <summary>
/// Provides access to the Defence Dice
/// associated with this particular piece of Armour.
/// </summary>
public List<Die> Dice
{
	get
	{
		return dice;
	}
	set
	{
		dice = value;
	}
}

#endregion IRollsDice Interface Support (Properties)

In addition to this, the classes have fully fledged members to determine what combinations of dice are associated with the Weapon and Armour types available (and what slots the Armour Types relate to).

At this point, we have dice to roll to reflect offensive and defensive abilities as well as Weapons and Armour that utilise these dice. Now we have these objects ready for use in our game we really need to code the hero characters to use these items. Below is another starter definition for our character based classes:

Current Character Class Hierarchy.

Current Character Class Hierarchy.

To back this class diagram up, here is a first implementation of the Hero base class and one example of a semi-bulked out Brawler class. Keep in mind this is for pure illustration purposes only; some of this code, particularly the property for defence dice, is a little nightmarish in its current form! However, I’ve resolved to show you things as they evolve and if they are, for want of a better word, a bit ‘crap’ to start with I’d rather show you and just mention it along the way. It essentially boils down to this being an iterative process and some pieces of code will always simply be in play for conceptual purposes (especially at this stage).

using EpicQuest.Interfaces;
using System;
using System.Collections.Generic;

namespace EpicQuest.Models
{
    /// <summary>
    /// Base class for all hero types.
    /// </summary>
    internal abstract class Hero : GameCharacter, ICombatant
    {
        #region Abstract Properties (ICombatant Interface Support)

        /// <summary>
        /// The heros health (overriden for each Hero).
        /// </summary>
        public abstract int Health { get; set; }
        
        /// <summary>
        /// The dice this hero can roll for defence
        /// (based on abilities/armour etc). Must be
        /// overriden for each particular hero.
        /// </summary>
        public abstract List<Die> DefenceDice { get; }
        
        /// <summary>
        /// The dice this hero can roll for offence
        /// (based on weapons/abilities). Must be 
        /// overriden for each particular hero.
        /// </summary>
        public abstract List<Die> AttackDice { get; }

        #endregion Abstract Properties (ICombatant Interface Support)

        #region Abstract Methods

        /// <summary>
        /// Abstract member that must be overriden for each
        /// particular hero (and will refresh the attack/defence
        /// dice state for a particular hero).
        /// </summary>
        public abstract void RefreshDiceState();

        #endregion Abstract Methods
    }
}
using System.Collections.Generic;

namespace EpicQuest.Models
{
    /// <summary>
    /// Implementation of the Brawler hero type.
    /// </summary>
    internal sealed class Brawler : Hero
    {
        #region Private Data Fields

        //The characters starting health value
        private int health = 10;

        //Fields that denote a heroes head and chest slot armour types and a base weapon for this hero to use. This is implemented in its current for for test purposes only
        private Armour headSlot = new Armour() { DefensiveItemType = GameEnums.HeroDefensiveItemType.LeatherHelm, DefensiveItemTypeSlot = GameEnums.HeroDefensiveItemSlotType.Head };
        private Armour chestSlot = new Armour() { DefensiveItemType = GameEnums.HeroDefensiveItemType.ChainMailChestPiece, DefensiveItemTypeSlot = GameEnums.HeroDefensiveItemSlotType.Chest };
        private Weapon heroWeapon = new Weapon(GameEnums.HeroOffensiveItemType.FineBarbedWireMace);

        #endregion Private Data Fields

        #region ICombatant (abstract overrides) Interface Support

        /// <summary>
        /// Access to the characters health (to reduce 
        /// during combat for example).
        /// </summary>
        public override int Health
        {
            get
            {
                return health;
            }
            set
            {
                health = value;
            }
        }

        /// <summary>
        /// Access to this characters defence dice
        /// for rolling during combat.
        /// </summary>
        public override List<Die> DefenceDice
        {
            get
            {
                //THIS IS NOT PERMANENT - FOR DEMONSTRATION PURPOSES ONLY (NIGHTMARISH CODE ALERT - WOULDN'T WANT A NEW LIST EACH TIME)
                List<Die> currentDefenceDice = new List<Die>(headSlot.Dice.Count + chestSlot.Dice.Count);
                currentDefenceDice.AddRange(headSlot.Dice);
                currentDefenceDice.AddRange(chestSlot.Dice);

                return currentDefenceDice;
            }
        }

        /// <summary>
        /// Access to this characters defence dice
        /// for rolling during combat.
        /// </summary>
        public override List<Die> AttackDice
        {
            get
            {
                return heroWeapon.Dice;
            }
        }

        /// <summary>
        /// Overriden method that will inspect this heroes 
        /// state and refresh the dice associated with attack/defence
        /// based on the items in scope.
        /// </summary>
        public override void RefreshDiceState()
        {
            //TODO
        }

        #endregion ICombatant (abstract overrides) Interface Support
    }
}

There are still quite a few details to take care of here, but this very simple class structure enables us to set-up and test a combat scenario, which I’ll take you through in a moment. Before moving on however, I want to illustrate two other parts of the class diagram (although I’m not going to go through the details yet) showing you the enumerations currently written and other miscellaneous parts of the code base. We’ll talk about these areas of the code base in future posts regardless.

Current Enum Structure.

Current Enum Structure.

Current Misc Class Hierarchy.

Current Misc Class Hierarchy.

Finally, as an introduction to the piece of combat code previously mentioned, here is a phase one representation of a Dungeon and Room class. The plan here is to provide a Dungeon that contains a random number of rooms and pieces of content (classes implementing IRoomContent) for the adventurers to interact with. Clearing all of the rooms, by killing monsters and collecting treasure, outlines the win condition for the game.

Current Dungeon/Room Class Hierarchy.

Current Dungeon/Room Class Hierarchy.

using EpicQuest.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace EpicQuest.Models.Dungeon
{
    /// <summary>
    /// Internal Sealed class that represents the Rooms
    /// in a Dungeon (filled with monsters and treasure).
    /// </summary>
    internal sealed class Room : IUniqueItem
    {
        #region Private Data Fields

        //A Rooms unique reference (should a room need to be uniquely identified) and a List of Room Content
        private readonly Guid uniqueRef = Guid.NewGuid();
        private List<IRoomContent> roomFeatures = new List<IRoomContent>();

        #endregion Private Data Fields

        #region Properties

        /// <summary>
        /// Access to this Rooms Unique Reference.
        /// </summary>
        public Guid UniqueRef
        {
            get
            {
                return uniqueRef;
            }
        }

        /// <summary>
        /// Helper property that checks to see if any 
        /// remaining Room Content is of type Monster.
        /// </summary>
        internal bool RoomClearedOfMonsters
        {
            get
            {
                if (roomFeatures.Any(feature => feature is Monster))
                {
                    return false;
                }

                return true;
            }
        }

        /// <summary>
        /// Access to a Rooms Content Features.
        /// </summary>
        internal List<IRoomContent> RoomFeatures 
        { 
            get
            {
                return roomFeatures;
            }
            private set
            {
                if (value != null && value.Count > 0)
                {
                    roomFeatures = value;
                }
            }
        }

        #endregion Properties

        #region Constructors

        /// <summary>
        /// Room constructor that allows a Room to be populated
        /// with a single piece of content (or an array of content).
        /// </summary>
        /// <param name="features">The content to place in the Room (single piece or an array of).</param>
        internal Room(params IRoomContent[] features)
            : this (features.ToList())
        {

        }

        /// <summary>
        /// Room constructor that allows a Room to be populated
        /// with a List of content.
        /// </summary>
        /// <param name="features">A list of content to place in the Room.</param>
        internal Room(List<IRoomContent> features)
        {
            if (features != null && features.Count > 0)
            {
                RoomFeatures = features;
            }
        }

        #endregion Constructors

        #region Internal Methods

        /// <summary>
        /// Internal method that allows a piece of content/multiple pieces
        /// of content to be added to this particular Room.
        /// </summary>
        /// <param name="features">A single piece of content/array of content pieces to add to the Room.</param>
        internal void AddContentToRoom(params IRoomContent[] features)
        {
            if (features != null && features.Count() > 0)
            {
                foreach (IRoomContent item in features)
                {
                    roomFeatures.Add(item);
                }
            }
        }

        /// <summary>
        /// Internal method that allows a piece of content/multiple pieces
        /// of content to be removed from this particular Room.
        /// </summary>
        /// <param name="features">>A single piece of content/array of content pieces to removed from the Room.</param>
        internal void RemoveContentFromRoom(params IRoomContent[] features)
        {
            if (features != null && features.Count() > 0)
            {
                foreach (IRoomContent item in features)
                {
                    roomFeatures.Remove(item);
                }
            }
        }

        #endregion Internal Methods

        #region Enumerator

        /// <summary>
        /// Enumerator that allows external classes
        /// to more easily iterate over the Room Features in this
        /// Room (depending on a developers preferences).
        /// </summary>
        /// <returns>The RoomFeatures Enumerator.</returns>
        public List<IRoomContent>.Enumerator GetEnumerator()
        {
            return RoomFeatures.GetEnumerator();
        }

        #endregion Enumerator

        #region Internal Static Methods

        /// <summary>
        /// Internal static helper method that generates and returns a random
        /// List of Room Content.
        /// </summary>
        /// <param name="contentAmountSeed">A seed to help determine the amount of pieces to generate.</param>
        /// <returns>A random List of Room Content.</returns>
        internal static List<IRoomContent> GenerateRandomRoomContent(int contentAmountSeed)
        {
            //CURRENTLY HARD CODED TO RETURN TWO MONSTERS AS CONTENT (COMBAT TEST)
            return new List<IRoomContent>(new IRoomContent[] { new Kobold(), new Skeleton() });
        }

        #endregion Internal Static Methods
    }
}
using System;
using System.Collections.Generic;
using System.Linq;

namespace EpicQuest.Models.Dungeon
{
    /// <summary>
    /// Class that represents a dungeon that adventurers
    /// can 'quest' through with rooms containing things
    /// for the heroes to contend with/collect and interact with.
    /// </summary>
    internal sealed class EpicDungeon
    {
        #region Private Data Fields

        //Represents the rooms that make this dungeon up
        private List<Room> dungeonRooms = new List<Room>();

        /// <summary>
        /// Access to the Rooms in this Dungeon
        /// (to outside classes).
        /// </summary>
        internal List<Room> DungeonRooms
        {
            get
            {
                return dungeonRooms;
            }
            private set
            {
                dungeonRooms = value;
            }
        }

        #endregion Private Data Fields

        #region Constructors

        /// <summary>
        /// Constructor that allows a Dungeon to be created
        /// based on an array of Room or comma separated 
        /// Room objects (individual instances).
        /// </summary>
        /// <param name="newRooms">Rooms that populate this Dungeon with.</param>
        internal EpicDungeon(params Room[] newRooms)
            : this (newRooms.ToList())
        {

        }

        /// <summary>
        /// Constructor that populates this Dungeon with 
        /// the supplied List or Rooms.
        /// </summary>
        /// <param name="newRooms">A List of Rooms to populate this Dungeon with.</param>
        internal EpicDungeon(List<Room> newRooms)
        {
            if (newRooms != null && newRooms.Count > 0)
            {
                DungeonRooms = newRooms;    
            }
        }

        #endregion Constructors

        #region Internal Methods

        /// <summary>
        /// Internal method that allows a Room/Rooms to
        /// be added to this Dungeon.
        /// </summary>
        /// <param name="rooms">A single Room or an array of Rooms to add.</param>
        internal void AddDungeonRoom(params Room[] rooms)
        {
            if (rooms != null && rooms.Count() > 0)
            {
                foreach (Room room in rooms)
                {
                    DungeonRooms.Add(room);
                }
            }
        }

        /// <summary>
        /// Internal method that allows a Room/Rooms to
        /// be removed from this Dungeon.
        /// </summary>
        /// <param name="rooms">A single Room or an array of Rooms to remove.</param>
        internal void RemoveDungeonRoom(params Room[] rooms)
        {
            if (rooms != null && rooms.Count() > 0)
            {
                foreach (Room room in rooms)
                {
                    DungeonRooms.Remove(room);
                }
            }
        }

        #endregion Internal Methods

        #region Enumerator

        /// <summary>
        /// Enumerator that allows external classes
        /// to more easily iterate over the Rooms in this
        /// Dungeon (depending on a developers preferences).
        /// </summary>
        /// <returns>The DungeonRooms Enumerator.</returns>
        public List<Room>.Enumerator GetEnumerator()
        {
            return DungeonRooms.GetEnumerator();
        }

        #endregion Enumerator

        #region Internal Static Methods

        /// <summary>
        /// Internal static helper method that generates a List
        /// of Room objects (based on the seed values provided)
        /// and Room content.
        /// </summary>
        /// <param name="roomAmountSeed">A seed that helps determine the amount of Rooms.</param>
        /// <param name="roomContentSeed">A seed that helps determine the amount of Room Content (in each Room).</param>
        /// <returns>A List of fully populated Rooms based on the seeds specified.</returns>
        internal static List<Room> GenerateRandomRoomConfiguration(int roomAmountSeed = 5, int roomContentSeed = 3)
        {
            List<Room> dungeonRooms = new List<Room>();

            //Get a Room count based on the seed
            Random randomRoomCountObj = new Random();
            int thisGameRoomCount = randomRoomCountObj.Next(roomAmountSeed, (roomAmountSeed + 2));

            //Generate Rooms and add Random content
            for (int i = 0; i < thisGameRoomCount; i++)
            {
                dungeonRooms.Add(new Room(Room.GenerateRandomRoomContent(roomContentSeed)));
            }

            return dungeonRooms;
        }

        #endregion Internal Static Methods
    }
}

The key thing to note here is that Rooms support content and these content pieces can be any class that supports IRoomContent. Currently, our monsters support this interface so they can exist within Rooms. When a hero encounters a monster within a Room combat will be initiated. Combat based classes will support the ICombatant interface and, as an early conceptual piece, I’ve authored and tested the following method to get a feel for how combat may work between two ICombatant compatible objects. Here we are seeing dice and character classes in action.

/// <summary>
/// Private method that shows, in principle, how combat
/// can be handled between two ICombatant based objects.
/// </summary>
/// <param name="aggressor">The instigator of the combat represented by a class implementing ICombatant.</param>
/// <param name="target">The defender represented by a class implementing ICombatant.</param>
private void HandleCombat(ICombatant aggressor, ICombatant target)
{
    //Prepare values representing the dice roll capabilities (random) and values to keep a tally of the offence, defence and difference totals
    int damageTotal = 0, defenceTotal = 0, difference = 0;
    Random rollDie = new Random();

    //Roll attack dice for the aggressor and get a damage total
    aggressor.AttackDice.ForEach(attackDie =>
        {
            damageTotal += (byte)attackDie.DieFaces[rollDie.Next(0, 5)];
        });

    //Roll defence dice for the defender and get a defence total
    target.DefenceDice.ForEach(defenceDie =>
        {
            defenceTotal += (byte)defenceDie.DieFaces[rollDie.Next(0, 5)];
        });

    //Determine the difference between the damage/defence totals and deal damage to the target if required
    difference = damageTotal - defenceTotal;

    if (difference > 0)
    {
        target.Health -= difference;
    }
}

This has been somewhat of a massive mind dump of information, early concepts and basic implementations. In future posts I’ll be narrowing down my focus on each part of the application and implementing classes more fully as required. At the end of this week I’ll also be tallying up the votes for the ‘which monster to include in the game’ poll; so vote if you haven’t already.

Bye for now.