Initial commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user