Initial commit

This commit is contained in:
2026-05-07 03:23:56 +00:00
commit 5e8575f42a
42 changed files with 2330 additions and 0 deletions
+236
View File
@@ -0,0 +1,236 @@
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);
}
}
}