I have a confession to make. Although I have a degree in Computer Science, I don’t ever recall implementing John Conway’s Game of Life. The program is less of a game and more about implementing an algorithm with observable results. In this post, we’ll implement Conway’s Game of Life using .NET, C#, and some fun emojis.
What Are The Rules
In 1970, British mathematician John Conway developed a zero-player game designed to mimic the behavior of life itself. Players are required to see the universe with an initial state, and then observe as the rules of life produce an outcome. Each game consists of a two-dimensional grid with a potential state of life or death enhabiting each cell. Each cell has the potential to shift its state based on a set of rules.
What are the rules to play the game of life?
- Any live cell with fewer than two live neighbors dies, as if by underpopulation.
- Any live cell with two or three live neighbors lives on to the next generation.
- Any live cell with more than three live neighbors dies, as if by overpopulation.
- Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
For programming, we can reduce these rules to a set of three algorithm steps.
- Any live cell with two or three live neighbors survives.
- Any dead cell with three live neighbors becomes a live cell.
- All other live cells die in the next generation. Similarly, all other dead cells stay dead.
Each generation continues to seed the next generation until the universe reaches an equilibrium. The game is a fun one to watch and hypnotic based on the size of the board.
reference: Wikipedia: Conway’s Game of Life
Game of Life in C#
We’ll be using a C# console application to implement the rules to the Game of Life. For the sake of following along, we’ll paste the app in its entirety below.
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace GameOfLife
{
static class Program
{
const int Rows = 25;
const int Columns = 50;
static bool runSimulation = true;
public static void Main()
{
var grid = new Status[Rows, Columns];
// randomly initialize our grid
for (var row = 0; row < Rows; row++)
{
for (var column = 0; column < Columns; column++)
{
grid[row, column] = (Status) RandomNumberGenerator.GetInt32(0, 2);
}
}
Console.CancelKeyPress += (sender, args) =>
{
runSimulation = false;
Console.WriteLine("\n👋 Ending simulation.");
};
// let's give our console
// a good scrubbing
Console.Clear();
// Displaying the grid
while (runSimulation)
{
Print(grid);
grid = NextGeneration(grid);
}
}
private static Status[,] NextGeneration(Status[,] currentGrid)
{
var nextGeneration = new Status[Rows, Columns];
// Loop through every cell
for (var row = 1; row < Rows - 1; row++)
for (var column = 1; column < Columns - 1; column++)
{
// find your alive neighbors
var aliveNeighbors = 0;
for (var i = -1; i <= 1; i++)
{
for (var j = -1; j <= 1; j++)
{
aliveNeighbors += currentGrid[row + i, column + j] == Status.Alive ? 1 : 0;
}
}
var currentCell = currentGrid[row, column];
// The cell needs to be subtracted
// from its neighbors as it was
// counted before
aliveNeighbors -= currentCell == Status.Alive ? 1 : 0;
// Implementing the Rules of Life
// Cell is lonely and dies
if (currentCell == Status.Alive && aliveNeighbors < 2)
{
nextGeneration[row,column] = Status.Dead;
}
// Cell dies due to over population
else if (currentCell == Status.Alive && aliveNeighbors > 3)
{
nextGeneration[row, column] = Status.Dead;
}
// A new cell is born
else if (currentCell == Status.Dead && aliveNeighbors == 3)
{
nextGeneration[row,column] = Status.Alive;
}
// stays the same
else
{
nextGeneration[row, column] = currentCell;
}
}
return nextGeneration;
}
private static void Print(Status[,] future, int timeout = 500)
{
var stringBuilder = new StringBuilder();
for (var row = 0; row < Rows; row++)
{
for (var column = 0; column < Columns; column++)
{
var cell = future[row, column];
stringBuilder.Append(cell == Status.Alive ? "😁" : "💀");
}
stringBuilder.Append("\n");
}
Console.BackgroundColor = ConsoleColor.Black;
Console.CursorVisible = false;
Console.SetCursorPosition(0, 0);
Console.Write(stringBuilder.ToString());
Thread.Sleep(timeout);
}
}
public enum Status
{
Dead,
Alive,
}
}
Some of the cool things to note in the code above include:
- A variable row and column setup.
- The use of
RandomNumberGenerator
to seed our grid. - Listening for
CancelKeyPress
event to stop the simulation. - The
Print
method builds a single string then writes to the console by repositioning the cursor. - The logic and UI are separated, allowing us to move the logic to another display format.
When we run the program, we see the following.
We can play with the emojis and get different outputs. Here is an example of aliens and astronauts.
Here is an example of cats and mice.
Here is a zombie apocalypse inspired layout.
With emojis and the game of life, the possibilities are endless. What other combinations are possible? Leave them in the comments below. The code is available on GitHub.