From 5e8575f42a153a9af47437231742c53fd24cf03c Mon Sep 17 00:00:00 2001 From: poprhythm Date: Thu, 7 May 2026 03:23:56 +0000 Subject: [PATCH] Initial commit --- .gitignore | 6 + GameOfLife.sln | 39 +++ GameOfLife/App.config | 6 + GameOfLife/CommonPatterns.csv | 101 ++++++++ GameOfLife/Entities/Cell.cs | 36 +++ GameOfLife/Entities/Class1.cs | 8 + GameOfLife/Entities/Pattern.cs | 236 ++++++++++++++++++ GameOfLife/Entities/PatternType.cs | 15 ++ GameOfLife/Entities/Projections.cs | 32 +++ GameOfLife/GameOfLife.csproj | 82 ++++++ GameOfLife/GameOfLifeBase.cs | 176 +++++++++++++ GameOfLife/ILife.cs | 17 ++ GameOfLife/IO/ApgcodeDecoder.cs | 70 ++++++ GameOfLife/IO/ApgcodePatternMetaData.cs | 35 +++ GameOfLife/IO/PatternMetadata.cs | 14 ++ GameOfLife/IO/RleDecoder.cs | 82 ++++++ GameOfLife/IO/RlePatternMetaData.cs | 41 +++ GameOfLife/LifeArray.cs | 84 +++++++ GameOfLife/LifeBase.cs | 96 +++++++ GameOfLife/LifeHashSet.cs | 83 ++++++ GameOfLife/Neighborhood.cs | 56 +++++ GameOfLife/PatternLibrary.cs | 89 +++++++ GameOfLife/Properties/AssemblyInfo.cs | 36 +++ GameOfLife/Search/Node.cs | 56 +++++ GameOfLife/Util.cs | 41 +++ GameOfLife/beehive.rle | 3 + GameOfLifeTests/ApgcodeDecoderTests.cs | 51 ++++ GameOfLifeTests/CellTests.cs | 97 +++++++ GameOfLifeTests/GameOfLifeTests.csproj | 19 ++ GameOfLifeTests/PatternLibraryTests.cs | 94 +++++++ GameOfLifeTests/PatternTestData.cs | 62 +++++ GameOfLifeTests/VariationsTests.cs | 141 +++++++++++ GolConsole/App.config | 6 + GolConsole/GolConsole.csproj | 77 ++++++ GolConsole/Patterns/ark1.rle | 3 + GolConsole/Patterns/ark2.rle | 3 + GolConsole/Patterns/blom.rle | 5 + GolConsole/Patterns/glider-stream-crystal.rle | 66 +++++ GolConsole/Patterns/glider.rle | 3 + .../Patterns/honey-farm-hassler-147.rle | 12 + GolConsole/Program.cs | 115 +++++++++ GolConsole/Properties/AssemblyInfo.cs | 36 +++ 42 files changed, 2330 insertions(+) create mode 100644 .gitignore create mode 100644 GameOfLife.sln create mode 100644 GameOfLife/App.config create mode 100644 GameOfLife/CommonPatterns.csv create mode 100644 GameOfLife/Entities/Cell.cs create mode 100644 GameOfLife/Entities/Class1.cs create mode 100644 GameOfLife/Entities/Pattern.cs create mode 100644 GameOfLife/Entities/PatternType.cs create mode 100644 GameOfLife/Entities/Projections.cs create mode 100644 GameOfLife/GameOfLife.csproj create mode 100644 GameOfLife/GameOfLifeBase.cs create mode 100644 GameOfLife/ILife.cs create mode 100644 GameOfLife/IO/ApgcodeDecoder.cs create mode 100644 GameOfLife/IO/ApgcodePatternMetaData.cs create mode 100644 GameOfLife/IO/PatternMetadata.cs create mode 100644 GameOfLife/IO/RleDecoder.cs create mode 100644 GameOfLife/IO/RlePatternMetaData.cs create mode 100644 GameOfLife/LifeArray.cs create mode 100644 GameOfLife/LifeBase.cs create mode 100644 GameOfLife/LifeHashSet.cs create mode 100644 GameOfLife/Neighborhood.cs create mode 100644 GameOfLife/PatternLibrary.cs create mode 100644 GameOfLife/Properties/AssemblyInfo.cs create mode 100644 GameOfLife/Search/Node.cs create mode 100644 GameOfLife/Util.cs create mode 100644 GameOfLife/beehive.rle create mode 100644 GameOfLifeTests/ApgcodeDecoderTests.cs create mode 100644 GameOfLifeTests/CellTests.cs create mode 100644 GameOfLifeTests/GameOfLifeTests.csproj create mode 100644 GameOfLifeTests/PatternLibraryTests.cs create mode 100644 GameOfLifeTests/PatternTestData.cs create mode 100644 GameOfLifeTests/VariationsTests.cs create mode 100644 GolConsole/App.config create mode 100644 GolConsole/GolConsole.csproj create mode 100644 GolConsole/Patterns/ark1.rle create mode 100644 GolConsole/Patterns/ark2.rle create mode 100644 GolConsole/Patterns/blom.rle create mode 100644 GolConsole/Patterns/glider-stream-crystal.rle create mode 100644 GolConsole/Patterns/glider.rle create mode 100644 GolConsole/Patterns/honey-farm-hassler-147.rle create mode 100644 GolConsole/Program.cs create mode 100644 GolConsole/Properties/AssemblyInfo.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62dfb68 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +bin/ +obj/ +.vs/ +*.user +*.suo +packages/ diff --git a/GameOfLife.sln b/GameOfLife.sln new file mode 100644 index 0000000..b6fbbf3 --- /dev/null +++ b/GameOfLife.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.33027.164 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameOfLife", "GameOfLife\GameOfLife.csproj", "{0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GolConsole", "GolConsole\GolConsole.csproj", "{8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CD87109A-F147-479C-B9C6-6FCCE53453B2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameOfLifeTests", "GameOfLifeTests\GameOfLifeTests.csproj", "{B45B230E-ED95-4524-9B54-0578E689E015}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0}.Release|Any CPU.Build.0 = Release|Any CPU + {8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD}.Release|Any CPU.Build.0 = Release|Any CPU + {B45B230E-ED95-4524-9B54-0578E689E015}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B45B230E-ED95-4524-9B54-0578E689E015}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B45B230E-ED95-4524-9B54-0578E689E015}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B45B230E-ED95-4524-9B54-0578E689E015}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E38811A1-A6A9-421C-A493-CE02CE3E0DDD} + EndGlobalSection +EndGlobal diff --git a/GameOfLife/App.config b/GameOfLife/App.config new file mode 100644 index 0000000..d740e88 --- /dev/null +++ b/GameOfLife/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GameOfLife/CommonPatterns.csv b/GameOfLife/CommonPatterns.csv new file mode 100644 index 0000000..3f09bb6 --- /dev/null +++ b/GameOfLife/CommonPatterns.csv @@ -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 \ No newline at end of file diff --git a/GameOfLife/Entities/Cell.cs b/GameOfLife/Entities/Cell.cs new file mode 100644 index 0000000..a9a2640 --- /dev/null +++ b/GameOfLife/Entities/Cell.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace GameOfLife.Entities +{ + public class Cell : Tuple + { + 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 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}]"; + } + } +} \ No newline at end of file diff --git a/GameOfLife/Entities/Class1.cs b/GameOfLife/Entities/Class1.cs new file mode 100644 index 0000000..0e6900d --- /dev/null +++ b/GameOfLife/Entities/Class1.cs @@ -0,0 +1,8 @@ +namespace GameOfLife.Entities +{ + public class Matcher + { + private Cell Key; + private Cell Left; + } +} \ No newline at end of file diff --git a/GameOfLife/Entities/Pattern.cs b/GameOfLife/Entities/Pattern.cs new file mode 100644 index 0000000..0e55660 --- /dev/null +++ b/GameOfLife/Entities/Pattern.cs @@ -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 + { + public Pattern(IEnumerable 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 cells) => cells.All(Contains); + public bool ContainsNone(IEnumerable 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(); + + /// + /// Extracts coordinates from text encoded pattern, treating 'char c' as cells + /// + public static Pattern Extract(string text, char c = 'o') + => Extract(text.SplitByLine(), c); + + public static Pattern Extract(IEnumerable text, char c = 'o') + => ExtractCells(text, c).ToPattern(); + + private static IEnumerable ExtractCells(IEnumerable 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 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 FindPatterns(Projections projections) + { + var livingCells = new ConcurrentBag(this); + var foundPatterns = new ConcurrentBag(); + + 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 FindPatterns_Serial(Projections projections) + { + var cells = new List(this); + var foundPatterns = new ConcurrentBag(); + + 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 FindPattern(Pattern pattern) + { + return FindPatterns(new Projections(pattern)); + } + + public override string ToString() + { + return string.Join(", ", this); + } + } +} diff --git a/GameOfLife/Entities/PatternType.cs b/GameOfLife/Entities/PatternType.cs new file mode 100644 index 0000000..9b44631 --- /dev/null +++ b/GameOfLife/Entities/PatternType.cs @@ -0,0 +1,15 @@ +namespace GameOfLife.Entities +{ + public enum PatternType + { + StillLife, + Oscillator, + Spaceship, + Periodic, + Methuselah, + Diehard, + Megasized, + //Oversized, + //Chaotic + } +} \ No newline at end of file diff --git a/GameOfLife/Entities/Projections.cs b/GameOfLife/Entities/Projections.cs new file mode 100644 index 0000000..08cfbde --- /dev/null +++ b/GameOfLife/Entities/Projections.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace GameOfLife.Entities +{ + public class Projections : ReadOnlyCollection + { + public Dictionary BoundaryCells { get; } + + public Projections(IEnumerable 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 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(); + } +} \ No newline at end of file diff --git a/GameOfLife/GameOfLife.csproj b/GameOfLife/GameOfLife.csproj new file mode 100644 index 0000000..e4b7065 --- /dev/null +++ b/GameOfLife/GameOfLife.csproj @@ -0,0 +1,82 @@ + + + + + Debug + AnyCPU + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0} + Library + Properties + GameOfLife + GameOfLife + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GameOfLife/GameOfLifeBase.cs b/GameOfLife/GameOfLifeBase.cs new file mode 100644 index 0000000..91420cb --- /dev/null +++ b/GameOfLife/GameOfLifeBase.cs @@ -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 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)); + } + } + } +} \ No newline at end of file diff --git a/GameOfLife/ILife.cs b/GameOfLife/ILife.cs new file mode 100644 index 0000000..dca1846 --- /dev/null +++ b/GameOfLife/ILife.cs @@ -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 cells); + bool AllDead(IEnumerable cells); + bool ToggleCell(Cell cell); + void IncrementGeneration(); + IEnumerable LivingCells { get; } + } +} \ No newline at end of file diff --git a/GameOfLife/IO/ApgcodeDecoder.cs b/GameOfLife/IO/ApgcodeDecoder.cs new file mode 100644 index 0000000..b880383 --- /dev/null +++ b/GameOfLife/IO/ApgcodeDecoder.cs @@ -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(@"(?
.+(?=_))?_?(?.+)"); + 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 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()); + } + } +} \ No newline at end of file diff --git a/GameOfLife/IO/ApgcodePatternMetaData.cs b/GameOfLife/IO/ApgcodePatternMetaData.cs new file mode 100644 index 0000000..20e8789 --- /dev/null +++ b/GameOfLife/IO/ApgcodePatternMetaData.cs @@ -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(@"(?\w{2})(?.*)"); + 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}"); + } + } + } +} \ No newline at end of file diff --git a/GameOfLife/IO/PatternMetadata.cs b/GameOfLife/IO/PatternMetadata.cs new file mode 100644 index 0000000..7c540db --- /dev/null +++ b/GameOfLife/IO/PatternMetadata.cs @@ -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; } + } +} \ No newline at end of file diff --git a/GameOfLife/IO/RleDecoder.cs b/GameOfLife/IO/RleDecoder.cs new file mode 100644 index 0000000..9140034 --- /dev/null +++ b/GameOfLife/IO/RleDecoder.cs @@ -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 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()); + } + + /// + /// Returns RLE encoded string into decoded expansion, breaking lines into an enumeration + /// + private static IEnumerable 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(@"(?\d*)(?[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 GetLines(FileSystemInfo fi) + { + if (!fi.Exists) + throw new FileNotFoundException(fi.FullName); + return File.ReadAllLines(fi.FullName); + } + } +} \ No newline at end of file diff --git a/GameOfLife/IO/RlePatternMetaData.cs b/GameOfLife/IO/RlePatternMetaData.cs new file mode 100644 index 0000000..64f85ba --- /dev/null +++ b/GameOfLife/IO/RlePatternMetaData.cs @@ -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 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; + } + } + } + } +} \ No newline at end of file diff --git a/GameOfLife/LifeArray.cs b/GameOfLife/LifeArray.cs new file mode 100644 index 0000000..aac8825 --- /dev/null +++ b/GameOfLife/LifeArray.cs @@ -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 LivingCells + { + get + { + for (short y=0; y IsCellAlive(new Cell(x,y)); + public bool AllAlive(IEnumerable cells) => cells.All(IsCellAlive); + public bool AllDead(IEnumerable cells) => cells.All(c => !IsCellAlive(c)); + + private IReadOnlyList> _neighborhoodField; + + public IReadOnlyList> 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 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; + } + } + + } +} \ No newline at end of file diff --git a/GameOfLife/LifeHashSet.cs b/GameOfLife/LifeHashSet.cs new file mode 100644 index 0000000..0e57e01 --- /dev/null +++ b/GameOfLife/LifeHashSet.cs @@ -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 World; + + public LifeHashSet() + { + World = new HashSet(); + } + + public LifeHashSet(IEnumerable 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 GetNextGeneration_Serial(IEnumerable> field) + { + return + new HashSet( + field.Where( + cellNeighborCount => + LifeRule(World.Contains(cellNeighborCount.Item1), cellNeighborCount.Item2)) + .Select(c => c.Item1)); + } + + private HashSet GetNextGeneration_Parallel(IEnumerable> field) + { + var nextGeneration = new HashSet(); + + Parallel.ForEach( + field, + () => new HashSet(), + (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 LivingCells => World; + } +} \ No newline at end of file diff --git a/GameOfLife/Neighborhood.cs b/GameOfLife/Neighborhood.cs new file mode 100644 index 0000000..2d6d2d0 --- /dev/null +++ b/GameOfLife/Neighborhood.cs @@ -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[] GetNeighborField(IEnumerable world) + { + var neighbors = new List(); + + Parallel.ForEach( + world, + () => new List(), + (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[] GetNeighborField_Serial(IEnumerable world) + { + var neighborCount = new Dictionary(); + + 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(); + } + } +} \ No newline at end of file diff --git a/GameOfLife/PatternLibrary.cs b/GameOfLife/PatternLibrary.cs new file mode 100644 index 0000000..02f8ef1 --- /dev/null +++ b/GameOfLife/PatternLibrary.cs @@ -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> PatternProjections; + public IReadOnlyList> Patterns; + public IReadOnlyDictionary PatternToMetadata; + + public PatternLibrary() + { + Patterns = ExtractPatterns(GetPatternFileLines()).AsReadOnly(); + PatternProjections = ExtractOscillationsAndProjections(Patterns); + PatternToMetadata = Patterns.ToDictionary(t => t.Item2, t => t.Item1); + } + + private static List> ExtractOscillationsAndProjections(IEnumerable> 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> ExtractPatterns(IEnumerable 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 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(); + using (var stream = assembly.GetManifestResourceStream(resourceName)) + if (stream != null) + using (var tx = new StreamReader(stream)) + { + while (!tx.EndOfStream) + fileLines.Add(tx.ReadLine()); + } + + return fileLines; + } + } +} \ No newline at end of file diff --git a/GameOfLife/Properties/AssemblyInfo.cs b/GameOfLife/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a8fd1da --- /dev/null +++ b/GameOfLife/Properties/AssemblyInfo.cs @@ -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")] diff --git a/GameOfLife/Search/Node.cs b/GameOfLife/Search/Node.cs new file mode 100644 index 0000000..23168a7 --- /dev/null +++ b/GameOfLife/Search/Node.cs @@ -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> _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> MatchLibraryPatternsLookup(IEnumerable cells) + //{ + // var pattern = cells.ToPattern(); + //} + + public IEnumerable> MatchLibraryPatterns(IEnumerable cells) + { + var inputCells = cells.ToPattern(); + + var matches = new ConcurrentBag>(); + + 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; + } + } +} \ No newline at end of file diff --git a/GameOfLife/Util.cs b/GameOfLife/Util.cs new file mode 100644 index 0000000..623edf6 --- /dev/null +++ b/GameOfLife/Util.cs @@ -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 cells) + { + return new Pattern(cells.ToList()); + } + + //https://stackoverflow.com/a/63820524 + public static IEnumerable SplitByLine(this string str) + { + return Regex + .Split(str, @"((\r)+)?(\n)+((\r)+)?") + .Select(i => i.Trim()) + .Where(i => !string.IsNullOrEmpty(i)); + } + + public static IEnumerable FindOscillation(this ILife gol, int maxPeriod = 50) + { + var list = new List(); + + 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 + } + } +} \ No newline at end of file diff --git a/GameOfLife/beehive.rle b/GameOfLife/beehive.rle new file mode 100644 index 0000000..86569a9 --- /dev/null +++ b/GameOfLife/beehive.rle @@ -0,0 +1,3 @@ +#Beehive +x = 4, y = 3, rule = B3/S23 +b2o$o2bo$b2o! diff --git a/GameOfLifeTests/ApgcodeDecoderTests.cs b/GameOfLifeTests/ApgcodeDecoderTests.cs new file mode 100644 index 0000000..3a3d239 --- /dev/null +++ b/GameOfLifeTests/ApgcodeDecoderTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Linq; +using GameOfLife; +using GameOfLife.Entities; +using GameOfLife.IO; +using NUnit.Framework; + +namespace GameOfLifeTests +{ + public class ApgcodeDecoderTests + { + [Test] + public void ConvertToUintTest() + { + Assert.AreEqual(0, ApgcodeDecoder.ConvertToUint('0')); + Assert.AreEqual(9, ApgcodeDecoder.ConvertToUint('9')); + Assert.AreEqual(10, ApgcodeDecoder.ConvertToUint('a')); + Assert.AreEqual(11, ApgcodeDecoder.ConvertToUint('b')); + } + + [Test] + public void ExpandAbbreviationsTests() + { + Assert.AreEqual("00", ApgcodeDecoder.ExpandAbbreviations("w")); + Assert.AreEqual("000", ApgcodeDecoder.ExpandAbbreviations("x")); + Assert.AreEqual("00000", ApgcodeDecoder.ExpandAbbreviations("wx")); + Assert.AreEqual("0000", ApgcodeDecoder.ExpandAbbreviations("y0")); + Assert.AreEqual("00000", ApgcodeDecoder.ExpandAbbreviations("y1")); + Assert.AreEqual(new string('0', 39), ApgcodeDecoder.ExpandAbbreviations("yz")); + } + + [Test] + public void Headerless_DecodeTests() + { + var apgcodeDecoder = new ApgcodeDecoder("0ca178b96z69d1d96"); + + foreach (var line in apgcodeDecoder.Pattern.ToGrid()) + { + Console.WriteLine(line); + } + } + [Test] + public void Xs_DecodeTests() + { + var apgcodeDecoder = new ApgcodeDecoder("xs4_33"); + Assert.AreEqual(4, apgcodeDecoder.Pattern.Count); + Assert.AreEqual(PatternType.StillLife, apgcodeDecoder.Metadata.Type); + Assert.AreEqual("4", apgcodeDecoder.Metadata.Comments.First()); + } + } +} diff --git a/GameOfLifeTests/CellTests.cs b/GameOfLifeTests/CellTests.cs new file mode 100644 index 0000000..03a4835 --- /dev/null +++ b/GameOfLifeTests/CellTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using GameOfLife; +using GameOfLife.Entities; + +namespace GameOfLifeTests +{ + public class CellTests + { + private readonly PatternTestData _patternTestData = new PatternTestData(); + + [SetUp] + public void Setup() + { + } + + [Test] + public void Equals_True_Test() + { + Assert.True(Util.Equals(_patternTestData.CornerPattern, _patternTestData.CornerCopy)); + } + + [Test] + public void Equals_False_Test() + { + Assert.False(Util.Equals(_patternTestData.CornerPattern, _patternTestData.SquarePattern)); + } + + [Test] + public void Rotate_Test() + { + var rotate = _patternTestData.CornerPattern.Rotate(); + Console.WriteLine(_patternTestData.CornerPattern); + Console.WriteLine(rotate); + Assert.Contains(-_patternTestData.CornerPattern[0].Item2, rotate.Select(r => r.Item1).ToList()); + Assert.Contains(_patternTestData.CornerPattern[0].Item1, rotate.Select(r => r.Item2).ToList()); + Assert.Contains(-_patternTestData.CornerPattern[0].Item2, rotate.Select(r => r.Item1).ToList()); + Assert.Contains(-_patternTestData.CornerPattern[0].Item2, rotate.Select(r => r.Item1).ToList()); + } + + [Test] + public void Rotate_Circle_Test() + { + var rotate = _patternTestData.CornerPattern.Rotate(4); + Assert.AreEqual(_patternTestData.CornerPattern, rotate); + } + + [Test] + public void GetFirstPoint_Test() + { + var fp = _patternTestData.CornerPattern.GetFirstRowFirstCell(); + Assert.True(fp.Item1 == 2); + Assert.True(fp.Item2 == 1); + } + + [Test] + public void Subtract_Test() + { + var fp = _patternTestData.CornerPattern[0] - new Cell(1,2); + Assert.True(fp.Item1 == 1); + Assert.True(fp.Item2 == -1); + } + + [Test] + public void Hash_Test() + { + var h1 = new Cell(1, 2).GetHashCode(); + var h2 = new Cell(2, 3).GetHashCode(); + var h3 = new Cell(1, 2).GetHashCode(); + + Assert.AreEqual(h1, h3); + Assert.AreNotEqual(h1, h2); + } + } + + public class PatternTests + { + [Test] + public void GetHashCode_Tests() + { + var p1 = new Pattern(new List() { new Cell(1, 2) }).GetHashCode(); + var p2 = new Pattern(new List() { new Cell(2, 3) }).GetHashCode(); + var p3 = new Pattern(new List() { new Cell(1, 2) }).GetHashCode(); + var p4 = new Pattern(new List() { new Cell(1, 2), new Cell(2,3) }).GetHashCode(); + var p5 = new Pattern(new List() { new Cell(1, 2), new Cell(2,3) }).GetHashCode(); + var p6 = new Pattern(new List() { new Cell(2, 3), new Cell(1, 2) }).GetHashCode(); + + Assert.AreEqual(p1, p3); + Assert.AreNotEqual(p1, p2); + Assert.AreNotEqual(p1, p4); + Assert.AreEqual(p4, p5); + Assert.AreEqual(p5, p6); + }// + } +} \ No newline at end of file diff --git a/GameOfLifeTests/GameOfLifeTests.csproj b/GameOfLifeTests/GameOfLifeTests.csproj new file mode 100644 index 0000000..f052a5f --- /dev/null +++ b/GameOfLifeTests/GameOfLifeTests.csproj @@ -0,0 +1,19 @@ + + + + net452 + + false + + + + + + + + + + + + + diff --git a/GameOfLifeTests/PatternLibraryTests.cs b/GameOfLifeTests/PatternLibraryTests.cs new file mode 100644 index 0000000..b6e355e --- /dev/null +++ b/GameOfLifeTests/PatternLibraryTests.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using GameOfLife; +using GameOfLife.Entities; +using NUnit.Framework; + +namespace GameOfLifeTests +{ + public class PatternLibraryTests + { + [Test] + public void PrintLibrary() + { + var patternLibrary = new PatternLibrary(); + //Assert.AreEqual(15, patternLibrary.PatternProjections.Count); + foreach (var p in patternLibrary.PatternProjections) + { + Console.WriteLine($"{p.Item1.Name}, {p.Item1.Type} {string.Join(" ", p.Item1.Comments)}"); + foreach (var v in p.Item2) + { + Console.WriteLine(v.First()); + PatternTestData.ConsoleWriteGrid(v.Offset(new Cell(2,0))); + Console.WriteLine(); + } + Console.WriteLine("* * * * "); + } + } + + [Test] + public void MatchLibraryPatterns_SelfTest() + { + var patternLibrary = new PatternLibrary(); + + foreach (var pattern in patternLibrary.PatternProjections) + { + var matches = patternLibrary.MatchLibraryPatterns(pattern.Item2[0]); + // pattern metadata is the same + Assert.AreEqual(matches.Count(m => m.Item1 == pattern.Item1), 1); + } + } + + [Test] + public void MultTest() + { + + var singleDigits = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }.Select(i=> i.ToString()[0]); + + string ReplaceAtIndex(string s, char c, int i) + { + var array = s.ToCharArray(); + array[i] = c; + return new string(array); + } + + IEnumerable EnumUnderscoreWithNumbers(string s) + { + var currIndex = s.IndexOf('_'); + if (currIndex == -1) return new[] { int.Parse(s) }; + var skip = currIndex == 0 ? 1 : 0; + return singleDigits.Skip(skip).SelectMany(d => EnumUnderscoreWithNumbers(ReplaceAtIndex(s, d, currIndex))); + } + + int PartialProduct(int m1, int m2, int digit) + { + var m2s = m2.ToString(); + var c = int.Parse(m2s[m2s.Length - digit].ToString()); + return m1 * c * (int)Math.Pow(10, digit - 1); + } + Console.WriteLine(PartialProduct(131, 73, 2)); + + + string multplicand = "_3_"; + string multiplier = "__"; + string partialProd1 = "3__9"; + string partialProd2 = "___"; + string product = "9__9"; + + var r = + from mcnd in EnumUnderscoreWithNumbers(multplicand) + from mplr in EnumUnderscoreWithNumbers(multiplier) + from prdc in EnumUnderscoreWithNumbers(product) + from prt in EnumUnderscoreWithNumbers(partialProd1) + where PartialProduct(mcnd, mplr, 1) == prt && mcnd * mplr == prdc + select new { mcnd, mplr, prdc }; + + foreach (var result in r) + { + Console.WriteLine($"{result.mcnd} * {result.mplr} = {result.prdc}"); + } + + } + } +} \ No newline at end of file diff --git a/GameOfLifeTests/PatternTestData.cs b/GameOfLifeTests/PatternTestData.cs new file mode 100644 index 0000000..db84728 --- /dev/null +++ b/GameOfLifeTests/PatternTestData.cs @@ -0,0 +1,62 @@ +using System; +using GameOfLife; +using GameOfLife.Entities; +using GameOfLife.IO; + +namespace GameOfLifeTests +{ + public class PatternTestData + { + public readonly string C0Text = "....." + Environment.NewLine + + "..o.." + Environment.NewLine + + "..oo." + Environment.NewLine + + "....."; + + public Pattern CornerPattern; + public Pattern CornerCopy; + + public readonly string C2SquareText = "....." + Environment.NewLine + + ".oo.." + Environment.NewLine + + ".oo.." + Environment.NewLine + + "....."; + + public Pattern SquarePattern; + + public readonly string Glider = "...o." + Environment.NewLine + + ".o.o." + Environment.NewLine + + "..oo." + Environment.NewLine + + "....."; + + public Pattern GliderPattern; + + public readonly string GliderApgcode = "xq4_153"; + + public Pattern GliderApgPattern; + + public PatternTestData() + { + CornerPattern = Pattern.Extract(C0Text); + CornerCopy = Pattern.Extract(C0Text); + SquarePattern = Pattern.Extract(C2SquareText); + GliderPattern = Pattern.Extract(Glider); + var apgcodeDecoder = new ApgcodeDecoder(GliderApgcode); + GliderApgPattern = apgcodeDecoder.Pattern; + } + + public static void ConsoleWrite(Pattern pattern) + { + foreach (var cell in pattern) + { + Console.WriteLine(cell); + } + } + + public static void ConsoleWriteGrid(Pattern pattern) + { + var enumerable = pattern.ToGrid(); + + foreach (var e in enumerable) + Console.WriteLine(e); + } + } +} \ No newline at end of file diff --git a/GameOfLifeTests/VariationsTests.cs b/GameOfLifeTests/VariationsTests.cs new file mode 100644 index 0000000..3d3aa57 --- /dev/null +++ b/GameOfLifeTests/VariationsTests.cs @@ -0,0 +1,141 @@ +using System; +using System.Linq; +using GameOfLife; +using GameOfLife.Entities; +using NUnit.Framework; + +namespace GameOfLifeTests +{ + public class VariationsTests + { + private readonly PatternTestData _patternTestData = new PatternTestData(); + + [SetUp] + public void Setup() + { + } + + [Test] + public void CornerTest() + { + var pattern = new Projections(_patternTestData.CornerPattern); + Assert.AreEqual(4, pattern.Count); + + var offsetCell = new Cell(2,2); + foreach (var variation in pattern) + { + PatternTestData.ConsoleWrite(variation); + PatternTestData.ConsoleWriteGrid(variation.Offset(offsetCell)); + Console.WriteLine(); + PatternTestData.ConsoleWriteGrid(variation.GetBoundary.Offset(offsetCell)); + Console.WriteLine("-----"); + Console.WriteLine(); + } + } + + [Test] + public void SquareTest() + { + var pattern = new Pattern(_patternTestData.SquarePattern); + PatternTestData.ConsoleWrite(pattern); + Assert.AreEqual(4, pattern.Count); + + foreach (var variation in new Projections(pattern)) + { + PatternTestData.ConsoleWriteGrid(variation); + + Console.WriteLine(); + } + } + + [Test] + public void GliderTest() + { + PatternTestData.ConsoleWrite(_patternTestData.GliderPattern); + var variations = new Projections(_patternTestData.GliderPattern); + + foreach (var variation in variations) + { + PatternTestData.ConsoleWriteGrid(variation.Offset(new Cell(2,0))); + + Console.WriteLine(); + } + } + + [Test] + public void FindPattern_Corner_Tests() + { + var gol = new LifeArray(_patternTestData.CornerPattern); + + var results = gol.LivingCells.ToPattern().FindPattern(_patternTestData.CornerPattern).ToArray(); + Assert.AreEqual(1, results.Length); + } + + [Test] + public void FindPattern_Corner_Square_Tests() + { + var gol = new LifeArray(_patternTestData.CornerPattern); + + var results = gol.LivingCells.ToPattern().FindPattern(_patternTestData.SquarePattern).ToArray(); + Assert.AreEqual(0, results.Length); + } + + [Test] + public void FindPattern_Corner_Rotated_Tests() + { + var gol = new LifeHashSet(_patternTestData.CornerPattern.Rotate(2).Normalize()); + + var results = gol.LivingCells.ToPattern().FindPattern(_patternTestData.CornerPattern).ToArray(); + Assert.AreEqual(1, results.Length); + } + + [Test] + public void FindPattern_TwoCorners_Tests() + { + var offsetCorner = _patternTestData.CornerPattern.Offset(new Cell(3,0)); + var pattern = _patternTestData.CornerPattern + offsetCorner; + //PatternTestData.ConsoleWriteGrid(pattern, 10); + + var gol = new LifeHashSet(pattern); + + var results = gol.LivingCellPattern.FindPattern(_patternTestData.CornerPattern).ToArray(); + Assert.AreEqual(2, results.Length); + } + + [Test] + public void FindPattern_Overlapping_Empty_Space_Tests() + { + var offsetCorner = _patternTestData.CornerPattern.Offset(new Cell(2, 0)); + var pattern = _patternTestData.CornerPattern + offsetCorner; + + var gol = new LifeHashSet(pattern); + + var results = gol.LivingCells.ToPattern().FindPattern(_patternTestData.CornerPattern).ToArray(); + Assert.AreEqual(0, results.Length); + } + + [Test] + public void FindPattern_Corner_and_Square_Tests() + { + var offsetCorner = _patternTestData.SquarePattern.Offset(new Cell(4, 0)); + var pattern = _patternTestData.CornerPattern + offsetCorner; + PatternTestData.ConsoleWriteGrid(pattern); + + var gol = new LifeHashSet(pattern); + + var cornerResults = gol.LivingCells.ToPattern().FindPattern(_patternTestData.CornerPattern).ToArray(); + Assert.AreEqual(1, cornerResults.Length); + + var squareResults = gol.LivingCells.ToPattern().FindPattern(_patternTestData.SquarePattern).ToArray(); + Assert.AreEqual(1, squareResults.Length); + } + + [Test] + public void FindOscillations_Tests() + { + var oscillations= new LifeHashSet(_patternTestData.GliderApgPattern).FindOscillation(); + Assert.Greater(oscillations.Count(), 1); + Console.WriteLine(oscillations.Count()); + } + } +} \ No newline at end of file diff --git a/GolConsole/App.config b/GolConsole/App.config new file mode 100644 index 0000000..5754728 --- /dev/null +++ b/GolConsole/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/GolConsole/GolConsole.csproj b/GolConsole/GolConsole.csproj new file mode 100644 index 0000000..2ec38fd --- /dev/null +++ b/GolConsole/GolConsole.csproj @@ -0,0 +1,77 @@ + + + + + Debug + AnyCPU + {8E6D5F09-E4A5-496D-8DCC-36F4780F4CCD} + Exe + GolConsole + GolConsole + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + {0BEA14A8-8E11-4A18-BD7D-628D3F1561B0} + GameOfLife + + + + \ No newline at end of file diff --git a/GolConsole/Patterns/ark1.rle b/GolConsole/Patterns/ark1.rle new file mode 100644 index 0000000..9f34601 --- /dev/null +++ b/GolConsole/Patterns/ark1.rle @@ -0,0 +1,3 @@ +# ark1 -- 16 cells, stabilizes at 736692 gens, found by Nick Gotts. +x = 32, y = 29, rule = B3/S23 +27bo$28bo$29bo$28bo$27bo$29b3o20$oo$bbo$bbo$3b4o! diff --git a/GolConsole/Patterns/ark2.rle b/GolConsole/Patterns/ark2.rle new file mode 100644 index 0000000..1c81b18 --- /dev/null +++ b/GolConsole/Patterns/ark2.rle @@ -0,0 +1,3 @@ +# ark2 -- 19 cells, stabilizes at 8120878 gens, found by Nick Gotts. +x = 53, y = 44, rule = B3/S23 +50b3o28$12bo$12bo$13boo$15bo$15bo$15bo$15bo6$oo$bbo$bbo$3b4o! diff --git a/GolConsole/Patterns/blom.rle b/GolConsole/Patterns/blom.rle new file mode 100644 index 0000000..070c1ac --- /dev/null +++ b/GolConsole/Patterns/blom.rle @@ -0,0 +1,5 @@ +#C Blom. Runs for 23314 gens. Initial pop = 13. Final pop = 2740. +#C Found by Dean Hickerson, 7 July 2002 +#C See also http://www.radicaleye.com/DRH/methuselahs.html +x = 12, y = 5, rule = B3/S23 +o10bo$b4o6bo$2b2o7bo$10bo$8bobo! diff --git a/GolConsole/Patterns/glider-stream-crystal.rle b/GolConsole/Patterns/glider-stream-crystal.rle new file mode 100644 index 0000000..f9f01b9 --- /dev/null +++ b/GolConsole/Patterns/glider-stream-crystal.rle @@ -0,0 +1,66 @@ +#C High-period oscillator built by David Dauthier, showing +#C a common crystal forming on a glider stream. Several adjustable +#C high-period guns have been constructed that make use of two +#C identical copies of this repeatable growth-and-decay reaction. +#C It is also possible to support a single crystal of adjustable +#C length, and extract a glider from either end of the reaction. +#C See, e.g., p08832.lif in Jason Summers' "guns2j" collection. +x = 252, y = 221, rule = B3/S23 +90b2o68b2o$90b2o16b2o32b2o16b2o$108b2o32b2o4$89b3o8b2o48b2o8b3o$89b3o +8b2o48b2o8b3o$88bo3bo66bo3bo$108b3o30b3o$27b2o58b2o3b2o6bo6bo3bo28bo3b +o6bo6b2o3b2o58b2o$29bo69bobo48bobo69bo$16b2o12bo67bo3bo3bo5bo26bo5bo3b +o3bo67bo12b2o$16b2o4bo7bo8b2o57b5o3b2o3b2o26b2o3b2o3b5o57b2o8bo7bo4b2o +$13b2o5b2o8bo8b2o56b2o3b2o44b2o3b2o56b2o8bo8b2o5b2o$5b2o5b3o5bo2b2o4bo +57b3o8b5o46b5o8b3o57bo4b2o2bo5b3o5b2o$5b2o6b2o6b5ob2o62b2o6b3o7bo32bo +7b3o6b2o62b2ob5o6b2o6b2o$16b2o4bo68b2o7bo7bob2o28b2obo7bo7b2o68bo4b2o$ +16b2o74b2o14bo34bo14b2o74b2o$90bobo15bo3bo26bo3bo15bobo$90b2o8bo8bo2bo +26bo2bo8bo8b2o$18b2o79bobo7b5o24b5o7bobo79b2o$16bo3bo78bo9b5o24b5o9bo +78bo3bo$10b2o3bo5bo63b2o3b2o16b2o3b2o22b2o3b2o16b2o3b2o63bo5bo3b2o$10b +2o2b2obo3bo2bobo58b2o3b2o17b5o24b5o17b2o3b2o58bobo2bo3bob2o2b2o$15bo5b +o3bo71b2o4bo6b3o26b3o6bo4b2o71bo3bo5bo$16bo3bo8b2o56b3o7b2o12bo28bo12b +2o7b3o56b2o8bo3bo$18b2o9b2o56b3o72b3o56b2o9b2o$88bo74bo3$13bo8b2o204b +2o8bo$11bobo6bo2bo204bo2bo6bobo$4b2o4bobo7bo3bo2b2o194b2o2bo3bo7bobo4b +2o$4b2o3bo2bo7bo2b2o2b3o57b2o74b2o57b3o2b2o2bo7bo2bo3b2o$10bobo16b2obo +12bo41b2o21b2o28b2o21b2o41bo12bob2o16bobo$11bobo4b3o8bo2bo5b2o6bo63b2o +28b2o63bo6b2o5bo2bo8b3o4bobo$13bo15b2obo5b2o4b3o158b3o4b2o5bob2o15bo$ +27b3o192b3o$27b2o194b2o$87bobo72bobo$87b2o74b2o$88bo74bo18$65b2o118b2o +$64b3o118b3o$64b2obo116bob2o$65b3o116b3o$66bo118bo13$23b2o202b2o$23b3o +200b3o$9bo15b2obo5b2o4b3o166b3o4b2o5bob2o15bo$7bobo4b3o8bo2bo5b2o6bo +166bo6b2o5bo2bo8b3o4bobo$6bobo16b2obo12bo168bo12bob2o16bobo$2o3bo2bo7b +o2b2o2b3o200b3o2b2o2bo7bo2bo3b2o$2o4bobo7bo3bo2b2o202b2o2bo3bo7bobo4b +2o$7bobo6bo2bo212bo2bo6bobo$9bo8b2o212b2o8bo4$14b2o9b2o198b2o9b2o$12bo +3bo8b2o198b2o8bo3bo$11bo5bo3bo208bo3bo5bo$6b2o2b2obo3bo2bobo206bobo2bo +3bob2o2b2o$6b2o3bo5bo216bo5bo3b2o$12bo3bo218bo3bo$14b2o220b2o3$12b2o +224b2o$12b2o4bo214bo4b2o$b2o6b2o6b5ob2o202b2ob5o6b2o6b2o$b2o5b3o5bo2b +2o4bo200bo4b2o2bo5b3o5b2o$9b2o5b2o8bo8b2o178b2o8bo8b2o5b2o$12b2o4bo7bo +8b2o178b2o8bo7bo4b2o$12b2o12bo198bo12b2o$25bo40b2o116b2o40bo$23b2o41b +3o114b3o41b2o$65bob2o114b2obo$65b3o116b3o$66bo118bo2$66bo118bo$65b3o +116b3o$65bob2o114b2obo$23b2o41b3o114b3o41b2o$25bo40b2o116b2o40bo$12b2o +12bo198bo12b2o$12b2o4bo7bo8b2o178b2o8bo7bo4b2o$9b2o5b2o8bo8b2o178b2o8b +o8b2o5b2o$b2o5b3o5bo2b2o4bo200bo4b2o2bo5b3o5b2o$b2o6b2o6b5ob2o202b2ob +5o6b2o6b2o$12b2o4bo214bo4b2o$12b2o224b2o3$14b2o220b2o$12bo3bo218bo3bo$ +6b2o3bo5bo216bo5bo3b2o$6b2o2b2obo3bo2bobo206bobo2bo3bob2o2b2o$11bo5bo +3bo208bo3bo5bo$12bo3bo8b2o198b2o8bo3bo$14b2o9b2o198b2o9b2o4$9bo8b2o +212b2o8bo$7bobo6bo2bo212bo2bo6bobo$2o4bobo7bo3bo2b2o202b2o2bo3bo7bobo +4b2o$2o3bo2bo7bo2b2o2b3o200b3o2b2o2bo7bo2bo3b2o$6bobo16b2obo12bo168bo +12bob2o16bobo$7bobo4b3o8bo2bo5b2o6bo166bo6b2o5bo2bo8b3o4bobo$9bo15b2ob +o5b2o4b3o166b3o4b2o5bob2o15bo$23b3o200b3o$23b2o202b2o13$66bo118bo$65b +3o116b3o$64b2obo116bob2o$64b3o118b3o$65b2o118b2o18$88bo74bo$87b2o74b2o +$87bobo72bobo$27b2o194b2o$27b3o192b3o$13bo15b2obo5b2o4b3o158b3o4b2o5bo +b2o15bo$11bobo4b3o8bo2bo5b2o6bo63b2o28b2o63bo6b2o5bo2bo8b3o4bobo$10bob +o16b2obo12bo41b2o21b2o28b2o21b2o41bo12bob2o16bobo$4b2o3bo2bo7bo2b2o2b +3o57b2o74b2o57b3o2b2o2bo7bo2bo3b2o$4b2o4bobo7bo3bo2b2o194b2o2bo3bo7bob +o4b2o$11bobo6bo2bo204bo2bo6bobo$13bo8b2o204b2o8bo3$88bo74bo$18b2o9b2o +56b3o72b3o56b2o9b2o$16bo3bo8b2o56b3o7b2o12bo28bo12b2o7b3o56b2o8bo3bo$ +15bo5bo3bo71b2o4bo6b3o26b3o6bo4b2o71bo3bo5bo$10b2o2b2obo3bo2bobo58b2o +3b2o17b5o24b5o17b2o3b2o58bobo2bo3bob2o2b2o$10b2o3bo5bo63b2o3b2o16b2o3b +2o22b2o3b2o16b2o3b2o63bo5bo3b2o$16bo3bo78bo9b5o24b5o9bo78bo3bo$18b2o +79bobo7b5o24b5o7bobo79b2o$90b2o8bo8bo2bo26bo2bo8bo8b2o$90bobo15bo3bo +26bo3bo15bobo$16b2o74b2o14bo34bo14b2o74b2o$16b2o4bo68b2o7bo7bob2o28b2o +bo7bo7b2o68bo4b2o$5b2o6b2o6b5ob2o62b2o6b3o7bo32bo7b3o6b2o62b2ob5o6b2o +6b2o$5b2o5b3o5bo2b2o4bo57b3o8b5o46b5o8b3o57bo4b2o2bo5b3o5b2o$13b2o5b2o +8bo8b2o56b2o3b2o44b2o3b2o56b2o8bo8b2o5b2o$16b2o4bo7bo8b2o57b5o3b2o3b2o +26b2o3b2o3b5o57b2o8bo7bo4b2o$16b2o12bo67bo3bo3bo5bo26bo5bo3bo3bo67bo +12b2o$29bo69bobo48bobo69bo$27b2o58b2o3b2o6bo6bo3bo28bo3bo6bo6b2o3b2o +58b2o$108b3o30b3o$88bo3bo66bo3bo$89b3o8b2o48b2o8b3o$89b3o8b2o48b2o8b3o +4$108b2o32b2o$90b2o16b2o32b2o16b2o$90b2o68b2o! diff --git a/GolConsole/Patterns/glider.rle b/GolConsole/Patterns/glider.rle new file mode 100644 index 0000000..e3e0533 --- /dev/null +++ b/GolConsole/Patterns/glider.rle @@ -0,0 +1,3 @@ +#C This is a glider. +x = 3, y = 3 +bo$2bo$3o! \ No newline at end of file diff --git a/GolConsole/Patterns/honey-farm-hassler-147.rle b/GolConsole/Patterns/honey-farm-hassler-147.rle new file mode 100644 index 0000000..c5eacec --- /dev/null +++ b/GolConsole/Patterns/honey-farm-hassler-147.rle @@ -0,0 +1,12 @@ +#CXRLE Pos=-28,-26 +x = 42, y = 49, rule = B3/S23 +13b2o12b2o$9b2o3bo12bo3b2o$10bo3bobo4bo3bobo3bo$10bob2obobob3o2bobob2o +bo$11bobobob2o3b3obobobo$20b2o$14bob4o2b4obo$9b2o3bobo2bo2bo2bobo3b2o$ +9b2o2bo4bo4bo4bo2b2o$14bob2o6b2obo3$2b2o34b2o$2bobo32bobo$3bo34bo3$bo +12b3o8b3o12bo$obo10bo3bo6bo3bo10bobo$2o10bo5bo4bo5bo10b2o$12bo5bo4bo5b +o$12bo5bo4bo5bo$13bo3bo6bo3bo$14b3o8b3o2$14b3o8b3o$13bo3bo6bo3bo$12bo +5bo4bo5bo$12bo5bo4bo5bo$2o10bo5bo4bo5bo10b2o$obo10bo3bo6bo3bo10bobo$bo +12b3o8b3o12bo3$3bo34bo$2bobo32bobo$2b2o34b2o3$14bob2o6b2obo$9b2o2bo4bo +4bo4bo2b2o$9b2o3bobo2bo2bo2bobo3b2o$14bob4o2b4obo$20b2o$11bobobob2o3b +3obobobo$10bob2obobob3o2bobob2obo$10bo3bobo4bo3bobo3bo$9b2o3bo12bo3b2o +$13b2o12b2o! diff --git a/GolConsole/Program.cs b/GolConsole/Program.cs new file mode 100644 index 0000000..ae2e0ec --- /dev/null +++ b/GolConsole/Program.cs @@ -0,0 +1,115 @@ +using System; +using System.Diagnostics; +using System.Linq; +using GameOfLife.Entities; +using GameOfLife.IO; +using Timer = System.Timers.Timer; + +namespace GameOfLife +{ + static class Program + { + static void Main(string[] args) + { + //var rleParser = new RleDecoder(@"PatternProjections/honey-farm-hassler-147.rle"); + //var rleParser = new RleDecoder(@"PatternProjections/glider-stream-crystal.rle"); + //var rleParser = new RleDecoder(@"PatternProjections/glider.rle"); + var rleParser = new RleDecoder(@"PatternProjections/blom.rle"); + + var pattern = rleParser.Pattern; + + var paddingX = 3; + var paddingY = 3; + + var life = new LifeHashSet(rleParser.Pattern); + var patternLibrary = new PatternLibrary(); + + var timer = new Stopwatch(); + timer.Start(); + do + { + life.IncrementGeneration(); + + Console.WriteLine($"{life.Generation} + {life.Population}"); + foreach (var p in patternLibrary.MatchLibraryPatterns(life.LivingCells)) + { + Console.WriteLine($"\t\t\tFound {p.Item1.Name} {p.Item2.Length}"); + } + + + //Console.Clear(); + //OutputBoard(sim); + //Console.ReadKey(); + + //Thread.Sleep(100); + } while (life.Population > 0 && life.Generation < 3000); + timer.Stop(); + Console.WriteLine($"Final: Generation:{life.Generation}, Pop:{life.Population}, Time:{timer.ElapsedMilliseconds}"); + Console.ReadKey(); + } + + private static void OutputBoard(LifeBase sim) + { + var neighborField = Neighborhood.GetNeighborField(sim.LivingCells); + + for (short x = 0; x < 40 /*sim.SizeX*/; x++) + { + for (short y = 0; y < 120 /*sim.SizeY*/; y++) + { + var cell = new Cell(x, y); + + var neighbors = neighborField.FirstOrDefault(n => n.Item1.Equals(cell)); + + Console.BackgroundColor = ConsoleColor.Black; + Console.Write(" "); + var isCellAlive = sim.IsCellAlive(cell); + + if (neighbors != null) + switch (neighbors.Item2) + { + case 0: + Console.BackgroundColor = ConsoleColor.Black; + Console.ForegroundColor = ConsoleColor.Gray; + break; + case 1 when isCellAlive: + Console.BackgroundColor = ConsoleColor.DarkMagenta; + Console.ForegroundColor = ConsoleColor.Gray; + break; + case 1: + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.ForegroundColor = ConsoleColor.Gray; + break; + + case 2 when isCellAlive: + Console.BackgroundColor = ConsoleColor.DarkGray; + Console.ForegroundColor = ConsoleColor.White; + break; + case 2: + Console.BackgroundColor = ConsoleColor.DarkGreen; + Console.ForegroundColor = ConsoleColor.Gray; + break; + + + case 3 when isCellAlive: + Console.BackgroundColor = ConsoleColor.DarkGray; + Console.ForegroundColor = ConsoleColor.White; + break; + case 3: + Console.BackgroundColor = ConsoleColor.Green; + Console.ForegroundColor = ConsoleColor.Gray; + break; + + default: + Console.BackgroundColor = ConsoleColor.DarkRed; + Console.ForegroundColor = ConsoleColor.White; + break; + } + + Console.Write(isCellAlive ? "O" : "."); + } + + Console.WriteLine(); + } + } + } +} diff --git a/GolConsole/Properties/AssemblyInfo.cs b/GolConsole/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..cd05006 --- /dev/null +++ b/GolConsole/Properties/AssemblyInfo.cs @@ -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("GolConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GolConsole")] +[assembly: AssemblyCopyright("Copyright © 2022")] +[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("8e6d5f09-e4a5-496d-8dcc-36f4780f4ccd")] + +// 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")]