diff --git a/55_Life/csharp/Program.cs b/55_Life/csharp/Program.cs index 47ed7386..eeb00465 100644 --- a/55_Life/csharp/Program.cs +++ b/55_Life/csharp/Program.cs @@ -1,30 +1,15 @@ -/* - * LIFE - * An implementation of John Conway's popular cellular automaton - * Ported by Dyego Alekssander Maas - * - * An example pattern would be: - * " * " - * "***" - * "DONE" (indicates that the simulation can start) - * - * You can find patterns to play with here: http://pi.math.cornell.edu/~lipa/mec/lesson6.html - * - * Optionally, you can run this program with the "--wait 1000" argument, the number being the time in milliseconds - * that the application will pause between each iteration. This is enables you to watch the simulation unfolding. - * By default, there is no pause between iterations. -*/ -using System.Text; +using System.Text; const int maxWidth = 70; const int maxHeight = 24; Console.WriteLine("ENTER YOUR PATTERN:"); -var pattern = ReadPattern(limitHeight: maxHeight).ToArray(); +var pattern = new Pattern(ReadPattern(limitHeight: maxHeight).ToArray()); -var (minX, minY) = FindTopLeftCorner(pattern); -var maxX = maxHeight; -var maxY = maxWidth; +var minX = 10 - pattern.Height / 2; +var minY = 34 - pattern.Width / 2; +var maxX = maxHeight - 1; +var maxY = maxWidth - 1; var matrix = new Matrix(height: maxHeight, width: maxWidth); var simulation = InitializeSimulation(pattern, matrix); @@ -39,7 +24,6 @@ IEnumerable ReadPattern(int limitHeight) var input = Console.ReadLine(); if (input.ToUpper() == "DONE") { - yield return string.Empty; break; } @@ -47,23 +31,12 @@ IEnumerable ReadPattern(int limitHeight) // game allowed you to input an '.' before the spaces to circumvent this limitation. This behavior was // kept for compatibility. if (input.StartsWith('.')) - yield return input.Substring(1, input.Length - 2); + yield return input.Substring(1, input.Length - 1); yield return input; } } -(int minX, int minY) FindTopLeftCorner(IEnumerable patternLines) -{ - var longestInput = patternLines - .Select((value, index) => (index, value)) - .OrderByDescending(input => input.value.Length) - .First(); - var centerX = (11 - longestInput.index / 2) - 1; - var centerY = (33 - longestInput.value.Length / 2) - 1; - return (centerX, centerY); -} - void PrintHeader() { void PrintCentered(string text) @@ -82,15 +55,16 @@ void PrintHeader() Console.WriteLine(); } -Simulation InitializeSimulation(IReadOnlyList inputPattern, Matrix matrixToInitialize) { +Simulation InitializeSimulation(Pattern pattern, Matrix matrixToInitialize) { var newSimulation = new Simulation(); - // translates the pattern to the middle of the simulation and counts initial population - for (var x = 0; x < inputPattern.Count; x++) + // transcribes the pattern to the middle of the simulation and counts initial population + for (var x = 0; x < pattern.Height; x++) { - for (var y = 0; y < inputPattern[x].Length; y++) + for (var y = 0; y < pattern.Width; y++) { - if (inputPattern[x][y] == ' ') continue; + if (pattern.Content[x][y] == ' ') + continue; matrixToInitialize[minX + x, minY + y] = CellState.Stable; newSimulation.IncreasePopulation(); @@ -102,15 +76,14 @@ Simulation InitializeSimulation(IReadOnlyList inputPattern, Matrix matri TimeSpan GetPauseBetweenIterations() { - if (args.Length == 2) + if (args.Length != 2) return TimeSpan.Zero; + + var parameter = args[0].ToLower(); + if (parameter.Contains("wait")) { - var parameter = args[0].ToLower(); - if (parameter.Contains("wait")) - { - var value = args[1]; - if (int.TryParse(value, out var sleepMilliseconds)) - return TimeSpan.FromMilliseconds(sleepMilliseconds); - } + var value = args[1]; + if (int.TryParse(value, out var sleepMilliseconds)) + return TimeSpan.FromMilliseconds(sleepMilliseconds); } return TimeSpan.Zero; @@ -123,28 +96,29 @@ void ProcessSimulation() while (true) { - Console.WriteLine($"GENERATION: {simulation.Generation}\tPOPULATION: {simulation.Population}"); - if (isInvalid) - Console.WriteLine("INVALID!"); + var invalidText = isInvalid ? "INVALID!" : ""; + Console.WriteLine($"GENERATION: {simulation.Generation}\tPOPULATION: {simulation.Population} {invalidText}"); simulation.StartNewGeneration(); var nextMinX = maxHeight - 1; var nextMinY = maxWidth - 1; var nextMaxX = 0; - var nextMaxY = 0; + var nextMaxY = 0; + + var matrixOutput = new StringBuilder(); // prints the empty lines before search area for (var x = 0; x < minX; x++) { - Console.WriteLine(); + matrixOutput.AppendLine(); } // refreshes the matrix and updates search area - for (var x = minX; x < maxX; x++) + for (var x = minX; x <= maxX; x++) { var printedLine = Enumerable.Repeat(' ', maxWidth).ToList(); - for (var y = minY; y < maxY; y++) + for (var y = minY; y <= maxY; y++) { if (matrix[x, y] == CellState.Dying) { @@ -163,20 +137,20 @@ void ProcessSimulation() printedLine[y] = '*'; nextMinX = Math.Min(x, nextMinX); - nextMaxX = Math.Max(x + 1, nextMaxX); + nextMaxX = Math.Max(x, nextMaxX); nextMinY = Math.Min(y, nextMinY); - nextMaxY = Math.Max(y + 1, nextMaxY); + nextMaxY = Math.Max(y, nextMaxY); } - Console.WriteLine(string.Join(separator: null, values: printedLine)); + matrixOutput.AppendLine(string.Join(separator: null, values: printedLine)); } // prints empty lines after search area for (var x = maxX + 1; x < maxHeight; x++) { - Console.WriteLine(); + matrixOutput.AppendLine(); } - Console.WriteLine(); + Console.Write(matrixOutput); void UpdateSearchArea() { @@ -185,42 +159,45 @@ void ProcessSimulation() minY = nextMinY; maxY = nextMaxY; - if (minX < 3) + const int limitX = 21; + const int limitY = 67; + + if (minX < 2) { - minX = 3; + minX = 2; + isInvalid = true; + } + + if (maxX > limitX) + { + maxX = limitX; isInvalid = true; } - if (maxX > 22) + if (minY < 2) { - maxX = 22; + minY = 2; isInvalid = true; } - if (minY < 3) + if (maxY > limitY) { - minY = 3; - isInvalid = true; - } - - if (maxY > 68) - { - maxY = 68; + maxY = limitY; isInvalid = true; } } UpdateSearchArea(); - for (var x = minX - 1; x < maxX + 2; x++) + for (var x = minX - 1; x <= maxX + 1; x++) { - for (var y = minY - 1; y < maxY + 2; y++) + for (var y = minY - 1; y <= maxY + 1; y++) { int CountNeighbors() { var neighbors = 0; - for (var i = x - 1; i < x + 2; i++) + for (var i = x - 1; i <= x + 1; i++) { - for (var j = y - 1; j < y + 2; j++) + for (var j = y - 1; j <= y + 1; j++) { if (matrix[i, j] == CellState.Stable || matrix[i, j] == CellState.Dying) neighbors++; @@ -231,7 +208,7 @@ void ProcessSimulation() } var neighbors = CountNeighbors(); - if (matrix[x, y] == 0) + if (matrix[x, y] == CellState.Empty) { if (neighbors == 3) { @@ -261,6 +238,27 @@ void ProcessSimulation() } } +public class Pattern +{ + public string[] Content { get; } + public int Height { get; } + public int Width { get; } + + public Pattern(IReadOnlyCollection patternLines) + { + Height = patternLines.Count; + Width = patternLines.Max(x => x.Length); + Content = NormalizeWidth(patternLines); + } + + private string[] NormalizeWidth(IReadOnlyCollection patternLines) + { + return patternLines + .Select(x => x.PadRight(Width, ' ')) + .ToArray(); + } +} + /// /// Indicates the state of a given cell in the simulation. /// diff --git a/55_Life/csharp/README.md b/55_Life/csharp/README.md index 4daabb5c..5b4ffe62 100644 --- a/55_Life/csharp/README.md +++ b/55_Life/csharp/README.md @@ -1,3 +1,71 @@ +# Life + +An implementation of John Conway's popular cellular automaton, also know as **Conway's Game of Life**. The original source was downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html). + +Ported by Dyego Alekssander Maas. + +## How to run + +This program requires you to install [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0). After installed, you just need to run `dotnet run` from this directory in the terminal. + +## Know more about Conway's Game of Life + +You can find more about Conway's Game of Life on this page of the [Cornell Math Explorers' Club](http://pi.math.cornell.edu/~lipa/mec/lesson6.html), alongside many examples of patterns you can try. + +### Optional parameters + +Optionally, you can run this program with the `--wait 1000` argument, the number being the time in milliseconds +that the application will pause between each iteration. This is enables you to watch the simulation unfolding. By default, there is no pause between iterations. + +The complete command would be `dotnet run --wait 1000`. + +## Entering patterns + +Once running the game, you are expected to enter a pattern. This pattern consists of multiple lines of text with either **spaces** or **some character**, usually an asterisk (`*`). + +Spaces represent empty cells. Asterisks represent alive cells. + +After entering the pattern, you need to enter the word "DONE". It is not case sensitive. An example of pattern would be: + +``` + * +*** +DONE +``` + +### Some patterns you could try + +``` + * +*** +``` + +``` +* +*** +``` + +``` +** +** +``` + +``` + * + * +* +``` + +This one is known as **glider**: + +``` +*** +* + * +``` + +## Instructions to the port + Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html) Conversion to [Microsoft C#](https://docs.microsoft.com/en-us/dotnet/csharp/)