The idea is to improve engineered solutions with randomization.
Better randomizer may mean better results with the same base pattern.
For example, this solution (lifespan 9437) differs from previous solution (8058) only because I applied different method for junk generation:
Code: Select all
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace TorusLife
{
class Pattern
{
public const int GridWidth = 40;
public const int GridHeight = 40;
int[,] cells;
public Pattern()
{
cells = new int[GridWidth, GridHeight];
}
public Pattern(Pattern source)
{
cells = new int[GridWidth, GridHeight];
for (int y = 0; y < GridHeight; y++)
for (int x = 0; x < GridWidth; x++)
cells[x, y] = source.cells[x, y];
}
public Pattern Generate()
{
var result = new Pattern();
for (int y = 0; y < GridHeight; y++)
{
int ym1 = y - 1;
int yp1 = y + 1;
if (ym1 < 0)
ym1 = GridHeight - 1;
else if (ym1 >= GridHeight)
ym1 = 0;
if (yp1 < 0)
yp1 = GridHeight - 1;
else if (yp1 >= GridHeight)
yp1 = 0;
for (int x = 0; x < GridWidth; x++)
{
int xm1 = x - 1;
int xp1 = x + 1;
if (xm1 < 0)
xm1 = GridWidth - 1;
else if (xm1 >= GridWidth)
xm1 = 0;
if (xp1 < 0)
xp1 = GridWidth - 1;
else if (xp1 >= GridWidth)
xp1 = 0;
int neighbours =
cells[xm1, ym1] +
cells[x, ym1] +
cells[xp1, ym1] +
cells[xm1, y] +
cells[xp1, y] +
cells[xm1, yp1] +
cells[x, yp1] +
cells[xp1, yp1];
if (cells[x, y] == 1)
result.cells[x, y] = (neighbours == 2 || neighbours == 3) ? 1 : 0;
else
result.cells[x, y] = neighbours == 3 ? 1 : 0;
}
}
return result;
}
public void PlaceJunk(Random rnd, Pattern mask)
{
int[][,] stamps = new int[2][,]
{
new int[6, 6] {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0 }
},
new int[7, 7] {
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 1, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0 },
}
};
int[][,] stampMasks = new int[2][,]
{
new int[6, 6] {
{ 0, 1, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1 },
{ 0, 1, 1, 1, 1, 0 }
},
new int[7, 7] {
{ 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1, 1, 1 },
{ 0, 1, 1, 1, 1, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0 },
}
};
for (int i = 0; i < 2000; i++)
{
int stampIndex = rnd.Next(stamps.Length);
int[,] stamp = stamps[stampIndex];
int[,] stampMask = stampMasks[stampIndex];
int stampWidth = stamp.GetLength(0);
int stampHeight = stamp.GetLength(1);
int offsetX = rnd.Next(GridWidth);
int offsetY = rnd.Next(GridHeight);
bool collided = false;
for (int y = 0; y < stampHeight; y++)
for (int x = 0; x < stampWidth; x++)
{
int sX = x + offsetX;
int sY = y + offsetY;
WrapCoordinates(ref sX, ref sY);
if (stampMask[x, y] == 1 &&
(mask.cells[sX, sY] == 1 ||
cells[sX, sY] == 1))
{
collided = true;
break;
}
if (collided)
break;
}
if (!collided)
{
for (int y = 0; y < stampHeight; y++)
for (int x = 0; x < stampWidth; x++)
{
int sX = x + offsetX;
int sY = y + offsetY;
WrapCoordinates(ref sX, ref sY);
cells[sX, sY] |= stamp[x, y];
}
}
}
}
public void Measure(out int lifespan, out int period)
{
var patterns = new List<Pattern>();
patterns.Add(this);
for (int i = 0; ; i++)
{
Pattern newPattern = patterns.Last().Generate();
int start = patterns.Count - 2;
if (start < 0)
start = 0;
for (int j = start; j < patterns.Count; j++)
{
if (newPattern.Equal(patterns[j]))
{
period = patterns.Count - j;
lifespan = i - period + 1;
return;
}
}
if (i > 12000) // hack
{
period = 0;
lifespan = 0;
return;
}
patterns.Add(newPattern);
}
}
public Pattern GetMask(int ticks)
{
Pattern mask = new Pattern();
Pattern current = this;
for (int i = 0; i < ticks; i++)
{
for (int y = 0; y < GridHeight; y++)
for (int x = 0; x < GridWidth; x++)
mask.cells[x, y] |= current.cells[x, y];
current = current.Generate();
}
return mask;
}
public void Merge(Pattern pattern)
{
for (int y = 0; y < GridHeight; y++)
for (int x = 0; x < GridWidth; x++)
cells[x, y] |= pattern.cells[x, y];
}
public void WriteRLE(string fileName, string comment = null)
{
var sb = new StringBuilder();
if (comment != null)
sb.AppendLine($"#C {comment}");
sb.AppendLine($"x = {GridWidth}, y = {GridHeight}, rule = B3/S23:T{GridWidth},{GridHeight}");
for (int y = 0; y < GridHeight; y++)
{
for (int x = 0; x < GridWidth; x++)
sb.Append(cells[x, y] == 1 ? 'o' : 'b');
sb.AppendLine(y == GridHeight - 1 ? "!" : "$");
}
File.WriteAllText(fileName, sb.ToString());
}
public void ReadRLE(string[] lines)
{
int x = 0;
int y = 0;
string scount = "";
foreach (var line in lines)
{
if (line.StartsWith("#"))
continue;
if (line.StartsWith("x"))
{
if (line.Split(':')[1] != $"T{GridWidth},{GridHeight}")
throw new Exception();
continue;
}
for (int i = 0; i < line.Length; i++)
{
char c = line[i];
if (c >= '0' && c <= '9')
{
scount += c;
}
else
{
if (c == '$')
{
x = 0;
int count = 1;
if (scount != "")
count = int.Parse(scount);
y += count;
scount = "";
}
else if (c == '!')
break;
else if (c == 'o' || c == 'b')
{
int count = 1;
if (scount != "")
count = int.Parse(scount);
for (int k = 0; k < count; k++)
{
cells[x, y] = c == 'o' ? 1 : 0;
x++;
WrapCoordinates(ref x, ref y);
}
scount = "";
}
}
}
}
}
public void ReadRLE(string fileName)
{
ReadRLE(File.ReadAllLines(fileName));
}
public bool Equal(Pattern pattern)
{
for (int y = 0; y < GridHeight; y++)
for (int x = 0; x < GridWidth; x++)
if (cells[x, y] != pattern.cells[x, y])
return false;
return true;
}
void WrapCoordinates(ref int x, ref int y)
{
if (x < 0)
x += GridWidth;
else if (x >= GridWidth)
x -= GridWidth;
if (y < 0)
y += GridHeight;
else if (y >= GridHeight)
y -= GridWidth;
}
}
class Program
{
Program()
{
Pattern source = new Pattern();
source.ReadRLE(new string[] {
"x = 40, y = 40, rule = B3/S23:T40,40",
"2bobo$3bo$o$bo3b2o32bo$o4b2o$37bo$20b2o6b2o6bobo$20b2o6b2o7bo$34bo$33b",
"obo$17b2o15bo$17b2o12bo$30bobo$20b2o9bo$20b2o$27b2o$26bobo$26b2o9$3b2o",
"5bo$2bobo4b3o$2bo5b2o2bo$b2o3$11b2o$9b3o$10bo3$9b2o$9b2o$3bo!"
});
int unstableTicks = 3203;
Pattern mask = source.GetMask(unstableTicks);
Pattern unstableSource = new Pattern(source);
for (int i = 0; i < unstableTicks; i++)
unstableSource = unstableSource.Generate();
int period;
int lifespan;
int totalMax = 0;
int countMax = 0;
int bestLifespan = 0;
int bestLifespanLocal = 0;
int failed = 0;
Random rnd = new Random();
for (int i = 0; ; i++)
{
Pattern junk = new Pattern();
junk.PlaceJunk(rnd, mask);
Pattern pattern = new Pattern(unstableSource);
pattern.Merge(junk);
pattern.Measure(out lifespan, out period);
lifespan += unstableTicks;
if (period == 0)
failed++;
if (i % 20 == 0 && i != 0)
{
totalMax += bestLifespanLocal;
countMax++;
int avgMax = totalMax / countMax;
Console.WriteLine($"Iteration: {i,6}, lifespan: {bestLifespan,4} | {bestLifespanLocal,4} | {avgMax,4}, failed: {failed}");
bestLifespanLocal = 0;
failed = 0;
}
if (lifespan > bestLifespan)
{
bestLifespan = lifespan;
pattern = new Pattern(source);
pattern.Merge(junk);
pattern.WriteRLE(
$"{Pattern.GridWidth}x{Pattern.GridHeight}_{lifespan}.rle",
$"lifespan: {lifespan}, period: {period}");
}
if (lifespan > bestLifespanLocal)
bestLifespanLocal = lifespan;
}
}
static void Main(string[] args)
{
new Program();
}
}
}