4
\$\begingroup\$

In the C# Player's Guide book, one of the challenges was to make the game Game of Life in the console.

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
namespace GameOfLife
{
 class Program
 {
 static void Main(string[] args)
 {
 string filePath = @"C:\Users\example\Documents\Visual Studio 2015\Projects\GameOfLife\GameOfLife\Grid.txt";
 // We use ReadAllLines because Windows adds \r\n and this gets rid of \r
 string gridText = string.Join("\n", File.ReadAllLines(filePath));
 int interval = 50;
 int generation = 0;
 Game game = new Game(gridText);
 while(true)
 {
 Console.Clear();
 Console.WriteLine($"Generation: {generation++}");
 Console.WriteLine(game.ShowGrid());
 game.NextGeneration();
 // Console.ReadKey()
 Thread.Sleep(interval);
 }
 // End
 }
 }
}

Game.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GameOfLife
{
 class Game
 {
 private bool[,] Grid { get; set; }
 private int Rows { get; }
 private int Cols { get; }
 public Game(string initialGridText)
 {
 this.Grid = MakeGrid(initialGridText);
 this.Rows = this.Grid.GetLength(0);
 this.Cols = this.Grid.GetLength(1);
 }
 public void NextGeneration() {
 Func<int, int, bool> getCellStatus =
 (row, col) =>
 {
 int aliveNeighbours = GetNeighbours(row, col)
 .Where(x => x)
 .Count();
 // If it's alive, it dies if it doesn't have 2 or 3 neighbours
 if (this.Grid[row, col])
 return aliveNeighbours > 1 && aliveNeighbours < 4;
 // If it's dead, it turns alive if it has 3 neighbours
 return aliveNeighbours == 3;
 };
 bool[,] newGrid = new bool[this.Rows, this.Cols];
 loopIndex(this.Rows, rowIndex =>
 loopIndex(this.Cols, colIndex =>
 newGrid[rowIndex, colIndex] = getCellStatus(rowIndex, colIndex)));
 this.Grid = newGrid;
 }
 public bool[] GetNeighbours(int row, int col)
 {
 // Left upper corner
 if (col == 0 && row == 0)
 return new bool[3] {
 this.Grid[0, 1],
 this.Grid[1, 0],
 this.Grid[1, 1]
 };
 // Left bottom corner
 if (col == 0 && row == this.Rows - 1)
 return new bool[3] {
 this.Grid[row, 1],
 this.Grid[row - 1, 0],
 this.Grid[row - 1, 1]
 };
 // Right upper corner
 if (col == this.Cols - 1 && row == 0)
 return new bool[3] {
 this.Grid[row, col - 1],
 this.Grid[row + 1, col],
 this.Grid[row + 1, col - 1]
 };
 // Right bottom corner
 if (col == this.Cols - 1 && row == this.Rows - 1)
 return new bool[3] {
 this.Grid[row - 1, col],
 this.Grid[row, col - 1],
 this.Grid[row - 1, col - 1]
 };
 // Left side
 if (col == 0)
 return new bool[5] {
 this.Grid[row - 1, col],
 this.Grid[row + 1, col],
 this.Grid[row, col + 1],
 this.Grid[row - 1, col + 1],
 this.Grid[row + 1, col + 1]
 };
 // Right side
 if (col == this.Cols - 1)
 return new bool[5] {
 this.Grid[row - 1, col],
 this.Grid[row + 1, col],
 this.Grid[row, col - 1],
 this.Grid[row + 1, col - 1],
 this.Grid[row - 1, col - 1]
 };
 // Top side
 if (row == 0)
 return new bool[5] {
 this.Grid[row + 1, col],
 this.Grid[row, col - 1],
 this.Grid[row, col + 1],
 this.Grid[row + 1, col - 1],
 this.Grid[row + 1, col + 1]
 };
 // Down side
 if (row == this.Rows - 1)
 return new bool[5] {
 this.Grid[row - 1, col],
 this.Grid[row, col - 1],
 this.Grid[row, col + 1],
 this.Grid[row - 1, col + 1],
 this.Grid[row - 1, col - 1]
 };
 // Middle
 return new bool[8] {
 this.Grid[row - 1, col],
 this.Grid[row + 1, col],
 this.Grid[row, col - 1],
 this.Grid[row, col + 1],
 this.Grid[row + 1, col + 1],
 this.Grid[row + 1, col - 1],
 this.Grid[row - 1, col + 1],
 this.Grid[row - 1, col - 1]
 };
 }
 public string ShowGrid()
 {
 string grid = "";
 loopIndex(this.Rows, rowIndex =>
 {
 loopIndex(this.Cols, colIndex =>
 {
 // We use 2 characters because console shows us
 // twice as tall as they are wide
 grid += this.Grid[rowIndex, colIndex] ? "XX" : "..";
 });
 grid += '\n';
 });
 return grid;
 }
 private static bool[,] MakeGrid(string gridText)
 {
 string[] rowsText = gridText.Split('\n');
 int rows = rowsText.Length;
 int cols = rowsText[0].Length;
 bool[,] newGrid = new bool[rows, cols];
 loopIndex(rows, rowIndex =>
 loopIndex(cols, colIndex =>
 newGrid[rowIndex, colIndex] =
 rowsText[rowIndex][colIndex] != '.'));
 return newGrid;
 }
 private static void loopIndex(int slots, Action<int> map) =>
 Enumerable.Range(0, slots).ToList().ForEach(map);
 }
}

Example of Grid.txt (Die Hard)

  • How can I improve my code?
asked Jul 3, 2016 at 22:27
\$\endgroup\$
3
  • \$\begingroup\$ To me it looks like a use-only-lambdas-game ;-) Lambdas are good but you definitely overused them. \$\endgroup\$ Commented Jul 4, 2016 at 4:08
  • \$\begingroup\$ @t3chb0t How did I? Suggest another way \$\endgroup\$ Commented Jul 4, 2016 at 18:28
  • \$\begingroup\$ getCellStatus should just be a function, loopindex nested inside itself could be done as Action<int, int> \$\endgroup\$ Commented Jul 8, 2016 at 23:52

1 Answer 1

2
\$\begingroup\$

For GetNeighbors(), you can filter out the indexes that are out of bounds instead of hard coding the indexes for each edge case. So we get the indexes that are in bounds, iterate through the combinations, and ignore the combination where the indexes equal row and col.

public bool[] GetNeighbours(int row, int col)
{
 var xIndexes = (new[] { col - 1, col, col + 1 })
 .Where(x => x >= 0 && x < this.Cols);
 var yIndexes = (new[] { row - 1, row, row + 1 })
 .Where(y => y >= 0 && y < this.Rows)
 .ToArray();
 return
 (from x in xIndexes
 from y in yIndexes
 where !(x == col && y == row)
 select this.Grid[x, y])
 .ToArray();
} 

If you want to use LINQ to create an array with an x and y, you can use jagged arrays instead of multidimensional arrays.

answered Jul 4, 2016 at 4:19
\$\endgroup\$
2
  • \$\begingroup\$ 1) Even better, you could create an isBetween lambda for the predicates in the Wheres. 2) Why do you use ToArrayon the yIndexes and not on the xIndexes? I think you don't need it. \$\endgroup\$ Commented Jul 4, 2016 at 4:31
  • 1
    \$\begingroup\$ @t3chb0t 1) Good idea. 2) I use ToArray on the yIndexes because they get iterated for each x in xIndexes, so Where gets called once when initializing the array instead of three times in the final LINQ query. Perhaps it's a micro-optimiztion, but I think it's a good habit to avoid calculating an IEnumerable more than once. \$\endgroup\$ Commented Jul 4, 2016 at 4:39

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.