Some new Direction

I found another C# roguelike project by Ed Kolis and really liked how he handled directions within his code. You can read the post on his project at r.g.r.d. and download the source at http://kolis.dyndns.org/sharproguelib/.

I had been using an Enum to represent directions within my dungeon generation code. Nothing wrong with that, but I always felt like I was repeating a lot of code when acting on a direction. For North it was always a negative change on the Y axis and so forth.

Ed has implemented directions using a Direction class. His class defines 8 compass directions North, South, East, West, Northwest, Northeast, Southwest, and Southeast as static instances on the Direction class as follows.

        public static readonly Direction North = new Direction() { Index = 0, Name = “North”, DX = 0, DY = -1 };

        public static readonly Direction East = new Direction() { Index = 1, Name = “East”, DX = 1, DY = 0 };

        public static readonly Direction South = new Direction() { Index = 2, Name = “South”, DX = 0, DY = 1 };

        public static readonly Direction West = new Direction() { Index = 3, Name = “West”, DX = -1, DY = 0 };

        public static readonly Direction Northeast = new Direction() { Index = 4, Name = “Northeast”, DX = 1, DY = -1 };

        public static readonly Direction Southeast = new Direction() { Index = 5, Name = “Southeast”, DX = 1, DY = 1 };

        public static readonly Direction Southwest = new Direction() { Index = 6, Name = “Southwest”, DX = -1, DY = 1 };

        public static readonly Direction Northwest = new Direction() { Index = 7, Name = “Northwest”, DX = -1, DY = -1 };

In the above code we instantiate 8 Direction instances as public static readonly variables on the Direction class. I have changed the original class to suit my needs, but the idea is still the same. For each instance we define a Index, Name, DX and DY value.

The Index property was added to allow me to match my old Enum code to the new Direction instances.

The Name property is self explanatory.

The DX and DY represent changes in direction in 2-dimensional space. For example North can be defined as a X change of zero and a Y change of -1.

This allows me to make a whole bunch of simplifications to the dungeon generation code. The following is an example using the new Direction class on the HasAdjacentCell method in the Map class.

        public bool HasAdjacentCell(Point location, Direction direction)

        {

            return Bounds.Contains(direction.ApplyTransform(location));

 

            //// Check that the location falls within the bounds of the map

            //if (!Bounds.Contains(location))

            //    return false;

 

            //// Check if there is an adjacent cell in the direction

            //switch (direction.Name)

            //{

            //    case “North”:

            //        return location.Y > 0;

            //    case “South”:

            //        return location.Y < (Height – 1);

            //    case “West”:

            //        return location.X > 0;

            //    case “East”:

            //        return location.X < (Width – 1);

            //    default:

            //        return false;

            //}

        }

In the above code you will notice that I commented out the whole function and replaced it with a single line of code. The new code does a bounds check on the new location returned by the ApplyTransform method below.

        /// <summary>

        /// Applies the change in direction to the provided point

        /// </summary>

        /// <param name=”location”>The location in 2-dimensional space</param>

        /// <returns>Returns a point after the direction changes were applied to the provided location.</returns>

        public Point ApplyTransform(Point location)

        {

            return new Point(location.X + DX, location.Y + DY);

        }

The only tricky part was to retain the behaviour of casting an Int value to a Direction (previously the Enum worked fine).

        private Direction PickDifferentDirection()

        {

            Direction directionPicked;

            do

            {

                directionPicked = (Direction)RogueLib.Common.Random.Instance.Next(3);

            } while ((directionPicked == previousDirection) && (directionsPicked.Count < 3));

 

            return directionPicked;

        }

I solved this by overriding the cast operator and mapping the Int value to the Direction index as follows.

        /// <summary>

        /// Allows us to cast from an Int to Direction based on the index value

        /// </summary>

        /// <param name=”value”>The integer index value to cast</param>

        /// <returns>The Direction instance matching the provided index</returns>

        static public implicit operator Direction(int value)

        {

            switch (value)

            {

                case 0:

                    return Direction.North;

                case 1:

                    return Direction.East;

                case 2:

                    return Direction.South;

                case 3:

                    return Direction.West;

                case 4:

                    return Direction.Northeast;

                case 5:

                    return Direction.Southeast;

                case 6:

                    return Direction.Southwest;

                case 7:

                    return Direction.Northwest;

                default:

                    return null;

            }

        }

I refactored the Cell class to define the sides as a function of their Direction. This meant that I could now access a side based on a direction at runtime. The new Cell class is now as follows.

        private Dictionary<Direction, SideType> sides = new Dictionary<Direction, SideType>() {

            { Direction.North, SideType.Wall },

            { Direction.East, SideType.Wall },

            { Direction.South, SideType.Wall },

            { Direction.West, SideType.Wall }

        };

 

        public SideType GetSideAt(Direction direction)

        {

            if (!sides.ContainsKey(direction)) throw new ArgumentException(“Invalid direction specified”, “direction”);

            return sides[direction];

        }

 

        public SideType SetSideAt(Direction direction, SideType sideType)

        {

            if (!sides.ContainsKey(direction)) throw new ArgumentException(“Invalid direction specified”, “direction”);

            return sides[direction] = sideType;

        }

I store each SideType in a Dictionary using the Direction as key. The new GetSideAt and SetSideAt methods allow me to set and retrieve the various SideTypes. I retained the Northside, Eastside, Southside and Westside properties for easy access to the various sides.

The next improvement was on the Dungeon class. I was able to simplify the CreateSide method by adding an Inverse property to the Direction class.

        private Point Createside(Point location, Direction direction, SideType sideType)

        {

            Point? target = GetTargetLocation(location, direction);

            if (target == null) throw new ArgumentException(“There is no adjacent cell in the given direction”, “location”);

 

            this[location].SetSideAt(direction, sideType);

            this[target.Value].SetSideAt(direction.Inverse, sideType);

 

            //switch (direction.Name)

            //{

            //    case “North”:

            //        this[location].Northside = sideType;

            //        this[target.Value].Southside = sideType;

            //        break;

            //    case “South”:

            //        this[location].Southside = sideType;

            //        this[target.Value].Northside = sideType;

            //        break;

            //    case “West”:

            //        this[location].Westside = sideType;

            //        this[target.Value].Eastside = sideType;

            //        break;

            //    case “East”:

            //        this[location].Eastside = sideType;

            //        this[target.Value].Westside = sideType;

            //        break;

            //}

 

            return target.Value;

        }

The SetSideAt method on the Cell class and the Direction.Inverse property allowed me to get rid of all the manual direction mapping code as seen above. The Inverse property on the Direction class can be seen below.

        /// <summary>

        /// The inverse direction of the current direction

        /// </summary>

        public Direction Inverse

        {

            get

            {

                switch (Name)

                {

                    case “North”:

                        return Direction.South;

                    case “South”:

                        return Direction.North;

                    case “West”:

                        return Direction.East;

                    case “East”:

                        return Direction.West;

                    case “Northwest”:

                        return Direction.Southeast;

                    case “Northeast”:

                        return Direction.Southwest;

                    case “Southwest”:

                        return Direction.Northeast;

                    case “Southeast”:

                        return Direction.Northwest;

                    default:

                        throw new InvalidOperationException();

                }

            }

        }

I am very pleased with the results of the new Direction class. I feel it makes the code a lot simpler to read and understand. Kudos to Ed for the great idea.