Overview



"The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970.

 

The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves, or, for advanced "players", by creating patterns with particular properties. This is a simplified version allowing 3 distinct seeds, a diamond shape, a square, and a cross shape.

 

The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead, or "populated" or "unpopulated". Every cell interacts with its eight neighbors, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

  1. Any live cell with fewer than two live neighbors dies, as if caused by under population.
  2. Any live cell with two or three live neighbors lives on to the next generation.
  3. Any live cell with more than three live neighbors dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.  [Wikipedia]



The gui Form



The Form is very simple, providing just an interface for the core Animation class, which is where the action takes place.
The Form contains a ComboBox for selecting a seed pattern, an extended DataGridView which displays the animation, and a Button to initiate the animation...




↑ 
Back to top


The gui exDGV (Extended DataGridView)



The exDGV restricts any form of user interaction, as it is intended purely for display purposes. There were though, two reasons for doing this. By default, a DataGridView is an input control, allowing a user to enter text, and also to select cells and ranges of cells. That was undesirable because it would ruin the animation effect, but also restricting those characteristics lets the DataGridView render faster.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
 
namespace Conways_Game_of_Life_cs
{
    /// <summary>
    /// Extended DataGridView
    /// DoubleBuffered. Restricts user selection of cells.
    /// </summary>
    /// <remarks></remarks>
    public class exDGV : DataGridView
    {
 
        int WM_LBUTTONDOWN = 0x201;
        int WM_LBUTTONDBLCLK = 0x203;
 
        int WM_KEYDOWN = 0x100;
        public exDGV()
        {
            this.DoubleBuffered = true;
        }
 
        protected override void OnRowPrePaint(System.Windows.Forms.DataGridViewRowPrePaintEventArgs e)
        {
            DataGridViewPaintParts paintParts = (int)e.PaintParts - DataGridViewPaintParts.Focus;
            e.PaintParts = paintParts;
            base.OnRowPrePaint(e);
        }
 
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            if (m.Msg == WM_LBUTTONDBLCLK || m.Msg == WM_KEYDOWN || m.Msg == WM_LBUTTONDOWN)
            {
                return;
            }
            base.WndProc(ref m);
        }
 
    }
}



The core Animation class




A new instance of the Animation class is initiated on creation of Form1, and this instance is used throughout the application.
The class contains two public methods and several private helper methods. The setSeed method is invoked when the ComboBox_SelectedIndexChanged fires. The animate method is invoked in a background thread when the Button is clicked. The animation continues until there is no space left to grow, or the user changes the seed by selecting in the ComboBox.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Drawing;
 
namespace Conways_Game_of_Life_cs
{
    public class Animation
    {
 
        private exDGV gridControl;
        private int[][] cellValues = new int[100][];
 
        public bool cancelled = false;
        public Animation(exDGV dgv)
        {
            this.gridControl = dgv;
            for (int r = 0; r <= 99; r++)
            {
                cellValues[r] = new int[100];
            }
        }
 
        private void clear()
        {
            for (int r = 0; r <= 99; r++)
            {
                for (int c = 0; c <= 99; c++)
                {
                    cellValues[r][c] = 0;
                }
            }
        }
 
        private void repaint()
        {
            if (cancelled)
                return;
            for (int r = 0; r <= 99; r++)
            {
                for (int c = 0; c <= 99; c++)
                {
                    if (cellValues[r][c] > 0)
                    {
                        gridControl.Rows[r].Cells[c].Style.BackColor = Color.Black;
                    }
                    else
                    {
                        gridControl.Rows[r].Cells[c].Style.BackColor = Color.White;
                    }
                }
            }
        }
 
        public void setSeed(int x)
        {
            cancelled = true;
            clear();
 
            switch (x)
            {
                case 1:
                    //diamond
                    for (int r = 47; r <= 48; r++)
                    {
                        for (int c = 49; c <= 50; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 49; r <= 50; r++)
                    {
                        for (int c = 49; c <= 50; c++)
                        {
                            cellValues[r][c] = -1;
                        }
                    }
 
                    for (int r = 49; r <= 50; r++)
                    {
                        for (int c = 51; c <= 52; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 51; r <= 52; r++)
                    {
                        for (int c = 49; c <= 50; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 49; r <= 50; r++)
                    {
                        for (int c = 47; c <= 48; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    break;
                case 2:
                    //square
                    for (int r = 48; r <= 51; r++)
                    {
                        for (int c = 48; c <= 51; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    break;
                case 3:
                    //cross
                    for (int r = 47; r <= 48; r++)
                    {
                        for (int c = 47; c <= 48; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 47; r <= 48; r++)
                    {
                        for (int c = 51; c <= 52; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 49; r <= 50; r++)
                    {
                        for (int c = 49; c <= 50; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 51; r <= 52; r++)
                    {
                        for (int c = 47; c <= 48; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    for (int r = 51; r <= 52; r++)
                    {
                        for (int c = 51; c <= 52; c++)
                        {
                            cellValues[r][c] = 1;
                        }
                    }
 
                    break;
            }
            cancelled = false;
            repaint();
            cancelled = true;
 
        }
 
        public void animate(object i)
        {
            int index = (int)i;
            cancelled = false;
            int l = 0;
            int t = 0;
            int r = 0;
            int b = 0;
            switch (index)
            {
                case 1:
                case 3:
                    t = 47;
                    b = 52;
                    l = 47;
                    r = 52;
                    break;
                case 2:
                    t = 48;
                    b = 51;
                    l = 48;
                    r = 51;
                    break;
            }
            int g = 2;
            while (!cancelled)
            {
                for (int y = t; y <= b; y++)
                {
                    for (int x = l; x <= r; x++)
                    {
                        if (cancelled)
                        {
                            return;
                        }
                        if (cellValues[y][x] > 0 && cellValues[y][x] < g)
                        {
                            grow(g, y, x);
                        }
                    }
                }
                //Pause for 50 milliseconds
                Thread.Sleep(50);
                for (int y = t; y <= b; y++)
                {
                    for (int x = l; x <= r; x++)
                    {
                        if (cancelled)
                        {
                            return;
                        }
                        if (cellValues[y][x] > 0 && cellValues[y][x] < g)
                        {
                            int count = surrounding(y, x);
                            if (count < 2)
                            {
                                cellValues[y][x] = -1;
                            }
                            else if (count > 3)
                            {
                                cellValues[y][x] = -1;
                            }
                        }
                    }
                }
                //Pause for 50 milliseconds
                Thread.Sleep(50);
                for (int y = t; y <= b; y++)
                {
                    for (int x = l; x <= r; x++)
                    {
                        if (cancelled)
                        {
                            return;
                        }
                        if (cellValues[y][x] == -1)
                        {
                            int count = surrounding(y, x);
                            if (count == 3)
                            {
                                cellValues[y][x] = 1;
                            }
                        }
                    }
                }
                //Pause for 50 milliseconds
                Thread.Sleep(50);
                t -= 1;
                l -= 1;
                b += 1;
                r += 1;
                g += 1;
                if (t < 0 || l < 0)
                {
                    cancelled = true;
                }
                repaint();
            }
        }
 
        private void grow(int g, int r, int c)
        {
            if (r > 0)
            {
                if (cellValues[r - 1][c] == 0)
                {
                    cellValues[r - 1][c] = g;
                }
                if (c > 0)
                {
                    if (cellValues[r - 1][c - 1] == 0)
                    {
                        cellValues[r - 1][c - 1] = g;
                    }
                }
                if (c < 99)
                {
                    if (cellValues[r - 1][c + 1] == 0)
                    {
                        cellValues[r - 1][c + 1] = g;
                    }
                }
            }
            if (c > 0)
            {
                if (cellValues[r][c - 1] == 0)
                {
                    cellValues[r][c - 1] = g;
                }
            }
            if (c < 99)
            {
                if (cellValues[r][c + 1] == 0)
                {
                    cellValues[r][c + 1] = g;
                }
            }
            if (r < 99)
            {
                if (cellValues[r + 1][c] == 0)
                {
                    cellValues[r + 1][c] = g;
                }
                if (c > 0)
                {
                    if (cellValues[r + 1][c - 1] == 0)
                    {
                        cellValues[r + 1][c - 1] = g;
                    }
                }
                if (c < 99)
                {
                    if (cellValues[r + 1][c + 1] == 0)
                    {
                        cellValues[r + 1][c + 1] = g;
                    }
                }
            }
        }
 
        private int surrounding(int r, int c)
        {
            int count = 0;
            if (r > 0)
            {
                if (cellValues[r - 1][c] > 0)
                {
                    count += 1;
                }
                if (c > 0)
                {
                    if (cellValues[r - 1][c - 1] > 0)
                    {
                        count += 1;
                    }
                }
                if (c < 99)
                {
                    if (cellValues[r - 1][c + 1] > 0)
                    {
                        count += 1;
                    }
                }
            }
            if (c > 0)
            {
                if (cellValues[r][c - 1] > 0)
                {
                    count += 1;
                }
            }
            if (c < 99)
            {
                if (cellValues[r][c + 1] > 0)
                {
                    count += 1;
                }
            }
            if (r < 99)
            {
                if (cellValues[r + 1][c] > 0)
                {
                    count += 1;
                }
                if (c > 0)
                {
                    if (cellValues[r + 1][c - 1] > 0)
                    {
                        count += 1;
                    }
                }
                if (c < 99)
                {
                    if (cellValues[r + 1][c + 1] > 0)
                    {
                        count += 1;
                    }
                }
            }
 
            return count;
        }
 
    }
}
 
↑ Back to top


Conclusion



This is an example of OOP Programming which uses an instance of the core code object, and a graphical object (the exDGV), along with a Form.
Through simple coding, the DataGridView is rendered completely readonly. The Form contains only some GUI setup code and some eventhandlers. The Animation class can work with any seed (pattern) you choose to add to the three seeds that are provided.




Other Resources




Download (C#/VB) at: https://code.msdn.microsoft.com/Conways-Game-of-Life-75508f8f




Articles related to game programming



VB.Net - WordSearch
VB.Net - Vertex
VB.Net - Perspective
VB.Net - MasterMind
VB.Net - OOP BlackJack
VB.Net - Numbers Game
VB.Net - HangMan
Console BlackJack - VB.Net | C#
TicTacToe - VB.Net | C#
OOP Sudoku - VB.Net | C#
OctoWords VB.Net | C#