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);
}
}
}