using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; namespace GameOfLife.Entities { public class Pattern : ReadOnlyCollection { public Pattern(IEnumerable pattern) : base(pattern.OrderBy(c => c.Item2).ThenBy(c => c.Item1).ToList()) { // https://stackoverflow.com/a/263416/99492 unchecked { _hashCodeCache = this //.OrderBy(c => c) .Aggregate(23, (hash, c) => hash * 37 + c.GetHashCode()); } } public Cell GetBoundaryMin() => new Cell( this.Min(c => c.Item1) - 1, this.Min(c => c.Item2) - 1); public Cell GetBoundaryMax() => new Cell( this.Max(c => c.Item1) + 1, this.Max(c => c.Item2) + 1); public Cell GetFirstRowFirstCell() => this.OrderBy(c => c.Item2).ThenBy(c => c.Item1).First(); public bool ContainsAll(IEnumerable cells) => cells.All(Contains); public bool ContainsNone(IEnumerable cells) => cells.All(c => !Contains(c)); public short GetMinY() => this.Min(c => c.Item2); // All neighboring cells public Pattern GetBoundary => this.SelectMany(cell => cell.NeighborCells).Distinct().ToPattern() - this; public Pattern Rotate() => /* Rotate 90deg clockwise * ..... ..... .oo.. ...o. * ..o.. -> .oo.. -> ..o.. -> ..oo. * ..oo. .o... ..... ..... * ..... ..... ..... ..... * [0,0] [0,0] [0,0] [0,0] * [0,1] [-1,0] [0,-1] [1,0] * [1,1] [-1,1] [-1,-1] [1,-1] */ this .Select(cell => new Cell(-cell.Item2, cell.Item1)) .ToPattern(); public Pattern Rotate(int count) { var p = new Pattern(this); for (var i = 0; i < count; i++) p = p.Rotate(); return p.ToPattern(); } public Pattern ReflectX() => /* ..... ..... ..oo. * ..o.. ..o.. ..o.. * ..oo. .oo.. ..... * ..... ..... ..... * [0,0] [0,0] [0,0] * [0,1] [0,1] [0,-1] * [1,1] [-1,1] [1,-1] */ this .Select(cell => new Cell((short)-cell.Item1, cell.Item2)) .ToPattern(); public Pattern ReflectY() => this .Select(cell => new Cell((short)cell.Item1, (short)-cell.Item2)) .ToPattern(); // move pattern to deterministic position public Pattern Normalize() => this.Select(c => c - GetFirstRowFirstCell()).ToPattern(); public Pattern Offset(Cell offsetCell) => this.Select(c => c + offsetCell).ToPattern(); // Subtract cells from another pattern public static Pattern operator -(Pattern p1, Pattern p2) => p1.Where(c => !p2.Contains(c)).ToPattern(); // Adds cells from another pattern public static Pattern operator +(Pattern p1, Pattern p2) => p1.Concat(p2).Distinct().ToPattern(); /// /// Extracts coordinates from text encoded pattern, treating 'char c' as cells /// public static Pattern Extract(string text, char c = 'o') => Extract(text.SplitByLine(), c); public static Pattern Extract(IEnumerable text, char c = 'o') => ExtractCells(text, c).ToPattern(); private static IEnumerable ExtractCells(IEnumerable text, char c = 'o') { short y = 0; foreach (var line in text) { for (short x = 0; x < line.Length; x++) if (line[x] == c) yield return new Cell(x, y); y++; } } public IEnumerable ToGrid(char live = 'O', char dead = '.') { var boundaryMin = GetBoundaryMin(); var boundaryMax = GetBoundaryMax(); for (var y = boundaryMin.Item2; y < boundaryMax.Item2; y++) { var s = ""; for (var x = boundaryMin.Item1; x < boundaryMax.Item1; x++) s += (Contains(new Cell(x, y)) ? live : dead) + " "; yield return s; } } public override bool Equals(object obj) { if (!(obj is Pattern pattern)) return false; return this.All(pattern.Contains) && pattern.All(this.Contains); } public override int GetHashCode() => _hashCodeCache; private readonly int _hashCodeCache; public IEnumerable FindPatterns(Projections projections) { var livingCells = new ConcurrentBag(this); var foundPatterns = new ConcurrentBag(); Parallel.ForEach(projections, projection => { foreach (var livingCell in livingCells) { var patternProjection = projection.Select(c => livingCell + c).ToArray(); var patternMatch = ContainsAll(patternProjection); if (!patternMatch) continue; // check if surrounding cells are not alive var boundaryProjection = projections.BoundaryCells[projection].Select(c => livingCell + c).ToArray(); var boundaryMatch = ContainsNone(boundaryProjection); if (boundaryMatch) foundPatterns.Add(patternProjection.ToPattern()); // shorten search time by removing these cells // from examination for other patterns foreach (var foundsCell in patternProjection) { // ReSharper disable once NotAccessedVariable var cell = new Cell(foundsCell.Item1, foundsCell.Item2); livingCells.TryTake(out cell); } } }); return foundPatterns; } public IEnumerable FindPatterns_Serial(Projections projections) { var cells = new List(this); var foundPatterns = new ConcurrentBag(); foreach (var projection in projections) { foreach (var livingCell in cells) { var patternProjection = projection.Select(c => livingCell + c).ToArray(); var patternMatch = ContainsAll(patternProjection); if (!patternMatch) continue; // check if surrounding cells are dead var boundaryProjection = projections.BoundaryCells[projection].Select(c => livingCell + c).ToArray(); var boundaryMatch = ContainsNone(boundaryProjection); if (boundaryMatch) foundPatterns.Add(patternProjection.ToPattern()); // shorten search time by removing these cells // from examination for other patterns //foreach (var foundsCell in patternProjection) //{ // // ReSharper disable once NotAccessedVariable // //var cell = new Cell(foundsCell.Item1, foundsCell.Item2); // cells.Remove(foundsCell); //} } } return foundPatterns; } public IEnumerable FindPattern(Pattern pattern) { return FindPatterns(new Projections(pattern)); } public override string ToString() { return string.Join(", ", this); } } }