This commit is contained in:
h z
2025-01-18 08:16:40 +00:00
commit 081863bde2
16 changed files with 863 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

13
.idea/.idea.InverseGameOfLife/.idea/.gitignore generated vendored Normal file
View 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

View 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>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@@ -0,0 +1,5 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
</profile>
</component>

View 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
View 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

View 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>

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}