Initial commit

This commit is contained in:
2026-05-07 03:23:56 +00:00
commit 5e8575f42a
42 changed files with 2330 additions and 0 deletions
+70
View File
@@ -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());
}
}
}
+35
View File
@@ -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}");
}
}
}
}
+14
View File
@@ -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; }
}
}
+82
View File
@@ -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);
}
}
}
+41
View File
@@ -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;
}
}
}
}
}