Files
game-of-life/GameOfLife/GameOfLifeBase.cs
T
2026-05-07 03:23:56 +00:00

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));
}
}
}
}