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.

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.

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:

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.


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.

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.