Cellular Automata: The Game of Life

I remember my dad getting a copy of Scientific American magazine every month when I was a kid. He kept back issues on a shelf in our living room. I remember being fascinated by the issue (Oct 1970) which described John Conway’s Game of Life.

 

A few decades later, in the mid 80’s I wrote my own version using the DeSmet C compiler on an original IBM PC (see Why was the original IBM PC 4.77 Megahertz?). I remember playing with gliders, glider guns and other formations.

 

A couple decades after that, I wrote one in C# (the date is 2003). Below is my original version from 2003, with one line of code added to make it run with more recent versions of VS.

 

(A subsequent article in Scientific American (Dec 84) described a cellular automata that simulated the Predator Prey ecosystem called Wator by Alexander Dewdney. This inspired me to write my own version of Wator in C++.)

 

Start VS 2008. File->New->Project->C#->Windows Forms

 

Edit the Program.cs code and replace it with the code below, then hit F5. Try moving the mouse around. Button1 results in an error.

 

The one line I had to add to get it to work was:

            Form.CheckForIllegalCrossThreadCalls = false;

 

 

Without that line, an exception would occur:

System.InvalidOperationException was unhandled

  Message="Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on."

  Source="System.Windows.Forms"

  StackTrace:

       at System.Windows.Forms.Control.get_Handle()

       at Life.Form1.DrawCells(Boolean fDrawAll) in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 47

       at Life.Form1.Shuffle() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 90

       at Life.Form1.Generations() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 184

       at Life.Form1.MyThread() in C:\Users\calvinh\AppData\Local\Temporary Projects\WindowsFormsApplication1\Program.cs:line 304

       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)

       at System.Threading.ThreadHelper.ThreadStart()

  InnerException:

 

Looking at the code, I see that indeed I wrote code that created a background thread that drew to the hWnd directly. So I added the code to set CheckForIllegalCrossThreadCalls  false.

 

<code sample>

 

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.Threading;

using System.Runtime.InteropServices;

namespace Life

{

    /// <summary>

    /// Summary description for Form1.

    /// </summary>

    public class Form1 : System.Windows.Forms.Form

    {

        /// <summary>

        /// Required designer variable.

        /// </summary>

        [DllImport("kernel32.dll")]

        public static extern bool Beep(int freq, int dura);

        private System.ComponentModel.Container components = null;

        private TextBox m_box;

        private Button m_button;

        private Thread m_Thread = null;

        bool m_nReset = false;

        static int m_numx = 200;

        static int m_numy = 200;

        static int m_cellx = 5;

        static int m_celly = 5;

        static int m_Offx = 10;

        static int m_Offy = 10;

        static int MAXCOLOR = 8;

        static int COLORDIV = 16; // # of gens before color changes

        bool m_fDrawAll = false;

        SolidBrush m_brushBlack = new SolidBrush(Color.Black);

        SolidBrush m_brushWhite = new SolidBrush(Color.White);

        SolidBrush[] m_brushArray = new SolidBrush[MAXCOLOR];

        Queue m_queue = new Queue();

        int[,] m_cells = new int[m_numx, m_numy];

        private System.Windows.Forms.Button button1;

        int[,] m_diff = new int[m_numx, m_numy];

        private int DrawCells(bool fDrawAll)

        {

            int nTotal = 0;

            Graphics g = Graphics.FromHwnd(this.Handle);

            int x, y;

            if (fDrawAll)

            {

                System.Diagnostics.Debug.Write("\nDraw");

            }

            for (x = 0; x < m_numx; x++)

            {

                for (y = 0; y < m_numy; y++)

                {

   if (fDrawAll || m_diff[x, y] == 1)

                    {

                        if (m_cells[x, y] == 0)

                        {

                            g.FillRectangle(m_brushWhite, m_Offx + x * m_cellx, m_Offy + y * m_celly, m_cellx - 1, m_celly - 1);

                        }

                        else

                        {

                            nTotal++;

                            g.FillRectangle(m_brushArray[m_cells[x, y] / COLORDIV], m_Offx + x * m_cellx, m_Offy + y * m_celly, m_cellx - 1, m_celly - 1);

                        }

                    }

                }

            }

            return nTotal;

        }

        private void Shuffle()

        {

            int x, y;

            Random myrandom = new Random();

            for (x = 0; x < m_numx; x++)

            {

                for (y = 0; y < m_numy; y++)

                {

                    m_diff[x, y] = m_cells[x, y] = myrandom.Next(100) < 50 ? 1 : 0;

                }

            }

          DrawCells(false);

        }

        private int CountNeighbors(int x, int y)

        {

            if (x >= 0 && x < m_numx && y >= 0 && y < m_numy)

            {

                return m_cells[x, y] > 0 ? 1 : 0;

            }

            return 0;

    }

        private int Generation()

        {

            int[,] tmp = new int[m_numx, m_numy];

            int x, y;

            for (x = 0; x < m_numx; x++)

            {

                for (y = 0; y < m_numy; y++)

                {

                    tmp[x, y] = CountNeighbors(x - 1, y - 1) +

                        CountNeighbors(x - 1, y) +

                        CountNeighbors(x - 1, y + 1) +

                        CountNeighbors(x, y - 1) +

                        CountNeighbors(x, y + 1) +

                        CountNeighbors(x + 1, y - 1) +

                        CountNeighbors(x + 1, y) +

                        CountNeighbors(x + 1, y + 1);

                }

            }

            for (x = 0; x < m_numx; x++)

        {

                for (y = 0; y < m_numy; y++)

                {

                    if (tmp[x, y] == 3 && m_cells[x, y] == 0) // an empty cell with exactly 3 neighbors: 1 is born

                    {

                        m_diff[x, y] = m_cells[x, y] = 1;

                    }

                    else if (tmp[x, y] >= 2 && tmp[x, y] <= 3 && m_cells[x, y] > 0) // a live cell with 2 or more neighbors lives

                    {

                        if (m_cells[x, y] == COLORDIV * MAXCOLOR - 1)

                        {

                            m_diff[x, y] = 0; // no change

                        }

                        else

                        {

                            m_cells[x, y]++;

                            m_diff[x, y] = 1;

                        }

                    }

                    else

                    { // loneliness

                        if (m_cells[x, y] == 0) // already empty

                        {

                            m_diff[x, y] = 0; // no change

                        }

                        else

                        {

                            m_cells[x, y] = 0;

                            m_diff[x, y] = 1;

                        }

                    }

                }

          }

            if (m_queue.Count > 0)

            {

                lock (m_queue.SyncRoot)

                {

                    foreach (Point pt in m_queue)

                    {

                        m_diff[pt.X, pt.Y] = 1;

                        if (m_cells[pt.X, pt.Y] == 0)

                        {

                            m_cells[pt.X, pt.Y] = 1;

                        }

                        else

                        {

                            m_cells[pt.X, pt.Y] = 0;

             }

                    }

                    m_queue.Clear();

                }

            }

            int nResult = DrawCells(m_fDrawAll);

            m_fDrawAll = false;

            return nResult;

        }

        private void Generations()

        {

            int nTotal;

            while (true)

            {

                Shuffle();

                for (int i = 0; i < 100000; i++)

                {

                    nTotal = Generation();

                    this.Text = i.ToString() + " : " + nTotal.ToString();

                    if (m_nReset)

                    {

                        m_nReset = false;

                        break;

                    }

                }

            }

        }

        private void handlethebutton(object sender, EventArgs e)

        {

            m_nReset = true;

        }

        private void OnClick(object sender, EventArgs e)

        {

        }

        private void Onmmove(object sender, MouseEventArgs e)

        {

      Point pt = this.PointToClient(Cursor.Position);

            Point ptcell = new Point();

            ptcell.X = (pt.X - m_Offx) / m_cellx;

            ptcell.Y = (pt.Y - m_Offy) / m_celly;

            if (ptcell.X >= 0 && ptcell.X < m_numx && ptcell.Y >= 0 && ptcell.Y < m_numy)

            {

                lock (m_queue.SyncRoot)

                {

                    m_queue.Enqueue(ptcell);

                }

            }

        }

        private void OnActivated(object sender, EventArgs e)

        {

            // Generations();

            if (m_Thread == null)

            {

                m_Thread = new Thread(new ThreadStart(MyThread));

                m_Thread.Start();

                while (!m_Thread.IsAlive)

                {

        m_Thread.Abort();

                }

            }

            else

            {

                m_fDrawAll = true;

            }

        }

        private void FormPaint(object sender, PaintEventArgs e)

        {

            m_fDrawAll = true;

        }

        /// <summary>

        /// Clean up any resources being used.

        /// </summary>

        protected override void Dispose(bool disposing)

        {

            if (m_Thread != null && m_Thread.IsAlive)

            {

                m_Thread.Abort();

            }

            if (disposing)

            {

                if (components != null)

                {

                    components.Dispose();

                }

            }

            base.Dispose(disposing);

        }

        #region Windows Form Designer generated code

        /// <summary>

        /// Required method for Designer support - do not modify

        /// the contents of this method with the code editor.

        /// </summary>

        private void InitializeComponent()

        {

            this.button1 = new System.Windows.Forms.Button();

            this.SuspendLayout();

            //

            // button1

            //

            this.button1.Location = new System.Drawing.Point(392, 0);

            this.button1.Name = "button1";

            this.button1.TabIndex = 0;

            this.button1.Text = "button1";

            this.button1.Click += new System.EventHandler(this.button1_Click);

            //

            // Form1

            //

            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

            this.ClientSize = new System.Drawing.Size(800, 558);

            this.Controls.AddRange(new System.Windows.Forms.Control[] {

                                                                                          this.button1});

            this.Name = "Form1";

            this.Text = "Life";

            this.Click += new System.EventHandler(this.Form1_Click);

            this.Load += new System.EventHandler(this.Form1_Load);

            this.ResumeLayout(false);

        }

        #endregion

        /// <summary>

        /// The main entry point for the application.

        /// </summary>

        [STAThread]

        static void Main()

        {

            Application.Run(new Form1());

        }

        private void MyThread()

        {

     Generations();

        }

        public Form1()

        {

            //

            // Required for Windows Form Designer support

            //

            InitializeComponent();

            this.Height = m_Offy + m_numy * m_celly;

            this.Width = m_Offx + m_numx * m_cellx;

            this.Left = 00;

            this.Top = 0;

            this.BackColor = Color.White;

            m_box = new TextBox();

            m_box.BackColor = Color.Cyan;

            m_box.Text = "Hi";

            m_box.Size = new Size(100, 100);

            m_button = new Button();

            m_button.BackColor = Color.Red;

            m_button.Location = new Point(150, 00);

            m_button.Text = "&Reset";

            Controls.Add(m_button);

    m_button.Click += new EventHandler(this.handlethebutton);

            Controls.Add(m_box);

            // this.Paint += new PaintEventHandler(this.FormPaint);

            this.Activated += new EventHandler(this.OnActivated);

            this.Click += new EventHandler(this.OnClick);

            this.MouseMove += new MouseEventHandler(this.Onmmove);

            this.Paint += new PaintEventHandler(this.FormPaint);

            for (int i = 0; i < MAXCOLOR; i++)

            {

                Color clr = Color.FromArgb(255, (MAXCOLOR - i - 1) * 32, 0, 0);

                m_brushArray[i] = new SolidBrush(clr);

            }

        }

        private void Form1_Load(object sender, System.EventArgs e)

        {

            Form.CheckForIllegalCrossThreadCalls = false;

            // Beep(1000,1000);

        }

        private void Form1_Click(object sender, System.EventArgs e)

        {

        }

        private void button1_Click(object sender, System.EventArgs e)

        {

            Generation();

        }

    }

}

 

</code sample>