Search code examples
c#listconways-game-of-lifelogic-error

Problem Implementing Conway's Game of Life


Conway's Game of Life : A cellular automata

  • Represented in an infinite two dimensional matrix of 'cells'
  • Each cell can be in two states, 'alive' or 'dead'
  • Each cell interacts with its horizontally, vertically, and diagonally adjacent cells
  • Rules:
    • Any live cell with fewer than two live neighbours dies
    • Any live cell with two or three live neighbours lives on to the next generation
    • Any live cell with more than three live neighbours dies
    • Any dead cell with exactly three live neighbours becomes a live cell

The issue I am experiencing is with my function to set the next state of each cell object. The problem is most likely technical to C#.

What does (should) the program do:

  • Implements Conway's Game of Life
  • This is done using a minimum size space/board which expands and shrinks to both allow a pseudo-infinite game and reduce use of storage

When using the seed for a glider, the progression to the next state does not work. The issue is that when FindNextGeneration is on the 5th row (index 4) of _space, it reaches the column with index 2 - when the function determines that the cell in that position should have it's NextState set to true, it also sets the next state of each cell in the 5th row to true, and when the column moves to index 3, it sets all the NextStates to false. I assume this occurs with each column/cell on the last row. The 5th row should only have the cell at index 2 with it's NextState set to true after the first generation.

Currently it is outputing the current generation, followed by the next generation.

// note: using System, System.Collections.Generics, system.Linq
class Program
    {
        static void Main(string[] args)
        {
            int[,] mySeed = new int[3, 3]
            {
                { 0, 1, 0 },
                { 0, 0, 1 },
                { 1, 1, 1 }
            };

            Life myGame = new Life(mySeed);
            myGame.PrintCurrentState();
            myGame.PrintNextState();
        }
    }


    class Life
    {
        private class Cell
        {
            private bool _state;
            private bool _nextState;

            public bool State { get => _state; set => _state = value; }
            public bool NextState { get => _nextState; set => _nextState = value; }

            public Cell(int initialState)
            {
                State = (initialState == 1);
            }

            public void Progress()
            {
                State = NextState;
            }
        }

        private List<List<Cell>> _space;
        private int _generation;
        private bool _noLiveCells;

        public int Generation { get => _generation; private set => _generation = value; }

        public Life(int[,] seed)
        {
            Generation = 0;
            _noLiveCells = true;
            _space = new List<List<Cell>>();
            for (int row = 0; row < seed.GetLength(0); row++)
            {
                _space.Add(new List<Cell>());
                for (int column = 0; column < seed.GetLength(1); column++)
                {
                    _space[row].Add(new Cell(seed[row, column]));
                    if (seed[row, column] == 1)
                    {
                        _noLiveCells = false;
                    }
                }
            }
            if (_noLiveCells == false)
            {
                UniformPadding();
            }
            FindNextGeneration();
        }

        private void Pad(char side)
        {
            if (side == 'N')
            {
                Cell[] paddingRow = new Cell[_space[0].Count];
                Array.Fill(paddingRow, new Cell(0));
                _space.Insert(0, new List<Cell>(paddingRow));
            }
            else if (side == 'S')
            {
                Cell[] paddingRow = new Cell[_space[0].Count];
                Array.Fill(paddingRow, new Cell(0));
                _space.Add(new List<Cell>(paddingRow));
            }
            else if (side == 'E')
            {
                foreach (List<Cell> cellList in _space)
                {
                    cellList.Add(new Cell(0));
                }
            }
            else if (side == 'W')
            {
                foreach (List<Cell> cellList in _space)
                {
                    cellList.Insert(0, new Cell(0));
                }
            }
            else
            {
                throw new ArgumentException("Argument for padding must be either 'N', 'E', 'S', or 'W'.");
            }
        }

        private void UniformPadding()
        {
            (bool N, bool E, bool S, bool W) requirePadding = (false, false, false, false);
            while (requirePadding.N == false)
            {
                if (!(_space[0].Any(cell => cell.State == true)) && !(_space[1].Any(cell => cell.State == true)))
                {
                    _space.RemoveAt(0);
                }
                else if (_space[0].Any(cell => cell.State == true))
                {
                    Pad('N');
                    requirePadding.N = true;
                }
                else
                {
                    requirePadding.N = true;
                }
            }
            while (requirePadding.S == false)
            {
                if (!(_space[_space.Count - 1].Any(cell => cell.State == true)) && !(_space[_space.Count - 2].Any(cell => cell.State == true)))
                {
                    _space.RemoveAt(_space.Count - 1);
                }
                else if (_space[_space.Count - 1].Any(cell => cell.State == true))
                {
                    Pad('S');
                    requirePadding.S = true;
                }
                else
                {
                    requirePadding.S = true;
                }
            }
            while (requirePadding.W == false)
            {
                if (!(_space.Any(cellList => cellList[0].State == true)) && !(_space.Any(cellList => cellList[1].State == true)))
                {
                    foreach (List<Cell> cellList in _space)
                    {
                        cellList.RemoveAt(0);
                    }
                }
                else if (_space.Any(cellList => cellList[0].State == true))
                {
                    Pad('W');
                    requirePadding.W = true;
                }
                else
                {
                    requirePadding.W = true;
                }
            }
            while (requirePadding.E == false)
            {
                if (!(_space.Any(cellList => cellList[cellList.Count - 1].State == true)) && !(_space.Any(cells => cells[cells.Count - 2].State == true)))
                {
                    foreach (List<Cell> cellList in _space)
                    {
                        cellList.RemoveAt(cellList.Count - 1);
                    }
                }
                else if (_space.Any(cellList => cellList[cellList.Count - 1].State == true))
                {
                    Pad('E');
                    requirePadding.E = true;
                }
                else
                {
                    requirePadding.E = true;
                }
            }
        }

        private void FindNextGeneration()
        {
            for (int row = 0; row < _space.Count; row++)
            {
                for (int column = 0; column < _space[row].Count; column++)
                {
                    int adjacentLiveCells = 0;
                    Cell cell = _space[row][column];
                    for (int j = row - 1; j <= row + 1; j++)
                    {
                        for (int i = column - 1; i <= column + 1; i++)
                        {
                            //Console.Write($"j: {j} | i: {i}\n");
                            if ((j >= 0) && (i >= 0) && (j < _space.Count) && (i < _space[0].Count) && ((j != row) || (i != column)))
                            {
                                if (_space[j][i].State == true)
                                {
                                    adjacentLiveCells++;
                                }
                            }
                        }
                    }
                    //Console.Write($"r: {row} | c: {column}\n");
                    if ((cell.State == true) && ((adjacentLiveCells < 2) || (adjacentLiveCells > 3)))
                    {
                        cell.NextState = false;
                    }
                    else if ((cell.State == false) && (adjacentLiveCells == 3))
                    {
                        cell.NextState = true;
                    }
                    else
                    {
                        cell.NextState = cell.State;
                    }
                }
            }
        }

        public void ProgressGeneration()
        {
            if (_noLiveCells == false)
            {
                _noLiveCells = true;
                for (int row = 0; row < _space.Count; row++)
                {
                    for (int column = 0; column < _space[0].Count; column++)
                    {
                        _space[row][column].Progress();
                        if (_space[row][column].State == true)
                        {
                            _noLiveCells = false;
                        }
                    }
                }
                if (_noLiveCells == false)
                {
                    UniformPadding();
                    FindNextGeneration();
                }
            }
            Generation++;
        }

        private void ProgressionMessage(int g)
        {
            Console.WriteLine();
            Console.WriteLine("|===============================|");
            Console.WriteLine($"|Generation:    {Generation,16}|");
            Console.WriteLine($"|Generations Progressed:  {g,6}|");
        }

        public void ProgressFor(int generations = 1)
        {
            if (Generation == 0)
            {
                ProgressionMessage(0);
                PrintCurrentState();
                Console.WriteLine(_space[4][3].NextState);//
            }
            for (int g = 1; g <= generations; g++)
            {
                ProgressGeneration();
                ProgressionMessage(g);
                PrintCurrentState();
                Console.WriteLine(_space[4][3].NextState);//
            }
        }

        public void ProgressBy(int generations = 1)
        {
            for (int g = 1; g <= generations; g++)
            {
                ProgressGeneration();
            }
            ProgressionMessage(generations);
            PrintCurrentState();
        }

        public void ProgressTo(int generation)
        {
            int progressions = 0;
            while (Generation < generation)
            {
                ProgressGeneration();
                progressions++;
            }
            ProgressionMessage(progressions);
            PrintCurrentState();
        }

        public void PrintCurrentState()
        {
            if (_noLiveCells == false)
            {
                Console.Write("=======================");//
                for (int row = 0; row < _space.Count; row++)
                {
                    Console.WriteLine();
                    Console.ResetColor();//
                    Console.Write("|||");//
                    for (int column = 0; column < _space[0].Count; column++)
                    {
                        if (_space[row][column].State == true)
                        {
                            Console.ForegroundColor = ConsoleColor.White;
                        }
                        else
                        {
                            Console.ForegroundColor = ConsoleColor.Black;
                        }
                        Console.Write("██");
                    }
                    Console.ResetColor();//
                    Console.Write("|||");//
                }
                Console.WriteLine();
                Console.ResetColor();//
                Console.Write("=======================");//
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("All cells are dead.");
                Console.ResetColor();
            }
        }
        public void PrintNextState()
        {
            for (int row = 0; row < _space.Count; row++)
            {
                Console.WriteLine();
                for (int column = 0; column < _space[0].Count; column++)
                {
                    if (_space[row][column].NextState == true)
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                    }
                    else
                    {
                        Console.ForegroundColor = ConsoleColor.Black;
                    }
                    Console.Write("██");
                }
            }
            Console.WriteLine();
            Console.ForegroundColor = ConsoleColor.White;
        }
    }

Solution

  • The problem is in your Pad function, where the cases "N" and "S" are handled. There you populate an Array with:

    Array.Fill(paddingRow, new Cell(0));
    

    But this creates just one Cell instance and stores its reference in all entries of the paddingRow array. That's causing issues as changing the state via only one of these entries will be seen in all entries, as they all reference that same Cell.

    If you fix this issue, it will work. For instance, like this:

                if (side == 'N')
                {
                    List<Cell> paddingRow = new List<Cell>();
                    for (int column = 0; column < _space[0].Count; column++)
                    {
                        paddingRow.Add(new Cell(0));
                    }
                    _space.Insert(0, paddingRow);
                }
                else if (side == 'S')
                {
                    List<Cell> paddingRow = new List<Cell>();
                    for (int column = 0; column < _space[0].Count; column++)
                    {
                        paddingRow.Add(new Cell(0));
                    }
                    _space.Add(paddingRow);
                }