diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e35e7f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs/ +bin/ +obj/ +Properties/ +App.config diff --git a/Cellular Automata.csproj b/Cellular Automata.csproj new file mode 100644 index 0000000..9ca3666 --- /dev/null +++ b/Cellular Automata.csproj @@ -0,0 +1,54 @@ + + + + + Debug + AnyCPU + {2B9C1C3B-985E-4EEA-A185-CE02A115652F} + Exe + Cellular_Automata + Cellular Automata + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Cellular Automata.sln b/Cellular Automata.sln new file mode 100644 index 0000000..b1f78fe --- /dev/null +++ b/Cellular Automata.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29411.108 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Cellular Automata", "Cellular Automata.csproj", "{2B9C1C3B-985E-4EEA-A185-CE02A115652F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2B9C1C3B-985E-4EEA-A185-CE02A115652F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B9C1C3B-985E-4EEA-A185-CE02A115652F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B9C1C3B-985E-4EEA-A185-CE02A115652F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B9C1C3B-985E-4EEA-A185-CE02A115652F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4CE6EFDD-63F1-49F5-8A62-992563D6EFC2} + EndGlobalSection +EndGlobal diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..64a1351 --- /dev/null +++ b/Program.cs @@ -0,0 +1,448 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Drawing; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace CellularAutomata +{ + // Zum Anzeigen des Bildes in einer Synchronen weise + static class BufferedDisplay + { + // Zeigt das Bild und verwaltet alle Schritte um dies zu ermöglichen + public static void ShowDisplay(Cell[,] cells) + { + // GetString Methode wird aufgerufen, um alle Zellen in einen String zu bekommen + string outputToCon = GetString(cells); + // Der aktive Bereich zum schreiben wird auf 0,0 gesetzt um das vorherige Bild zu ersetzen + Console.SetCursorPosition(0, 0); + // Bild wird auf der Konsole ausgegeben + Console.WriteLine(outputToCon); + return; + } + + // Die Zellen werden in einen String geschrieben um das Bild anzeigen zu können + private static string GetString(Cell[,] cells) + { + // Strinbuilder, weil schneller als "+=" + StringBuilder sb = new StringBuilder(); + + for (int y = 0; y < cells.GetUpperBound(1) + 1; y++) + { + for (int x = 0; x < cells.GetUpperBound(0) + 1; x++) + { + // Hinzufügen eines "X" wenn Zelle am Leben / eines " " (Leerzeichens) wenn Zelle Tot + sb.Append(cells[x, y].Alive ? "X" : " "); + } + // Linienende, es muss auf der nächsten Linie weiter geschrieben werden sonst macht das keinen sinn + sb.Append("\n"); + } + // Rückgabe an Verwalter um String auf Konsole auszugeben + return sb.ToString(); + } + } + + // Das Herzstück des Programmes, ohne das nichts laufen würde + class Cell + { + // Ob die Zelle am leben oder Tot ist + public bool Alive { get => _alive; set => _alive = value; } + private bool _alive; + + public bool Pattern = false; + // Durch synchronität ob die Zelle am ende der "Runde" sterben wird oder nicht + private bool _gonnaDie; + // Durch synchronität ob die Zelle am ende der "Runde" wiederbelebt wird oder nicht + private bool _gonnaRespawn; + private readonly int _x; + private readonly int _y; + + + private bool[] _latest = new bool[12]; + + // Die Nachbarn der Zelle, sehr wichtig für die Berechnungen ob eine Zelle stirbt / lebt / wiederbelebt wird + public List _neighbours = new List(); + + + public Cell(int x, int y, bool alive) + { + _alive = alive; + _gonnaDie = false; + _x = x; + _y = y; + _gonnaDie = false; + _gonnaRespawn = false; + } + + // Findung der Nachbarn + public void FindNeighbours(Cell[,] allCells) + // TODO: Modular machen + { + if (_x - 1 >= 0) + _neighbours.Add(allCells[_x - 1, _y]); + if (_y - 1 >= 0) + _neighbours.Add(allCells[_x, _y - 1]); + if (_x + 1 <= allCells.GetUpperBound(0)) + _neighbours.Add(allCells[_x + 1, _y]); + if (_y + 1 <= allCells.GetUpperBound(1)) + _neighbours.Add(allCells[_x, _y + 1]); + if (_y - 1 >= 0 & _x - 1 >= 0) + _neighbours.Add(allCells[_x - 1, _y - 1]); + if (_y + 1 <= allCells.GetUpperBound(1) & _x + 1 <= allCells.GetUpperBound(0)) + _neighbours.Add(allCells[_x + 1, _y + 1]); + if (_x + 1 <= allCells.GetUpperBound(0) && _y - 1 >= 0) + _neighbours.Add(allCells[_x + 1, _y - 1]); + if (_y + 1 <= allCells.GetUpperBound(1) && _x - 1 >= 0) + _neighbours.Add(allCells[_x - 1, _y + 1]); + } + + // Zelle wird makiert für Tot / leben / wiederbelebung + public void DeadOrAlive() + { + int counter = 0; + // Es wird gezählt wieviele Lebende Nachbarn eine Zelle hat + foreach (var item in _neighbours) + { + if (item._alive) + { + counter++; + } + } + + // Um rechenleistung zu schonen wird nur geguckt ob die Zelle stirbt, wenn sie auch lebt um sterben zu Können + if (_alive) + { + // Anpassungsmöglichkeiten wann eine Zelle stirbt + if (counter < 2 || counter > 3) + { + _gonnaDie = true; + } + // Oder wann nicht + else + { + _gonnaDie = false; + } + } + if (!_alive && counter == 3) + { + _gonnaRespawn = true; + } + } + + // Synchrones sterben / wiederbeleben / Nichts passieren + public void Die() + { + // Pattern recognition, damit man nicht alle 3000 Runden warten muss wenn es nur noch statisch ist + Pattern = true; + + _latest[0] = _latest[1]; + _latest[1] = _latest[2]; + _latest[2] = _latest[3]; + _latest[3] = _latest[4]; + _latest[4] = _latest[5]; + _latest[5] = _latest[6]; + + _latest[6] = _latest[7]; + _latest[7] = _latest[8]; + _latest[8] = _latest[9]; + _latest[9] = _latest[10]; + _latest[10] = _latest[11]; + _latest[11] = _alive; + + for (int i = 0; i < 5; i++) + { + if (!_latest[i] == _latest[i + 6]) + { + Pattern = false; + } + } + // Wird sterben + if (_gonnaDie == true) + { + _alive = false; + } + // Wird wiederbelebt + if (_gonnaRespawn == true) + { + _alive = true; + _gonnaRespawn = false; + } + _gonnaDie = false; + } + } + + // Einlese System von + class AutomataReader + { + public string[] lines; + private readonly int _maxX; + private readonly int _maxY; + + public AutomataReader(string path, int x = 0, int y = 0) + { + // Die Datei wird eingelesen und in einen String gepackt + lines = System.IO.File.ReadAllLines(path); + _maxX = x; + _maxY = y; + } + + // Eher veraltet / findet die längste linie um die variable maxX zu setzen + public int FindLongestLine() + { + int longestLine = 0; + int tmpCounter = 0; + foreach (var line in lines) + { + foreach (var chr in line) + { + tmpCounter++; + } + longestLine = tmpCounter > longestLine ? tmpCounter : longestLine; + tmpCounter = 0; + } + return longestLine; + } + + // Umwandlung des Strings in Zellen (Obj) + public Cell[,] FromFile() + { + Cell[,] output; + + // Wenn angabe für größe des "Brettes" gegeben wird dieses Brett auf die Angabe gesetzt + if (_maxX > 0 && _maxY > 0) + { + output = new Cell[_maxX, _maxY]; + } + // Falls keine Angabe wird die Größe des "Brettes" auf die Längste Linie gesetzt und wieviele Linien es gibt + else + { + output = new Cell[FindLongestLine(), lines.Length]; + } + //Initialisierung der Zellen + for (int i = 0; i < output.GetUpperBound(0) + 1; ++i) + { + for (int j = 0; j < output.GetUpperBound(1) + 1; j++) + { + output[i, j] = new Cell(i, j, false); + } + } + + int x = 0, y = 0; + // Setzung der Zellen auf Lebendig oder Tot abhängig davon was in der Datei angegeben wurde + foreach (var line in lines) + { + foreach (var chr in line) + { + output[x, y].Alive = chr == 'X'; + x++; + } + x = 0; + y++; + } + return output; + } + } + + class Program + { + static void Main(string[] args) + { + // Alle Zellen + Cell[,] allCells; + + // Stopwatch zum Ermöglichen von gleich langen "Runden" + Stopwatch sw = new Stopwatch(); + Random r = new Random(); + + // Angabe wie groß das Feld mit Zellen sein soll in Vertikal. Horizontal = Vertikal * 2 + int size = 60; + bool loadFromFile = false; + long timeToNextFrame = 100; + bool done = false; + bool patternRecognition = true; + + Restart: + Console.Clear(); + while (true) + { + Console.WriteLine("Welcome to my Cellular Automata simulation."); + Console.WriteLine("-------------------------------------------"); + Console.WriteLine("[S]tart"); + Console.WriteLine("S[e]ttings"); + Console.WriteLine("E[n]d"); + string input = Console.ReadLine(); + Console.Clear(); + + switch (input.ToLower()) + { + case "s": + goto Start; //DONT DO THIS!!!! + + case "e": + while (!done) + { + Console.WriteLine("[S]ize of Field: " + size); + Console.WriteLine("[L]oad from File: " + loadFromFile); + Console.WriteLine("[T]ime to next Frame: " + timeToNextFrame); + Console.WriteLine("[Q]uit to Main Menu"); + string input2 = Console.ReadLine(); + + switch (input2.ToLower()) + { + case "s": + Console.WriteLine("Enter new Size"); + string newSize = Console.ReadLine(); + if (!int.TryParse(newSize, out size)) + { + Console.WriteLine("Input wasnt accepted. Please try again."); + Console.ReadKey(true); + } + break; + + case "l": + Console.WriteLine("Enter [T]rue / [F]alse"); + loadFromFile = Console.ReadLine().ToLower() == "t" ? true : false; + break; + + case "t": + Console.WriteLine("Enter new time to next Frame"); + string newttnf = Console.ReadLine(); + if (!long.TryParse(newttnf, out timeToNextFrame)) + { + Console.WriteLine("Input wasnt accepted. Please try again."); + } + break; + + case "q": + done = true; + break; + + default: + Console.WriteLine("Your input wasnt recognised please try again."); + break; + } + Console.Clear(); + } + done = false; + break; + + case "n": + System.Environment.Exit(0); + break; + + default: + Console.WriteLine("Wrong input"); + break; + } + Console.Clear(); + } + // TODO: Menu und Settings verbessern + Start: + // Initialisierungsprozess + // Check ob Input Datei vorhanden falls man ein Eigenes Design Importieren möchte + if (loadFromFile && File.Exists(@"XXXXXX\Manual.txt")) + { + // TODO: -> zu Static klasse + AutomataReader automataReader = new AutomataReader(@"XXXXXX\Manual.txt", 60, 180); + // Der richtige Import von der Datei + allCells = automataReader.FromFile(); + } + else + { + // Größe des Feldes der Zellen wird gesetzt + allCells = new Cell[size * 2, size]; + for (int x = 0; x < allCells.GetUpperBound(0) + 1; x++) + { + for (int y = 0; y < allCells.GetUpperBound(1) + 1; y++) + { + allCells[x, y] = new Cell(x, y, r.Next(0, 100) > 30 ? false : true); + } + } + } + + // Warten auf eingabe um Positionierung der Konsole zu ermöglichen + Console.ReadKey(true); + for (int x = 0; x < allCells.GetUpperBound(0) + 1; x++) + { + for (int y = 0; y < allCells.GetUpperBound(1) + 1; y++) + { + // Jeder Zelle werden seine Nachbarn zugewiesen + allCells[x, y].FindNeighbours(allCells); + } + } + + // Zeigen des Aufbaus der Zellen mit einem Standbild um möglicherweise zu reproduzieren des Versuches + BufferedDisplay.ShowDisplay(allCells); + // Warten auf eingabe um das Programm starten zu lassen + Console.ReadKey(true); + + patternRecognition = true; + + // So oft wie die Anzahl es bestimmt, meistens befindet sich das Bild aber in einem "Stillstand", dadurch das alle Zellen Tod sind + // Oder nicht sterben können + for (int i = 0; i < 3000; i++) + { + if (i > 12 && patternRecognition) + { + bool foundPattern = true; + foreach (var item in allCells) + { + if (!item.Pattern) + { + foundPattern = false; + break; + } + } + if (foundPattern) + { + Console.WriteLine("Pattern found!"); + Console.ReadKey(true); + goto Restart; + } + } + + int height = allCells.GetLength(0); + int width = allCells.GetLength(1); + + Parallel.For(0, height, y => + { + for (int x = 0; x < width; x++) + { + // Zelle wird zum sterben / überleben / wiedergeboren werden makiert + // Dieser Step ist notwendig, damit die Zellen nicht asynchron von einander operieren + allCells[y, x].DeadOrAlive(); + } + }); + + // Zeigt den Bildschirm + BufferedDisplay.ShowDisplay(allCells); + + Parallel.For(0, height, y => + { + for (int x = 0; x < width; x++) + { + // Lässt die Zelle sterben falls sie stirbt / Zelle wird geboren falls sie geboren werden soll + allCells[y, x].Die(); + } + }); + + // Um für eine "Runde" immer gleich viel Zeit zu brauchen, wird gemessen wie lange alles gebraucht hat und die restlichen ms werden dann abgewartet + sw.Stop(); + + // Prüfung ob die "Runde" länger als vorgeschriebe Zeit gedauert hat + if (sw.ElapsedMilliseconds < timeToNextFrame) + { + // Wenn nicht dann wird die Zeit hier von der vorgeschriebenen Zeit subtrahiert + long time = timeToNextFrame - sw.ElapsedMilliseconds; + // Und die rest Zeit wird hier gewartet, um gleich Lange "Runden" zu garantieren + System.Threading.Thread.Sleep((int)time); + } + // Neustart der Zeit messung + sw.Restart(); + } + goto Restart; //DONT DO THIS!!!! + } + } +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..75f72dc --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Cellular Automata + +Cellular Automata or also known as Conways Game of Life is one of my favorite projects to programm. +This one is a pretty nice Version with Settings and Multithreading. +Still 2 Years old for me uploading it. +Dont exactly know whats in there \ No newline at end of file