Jonathon Duerig recently posted the following comment on the Some New Direction article.
I really like this idea and was inspired to put this in my own project. However, I’m stumped on how to put in relations between into it. I want to be flexible enough for algorithms based on the directions to be able to use four-way, eight-way, up and down, etc. kinds of directions without modification.
I also want to work with directions in terms of a relationships between them. I want to be able to find the next direction clockwise of a given direction, for instance. Or choose a random direction perpendicular to a given direction. Or find the direction opposite of a given direction.
Having a flexible interface to setup these relationships at startup is proving difficult. Ideas?
I rambled off an initial response without actually thinking about the problem and why he needed this functionality. I started thinking about his request and realised that it would be quite useful. Hence this blog post.
I am going to deal with the relationships between directions first. Lets say we would like to find the next clockwise direction. The test for this functionality looks as follows.
public void TestNextDirection()
In the above test we make use of the new “Next” property on the Direction class. The idea is that the “Next” property returns the next clockwise direction based on our current direction.
The code that makes the above test compile is as follows.
/// The next clockwise direction of the current direction
public Direction Next
throw new InvalidOperationException();
In the above listing we hardcode the next clockwise direction for the current direction using a simple case statement. I compile the code and rerun the test. It passes with no issues.
I won’t cover the implementation for the other relationships between directions as they are almost identical to the one above. The additional properties I added are “Previous”, “NextPerpendicular” and “PreviousPerpendicular”. The “Previous” property returns the next anti-clockwise direction. The “NextPerpendicular” and “PreviousPerpendicular” properties return the next clockwise and anti-clockwise perpendicular directions respectively.
The class diagram for the direction class is now as follows.
The next part of the functionality he required was to be able to limit the number of directions available. In my initial response I recommended a helper class such as the DirectionPicker class I used in my DungeonGeneration article series.
In order to support working with different combinations of directions we need to modify the DirectionPicker class to use a list of available directions for all its operations.
The current implementation picks a random direction (previously only the main compass directions north, east, south and west) and adds it to a list of directions picked. By specifying a list of available directions we can implement the same functionality by removing the direction picked from the provided available directions.
The random direction is now selected by choosing a random index within the list of available directions. This allows us to select random directions in any combination as specified by the available directions.
The code affected by these changes can be seen below (the original code has been commented out)
public bool HasDirectionToPick
return directionsAvailable.Count > 0;
//return directionsPicked.Count < 4;
private bool MustChangeDirection
// changeDirectionModifier of 100 will always change direction
// value of 0 will never change direction
return ((directionWasPicked) || (changeDirectionModifier > RogueLib.Common.Random.Instance.Next(0, 99)));
//return ((directionsPicked.Count > 0) || (changeDirectionModifier > RogueLib.Common.Random.Instance.Next(0, 99)));
private Direction PickDifferentRandomDirection()
directionPicked = directionsAvailable[RogueLib.Common.Random.Instance.Next(directionsAvailable.Count – 1)];
//directionPicked = directionsAvailable[RogueLib.Common.Random.Instance.Next(3)];
} while ((directionPicked == previousDirection) && (directionsAvailable.Count > 1));
//} while ((directionPicked == previousDirection) && (directionsPicked.Count < 3));
public Direction PickRandomDirection()
if (!HasDirectionToPick) throw new InvalidOperationException(“No directions available”);
directionPicked = MustChangeDirection ? PickDifferentRandomDirection() : previousDirection;
} while (!directionsAvailable.Contains(directionPicked));
//} while (directionsPicked.Contains(directionPicked));
directionWasPicked = true;
I created three constructors to allow me to easily configure the available directions for the DirectionPicker. The default constructor makes use of the main compass directions (north, east, south, west) as was the default behaviour before we made the above changes.
The second constructor takes a DirectionsAvailableType enum as input parameter and sets up the available directions based on the provided DirectionsAvailableType. The two available types are AllCompassDirections and MainCompassDirections.
The third constructor takes a params array of Directions as input parameter whereby the user can specify a custom list of available directions. I would use this constructor to satisfy Jonathon’s requirement of selecting a random perpendicular direction to a given direction for example.
public void TestSelectionOfRandomPerpendicularDirection()
Direction currentDirection = Direction.North;
DirectionPicker directionPicker = new DirectionPicker(Direction.North, 100, currentDirection.PreviousPerpendicular, currentDirection.NextPerpendicular);
List<Direction> directionsPicked = new List<Direction>();
In the above test we instantiate our direction picker with the previous (West) and next (East) perpendicular directions from our current direction (North). We then instantiate a new list that will contain the directions picked and then proceed to pick two random directions. The test then asserts that both the perpendicular directions were indeed selected and that there are no more directions available for selection.
The above improvements to the Direction and DirectionPicker classes have added great flexibility to our Direction handling code. Any suggestions or comments would, as always, be appreciated.