Conway’s Game of Life in C#

I wrote this for fun on the train a while ago, and just came across it again recently. So I figured I may as well post it. The code implements a simple game of life simulation, but the interesting bit is that it parallelizes the process using the TPL. I uploaded it to github as a gist, so please feel free to check it out, and see if you can do anything interesting with it :-)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
using System;
using System.Threading.Tasks;
 
namespace Life
{
public class LifeSimulation
{
private bool[,] world;
private bool[,] nextGeneration;
private Task processTask;
 
public LifeSimulation(int size)
{
if (size < 0) throw new ArgumentOutOfRangeException("Size must be greater than zero");
this.Size = size;
world = new bool[size, size];
nextGeneration = new bool[size, size];
}
 
public int Size { get; private set; }
public int Generation { get; private set; }
 
public Action<bool[,]> NextGenerationCompleted;
 
public bool this[int x, int y]
{
get { return this.world[x, y]; }
set { this.world[x, y] = value; }
}
 
public bool ToggleCell(int x, int y)
{
bool currentValue = this.world[x, y];
return this.world[x, y] = !currentValue;
}
 
public void Update()
{
if (this.processTask != null && this.processTask.IsCompleted)
{
// when a generation has completed
// now flip the back buffer so we can start processing on the next generation
var flip = this.nextGeneration;
this.nextGeneration = this.world;
this.world = flip;
Generation++;
 
// begin the next generation's processing asynchronously
this.processTask = this.ProcessGeneration();
 
if (NextGenerationCompleted != null) NextGenerationCompleted(this.world);
}
}
 
public void BeginGeneration()
{
if (this.processTask == null || (this.processTask != null && this.processTask.IsCompleted))
{
// only begin the generation if the previous process was completed
this.processTask = this.ProcessGeneration();
}
}
 
public void Wait()
{
if (this.processTask != null)
{
this.processTask.Wait();
}
}
 
private Task ProcessGeneration()
{
return Task.Factory.StartNew(() =>
{
Parallel.For(0, Size, x =>
{
Parallel.For(0, Size, y =>
{
int numberOfNeighbors = IsNeighborAlive(world, Size, x, y, -1, 0)
+ IsNeighborAlive(world, Size, x, y, -1, 1)
+ IsNeighborAlive(world, Size, x, y, 0, 1)
+ IsNeighborAlive(world, Size, x, y, 1, 1)
+ IsNeighborAlive(world, Size, x, y, 1, 0)
+ IsNeighborAlive(world, Size, x, y, 1, -1)
+ IsNeighborAlive(world, Size, x, y, 0, -1)
+ IsNeighborAlive(world, Size, x, y, -1, -1);
 
bool shouldLive = false;
bool isAlive = world[x, y];
 
if (isAlive && (numberOfNeighbors == 2 || numberOfNeighbors == 3))
{
shouldLive = true;
}
else if (!isAlive && numberOfNeighbors == 3) // zombification
{
shouldLive = true;
}
 
nextGeneration[x, y] = shouldLive;
 
});
});
});
}
 
private static int IsNeighborAlive(bool[,] world, int size, int x, int y, int offsetx, int offsety)
{
int result = 0;
 
int proposedOffsetX = x + offsetx;
int proposedOffsetY = y + offsety;
bool outOfBounds = proposedOffsetX < 0 || proposedOffsetX >= size | proposedOffsetY < 0 || proposedOffsetY >= size;
if (!outOfBounds)
{
result = world[x + offsetx, y + offsety] ? 1 : 0;
}
return result;
}
}
 
class Program
{
static void Main(string[] args)
{
LifeSimulation sim = new LifeSimulation(10);
 
// initialize with a blinker
sim.ToggleCell(5, 5);
sim.ToggleCell(5, 6);
sim.ToggleCell(5, 7);
 
sim.BeginGeneration();
sim.Wait();
OutputBoard(sim);
 
sim.Update();
sim.Wait();
OutputBoard(sim);
 
sim.Update();
sim.Wait();
OutputBoard(sim);
 
Console.ReadKey();
}
 
private static void OutputBoard(LifeSimulation sim)
{
var line = new String('-', sim.Size);
Console.WriteLine(line);
 
for (int y = 0; y < sim.Size; y++)
{
for (int x = 0; x < sim.Size; x++)
{
Console.Write(sim[x, y] ? "1" : "0");
}
 
Console.WriteLine();
}
}
}
}
,
view raw GameOfLife.cs This Gist brought to you by GitHub.

The included sample program that uses the ‘LifeSimulation’ class initializes the a simple blinker, and then generates and outputs 3 generations.

1 Comment »

  1. Ryan Said,

    September 24, 2011 @ 12:47 am

    Nice!

    Was reading this today: http://blog.fogus.me/2011/08/14/perlis-languages/ and the Game of Life implemented in APL blew my mind.

RSS feed for comments on this post

Leave a Comment