Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
</startup>
|
||||
</configuration>
|
||||
@@ -0,0 +1,101 @@
|
||||
Name,Apgcode
|
||||
Block,xs4_33
|
||||
Blinker,xp2_7
|
||||
Beehive,xs6_696
|
||||
Glider,xq4_153
|
||||
Loaf,xs7_2596
|
||||
Boat,xs5_253
|
||||
Ship,xs6_356
|
||||
Tub,xs4_252
|
||||
Pond,xs8_6996
|
||||
Long boat,xs7_25ac
|
||||
Toad,xp2_7e
|
||||
Ship-tie,xs12_g8o653z11
|
||||
Beacon,xp2_318c
|
||||
Barge,xs6_25a4
|
||||
Half-bakery,xs14_g88m952z121
|
||||
Mango,xs8_69ic
|
||||
Eater 1,xs7_178c
|
||||
Lightweight spaceship,xq4_6frc
|
||||
Long barge,xs8_25ak8
|
||||
Aircraft carrier,xs6_39c
|
||||
Pulsar,xp3_co9nas0san9oczgoldlo0oldlogz1047210127401
|
||||
Paperclip,xs14_69bqic
|
||||
Middleweight spaceship,xq4_27dee6
|
||||
Long ship,xs8_35ac
|
||||
Integral sign,xs9_31ego
|
||||
Shillelagh,xs8_3pm
|
||||
Boat-tie,xs10_g8o652z01
|
||||
Snake,xs6_bd
|
||||
Big S,xs14_g88b96z123
|
||||
Bipond,xs16_g88m996z1221
|
||||
Trans-boat with tail,xs9_178ko
|
||||
Boat tie ship,xs11_g8o652z11
|
||||
Hat,xs9_4aar
|
||||
Very long ship,xs10_35ako
|
||||
Heavyweight spaceship,xq4_27deee6
|
||||
Very long boat,xs9_25ako
|
||||
Tub with tail,xs8_178k8
|
||||
Mirrored table,xs12_raar
|
||||
Dead spark coil,xs18_rhe0ehr
|
||||
Canoe,xs8_312ko
|
||||
Beehive on dock,xs16_j1u0696z11
|
||||
Cis-mirrored bun,xs14_6970796
|
||||
Moose antlers,xs15_354cgc453
|
||||
Block on table,xs10_32qr
|
||||
Block on dock,xs14_j1u066z11
|
||||
Scorpion,xs16_69egmiczx1
|
||||
Beehive with tail,xs10_178kk8
|
||||
Twin hat,xs17_2ege1ege2
|
||||
Loop,xs10_69ar
|
||||
Long snake,xs7_3lo
|
||||
Fourteener,xs14_69bo8a6
|
||||
Pentadecathlon,xp15_4r4z4r4
|
||||
Cis-mirrored bookend,xs14_39e0e93
|
||||
Cis-boat with tail,xs9_178kc
|
||||
Cis-rotated bookend,xs14_6is079c
|
||||
Elevener,xs11_g0s453z11
|
||||
Mirrored dock,xs20_3lkkl3z32w23
|
||||
Block on cap,xs12_330f96
|
||||
Trans-loaf with tail,xs11_ggm952z1
|
||||
Cis-shillelagh,xs10_358gkc
|
||||
Trans-mirrored bun,xs14_69e0eic
|
||||
Clock,xp2_2a54
|
||||
Trans-block on long bookend,xs12_330fho
|
||||
Block-laying switch engine,yl144_1_16_afb5f3db909e60548f086e22ee3353ac
|
||||
Prodigal,xs10_g0s252z11
|
||||
Broken snake,xs10_0drz32
|
||||
Trans-bookend and bun,xs14_39e0eic
|
||||
Eater with nine,xs12_178c453
|
||||
Block on cover,xs12_178br
|
||||
Cis-boat on dock,xs15_j1u06a4z11
|
||||
Cis-block on long bookend,xs12_3hu066
|
||||
Very long snake,xs8_31248c
|
||||
Boat with long tail,xs10_3215ac
|
||||
Long shillelagh,xs9_312453
|
||||
Beehive at loaf,xs13_g88m96z121
|
||||
Trans-bun and wing,xs15_259e0eic
|
||||
Long integral,xs10_3542ac
|
||||
Tub with long tail,xs9_25a84c
|
||||
Cis-bookend and bun,xs14_39e0e96
|
||||
Hook with tail,xs8_32qk
|
||||
Loaf siamese loaf,xs11_69lic
|
||||
Long canoe,xs9_g0g853z11
|
||||
Eleven loop,xs11_178jd
|
||||
Trans-loaf on table,xs13_4a960ui
|
||||
Cis-loaf with tail,xs11_178kic
|
||||
Symmetric scorpion,xs16_69bob96
|
||||
Claw with tail,xs10_1784ko
|
||||
Bee hat,xs15_3lkm96z01
|
||||
Cis-mirrored dove,xs18_69is0si96
|
||||
Trans-rotated bun,xs14_g8o0e96z121
|
||||
Glider-producing switch engine,yl384_1_59_7aeb1999980c43b4945fb7fcdb023326
|
||||
Cis-mirrored wing,xs16_259e0e952
|
||||
Trans-snake on bun,xs13_69e0mq
|
||||
Boat tie eater tail,xs12_256o8a6
|
||||
Snorkel loop,xs12_2egm93
|
||||
Beehive on table,xs12_6960ui
|
||||
Cis-boat on table,xs11_2530f9
|
||||
Trans-barge with tail,xs10_ggka52z1
|
||||
Trans-boat on dock,xs15_3lk453z121
|
||||
Beehive on cap,xs14_6960uic
|
||||
|
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GameOfLife.Entities
|
||||
{
|
||||
public class Cell : Tuple<short, short>
|
||||
{
|
||||
public Cell(short x, short y) : base(x, y)
|
||||
{
|
||||
}
|
||||
public Cell(short x, int y) : base(x, (short)y) { }
|
||||
public Cell(int x, short y) : base((short)x, y) { }
|
||||
public Cell(int x, int y) : base((short)x, (short)y) { }
|
||||
|
||||
public static Cell operator +(Cell cell1, Cell cell2)
|
||||
=> new Cell(cell1.Item1 + cell2.Item1, cell1.Item2 + cell2.Item2);
|
||||
|
||||
public static Cell operator -(Cell cell1, Cell cell2)
|
||||
=> new Cell(cell1.Item1 - cell2.Item1, cell1.Item2 - cell2.Item2);
|
||||
|
||||
public Cell Negate()
|
||||
=> new Cell(-Item1, -Item2);
|
||||
|
||||
public IEnumerable<Cell> NeighborCells => new []
|
||||
{
|
||||
new Cell(Item1 - 1, Item2 - 1), new Cell(Item1 - 1, Item2), new Cell(Item1 - 1, Item2 + 1),
|
||||
new Cell(Item1, Item2 - 1), new Cell(Item1, Item2 + 1),
|
||||
new Cell(Item1 + 1, Item2 - 1), new Cell(Item1 + 1, Item2), new Cell(Item1 + 1, Item2 + 1),
|
||||
};
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{Item1}, {Item2}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GameOfLife.Entities
|
||||
{
|
||||
public class Matcher
|
||||
{
|
||||
private Cell Key;
|
||||
private Cell Left;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace GameOfLife.Entities
|
||||
{
|
||||
public enum PatternType
|
||||
{
|
||||
StillLife,
|
||||
Oscillator,
|
||||
Spaceship,
|
||||
Periodic,
|
||||
Methuselah,
|
||||
Diehard,
|
||||
Megasized,
|
||||
//Oversized,
|
||||
//Chaotic
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace GameOfLife.Entities
|
||||
{
|
||||
public class Projections : ReadOnlyCollection<Pattern>
|
||||
{
|
||||
public Dictionary<Pattern, Pattern> BoundaryCells { get; }
|
||||
|
||||
public Projections(IEnumerable<Pattern> pattern)
|
||||
: base(pattern.SelectMany(GeneratePatterns).Distinct().ToList())
|
||||
{
|
||||
BoundaryCells = this.ToDictionary(p => p, p => p.GetBoundary);
|
||||
}
|
||||
|
||||
public Projections(Pattern pattern) : this(new[] { pattern }) { }
|
||||
|
||||
private static List<Pattern> GeneratePatterns(Pattern pattern) =>
|
||||
new[]
|
||||
{
|
||||
pattern.Normalize(),
|
||||
pattern.Rotate(1).Normalize(),
|
||||
pattern.Rotate(2).Normalize(),
|
||||
pattern.Rotate(3).Normalize(),
|
||||
pattern.ReflectX().Normalize(),
|
||||
pattern.ReflectY().Normalize(),
|
||||
pattern.ReflectX().Rotate().Normalize(),
|
||||
pattern.ReflectY().Rotate().Normalize()
|
||||
}.Distinct().ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GameOfLife</RootNamespace>
|
||||
<AssemblyName>GameOfLife</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Entities\Cell.cs" />
|
||||
<Compile Include="Entities\Class1.cs" />
|
||||
<Compile Include="PatternLibrary.cs" />
|
||||
<Compile Include="Entities\PatternType.cs" />
|
||||
<Compile Include="IO\ApgcodeDecoder.cs" />
|
||||
<Compile Include="IO\ApgcodePatternMetaData.cs" />
|
||||
<Compile Include="IO\RlePatternMetaData.cs" />
|
||||
<Compile Include="Neighborhood.cs" />
|
||||
<Compile Include="Search\Node.cs" />
|
||||
<Compile Include="Util.cs" />
|
||||
<Compile Include="LifeBase.cs" />
|
||||
<Compile Include="LifeArray.cs" />
|
||||
<Compile Include="LifeHashSet.cs" />
|
||||
<Compile Include="ILife.cs" />
|
||||
<Compile Include="Entities\Pattern.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="IO\PatternMetadata.cs" />
|
||||
<Compile Include="IO\RleDecoder.cs" />
|
||||
<Compile Include="Entities\Projections.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
<EmbeddedResource Include="CommonPatterns.csv" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
@@ -0,0 +1,176 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public interface ILife
|
||||
{
|
||||
int Generation { get; }
|
||||
int Population { get; }
|
||||
bool IsCellAlive(Cell cell);
|
||||
bool AllAlive(IEnumerable<Cell> cells);
|
||||
bool AllDead(IEnumerable<Cell> cells);
|
||||
bool ToggleCell(Cell cell);
|
||||
void IncrementGeneration();
|
||||
IEnumerable<Cell> LivingCells { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife.IO
|
||||
{
|
||||
// https://conwaylife.com/wiki/Apgcode
|
||||
public class ApgcodeDecoder
|
||||
{
|
||||
private static readonly Regex PrefixRegex = new Regex(@"(?<header>.+(?=_))?_?(?<code>.+)");
|
||||
private static readonly Regex ExpansionRegex = new Regex(@"y(\w+)");
|
||||
public Pattern Pattern { get; }
|
||||
public PatternMetadata Metadata { get; }
|
||||
|
||||
public ApgcodeDecoder(string apgcode, string name = null)
|
||||
{
|
||||
var match = PrefixRegex.Match(apgcode);
|
||||
if (!match.Success)
|
||||
throw new ArgumentException("Invalid apgcode");
|
||||
var header = match.Groups["header"].Value;
|
||||
var code = match.Groups["code"].Value;
|
||||
if (!string.IsNullOrEmpty(header))
|
||||
Metadata = new ApgcodePatternMetaData(header,name);
|
||||
|
||||
var expanded = ExpandAbbreviations(code);
|
||||
Pattern = DecodeCells(expanded).ToPattern();
|
||||
}
|
||||
|
||||
private IEnumerable<Cell> DecodeCells(string expanded)
|
||||
{
|
||||
var rowNum = 0;
|
||||
foreach (var strip in expanded.Split('z'))
|
||||
{
|
||||
var colBits = strip.Select(ConvertToUint).ToArray();
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
var colNum = 0;
|
||||
foreach (var colBit in colBits)
|
||||
{
|
||||
if ((colBit >> i & 0b_1) == 1)
|
||||
yield return new Cell(colNum, rowNum);
|
||||
colNum++;
|
||||
}
|
||||
|
||||
rowNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExpandAbbreviations(string apgcode)
|
||||
{
|
||||
apgcode = apgcode.Replace("w", "00");
|
||||
apgcode = apgcode.Replace("x", "000");
|
||||
apgcode = ExpansionRegex.Replace(apgcode, m =>
|
||||
new string('0', 4 + Convert.ToInt32(ConvertToUint(m.Groups[1].Value[0]))));
|
||||
return apgcode;
|
||||
}
|
||||
|
||||
public static uint ConvertToUint(char c)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return (uint)(c - '0');
|
||||
if (c >= 'a' && c <= 'z')
|
||||
return (uint)(c - 'a' + 10);
|
||||
throw new Exception("Cannot convert char " + c.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife.IO
|
||||
{
|
||||
public class ApgcodePatternMetaData : PatternMetadata
|
||||
{
|
||||
private static readonly Regex HeaderRegex = new Regex(@"(?<prefix>\w{2})(?<suffix>.*)");
|
||||
public ApgcodePatternMetaData(string header, string name=null)
|
||||
{
|
||||
if (name != null)
|
||||
Name = name;
|
||||
var match = HeaderRegex.Match(header);
|
||||
Type = Prefix(match.Groups["prefix"].Value);
|
||||
Comments = new [] { match.Groups["suffix"].Value };
|
||||
}
|
||||
|
||||
private PatternType Prefix(string prefix)
|
||||
{
|
||||
switch (prefix)
|
||||
{
|
||||
case "xs": return PatternType.StillLife;
|
||||
case "xp": return PatternType.Oscillator;
|
||||
case "xq": return PatternType.Spaceship;
|
||||
case "yl": return PatternType.Periodic;
|
||||
case "methuselah" : return PatternType.Methuselah;
|
||||
case "messless" : return PatternType.Diehard;
|
||||
case "megasized" : return PatternType.Megasized;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown prefix: {prefix}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife.IO
|
||||
{
|
||||
public abstract class PatternMetadata
|
||||
{
|
||||
public string Name { protected set; get; }
|
||||
public PatternType Type { get; protected set; }
|
||||
public string Rules { get; protected set; }
|
||||
public string[] Comments { get; protected set; }
|
||||
public int Width { get; protected set; }
|
||||
public int Height { get; protected set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife.IO
|
||||
{
|
||||
public class RleDecoder
|
||||
{
|
||||
public Pattern Pattern { get; }
|
||||
public PatternMetadata Metadata { get; }
|
||||
|
||||
public RleDecoder(string filename) : this(new FileInfo(filename)) { }
|
||||
|
||||
public RleDecoder(FileSystemInfo fi) : this(GetLines(fi)) { }
|
||||
|
||||
public RleDecoder(IEnumerable<string> encodedRle)
|
||||
{
|
||||
var groups =
|
||||
(from line in encodedRle
|
||||
let trimmed = line.Trim()
|
||||
where
|
||||
!string.IsNullOrEmpty(trimmed)
|
||||
group line by line.StartsWith("#") into g
|
||||
select g
|
||||
).ToArray();
|
||||
|
||||
var comments = groups.First(g => g.Key);
|
||||
var nonComments = groups.First(g => !g.Key).ToArray();
|
||||
|
||||
Metadata = new RlePatternMetaData(nonComments.First(), comments);
|
||||
var rleData = string.Join("", nonComments.Skip(1));
|
||||
Pattern = Pattern.Extract(ExpandRle(rleData).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns RLE encoded string into decoded expansion, breaking lines into an enumeration
|
||||
/// </summary>
|
||||
private static IEnumerable<string> ExpandRle(string rleData)
|
||||
{
|
||||
if (!rleData.Contains("!"))
|
||||
throw new ArgumentException("RLE pattern did not contain terminating character '!'");
|
||||
|
||||
var encodedRle
|
||||
= rleData.Substring(0, rleData.IndexOf("!", StringComparison.Ordinal));
|
||||
|
||||
var rleDecodingRegex = new Regex(@"(?<count>\d*)(?<char>[a-z$])");
|
||||
|
||||
var matches = rleDecodingRegex.Matches(encodedRle);
|
||||
var decoded = string.Empty;
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
var countStr = match.Groups["count"].Value;
|
||||
var count = string.IsNullOrEmpty(countStr) ? 1 : int.Parse(countStr);
|
||||
var c = match.Groups["char"].Value.First();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '$':
|
||||
yield return decoded;
|
||||
decoded = string.Empty;
|
||||
break;
|
||||
default:
|
||||
decoded += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
yield return decoded;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetLines(FileSystemInfo fi)
|
||||
{
|
||||
if (!fi.Exists)
|
||||
throw new FileNotFoundException(fi.FullName);
|
||||
return File.ReadAllLines(fi.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GameOfLife.IO
|
||||
{
|
||||
public class RlePatternMetaData : PatternMetadata
|
||||
{
|
||||
public RlePatternMetaData(string line, IEnumerable<string> comments)
|
||||
{
|
||||
Comments = comments.ToArray();
|
||||
|
||||
var mdLine = Regex.Replace(line, @"\s+", "");
|
||||
var props = mdLine.Split(',');
|
||||
if (props.Length < 2)
|
||||
throw new ArgumentException("Line does not contain two properties");
|
||||
|
||||
var mp = new Regex(@"(\w+)=(.*)");
|
||||
foreach (var prop in props)
|
||||
{
|
||||
var capture = mp.Match(prop).Groups;
|
||||
var name = capture[1].Value;
|
||||
var val = capture[2].Value;
|
||||
switch (name)
|
||||
{
|
||||
case "x":
|
||||
Width = int.Parse(val);
|
||||
break;
|
||||
case "y":
|
||||
Height = int.Parse(val);
|
||||
break;
|
||||
// TODO: decode the rules
|
||||
case "rules":
|
||||
Rules = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public class LifeArray : LifeBase
|
||||
{
|
||||
protected bool[,] World;
|
||||
private bool[,] _nextGeneration;
|
||||
|
||||
//private Task _processTask;
|
||||
public int SizeX { get; private set; }
|
||||
public int SizeY { get; private set; }
|
||||
|
||||
public override bool IsCellAlive(Cell cell)
|
||||
{
|
||||
return IsCellAlive(cell.Item1, cell.Item2);
|
||||
}
|
||||
|
||||
public override bool IsCellAlive(short x, short y)
|
||||
{
|
||||
// first check boundaries
|
||||
if (x < 0 || x >= SizeX || y < 0 || y >= SizeY)
|
||||
return false;
|
||||
return IsCellAlive_NoBoundaryCheck(x, y);
|
||||
}
|
||||
|
||||
private bool IsCellAlive_NoBoundaryCheck(short x, short y) => World[x, y];
|
||||
|
||||
public LifeArray(Pattern pattern)
|
||||
: this(pattern.GetBoundaryMax().Item1, pattern.GetBoundaryMax().Item2)
|
||||
{
|
||||
foreach (var cell in pattern)
|
||||
{
|
||||
ToggleCell(cell);
|
||||
}
|
||||
}
|
||||
|
||||
public LifeArray(Cell sizes) : this (sizes.Item1, sizes.Item2) { }
|
||||
|
||||
public LifeArray(short sizeX, short sizeY)
|
||||
{
|
||||
if (sizeX <= 0 || sizeY <= 0) throw new ArgumentOutOfRangeException("sizeX", "Size must be greater than zero");
|
||||
SizeX = sizeX;
|
||||
SizeY = sizeY;
|
||||
World = new bool[sizeX, sizeY];
|
||||
_nextGeneration = new bool[sizeX, sizeY];
|
||||
}
|
||||
|
||||
protected override bool ToggleCell_Internal(Cell cell)
|
||||
{
|
||||
return World[cell.Item1, cell.Item2] = !World[cell.Item1, cell.Item2];
|
||||
}
|
||||
|
||||
protected override int IncrementGeneration_Internal()
|
||||
{
|
||||
for (short x = 0; x < SizeX; x++)
|
||||
for (short y = 0; y < SizeY; y++)
|
||||
{
|
||||
var shouldLive = ShouldLive(x, y);
|
||||
_nextGeneration[x, y] = shouldLive;
|
||||
}
|
||||
|
||||
// now flip the back buffer so we can start processing on the next generation
|
||||
var flip = _nextGeneration;
|
||||
_nextGeneration = World;
|
||||
World = flip;
|
||||
|
||||
return Population;
|
||||
}
|
||||
|
||||
public override IEnumerable<Cell> LivingCells
|
||||
{
|
||||
get
|
||||
{
|
||||
for (short y=0; y<SizeY;y++)
|
||||
for (short x =0; x<SizeX;x++)
|
||||
if (IsCellAlive_NoBoundaryCheck(x, y))
|
||||
yield return new Cell(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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 AllAlive(IEnumerable<Cell> cells) => cells.All(IsCellAlive);
|
||||
public bool AllDead(IEnumerable<Cell> cells) => cells.All(c => !IsCellAlive(c));
|
||||
|
||||
private IReadOnlyList<Tuple<Cell, short>> _neighborhoodField;
|
||||
|
||||
public IReadOnlyList<Tuple<Cell, short>> NeighborhoodField =>
|
||||
// cache values this expensive operation
|
||||
_neighborhoodField ?? (_neighborhoodField = Neighborhood.GetNeighborField(LivingCells).ToList());
|
||||
|
||||
private Pattern _livingCellPattern;
|
||||
|
||||
public Pattern LivingCellPattern =>
|
||||
_livingCellPattern ?? (_livingCellPattern = LivingCells.ToPattern());
|
||||
|
||||
public bool ToggleCell(Cell cell)
|
||||
{
|
||||
var alive = ToggleCell_Internal(cell);
|
||||
Population += alive ? 1 : -1;
|
||||
return alive;
|
||||
}
|
||||
|
||||
protected abstract bool ToggleCell_Internal(Cell cell);
|
||||
|
||||
public void IncrementGeneration()
|
||||
{
|
||||
_neighborhoodField = null;
|
||||
_livingCellPattern = null;
|
||||
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 LifeRule(isAlive, numberOfNeighbors);
|
||||
}
|
||||
|
||||
protected bool LifeRule(bool isAlive, int neighborCount)
|
||||
{
|
||||
switch (isAlive)
|
||||
{
|
||||
case false
|
||||
when neighborCount == 3:
|
||||
case true
|
||||
when neighborCount == 2
|
||||
|| neighborCount == 3:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public class LifeHashSet : LifeBase
|
||||
{
|
||||
protected HashSet<Cell> World;
|
||||
|
||||
public LifeHashSet()
|
||||
{
|
||||
World = new HashSet<Cell>();
|
||||
}
|
||||
|
||||
public LifeHashSet(IEnumerable<Cell> cells) : this()
|
||||
{
|
||||
foreach (var cell in cells)
|
||||
World.Add(cell);
|
||||
}
|
||||
|
||||
public override bool IsCellAlive(Cell cell) => World.Contains(cell);
|
||||
|
||||
protected override bool ToggleCell_Internal(Cell cell)
|
||||
{
|
||||
if (IsCellAlive(cell))
|
||||
{
|
||||
World.Remove(cell);
|
||||
return false;
|
||||
}
|
||||
|
||||
World.Add(cell);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override int IncrementGeneration_Internal()
|
||||
{
|
||||
World = GetNextGeneration_Parallel(NeighborhoodField);
|
||||
return World.Count;
|
||||
}
|
||||
|
||||
private HashSet<Cell> GetNextGeneration_Serial(IEnumerable<Tuple<Cell, short>> field)
|
||||
{
|
||||
return
|
||||
new HashSet<Cell>(
|
||||
field.Where(
|
||||
cellNeighborCount =>
|
||||
LifeRule(World.Contains(cellNeighborCount.Item1), cellNeighborCount.Item2))
|
||||
.Select(c => c.Item1));
|
||||
}
|
||||
|
||||
private HashSet<Cell> GetNextGeneration_Parallel(IEnumerable<Tuple<Cell, short>> field)
|
||||
{
|
||||
var nextGeneration = new HashSet<Cell>();
|
||||
|
||||
Parallel.ForEach(
|
||||
field,
|
||||
() => new HashSet<Cell>(),
|
||||
(cellNeighborCount, loop, localNextGen) =>
|
||||
{
|
||||
var cell = cellNeighborCount.Item1;
|
||||
var neighborCount = cellNeighborCount.Item2;
|
||||
|
||||
if (LifeRule(World.Contains(cell), neighborCount))
|
||||
localNextGen.Add(cell);
|
||||
|
||||
return localNextGen;
|
||||
},
|
||||
globalNextGen =>
|
||||
{
|
||||
lock (nextGeneration)
|
||||
foreach (var cell in globalNextGen)
|
||||
nextGeneration.Add(cell);
|
||||
}
|
||||
);
|
||||
return nextGeneration;
|
||||
}
|
||||
|
||||
public override IEnumerable<Cell> LivingCells => World;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public static class Neighborhood
|
||||
{
|
||||
public static Tuple<Cell, short>[] GetNeighborField(IEnumerable<Cell> world)
|
||||
{
|
||||
var neighbors = new List<Cell>();
|
||||
|
||||
Parallel.ForEach(
|
||||
world,
|
||||
() => new List<Cell>(),
|
||||
(cell, loop, localNeighbors) =>
|
||||
{
|
||||
localNeighbors.AddRange(cell.NeighborCells);
|
||||
return localNeighbors;
|
||||
},
|
||||
globalNeighbors =>
|
||||
{
|
||||
lock(neighbors)
|
||||
neighbors.AddRange(globalNeighbors);
|
||||
});
|
||||
|
||||
var neighborCount =
|
||||
neighbors
|
||||
.GroupBy(n => n)
|
||||
.Select(n => Tuple.Create(n.Key, (short)n.Count()))
|
||||
.ToArray();
|
||||
|
||||
return neighborCount;
|
||||
}
|
||||
|
||||
public static Tuple<Cell, short>[] GetNeighborField_Serial(IEnumerable<Cell> world)
|
||||
{
|
||||
var neighborCount = new Dictionary<Cell, short>();
|
||||
|
||||
foreach (var cell in world)
|
||||
{
|
||||
foreach (var neighbor in cell.NeighborCells)
|
||||
{
|
||||
if (!neighborCount.ContainsKey(neighbor))
|
||||
neighborCount[neighbor] = 1;
|
||||
else
|
||||
neighborCount[neighbor] += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return neighborCount.Select(pair => Tuple.Create(pair.Key, pair.Value)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using GameOfLife.Entities;
|
||||
using GameOfLife.IO;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public class PatternLibrary
|
||||
{
|
||||
public const int PatternLoadLimit = 100;
|
||||
public const int OscillationMaxPeriod = 50;
|
||||
|
||||
public IReadOnlyList<Tuple<PatternMetadata, Projections>> PatternProjections;
|
||||
public IReadOnlyList<Tuple<PatternMetadata, Pattern>> Patterns;
|
||||
public IReadOnlyDictionary<Pattern, PatternMetadata> PatternToMetadata;
|
||||
|
||||
public PatternLibrary()
|
||||
{
|
||||
Patterns = ExtractPatterns(GetPatternFileLines()).AsReadOnly();
|
||||
PatternProjections = ExtractOscillationsAndProjections(Patterns);
|
||||
PatternToMetadata = Patterns.ToDictionary(t => t.Item2, t => t.Item1);
|
||||
}
|
||||
|
||||
private static List<Tuple<PatternMetadata, Projections>> ExtractOscillationsAndProjections(IEnumerable<Tuple<PatternMetadata, Pattern>> patterns)
|
||||
{
|
||||
var results =
|
||||
from pattern in patterns.AsParallel()
|
||||
let life = new LifeHashSet(pattern.Item2)
|
||||
let oscillation = life.FindOscillation(OscillationMaxPeriod)
|
||||
let variations = new Projections(oscillation)
|
||||
select Tuple.Create(pattern.Item1, variations);
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
||||
private static List<Tuple<PatternMetadata, Pattern>> ExtractPatterns(IEnumerable<string> fileLines)
|
||||
{
|
||||
// first line is the header
|
||||
var pattern =
|
||||
from line in fileLines.Skip(1).Take(PatternLoadLimit)
|
||||
select line.Split(',')
|
||||
into split
|
||||
let name = split[0].Trim()
|
||||
let apgCode = split[1].Trim()
|
||||
select new ApgcodeDecoder(apgCode, name)
|
||||
into decoder
|
||||
select Tuple.Create(decoder.Metadata, decoder.Pattern.Normalize());
|
||||
|
||||
|
||||
var extractPatterns = pattern.ToList();
|
||||
|
||||
// ensure this patterns are all unique
|
||||
var duplicates =
|
||||
string.Join(", ",
|
||||
extractPatterns
|
||||
.GroupBy(tuple => tuple)
|
||||
.Where(tuples => tuples.Count() > 1)
|
||||
.Select(t => t.Key.Item1.Name));
|
||||
if (duplicates.Length > 0)
|
||||
throw new Exception($"Duplicate patterns: " + duplicates);
|
||||
|
||||
return extractPatterns;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetPatternFileLines()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
//Getting names of all embedded resources
|
||||
var allResourceNames = assembly.GetManifestResourceNames();
|
||||
|
||||
var resourceName = allResourceNames.First(n => n.Contains("CommonPatterns.csv"));
|
||||
var fileLines = new List<string>();
|
||||
using (var stream = assembly.GetManifestResourceStream(resourceName))
|
||||
if (stream != null)
|
||||
using (var tx = new StreamReader(stream))
|
||||
{
|
||||
while (!tx.EndOfStream)
|
||||
fileLines.Add(tx.ReadLine());
|
||||
}
|
||||
|
||||
return fileLines;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GameOfLife")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("GameOfLife")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("0bea14a8-8e11-4a18-bd7d-628d3f1561b0")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Threading.Tasks;
|
||||
using GameOfLife.Entities;
|
||||
using GameOfLife.IO;
|
||||
|
||||
namespace GameOfLife.Search
|
||||
{
|
||||
public class LibrarySearcher
|
||||
{
|
||||
private readonly PatternLibrary _patternLibrary;
|
||||
private Dictionary<Cell, List<Pattern>> _cellPatterns;
|
||||
|
||||
public LibrarySearcher(PatternLibrary patternLibrary)
|
||||
{
|
||||
_patternLibrary = patternLibrary;
|
||||
|
||||
var cellPatterns =
|
||||
from patternProjections in _patternLibrary.PatternProjections
|
||||
from projection in patternProjections.Item2
|
||||
from cell in projection
|
||||
group projection by cell into cellGroups
|
||||
select cellGroups;
|
||||
|
||||
_cellPatterns = cellPatterns.ToDictionary(t => t.Key, t => t.ToList());
|
||||
}
|
||||
|
||||
//public IEnumerable<Tuple<PatternMetadata, Pattern[]>> MatchLibraryPatternsLookup(IEnumerable<Cell> cells)
|
||||
//{
|
||||
// var pattern = cells.ToPattern();
|
||||
//}
|
||||
|
||||
public IEnumerable<Tuple<PatternMetadata, Pattern[]>> MatchLibraryPatterns(IEnumerable<Cell> cells)
|
||||
{
|
||||
var inputCells = cells.ToPattern();
|
||||
|
||||
var matches = new ConcurrentBag<Tuple<PatternMetadata, Pattern[]>>();
|
||||
|
||||
Parallel.ForEach(_patternLibrary.PatternProjections, tuple =>
|
||||
{
|
||||
var patternMetadata = tuple.Item1;
|
||||
var projections = tuple.Item2;
|
||||
|
||||
var foundPatterns = inputCells.FindPatterns_Serial(projections).ToArray();
|
||||
if (foundPatterns.Any())
|
||||
matches.Add(Tuple.Create(patternMetadata, foundPatterns.ToArray()));
|
||||
}
|
||||
);
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameOfLife.Entities;
|
||||
|
||||
namespace GameOfLife
|
||||
{
|
||||
public static class Util
|
||||
{
|
||||
public static Pattern ToPattern(this IEnumerable<Cell> cells)
|
||||
{
|
||||
return new Pattern(cells.ToList());
|
||||
}
|
||||
|
||||
//https://stackoverflow.com/a/63820524
|
||||
public static IEnumerable<string> SplitByLine(this string str)
|
||||
{
|
||||
return Regex
|
||||
.Split(str, @"((\r)+)?(\n)+((\r)+)?")
|
||||
.Select(i => i.Trim())
|
||||
.Where(i => !string.IsNullOrEmpty(i));
|
||||
}
|
||||
|
||||
public static IEnumerable<Pattern> FindOscillation(this ILife gol, int maxPeriod = 50)
|
||||
{
|
||||
var list = new List<Pattern>();
|
||||
|
||||
while (gol.Generation < maxPeriod)
|
||||
{
|
||||
var currentPattern = gol.LivingCells.ToPattern().Normalize();
|
||||
if (list.Contains(currentPattern))
|
||||
return list;
|
||||
|
||||
list.Add(currentPattern);
|
||||
gol.IncrementGeneration();
|
||||
}
|
||||
|
||||
return list.Take(1); // didn't find any oscillations, return the first pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#Beehive
|
||||
x = 4, y = 3, rule = B3/S23
|
||||
b2o$o2bo$b2o!
|
||||
Reference in New Issue
Block a user