using System; using System.Collections.Generic; using System.Linq; using Chernobyl.Collections.Generic; using Enumerable = Chernobyl.Collections.Generic.Enumerable; using SysEnumerable = System.Linq.Enumerable; namespace Chernobyl.Generation.Old { /// /// Dictates the relative difference in each value over an entire pattern. /// public enum Direction { /// /// The pattern falls or moves backwards away from some origin. /// Fall = -1, /// /// The pattern stays the same. /// Flat = 0, /// /// The pattern rises or moves forward away from some origin. /// Rise = 1 } /// /// Extensions methods for the type. /// public static class Pattern { /// /// Holds a base and the conflict for a pattern. /// class RawPattern { public RawPattern(IEnumerable @base, IEnumerable conflict) { Base = @base.ToArray(); Conflict = conflict.ToArray(); } /// /// Applies a conflict to the specified pattern. /// public IEnumerable Combine(Random rand) { var basePatternArray = Base.ToArray(); var conflictArray = Conflict.ToArray(); // Create method to order the base and conflict together. var groupLength = Base.Length + Conflict.Length; var indecies = Enumerable.From(i => i).Take(groupLength).OrderBy(i => rand.Next(groupLength)).ToList(); int IndexCreate() => indecies.Pull(0); // TODO: sort the conflict into the base pattern such that it remains a conflict. i.e. take // into account the direction of the base pattern. var result = new T[basePatternArray.Length + conflictArray.Length]; foreach (T c in conflictArray.Concat(basePatternArray)) { var overwriteIndex = IndexCreate(); result[overwriteIndex] = c; } return result; } T[] Base { get; } T[] Conflict { get; } } /// /// Creates a pattern whose values rise or fall based upon the specified direction. /// /// Whether the pattern should incorporate a sorted aspect. /// The values that are to be the base. /// The size of the resulting pattern. static IEnumerable CreateDirectionalPattern(Direction direction, IEnumerable patterns, int length) { List pattern = new List(length); if (direction == Direction.Flat) pattern.AddRange(SysEnumerable.Repeat(patterns.Single(), length)); else { switch (direction) { case Direction.Fall: pattern.AddRange(patterns.OrderByDescending(value => value)); break; case Direction.Rise: pattern.AddRange(patterns.OrderBy(value => value)); break; } } return pattern; } /// /// Determines the length of the conflict based on the length of the entire pattern () /// /// The length of the entire pattern. static int CalculateConflictLength(int totalLength) { var conflictLength = totalLength / 3; return conflictLength == 0 ? 1 : conflictLength; } /// /// Creates a random conflicted pattern from a list of accepted values. /// /// The patterns that are to be the base and conflict. /// The direction of the base pattern in the overall pattern. /// The size of the returned array. static RawPattern CreateBaseAndConflict(IEnumerable patterns, Direction direction, int length) { // We will use the "Rule of Thirds" to determine how much conflict should be in the pattern. var conflictLength = CalculateConflictLength(length); var basePatternLength = length - conflictLength; // Create the base and conflict parts of the pattern. var conflicts = patterns.Take(conflictLength); var basePattern = CreateDirectionalPattern(direction, patterns.Skip(conflictLength), basePatternLength); // Now we'll overwrite the pattern with the conflict. We'll keep a list of indices that // we can overwrite to prevent us from using the overwriting the same index twice. return new RawPattern(basePattern, conflicts); } static readonly Direction[] Directions = Enum.GetValues(typeof(Direction)).Cast().ToArray(); // TODO: allow group length to be a conflicted pattern. /// /// The minimum size of the groups created. /// const int MinGroupLength = 3; /// /// The maximum size of the groups created. /// const int MaxGroupLength = 5; struct PatternDescription { public int GroupLength; public Direction Direction; } /// /// Create a set of conflicted pattern groups until there is one or none in /// . The created patterns are the 'parents' of the /// . /// /// The list of available values for both the patten and its conflict. /// The instance that decides which values will be in the returned pattern. static IEnumerable CreateParents(this IEnumerable children, Random rand) { (int, PatternDescription) GroupSize() { // We add 1 to MaxGroupLength because the maxValue parameter of the Next method is exclusive. var groupLength = rand.Next(MinGroupLength, MaxGroupLength + 1); var direction = groupLength > 2 // We need plenty of runway for an effective non-flat pattern. ? Directions[rand.Next(Directions.Length)] : Direction.Flat; direction = Direction.Flat; var conflictLength = CalculateConflictLength(groupLength); var takeLength = direction == Direction.Flat ? conflictLength + 1 : groupLength; var description = new PatternDescription { GroupLength = groupLength, Direction = direction }; return (takeLength, description); } // BatchSelect will do its best to create something of the selected size but leftovers might // cause patterns with size of less than one. So we'll remove those using Where. Note that we // cache the groups by calling Cache(). This is done because GroupSize and Combine create // random sizes and orders, and we don't want those changing every time the group is enumerated. return children.BatchSelect(GroupSize, (batch, size) => (batch.ToArray(), size.Item2)) .Where(pattern => pattern.Item1.Length > 1) .Select(pattern => CreateBaseAndConflict(pattern.Item1, pattern.Item2.Direction, pattern.Item2.GroupLength)) .Select(raw => raw.Combine(rand).Cache()) .Select(group => new CompositePattern(group)); } /// /// Creates a hierarchy of grouped and conflicted instances where the /// make up the base (leaves) of the hierarchy. /// /// The that are to make up the base of the /// hiearchy. /// The instance used to determine grouping and the size of the groups. public static IPattern CreateParent(this IEnumerable available, Random rand) { // Make sure there is atleast two in the list. var firstTwo = available.Take(2).ToArray(); firstTwo.Length.Throw(nameof(available) + ".Count()").IfLessThanOrEqualTo(0); if (firstTwo.Length == 1) return firstTwo[0]; // Take the patterns we were given and composite into the patterns above. Note that we // cache the child patterns by calling Cache(). This is done because CreateConflictedPatterns // uses random pattern lengths and we don't want those changing every time the list is enumerated. return available.CreateParents(rand).Cache().CreateParent(rand); } /// /// Creates a hierarchy of grouped and conflicted instances whose /// resulting pattern is a series of values ranging from 0 to 1. /// /// The number of values to build the pattern from. The higher the value, /// the larger the pattern size. /// The instance used to determine grouping and the size of the groups. public static IPattern BuildHierarchy(int count, Random rand) { // TODO: make sure values produced are unique for some amount of time before they repeat. // TODO: make sure values are far enough apart for some amount of time before they are close together again. return Enumerable.From(() => new BasicPattern(rand.NextDouble())).Take(count).CreateParent(rand); } } }