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?
\$\endgroup\$
  • \$\begingroup\$ To me it looks like a use-only-lambdas-game ;-) Lambdas are good but you definitely overused them. \$\endgroup\$ – t3chb0t Jul 4 '16 at 4:08
  • \$\begingroup\$ @t3chb0t How did I? Suggest another way \$\endgroup\$ – Afonso Matos Jul 4 '16 at 18:28
  • \$\begingroup\$ getCellStatus should just be a function, loopindex nested inside itself could be done as Action<int, int> \$\endgroup\$ – Caleth Jul 8 '16 at 23:52
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.

\$\endgroup\$
  • \$\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\$ – t3chb0t Jul 4 '16 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\$ – Risky Martin Jul 4 '16 at 4:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.