237 lines
8.6 KiB
C#
237 lines
8.6 KiB
C#
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<Cell>
|
|
{
|
|
public Pattern(IEnumerable<Cell> 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<Cell> cells) => cells.All(Contains);
|
|
public bool ContainsNone(IEnumerable<Cell> 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();
|
|
|
|
/// <summary>
|
|
/// Extracts coordinates from text encoded pattern, treating 'char c' as cells
|
|
/// </summary>
|
|
public static Pattern Extract(string text, char c = 'o')
|
|
=> Extract(text.SplitByLine(), c);
|
|
|
|
public static Pattern Extract(IEnumerable<string> text, char c = 'o')
|
|
=> ExtractCells(text, c).ToPattern();
|
|
|
|
private static IEnumerable<Cell> ExtractCells(IEnumerable<string> 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<string> 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<Pattern> FindPatterns(Projections projections)
|
|
{
|
|
var livingCells = new ConcurrentBag<Cell>(this);
|
|
var foundPatterns = new ConcurrentBag<Pattern>();
|
|
|
|
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<Pattern> FindPatterns_Serial(Projections projections)
|
|
{
|
|
var cells = new List<Cell>(this);
|
|
var foundPatterns = new ConcurrentBag<Pattern>();
|
|
|
|
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<Pattern> FindPattern(Pattern pattern)
|
|
{
|
|
return FindPatterns(new Projections(pattern));
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return string.Join(", ", this);
|
|
}
|
|
}
|
|
}
|