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