176 lines
6.0 KiB
C#
176 lines
6.0 KiB
C#
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<Cell> cells) => cells.All(IsCellAlive);
|
|
public bool AreAllCellsDead(IEnumerable<Cell> 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<Cell> 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<Cell, short>[] GetNeighborField() => GetNeighborField(LivingCells);
|
|
|
|
public static Tuple<Cell, short>[] GetNeighborField(IEnumerable<Cell> world)
|
|
{
|
|
var neighborCount = new Dictionary<Cell, short>();
|
|
|
|
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<Cell, short>[] GetNeighborField_Parallel(IEnumerable<Cell> world)
|
|
{
|
|
//var neighborCount = new Dictionary<Tuple<int, int>, int>();
|
|
var neighbors = new List<Cell>();
|
|
|
|
Parallel.ForEach(
|
|
world,
|
|
() => new List<Cell>(),
|
|
(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<Pattern> FindPattern(Pattern pattern)
|
|
{
|
|
var variations = new Variations(pattern);
|
|
var livingCells = LivingCells.ToList();
|
|
foreach (var patternVar in variations)
|
|
{
|
|
var foundsCells = new List<Cell>();
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
} |