using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using GameOfLife.Entities; namespace GameOfLife { public abstract class LifeBase : ILife { public int Generation { get; protected set; } public int Population { get; protected set; } public abstract bool IsCellAlive(Cell cell); public virtual bool IsCellAlive(short x, short y) => IsCellAlive(new Cell(x,y)); public bool AreAllCellsAlive(IEnumerable cells) => cells.All(IsCellAlive); public bool AreAllCellsDead(IEnumerable cells) => cells.All(c => !IsCellAlive(c)); public bool ToggleCell(Cell cell) { var r = ToggleCell_Internal(cell); Population += r ? 1 : -1; return r; } protected abstract bool ToggleCell_Internal(Cell cell); public void IncrementGeneration() { Population = IncrementGeneration_Internal(); Generation++; } protected abstract int IncrementGeneration_Internal(); public abstract IEnumerable LivingCells { get; } public static readonly short[,] NeighborOffsets = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 }, { 0, 1 }, { 1, -1 }, { 1, 0 }, { 1, 1 }, }; public byte GetNeighborCount(short x, short y) { byte numberOfNeighbors = 0; for (var i = 0; i < 8; i++) { numberOfNeighbors += IsCellAlive((short)(x + NeighborOffsets[i,0]), (short)(y + NeighborOffsets[i,1])) ? (byte)1 : (byte)0; } return numberOfNeighbors; } protected bool ShouldLive(short x, short y) { var numberOfNeighbors = GetNeighborCount(x, y); var isAlive = IsCellAlive(x, y); return GameRule(isAlive, numberOfNeighbors); } protected bool GameRule(bool isAlive, int neighborCount) { switch (isAlive) { case false when neighborCount == (byte)3: case true when neighborCount == (byte)2 || neighborCount == (byte)3: return true; default: return false; } } public Tuple[] GetNeighborField() => GetNeighborField(LivingCells); public static Tuple[] GetNeighborField(IEnumerable world) { var neighborCount = new Dictionary(); foreach (var cell in world) { for (var i = 0; i < 8; i++) { var xOffset = LifeBase.NeighborOffsets[i, 0]; var yOffset = LifeBase.NeighborOffsets[i, 1]; var neighbor = new Cell( (cell.Item1 + xOffset), (cell.Item2 + yOffset)); if (!neighborCount.ContainsKey(neighbor)) neighborCount[neighbor] = 1; else neighborCount[neighbor] += 1; } } return neighborCount.Select(pair => Tuple.Create(pair.Key, pair.Value)).ToArray(); } protected static Tuple[] GetNeighborField_Parallel(IEnumerable world) { //var neighborCount = new Dictionary, int>(); var neighbors = new List(); Parallel.ForEach( world, () => new List(), (cell, loop, localNeighbors) => { for (var i = 0; i < 8; i++) { var neighbor = new Cell( (cell.Item1 + LifeBase.NeighborOffsets[i, 0]), (cell.Item2 + LifeBase.NeighborOffsets[i, 1])); localNeighbors.Add(neighbor); } return localNeighbors; }, finalNeighbors => { lock(neighbors) neighbors.AddRange(finalNeighbors); }); var neighborCount = neighbors .GroupBy(n => n) .Select(n => Tuple.Create(n.Key, (short) n.Count())) .ToArray(); return neighborCount; } public IEnumerable FindPattern(Pattern pattern) { var variations = new Variations(pattern); var livingCells = LivingCells.ToList(); foreach (var patternVar in variations) { var foundsCells = new List(); foreach (var livingCell in livingCells) { var patternProjection = patternVar.Select(c => livingCell + c).ToArray(); var patternMatch = AreAllCellsAlive(patternProjection); if (!patternMatch) continue; // check if surrounding cells are not alive var boundaryProjection = variations.BoundaryCells[patternVar].Select(c => livingCell + c).ToArray(); var boundaryMatch = AreAllCellsDead(boundaryProjection); if (boundaryMatch) yield return patternProjection.ToPattern(); foundsCells.AddRange(patternProjection); } livingCells.RemoveAll(c => foundsCells.Contains(c)); } } } }