init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
/packages/
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
13
.idea/.idea.InverseGameOfLife/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.InverseGameOfLife/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/modules.xml
|
||||||
|
/contentModel.xml
|
||||||
|
/.idea.InverseGameOfLife.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
4
.idea/.idea.InverseGameOfLife/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.InverseGameOfLife/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/.idea.InverseGameOfLife/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.InverseGameOfLife/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
5
.idea/.idea.InverseGameOfLife/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
5
.idea/.idea.InverseGameOfLife/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/.idea.InverseGameOfLife/.idea/vcs.xml
generated
Normal file
6
.idea/.idea.InverseGameOfLife/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
13
.idea/.idea.InverseOfLife/.idea/.gitignore
generated
vendored
Normal file
13
.idea/.idea.InverseOfLife/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Rider ignored files
|
||||||
|
/contentModel.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/modules.xml
|
||||||
|
/.idea.InverseOfLife.iml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
4
.idea/.idea.InverseOfLife/.idea/encodings.xml
generated
Normal file
4
.idea/.idea.InverseOfLife/.idea/encodings.xml
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
8
.idea/.idea.InverseOfLife/.idea/indexLayout.xml
generated
Normal file
8
.idea/.idea.InverseOfLife/.idea/indexLayout.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
InverseOfLife.csproj
Normal file
9
InverseOfLife.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
28
InverseOfLife.sln
Normal file
28
InverseOfLife.sln
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InverseOfLife", "InverseOfLife.csproj", "{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4DEC9892-1EE7-4054-9C9F-4ED9E270BF91}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{736B41B9-3DE3-47AC-A4EC-95C68DF07040}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F4D733EA-AEDD-4676-9494-A6F6F88EE206}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{60B6F87D-0805-4F1A-9706-48297A3013E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{60B6F87D-0805-4F1A-9706-48297A3013E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{60B6F87D-0805-4F1A-9706-48297A3013E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{60B6F87D-0805-4F1A-9706-48297A3013E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
142
src/Board.cs
Normal file
142
src/Board.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace InverseOfLife;
|
||||||
|
|
||||||
|
public class Board
|
||||||
|
{
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public Board(int w, int h, bool qx = false, bool qy = false, bool useTracer = false)
|
||||||
|
{
|
||||||
|
Width = w;
|
||||||
|
Height = h;
|
||||||
|
QuotientX = qx;
|
||||||
|
QuotientY = qy;
|
||||||
|
Lives = new();
|
||||||
|
if (useTracer)
|
||||||
|
Tracer = new CellTracer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public required int Width { get; init; }
|
||||||
|
public required int Height { get; init; }
|
||||||
|
public required bool QuotientX { get; init; }
|
||||||
|
public required bool QuotientY { get; init; }
|
||||||
|
public HashSet<(int, int)> Lives { get; init; }
|
||||||
|
public CellTracer? Tracer { get; init; }
|
||||||
|
|
||||||
|
public void Toggle(int x, int y, bool initialConfig=false)
|
||||||
|
{
|
||||||
|
bool added = Lives.Add((x, y));
|
||||||
|
if (!added)
|
||||||
|
{
|
||||||
|
Lives.Remove((x, y));
|
||||||
|
if (initialConfig && Tracer is not null)
|
||||||
|
Tracer.Contribution.Remove((x, y));
|
||||||
|
}
|
||||||
|
else if (initialConfig && Tracer is not null)
|
||||||
|
{
|
||||||
|
Tracer.Contribution[(x, y)] = new Dictionary<(int, int), double>
|
||||||
|
{
|
||||||
|
{ (x, y), 1d }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Toggle((int x, int y) cell, bool initialConfig = false) => Toggle(cell.x, cell.y, initialConfig);
|
||||||
|
|
||||||
|
public void Evaluate(int steps)
|
||||||
|
{
|
||||||
|
for(int i = 0; i< steps; i++)
|
||||||
|
Evaluate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Evaluate()
|
||||||
|
{
|
||||||
|
Dictionary<(int, int), int> neighbourCount = new();
|
||||||
|
foreach ((int x, int y) in Lives)
|
||||||
|
{
|
||||||
|
if (!neighbourCount.ContainsKey((x, y)))
|
||||||
|
neighbourCount[(x, y)] = 0;
|
||||||
|
Populate(x, y, neighbourCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((int x, int y) in neighbourCount.Keys)
|
||||||
|
{
|
||||||
|
if (Lives.Contains((x, y)))
|
||||||
|
{
|
||||||
|
if (neighbourCount[(x, y)] != 2 && neighbourCount[(x, y)] != 3)
|
||||||
|
{
|
||||||
|
Lives.Remove((x, y));
|
||||||
|
if (Tracer is not null)
|
||||||
|
Tracer.DeferredRemove((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (neighbourCount[(x, y)] == 3)
|
||||||
|
{
|
||||||
|
Lives.Add((x, y));
|
||||||
|
if (Tracer is not null)
|
||||||
|
Tracer.DeferredAdd((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Tracer is not null)
|
||||||
|
Tracer.BatchProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Populate(int x, int y, Dictionary<(int, int), int> count)
|
||||||
|
{
|
||||||
|
for (int dx = -1; dx <= 1; dx++)
|
||||||
|
{
|
||||||
|
for (int dy = -1; dy <= 1; dy++)
|
||||||
|
{
|
||||||
|
if (dx == 0 && dy == 0)
|
||||||
|
continue;
|
||||||
|
int nx = QuotientX ? (x + dx + Width) % Width : x + dx;
|
||||||
|
int ny = QuotientY ? (y + dy + Height) % Height : y + dy;
|
||||||
|
if(nx >= Width || nx < 0)
|
||||||
|
continue;
|
||||||
|
if(ny >= Height || ny < 0)
|
||||||
|
continue;
|
||||||
|
if (!count.ContainsKey((nx, ny)))
|
||||||
|
count[(nx, ny)] = 0;
|
||||||
|
count[(nx, ny)] += 1;
|
||||||
|
if (Tracer is not null)
|
||||||
|
{
|
||||||
|
if (!Tracer.Parents.ContainsKey((nx, ny)))
|
||||||
|
Tracer.Parents[(nx, ny)] = new HashSet<(int, int)>();
|
||||||
|
Tracer.Parents[(nx, ny)].Add((x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
|
||||||
|
var builder = new System.Text.StringBuilder();
|
||||||
|
|
||||||
|
for (int y = 0; y < Height; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < Width; x++)
|
||||||
|
builder.Append(Lives.Contains((x, y)) ? 'o' : ' ');
|
||||||
|
|
||||||
|
builder.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Play(int generations, int delay = 200)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < generations; i++)
|
||||||
|
{
|
||||||
|
Console.Clear();
|
||||||
|
Console.WriteLine(ToString());
|
||||||
|
Evaluate();
|
||||||
|
Thread.Sleep(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/CellTracer.cs
Normal file
57
src/CellTracer.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
namespace InverseOfLife;
|
||||||
|
|
||||||
|
public class CellTracer
|
||||||
|
{
|
||||||
|
public Dictionary<(int, int), Dictionary<(int, int), double>> Contribution { get; init; }
|
||||||
|
|
||||||
|
public CellTracer()
|
||||||
|
{
|
||||||
|
Contribution = new();
|
||||||
|
ToAdd = new();
|
||||||
|
ToRemove = new();
|
||||||
|
Parents = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddCell((int, int) cell, IEnumerable<(int, int)> parents)
|
||||||
|
{
|
||||||
|
//double[] contribute = new double[];
|
||||||
|
Dictionary<(int, int), double> contribute = new();
|
||||||
|
|
||||||
|
foreach ((int, int) parent in parents)
|
||||||
|
{
|
||||||
|
if (!Contribution.ContainsKey(parent))
|
||||||
|
throw new Exception($"parent {parent} must exist");
|
||||||
|
foreach ((int, int) root in Contribution[parent].Keys)
|
||||||
|
{
|
||||||
|
contribute.TryAdd(root, 0);
|
||||||
|
contribute[root] += Contribution[parent][root] / 3d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Contribution[cell] = contribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveCell((int, int) cell)
|
||||||
|
{
|
||||||
|
Contribution.Remove(cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
private HashSet<(int, int)> ToRemove { get; set; }
|
||||||
|
private HashSet<(int, int)> ToAdd { get; set; }
|
||||||
|
public Dictionary<(int, int), HashSet<(int, int)>> Parents { get; set; }
|
||||||
|
public void DeferredAdd((int, int) cell) => ToAdd.Add(cell);
|
||||||
|
public void DeferredRemove((int, int) cell) => ToRemove.Add(cell);
|
||||||
|
|
||||||
|
|
||||||
|
public void BatchProcess()
|
||||||
|
{
|
||||||
|
foreach ((int, int) cell in ToAdd)
|
||||||
|
AddCell(cell, Parents[cell]);
|
||||||
|
foreach ((int, int) cell in ToRemove)
|
||||||
|
RemoveCell(cell);
|
||||||
|
ToAdd = new();
|
||||||
|
ToRemove = new();
|
||||||
|
Parents = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
323
src/Gene.cs
Normal file
323
src/Gene.cs
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace InverseOfLife;
|
||||||
|
|
||||||
|
public class Gene
|
||||||
|
{
|
||||||
|
|
||||||
|
public int Length => Granularity switch
|
||||||
|
{
|
||||||
|
< 0 => -RiboseSequence.Length * Granularity,
|
||||||
|
_ => RiboseSequence.Length / Granularity
|
||||||
|
};
|
||||||
|
public byte[] this[int idx]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Granularity > 0)
|
||||||
|
{
|
||||||
|
int byteStart = idx * Granularity;
|
||||||
|
if (byteStart + Granularity > RiboseSequence.Length)
|
||||||
|
throw new IndexOutOfRangeException("Index out of bounds for RiboseSequence.");
|
||||||
|
|
||||||
|
return RiboseSequence.Skip(byteStart).Take(Granularity).ToArray();
|
||||||
|
}
|
||||||
|
if (Granularity < 0)
|
||||||
|
{
|
||||||
|
int bitsPerGene = 8 / -Granularity;
|
||||||
|
int bitStart = idx * bitsPerGene;
|
||||||
|
int byteStart = bitStart / 8;
|
||||||
|
int bitOffset = bitStart % 8;
|
||||||
|
|
||||||
|
byte[] result = new byte[(bitsPerGene + 7) / 8];
|
||||||
|
int bitsRead = 0;
|
||||||
|
|
||||||
|
while (bitsRead < bitsPerGene)
|
||||||
|
{
|
||||||
|
int currentByte = RiboseSequence[byteStart];
|
||||||
|
int remainingBitsInByte = 8 - bitOffset;
|
||||||
|
int bitsToCopy = Math.Min(bitsPerGene - bitsRead, remainingBitsInByte);
|
||||||
|
|
||||||
|
int mask = ((1 << bitsToCopy) - 1) << bitOffset;
|
||||||
|
int extractedBits = (currentByte & mask) >> bitOffset;
|
||||||
|
|
||||||
|
result[bitsRead / 8] |= (byte)(extractedBits << (bitsRead % 8));
|
||||||
|
|
||||||
|
bitsRead += bitsToCopy;
|
||||||
|
bitOffset = 0;
|
||||||
|
byteStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new InvalidOperationException("Invalid Granularity value.");
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (Granularity > 0)
|
||||||
|
{
|
||||||
|
int byteStart = idx * Granularity;
|
||||||
|
if (byteStart + Granularity > RiboseSequence.Length)
|
||||||
|
throw new IndexOutOfRangeException("Index out of bounds for RiboseSequence.");
|
||||||
|
if (value.Length > Granularity)
|
||||||
|
throw new ArgumentException($"Input byte array length exceeds Granularity ({Granularity} bytes).");
|
||||||
|
|
||||||
|
Array.Copy(value, 0, RiboseSequence, byteStart, value.Length);
|
||||||
|
}
|
||||||
|
else if (Granularity < 0)
|
||||||
|
{
|
||||||
|
int bitsPerGene = 8 / -Granularity;
|
||||||
|
int bitStart = idx * bitsPerGene;
|
||||||
|
int byteStart = bitStart / 8;
|
||||||
|
int bitOffset = bitStart % 8;
|
||||||
|
|
||||||
|
int bitsWritten = 0;
|
||||||
|
int byteIndex = 0;
|
||||||
|
|
||||||
|
while (bitsWritten < bitsPerGene)
|
||||||
|
{
|
||||||
|
int remainingBitsInByte = 8 - bitOffset;
|
||||||
|
int bitsToWrite = Math.Min(bitsPerGene - bitsWritten, remainingBitsInByte);
|
||||||
|
|
||||||
|
int mask = ((1 << bitsToWrite) - 1) << bitOffset;
|
||||||
|
int newBits = (value[byteIndex] >> (bitsWritten % 8)) & ((1 << bitsToWrite) - 1);
|
||||||
|
|
||||||
|
RiboseSequence[byteStart] &= (byte)~mask;
|
||||||
|
RiboseSequence[byteStart] |= (byte)(newBits << bitOffset);
|
||||||
|
|
||||||
|
bitsWritten += bitsToWrite;
|
||||||
|
bitOffset = 0;
|
||||||
|
byteStart++;
|
||||||
|
if (bitsWritten % 8 == 0) byteIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Invalid Granularity value.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public Gene(int resolution, byte[] riboseSequence)
|
||||||
|
{
|
||||||
|
Resolution = resolution;
|
||||||
|
RiboseSequence = riboseSequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetsRequiredMembers]
|
||||||
|
public Gene(int resolution, Board board)
|
||||||
|
{
|
||||||
|
Resolution = resolution;
|
||||||
|
|
||||||
|
int subGridBits = resolution * resolution;
|
||||||
|
int subGridBytes = (subGridBits + 7) / 8;
|
||||||
|
int subGridWidth = (board.Width + resolution - 1) / resolution;
|
||||||
|
int subGridHeight = (board.Height + resolution - 1) / resolution;
|
||||||
|
|
||||||
|
RiboseSequence = subGridBits switch
|
||||||
|
{
|
||||||
|
1 => new byte[(board.Width * board.Height + 7) / 8],
|
||||||
|
4 => new byte[(subGridWidth * subGridHeight + 1) / 2],
|
||||||
|
_ => new byte[subGridWidth * subGridHeight * subGridBytes]
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var (x, y) in board.Lives)
|
||||||
|
{
|
||||||
|
EncodeCell(x, y, board.Width, subGridBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board Restore(int width, int height, bool qX = false, bool qY = false, bool useTracer = false)
|
||||||
|
{
|
||||||
|
var board = new Board(width, height, qX, qY, useTracer);
|
||||||
|
|
||||||
|
for (int subGridIndex = 0; subGridIndex < Length; subGridIndex++)
|
||||||
|
DecodeSubGrid(subGridIndex, width, board);
|
||||||
|
|
||||||
|
return board;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EncodeCell(int x, int y, int width, int subGridBytes)
|
||||||
|
{
|
||||||
|
int subGridX = x / Resolution;
|
||||||
|
int subGridY = y / Resolution;
|
||||||
|
int subGridWidth = (width + Resolution - 1) / Resolution;
|
||||||
|
|
||||||
|
if (Resolution == 1)
|
||||||
|
{
|
||||||
|
int idx = y * width + x;
|
||||||
|
int bIdx = idx / 8;
|
||||||
|
int offset = idx % 8;
|
||||||
|
RiboseSequence[bIdx] |= (byte)(1 << offset);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Resolution == 2)
|
||||||
|
{
|
||||||
|
int idx = subGridY * subGridWidth + subGridX;
|
||||||
|
int bIdx = idx / 2;
|
||||||
|
int offset = (idx % 2) * 4;
|
||||||
|
int lX = x % 2;
|
||||||
|
int lY = y % 2;
|
||||||
|
int bitIdx = lY * 2 + lX;
|
||||||
|
RiboseSequence[bIdx] |= (byte)(1 << (offset + bitIdx));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int subGridIndex = subGridY * subGridWidth + subGridX;
|
||||||
|
int subGridByteOffset = subGridIndex * subGridBytes;
|
||||||
|
|
||||||
|
int localX = x % Resolution;
|
||||||
|
int localY = y % Resolution;
|
||||||
|
int bitIndex = localY * Resolution + localX;
|
||||||
|
|
||||||
|
int byteIndex = subGridByteOffset + (bitIndex / 8);
|
||||||
|
int bitOffset = bitIndex % 8;
|
||||||
|
|
||||||
|
RiboseSequence[byteIndex] |= (byte)(1 << bitOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecodeSubGrid(int subGridIndex, int width, Board board)
|
||||||
|
{
|
||||||
|
int subGridWidth = (width + Resolution - 1) / Resolution;
|
||||||
|
int subGridX = subGridIndex % subGridWidth;
|
||||||
|
int subGridY = subGridIndex / subGridWidth;
|
||||||
|
|
||||||
|
if (Resolution == 1)
|
||||||
|
{
|
||||||
|
int idx = subGridIndex;
|
||||||
|
int byteIndex = idx / 8;
|
||||||
|
int bitOffset = idx % 8;
|
||||||
|
|
||||||
|
if ((RiboseSequence[byteIndex] & (1 << bitOffset)) != 0)
|
||||||
|
{
|
||||||
|
int globalX = idx % width;
|
||||||
|
int globalY = idx / width;
|
||||||
|
board.Toggle(globalX, globalY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Resolution == 2)
|
||||||
|
{
|
||||||
|
int byteIndex = subGridIndex / 2;
|
||||||
|
int bitOffset = (subGridIndex % 2) * 4;
|
||||||
|
|
||||||
|
for (int bitIndex = 0; bitIndex < 4; bitIndex++)
|
||||||
|
{
|
||||||
|
if ((RiboseSequence[byteIndex] & (1 << (bitOffset + bitIndex))) != 0)
|
||||||
|
{
|
||||||
|
int localX = bitIndex % 2;
|
||||||
|
int localY = bitIndex / 2;
|
||||||
|
|
||||||
|
int globalX = subGridX * Resolution + localX;
|
||||||
|
int globalY = subGridY * Resolution + localY;
|
||||||
|
|
||||||
|
board.Toggle(globalX, globalY, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int subGridBytes = (Resolution * Resolution + 7) / 8;
|
||||||
|
int subGridByteOffset = subGridIndex * subGridBytes;
|
||||||
|
|
||||||
|
for (int bitIndex = 0; bitIndex < Resolution * Resolution; bitIndex++)
|
||||||
|
{
|
||||||
|
int byteIndex = subGridByteOffset + (bitIndex / 8);
|
||||||
|
int bitOffset = bitIndex % 8;
|
||||||
|
|
||||||
|
if ((RiboseSequence[byteIndex] & (1 << bitOffset)) != 0)
|
||||||
|
{
|
||||||
|
int localX = bitIndex % Resolution;
|
||||||
|
int localY = bitIndex / Resolution;
|
||||||
|
|
||||||
|
int globalX = subGridX * Resolution + localX;
|
||||||
|
int globalY = subGridY * Resolution + localY;
|
||||||
|
|
||||||
|
board.Toggle(globalX, globalY, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public required int Resolution { get; init; }
|
||||||
|
public byte[] RiboseSequence { get; private set; }
|
||||||
|
|
||||||
|
public int Granularity => Resolution switch
|
||||||
|
{
|
||||||
|
1 => -8,
|
||||||
|
2 => -2,
|
||||||
|
_ => (Resolution * Resolution + 7) / 8
|
||||||
|
};
|
||||||
|
|
||||||
|
public Gene Mutate(double[] weight, float rate = 0.1f, bool inPlace = false)
|
||||||
|
{
|
||||||
|
if (weight.Length != Length)
|
||||||
|
throw new Exception();
|
||||||
|
double sx = weight.Sum() + 0.01;
|
||||||
|
Random random = new();
|
||||||
|
Gene res = inPlace ? this : new Gene(Resolution, (byte[])RiboseSequence.Clone());
|
||||||
|
for (int i = 0; i < Length; i++)
|
||||||
|
{
|
||||||
|
if (random.NextDouble() > weight[i] / sx && random.NextDouble() > weight[i] / sx)
|
||||||
|
res = res.Mutate(i, rate, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Gene Mutate(int riboseIndex, float rate = 0.1f, bool inPlace = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
Gene res = inPlace ? this: new Gene(Resolution, (byte[])RiboseSequence.Clone());
|
||||||
|
byte[] ribose = res[riboseIndex];
|
||||||
|
Random random = new();
|
||||||
|
|
||||||
|
for (int i = 0; i < ribose.Length; i++)
|
||||||
|
{
|
||||||
|
for (int bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
if (random.NextDouble() < rate)
|
||||||
|
{
|
||||||
|
ribose[i] ^= (byte)(1 << bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res[riboseIndex] = ribose;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Gene Hybrid(Gene a, Gene b, double[]? weightA = null, double[]? weightB = null)
|
||||||
|
{
|
||||||
|
if (a.Resolution != b.Resolution)
|
||||||
|
throw new ArgumentException("Resolutions of both genes must match.");
|
||||||
|
if (weightA == null || weightB == null)
|
||||||
|
{
|
||||||
|
weightA = Enumerable.Repeat(1.0, a.Length).ToArray();
|
||||||
|
weightB = Enumerable.Repeat(1.0, b.Length).ToArray();
|
||||||
|
}
|
||||||
|
else if (weightA.Length != a.Length || weightB.Length != b.Length)
|
||||||
|
throw new ArgumentException("Weight arrays must match the number of riboses.");
|
||||||
|
|
||||||
|
if (a.Length != b.Length)
|
||||||
|
throw new ArgumentException("Gene must have same size");
|
||||||
|
Random random = new();
|
||||||
|
Gene res = new Gene(a.Resolution, (byte[])a.RiboseSequence.Clone());
|
||||||
|
for (int i = 0; i < res.Length; i++)
|
||||||
|
{
|
||||||
|
byte[] ra = a[i];
|
||||||
|
byte[] rb = b[i];
|
||||||
|
double ratio = weightA[i] / (weightA[i] + weightB[i] + 1E-5d);
|
||||||
|
res[i] = random.NextDouble() < ratio ? ra : rb;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
87
src/Solver.cs
Normal file
87
src/Solver.cs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
namespace InverseOfLife;
|
||||||
|
|
||||||
|
public class Solver
|
||||||
|
{
|
||||||
|
public Solver(Board target, int steps)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
Steps = steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board Target { get; init; }
|
||||||
|
private int Steps { get; set; }
|
||||||
|
|
||||||
|
private static void SortBest((Board? b, Gene? g, double s)[] best)
|
||||||
|
{
|
||||||
|
int c = best.Length-1;
|
||||||
|
while (c != 0)
|
||||||
|
{
|
||||||
|
if (best[c].s > best[c - 1].s)
|
||||||
|
(best[c], best[c - 1]) = (best[c - 1], best[c]);
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
c--;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public (Gene, double) Solve(int withResolution=2, int maxGenerations=50, int topN=10, float mutationRate=0.1f)
|
||||||
|
{
|
||||||
|
List<(Board, Gene)> currentGeneration = GetInitialGeneration()
|
||||||
|
.Select(x => (x, new Gene(withResolution, x)))
|
||||||
|
.ToList();
|
||||||
|
Gene res;
|
||||||
|
Random random = new();
|
||||||
|
(int, int) selectParent() => (random.Next() % topN, random.Next() % topN);
|
||||||
|
(Board? b, Gene? gene, double score)[] best = new (Board?, Gene?, double)[topN + 1];
|
||||||
|
for (int i = 0; i < topN + 1; i++)
|
||||||
|
best[i] = (null, null, 0);
|
||||||
|
for (int gen = 0; gen < maxGenerations; gen++)
|
||||||
|
{
|
||||||
|
HashSet<(Board, Gene)> nextGeneration = new();
|
||||||
|
foreach ((Board b, Gene g) in currentGeneration)
|
||||||
|
{
|
||||||
|
b.Evaluate(Steps);
|
||||||
|
double score = Summarizer.Score(b, Target, false);
|
||||||
|
best[topN] = (b, g, score);
|
||||||
|
SortBest(best);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < maxGenerations; i++)
|
||||||
|
{
|
||||||
|
(int pa, int pb) = selectParent();
|
||||||
|
|
||||||
|
double[] weightA = Summarizer.GetWeight(best[pa].b!, Target, withResolution, true);
|
||||||
|
double[] weightB = Summarizer.GetWeight(best[pb].b!, Target, withResolution, true);
|
||||||
|
Gene parentA = best[pa].gene!.Mutate(weightA, mutationRate);
|
||||||
|
Gene parentB = best[pb].gene!.Mutate(weightB, mutationRate);
|
||||||
|
Gene newGene = Gene.Hybrid(parentA, parentB, weightA, weightB);
|
||||||
|
Board b = newGene.Restore(Target.Width, Target.Height, Target.QuotientX, Target.QuotientY, true);
|
||||||
|
nextGeneration.Add((b, newGene));
|
||||||
|
}
|
||||||
|
Console.WriteLine($"--------{best[0].score}");
|
||||||
|
currentGeneration = nextGeneration.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (best[0].gene!, best[0].score);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<Board> GetInitialGeneration(int amount=50, float operationRatio=0.25f)
|
||||||
|
{
|
||||||
|
int totalCells = Target.Height * Target.Width;
|
||||||
|
int activeCells = (int)Math.Ceiling(totalCells * operationRatio);
|
||||||
|
if (activeCells <= 0)
|
||||||
|
activeCells = 1;
|
||||||
|
Random random = new Random();
|
||||||
|
|
||||||
|
(int, int) randomCell() => (random.Next() % Target.Width, random.Next() % Target.Height);
|
||||||
|
|
||||||
|
for (int i = 0; i < amount; i++)
|
||||||
|
{
|
||||||
|
Board b = new Board(Target.Width, Target.Height, Target.QuotientX, Target.QuotientY, true);
|
||||||
|
for (int j = 0; j < activeCells; j++)
|
||||||
|
b.Toggle(randomCell(), true);
|
||||||
|
yield return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
151
src/Summarizer.cs
Normal file
151
src/Summarizer.cs
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
namespace InverseOfLife;
|
||||||
|
|
||||||
|
public static class Summarizer
|
||||||
|
{
|
||||||
|
public static Dictionary<int, Dictionary<int, double>> ContributionBySubGrid(Board board, int resolution)
|
||||||
|
{
|
||||||
|
if (board.Tracer is null)
|
||||||
|
throw new Exception("tracer not enabled");
|
||||||
|
Dictionary<int, Dictionary<int, double>> res = new();
|
||||||
|
|
||||||
|
int mapper(int x, int y)
|
||||||
|
{
|
||||||
|
int sgw = (board.Width + resolution - 1) / resolution;
|
||||||
|
int subX = x / resolution;
|
||||||
|
int subY = y / resolution;
|
||||||
|
return subY * sgw + subX;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((int cellX, int cellY) in board.Tracer.Contribution.Keys)
|
||||||
|
{
|
||||||
|
int targetSGid = mapper(cellX, cellY);
|
||||||
|
if (!res.ContainsKey(targetSGid))
|
||||||
|
res[targetSGid] = new Dictionary<int, double>();
|
||||||
|
foreach (((int, int)sourceCell, double sourceContribution) in board.Tracer.Contribution[
|
||||||
|
(cellX, cellY)])
|
||||||
|
{
|
||||||
|
int sourceSGid = mapper(sourceCell.Item1, sourceCell.Item2);
|
||||||
|
if (!res[targetSGid].ContainsKey(sourceSGid))
|
||||||
|
res[targetSGid][sourceSGid] = 0;
|
||||||
|
res[targetSGid][sourceSGid] += sourceContribution;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double Score(Board yAct, Board yGnd, bool tpOnly = false)
|
||||||
|
{
|
||||||
|
if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX ||
|
||||||
|
yAct.QuotientY != yGnd.QuotientY)
|
||||||
|
throw new Exception();
|
||||||
|
double tp = 0;
|
||||||
|
HashSet<(int, int)> allAlives = yAct.Lives.Union(yGnd.Lives).ToHashSet();
|
||||||
|
double tn = yGnd.Height * yGnd.Width - allAlives.Count;
|
||||||
|
foreach ((int, int) cell in yAct.Lives)
|
||||||
|
{
|
||||||
|
if (yGnd.Lives.Contains(cell))
|
||||||
|
tp += 1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tpOnly)
|
||||||
|
{
|
||||||
|
if (yGnd.Lives.Count > 0)
|
||||||
|
return tp / yGnd.Lives.Count;
|
||||||
|
return 1d;
|
||||||
|
}
|
||||||
|
return (tp + tn) / (yGnd.Height * yGnd.Width);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static double[] ScoreBySubGrid(Board yAct, Board yGnd, int resolution, bool tpOnly = false)
|
||||||
|
{
|
||||||
|
if (yAct.Height != yGnd.Height || yAct.Width != yGnd.Width || yAct.QuotientX != yGnd.QuotientX ||
|
||||||
|
yAct.QuotientY != yGnd.QuotientY)
|
||||||
|
throw new ArgumentException("Boards must have the same dimensions and wrapping properties.");
|
||||||
|
int subGridWidth = (yAct.Width + resolution - 1) / resolution;
|
||||||
|
int subGridHeight = (yAct.Height + resolution - 1) / resolution;
|
||||||
|
double[] res = new double[subGridWidth * subGridHeight];
|
||||||
|
|
||||||
|
int mapper(int x, int y)
|
||||||
|
{
|
||||||
|
int subX = x / resolution;
|
||||||
|
int subY = y / resolution;
|
||||||
|
return subY * subGridWidth + subX;
|
||||||
|
}
|
||||||
|
|
||||||
|
double[] tps = new double[subGridHeight * subGridWidth];
|
||||||
|
HashSet<(int, int)>[] actAlive = new HashSet<(int, int)>[subGridHeight * subGridWidth];
|
||||||
|
HashSet<(int, int)>[] gndAlive = new HashSet<(int, int)>[subGridWidth * subGridHeight];
|
||||||
|
for (int i = 0; i < subGridHeight * subGridWidth; i++)
|
||||||
|
{
|
||||||
|
actAlive[i] = new HashSet<(int, int)>();
|
||||||
|
gndAlive[i] = new HashSet<(int, int)>();
|
||||||
|
}
|
||||||
|
foreach ((int x, int y) in yAct.Lives)
|
||||||
|
{
|
||||||
|
int subGridIdx = mapper(x, y);
|
||||||
|
actAlive[subGridIdx].Add((x, y));
|
||||||
|
if (yGnd.Lives.Contains((x, y)))
|
||||||
|
tps[subGridIdx] += 1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((int x, int y) in yGnd.Lives)
|
||||||
|
{
|
||||||
|
int subGridIdx = mapper(x, y);
|
||||||
|
gndAlive[subGridIdx].Add((x, y));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int subGridY = 0; subGridY < subGridHeight; subGridY++)
|
||||||
|
{
|
||||||
|
for (int subGridX = 0; subGridX < subGridWidth; subGridX++)
|
||||||
|
{
|
||||||
|
int subGridIdx = subGridY * subGridWidth + subGridX;
|
||||||
|
|
||||||
|
int subGridStartX = subGridX * resolution;
|
||||||
|
int subGridStartY = subGridY * resolution;
|
||||||
|
int subGridEndX = Math.Min(subGridStartX + resolution, yAct.Width);
|
||||||
|
int subGridEndY = Math.Min(subGridStartY + resolution, yAct.Height);
|
||||||
|
|
||||||
|
int cellsInSubGrid = (subGridEndX - subGridStartX) * (subGridEndY - subGridStartY);
|
||||||
|
double tn = cellsInSubGrid - actAlive[subGridIdx].Union(gndAlive[subGridIdx]).Count();
|
||||||
|
double tp = tps[subGridIdx];
|
||||||
|
if(tpOnly)
|
||||||
|
{
|
||||||
|
if (gndAlive[subGridIdx].Count > 0)
|
||||||
|
res[subGridIdx] = tp / gndAlive[subGridIdx].Count;
|
||||||
|
else
|
||||||
|
res[subGridIdx] = 1d;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
res[subGridIdx] = (tp + tn) / cellsInSubGrid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double[] GetWeight(Board yAct, Board yGnd, int resolution, bool tpOnly=false)
|
||||||
|
{
|
||||||
|
if (yAct.Tracer is null)
|
||||||
|
throw new Exception("tracer was not enabled");
|
||||||
|
Dictionary<int, Dictionary<int, double>> contribution = ContributionBySubGrid(yAct, resolution);
|
||||||
|
double[] score = ScoreBySubGrid(yAct, yGnd, resolution, tpOnly);
|
||||||
|
int subgridWidth = (yAct.Width + resolution - 1) / resolution;
|
||||||
|
int subgridHeight = (yAct.Height + resolution - 1) / resolution;
|
||||||
|
double[] res = new double[subgridWidth * subgridHeight];
|
||||||
|
for (int i = 0; i < res.Length; i++)
|
||||||
|
{
|
||||||
|
double weight = 0;
|
||||||
|
foreach (int targetSubGridIdx in contribution.Keys)
|
||||||
|
{
|
||||||
|
double subGridScore = score[targetSubGridIdx];
|
||||||
|
if (!contribution[targetSubGridIdx].ContainsKey(i))
|
||||||
|
continue;
|
||||||
|
weight += contribution[targetSubGridIdx][i] * subGridScore;
|
||||||
|
}
|
||||||
|
res[i] = weight;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user