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; internal set; } 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 HashSet<(int, int)>[] Frames(int steps) { HashSet<(int, int)>[] res = new HashSet<(int, int)>[steps]; for (int i = 0; i < steps; i++) { Evaluate(); res[i] = CopyLives(); } return res; } private HashSet<(int, int)> CopyLives() { HashSet<(int, int)> res = new(); foreach ((int, int) cell in Lives) res.Add(cell); return res; } public void Play(int generations, int delay = 200) { for (int i = 0; i < generations; i++) { Console.Clear(); Console.WriteLine(ToString()); Evaluate(); Thread.Sleep(delay); } } }