# Conway's Game of Life in Swift

Written by LearnAppMaking on December 18 2020 in App Development, iOS, Play With Code, Swift

Conway’s Game of Life is a fun simulation game, and we’re going to code it in Swift! Based on 3 simple rules, we’ll see which of the pixels makes it to the next generation. It’s great coding practice, perfect for a Sunday afternoon.

## What’s Game of Life?

Game of Life is a cellular automaton invented by British mathematician John Conway (1937-2020). It’s a simulation that defines simple rules about how a population (of pixels!) evolves after creating an initial setup.

That sounds boring, but it’s absolutely fascinating. Check this out:

(Image: Lucas Vieira, CC BY SA 3.0)

What you see here is Gosper’s glider gun. It’s a configuration that produces gliders, the tiny spaceship-like things that shoot out the middle. Based on 3 simple rules, and an initial setup, this “game” continues indefinitely.

The Game of Life takes place on a 2-dimensional grid of cells, for example, like a pixel image. Each cell can be alive or dead. Every generation of the game, you determine which cells live on to the next generation. This happens based on the alive/dead state of the 8 neighbors of a cell, and 3 rules.

These rules, to be exact:

1. A live cell with 2 or 3 live neighbours survives.
2. A dead cell with 3 live neighbours becomes a live cell.
3. All other live cells die in the next generation, and all other dead cells stay dead.

You could say that a cell stays alive if it has a few cells around it (1). New cells are “born” when there are 3 cells around it (2). All else is lost (3).

Here’s an example of how that works for the gliders you’ve seen before.

You’re looking at the starting configuration of a glider. The neighbors of the center cell are highlighted. Will this pixel survive to the next generation? Count the number of alive neighbors, and see for yourself! (The state of the other cells is also shown in the second image.)

What about a blinker? It’s a simple configuration of 3 cells, that switches between a horizontal and vertical line. It’s stable, so it’ll continue blinking forever.

Why, though? The center cell will always stay alive. The 2 cells at the ends alternate between horizontal and vertical, because they’ll always have 3 neighbors. Intriguing, right?

That’s not all…

• The Game of Life can simulate a Turing machine; it’s Turing complete. You can essentially simulate every possible algorithm in the Game of Life. You can, theoretically, create an initial state for Life that produces the digits of Pi. You can create Game of Life in itself. Your imagination will run out of ideas before you’ve exhausted Game of Life!
• A concept within Game of Life is whether a pattern of cells stabilizes in a given number of generations. Many patterns will stay chaotic for a long time, until stabilizing. Thanks to the halting problem, a common rule (or challenge) in computation theory, no algorithm exists that can predict if a later pattern will appear. You can literally keep playing the Game of Life indefinitely. It’s inevitable!
• Game of Life is fascinating, and pretty crazy. A quick search online shows you plenty of videos of intricate, chaotic configurations that produce the most astounding patterns. You don’t have to go crazy to get some neat patterns though; with a simple initial configuration, you get gliders, spaceships, blinkers, pulsars, loafs, boats, and so on.

Author’s Note: John Horton Conway (age 82) died on April 11, 2020 from complications of COVID-19. His invaluable contributions to mathematics, game theory and computer science go far beyond my comprehension. At the same time, I’m infinitely mesmerized by the simple nature of Game of Life.

## Example Code

You can get the example code for this tutorial on this GitHub repository. You’ll find 3 projects:

1. Game of Life with arrays: A playground with the code in this tutorial
2. Game of Life with Sets: An alternative implementation based on `Set`
3. Game of Life Xcode project: An iOS app with better performance on iPhone

In this tutorial we’ll create the implementation that uses arrays, because it performs better in an Xcode playground. The Game of Life implementation that uses `Set` is quite elegant, but due to heavy object create/destroy it performs poorly in an Xcode playground.

The code in this tutorial was inspired by Conway’s Game of Life on Wikipedia, The Game of Life with Functional Swift by Colin Eberhardt, and Conway’s Game of Life on Rosetta Code.

## How Life Works in Swift

Before we begin, let’s discuss the structure of the code we’re about to write. From a birds-eye view, this Game of Life implementation has 2 components:

1. The `Grid` struct, which represents the Game of Life’s cells in a 2D array. It’s responsible for calculating the next generation.
2. The `GridView` view (UIView), which will draw the `Grid` on screen. It simply iterates over the cells, drawing black pixels if a cell is alive.

We’ll also create a `Factory` struct, which has static functions that produce a Game of Life pattern. With a bit of X/Y wizardry, we’re going to add those patterns to the grid so you can create your initial setup easily.

Let’s get to it!

## Getting Started: The Grid

Start your project by creating an empty playground in Xcode. We’ll start with a clean slate – exciting!

Then, add the following code at the top of the playground:

``````import UIKit
import PlaygroundSupport
``````

We’re importing `UIKit` for the `UIView` type, and `PlaygroundSupport` so we can run the playground indefinitely.

``````struct Grid
{
var size = (width: 50, height: 50)
var cells:[[Int]]
}
``````

This `Grid` struct is the data structure for the cells in Game of Life. It’ll house the functions that will compute a new generation, for example.

We’ve added 2 properties, `size` and `cells`. The type of `size` is `(Int, Int)`, which is a tuple. We’ve named the two values in the tuple `width` and `height`. They’re the size of the grid, so now we’ve got a grid of 50×50 cells.

The type of `cells` is `[[Int]]`. This is an array of arrays of integers, or rather, a 2-dimensional array of integers. You can picture this as a 2D X/Y grid of 1’s and 0’s. We can get to the state of each cell with `cells[x][y]`.

Finally, add the following initializer to the `Grid` struct:

``````init() {
self.cells = Array(repeating: Array(repeating: 0, count: size.height), count: size.width)
}
``````

What’s going on here? The above code will initialize the `cells` property with a 2D array of zeroes. The resulting array will have a size of `width` by `height`. It’s an empty grid of cells; the empty beginnings of the Game of Life.

If you look closely, you’ll see that we’re making 2 calls to `Array(repeating:count:)`. The inner call will repeat `0` for `size.height` times, i.e. a row of zeroes. The outer call will repeat that inner `Array(···)` for `size.width` times, i.e. a row of rows of zeroes.

†: For the sake of simplicity, we’re using the `cells` grid as `cells[x][y]` and call that an X/Y grid. A smart reader will now point out that, as is, the indices in the `cells` array will correspond to the Y coordinate and the indices for `cells[y]` will correspond to the X coordinate. This means that if you print out the values in `cells`, you’ll see an Y/X grid. If that bothers you, feel free to transpose the array!

## Coding The Glider Factory

OK, next up, the `Factory` struct. This component will have some hard-coded cell patterns that we can insert into the `Grid` struct. Its API allows you to quickly create some neat initial configurations for Game of Life without coding every 1 and 0 by hand.

``````struct Factory
{
static func glider() -> [[Int]]
{
return [
[0, 1, 0],
[0, 0, 1],
[1, 1, 1],
]
}

{
return [
[0, 1, 0],
[0, 1, 0],
[0, 1, 0],
]
}

static func random(size: Int) -> [[Int]]
{
var cells = Array(repeating: Array(repeating: 0, count: size), count: size)
let odds = 1.0 / 5.0

for x in 0..<size {
for y in 0..<size {
if Double.random(in: 0..<1) < odds {
cells[x][y] = 1
}
}
}

return cells
}
}
``````

You can find the complete Factory struct here. It includes Gosper’s glider gun, which was too big to include here. Keep in mind the grid is Y/X.

Here’s how the `Factory` struct works. We’ve got a static function `glider()` and `blinker()`, which both return a 2D array of type `[[Int]]`. This is essentially a small version of a grid with cells.

The `random()` function creates a random 2D grid of alive/dead cells, in a square grid of cells with size `size`. First, an empty 2D array is created. Then, we’re looping over the X/Y cells in the grid. For each cell, we’re doing a random dice throw that has a 1 in 5 chance of producing a 1 (alive cell).

Next up, we’re going to need a way to add these patterns from `Factory` to the `Grid`. Let’s code a function for that!

Add the following function to the `Grid` (!) struct:

``````mutating func insertCells(_ insertedCells: [[Int]], at start: (x: Int, y: Int))
{
for x in 0..<insertedCells.count {
for y in 0..<insertedCells[x].count {
let xd = x + start.x
let yd = y + start.y

if xd >= 0 && yd >= 0 && xd < size.width && yd < size.height {
cells[xd][yd] = insertedCells[x][y]
}
}
}
}
``````

Let’s take a look at how that works. This function has 4 important aspects:

1. The function is called `insertCells(_:at:)`. You can insert a 2D array, i.e. a grid of cells, at a `start.x` and `start.y` coordinate.
2. Inside the function, we’re looping over the 2D `insertedCells` array. We’re looking at each single cell in the 2D array.
3. Inside the loop, we’re first calculating the destination coordinate `xd` and `yd`. We do this by offsetting the 2D coordinate a cell with the `start.x` and `start.y` of the starting point.
4. Finally, we’re checking if the destination `xd` and `yd` are within the bounds of `cells`. If so, we’re adding the cell’s value from `insertedCells` to the `cells` property of the grid.

See how this works? We’re essentially taking the 2D array – the (small) pattern – and add that to the (big) grid for Game of Life. An added benefit is the starting point, for example, you can add a glider in the middle of the grid by providing a value for `start.x` and `start.y`.

## Drawing The Grid with GridView

Now that some code is in place for the grid, it’s time to draw that grid on screen. We’re going to do so by defining `GridView`, which is a subclass of the `UIView` type. You can put this view in any UIKit-based app.

First, add the following code to the playground:

``````class GridView: UIView
{
var grid = Grid()
}
``````

This is the `GridView` class, which is a subclass of `UIView`. It has one property called `grid` of type `Grid`. This is the struct we defined earlier; we’re essentially tacking that data structure onto the `GridView` view.

Next up, add the following function to the `GridView` class:

``````override func draw(_ rect: CGRect)
{

}
``````

This `draw(_:)` function is part of `UIView`, and we’re overriding it here with our own implementation. It’s called every time that the view needs to be (re)drawn. Whatever we “draw” in this function is shown in the view, so that’s a perfect hook into drawing the contents of the grid (in pixels).

Here’s how the drawing is going to work:

1. Get the graphics context, i.e. the “canvas” we’re going to draw onto
2. Clear the canvas, so we’re starting with a clean slate
3. Fill the canvas with a white background
4. Determine the size of a cell in pixels, based on the grid and view size
5. Loop over each X/Y coordinate in the cell grid, and if the cell is, draw a black rectangle on the canvas at the corresponding coordinate

Let’s go!

### Setting Up The Canvas

First, add the following code to the `draw(_:)` function:

``````guard let context = UIGraphicsGetCurrentContext() else {
return
}

context.clear(CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height))
``````

Here’s what’s happening:

• First, we get a reference to the graphics context and assign it to `context`. When that fails, the function returns and exits execution.
• Then, we’re clearing the graphics context. Everything that’s on there is removed. We’re doing so within the rectangle `(0, 0, width, height)`.

### Filling White Background

Next, add this code to the `draw(_:)` function:

``````context.setFillColor(UIColor.white.cgColor)
context.addRect(CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height))
context.fillPath()
``````

That does this:

1. Set the fill color to white, i.e. grab the white paint bucket
2. Define a rectangle that has the same size as the view
3. Fill this rectangle with the white color

We now have drawn a completely white view.

### Drawing The Cells

Before we can draw the Game of Life’s cells on screen, we’ll need to determine size of a cell in pixels. For example, our grid is 50×50 cells, and the view’s size could be 400×400 points (pixels), so that means 1 cell is 8×8 pixels in size.

Add the following code to the function:

``````let cellSize = (width: bounds.width / CGFloat(grid.size.width), height: bounds.height / CGFloat(grid.size.height))
``````

The `cellSize` constant is a tuple with `width` and `height` values. Both are calculated by dividing the width of the view by `width.grid`, and view height divided by `grid.height` respectively. The view is divided by the grid, and now we have an individual cell size of `cellSize.width` × `cellSize.height` pixels.

†: Technically, iOS apps use the concept of “points” to account for screen densities (DPI) between different iPhone/iPad devices. In this tutorial, you can regard points and pixels to be synonymous. Learn more here: 1x, 2x and 3x Image Scaling on iOS Explained

Then, add the following code. It’ll set the fill color to black:

``````context.setFillColor(UIColor.black.cgColor)
``````

Finally, add the following code to the `draw(_:)` function:

``````for x in 0..<grid.size.width {
for y in 0..<grid.size.height {
if grid.cells[x][y] == 1 {
context.addRect(CGRect(x: CGFloat(x) * cellSize.width, y: CGFloat(y) * cellSize.height, width: cellSize.width, height: cellSize.height))
context.fillPath()
}
}
}
``````

Let’s take a closer look at that code. First, the 2 nested for loops. You’re going to see more of those! How do they work?

You already know that `grid` includes a `cells` property that is a 2D array of integer values. Its size is determined by `grid.size.width` and `grid.size.height`. When both are `50`, the `grid.cells` array has size 50×50 = 2500 cells.

Each of those cells has a coordinate. This coordinate space lies between (0, 0) and (49, 49). We can reach every cell in the grid by their X/Y coordinates between those values. This means looping from 0 to 49, and in each of those loops, looping from 0 to 49 again.

``````(0, 0) (0, 1) (0, 2) (0, 3) (0, 4) ··· (0, 48) (0, 49)
(1, 0) (1, 1) (1, 2) (1, 3) (1, 4) ··· (0, 48) (0, 49)
···
(49, 0) (49, 1) ···
``````

See how that works? In Swift, we express both loops with a range. For example, `for x in 0..<grid.size.width`. That means: Loop from zero until 50, not including 50. Within the loop, we have access to the current value of `x`. Combine that with another loop `for y in ···`, and you’ve got X/Y coordinates for each cell in the grid.

What’s going on inside the loop? Here’s what:

1. Check if the value of the cell at that coordinate is `1`, because otherwise we don’t have to paint it black (it’s white already)
2. Add a rectangle at the corresponding coordinate in the view, i.e. multiply X/Y in the grid to X/Y in the view based on `cellSize`
3. Fill the rectangle with a black color

Awesome!

## Creating The Game of Life Environment

So far, we’ve created the `Grid` with cells, created a `Factory` for cell patterns (like a glider), and created the `GridView` that’ll draw the Game of Life grid on screen. Let’s put that code to use!

Add the following code to your playground, at the bottom of the code, so below everything else:

``````PlaygroundPage.current.needsIndefiniteExecution = true

let gridView = GridView(frame: CGRect(x: 0, y: 0, width: 400, height: 400))
PlaygroundPage.current.liveView = gridView

gridView.grid.insertCells(Factory.glider(), at: (x: 2, y: 2))
gridView.grid.insertCells(Factory.glider(), at: (x: 10, y: 10))
gridView.grid.insertCells(Factory.blinker(), at: (x: 5, y: 10))
gridView.grid.insertCells(Factory.random(size: 20), at: (x: 20, y: 20))

gridView.setNeedsDisplay()
``````

Here’s what the code does:

1. Enable infinite execution for this playground; this means the playground won’t stop executing at the end of the code, so we’ll be able to use timers and async programming. (We’ll need this setting for later.)
2. Create a `GridView` instance of 400×400 points, and assign that to the `liveView` component of the playground. When set, this grid will now show up in the playground’s Live View. You can show/hide it with Option + Command + Enter.
3. With `insertCells(_:at:)` we’re adding a bunch of preset Game of Life patterns to the grid. You’re looking at a bunch of gliders, a blinker, and some random dots. Feel free to add some more! (Only within the (0, 0, 50, 50) rectangle.)
4. Finally, `setNeedsDisplay()` will ping the `gridView` that it needs to be redrawn. This will invoke the `draw(_:)` function, which will draw the contents of `gridView.grid.cells` on screen.

Here’s what you should see on your screen now:

## Which Cells Stay Alive?

In the next section, we’re going to compute the next Game of Life generation by looping over each cell and checking if they’re alive or dead. But before we can do so, we’ll need to code a function that determines if an individual cell survives to the next generation. Let’s code that!

First, add the following function to the `Grid` (!) struct:

``````func staysAlive(_ x: Int, _ y: Int, isAlive: Bool) -> Bool
{

}
``````

The `staysAlive(_:_:isAlive:)` function determines if a cell at grid coordinate `(x, y)` stays alive in the next generation. It will return `true` for alive, and `false` for dead.

The `isAlive` parameter, of type `Bool`, is used to indicate that the cell is alive in the current generation. This status is important for determining if the cell stays alive in the next generation.

Inside the `staysAlive(···)` function, we’re going to have to determine if a cell stays alive. As we’ve discussed at the beginning of this tutorial, we’ll use 3 rules to determine a cell’s fate:

1. A live cell with 2 or 3 live neighbours survives.
2. A dead cell with 3 live neighbours becomes a live cell.
3. All other live cells die in the next generation, and all other dead cells stay dead.

The algorithm we use for this simpler than you think – it only has 2 components! We first count the number of alive neighbors, and then take a decision on that number and the state of `isAlive`. Easy-peasy!

First, add the following code to the function:

``````var count = 0

let pairs = [
[-1,-1], [0,-1], [1,-1],
[-1, 0],         [1, 0],
[-1, 1], [0, 1], [1, 1]
]
``````

The `count` variable is used to keep track of the number of alive neighbors. It starts at zero, of course.

The `pairs` constant is a 2D array with relative X/Y coordinates. It’s essentially a matrix of X/Y pairs. Imagine a cell, and then imagine placing this 3×3 matrix on top of it.

Each of the 8 neighbors around the cell correspond to an item in the `pairs` array. For example, (-1, -1) is the cell in the top-left corner relative to the center cell. (We’re using some formatting to make this code easier to read.)

Next, we’re going to loop over the pairs. Add this code to the function:

``````for pair in pairs
{
let xd = x + pair[0]
let yd = y + pair[1]

}
``````

Looks familiar? As we’re looping over the `pairs` array, we’re taking the X and Y values, `pair[0]` and `pair[1]` respectively, and add those to the `x` and `y` parameters of the `staysAlive()` function.

An example:

• We’re determining if the cell at `(3, 3)` should stay alive
• Looping over `pairs`, we find the relative coordinate `(-1, -1)`
• This corresponds to absolute coordinate `(2, 2)` because `(2, 2) == (3 + -1, 3 + -1) == (3 - 1, 3 - 1)`. (Remember, plus and minus is minus!)

Next, add the following code inside the `for in` loop, below the existing code:

``````if  xd >= 0 && yd >= 0 &&
xd < size.width && yd < size.height &&
cells[xd][yd] == 1 {

count += 1
}
``````

What’s going on here? You’re looking at 4 steps:

1. With the relative coordinates `xd` and `yd` set, check if they’re greater than or equal to zero, i.e. inside the bounds of the grid
2. Check if they’re smaller than the `width` and `height` of the grid, i.e. within the bounds of the grid
3. Check if the cell at `cells[xd][yd]`, i.e. the neighbor cell, is alive – its value is `1` if it’s alive, and `0` if it’s dead
4. If all that is true, increase `count` with 1, because we’ve found an alive neighbor cell!

Let’s do a quick recap now. We’re trying to find out if a given cell in the grid should stay alive in the next generation. We know its coordinate, so by using a matrix of cells around that coordinate, we’re checking their status. Looping over each of those neighboring cells, we check if they’re alive. If a neighbor is alive, we increase `count` by 1.

Finally, add the following code to the `staysAlive()` function, outside the `for in` loop, below the existing code:

``````if isAlive && (count == 2 || count == 3) {
return true
} else if !isAlive && count == 3 {
return true
}

return false
``````

Ah, what’s that!? This looks like the rules for Game of Life, right? Who knew that could be so simple…

1. If the cell that we’re checking is currently alive, and it’s alive neighbors `count` is either 2 or 3, the current cell stays alive.
2. If the cell that we’re checking is not alive, and it has 3 alive neighbors, then the current cell stays/becomes alive.
3. Anything else? Sorry, you’re dead!

Awesome! This concludes the work on the function `staysAlive()`. We’re ready to apply that to the grid now, and calculate the next generation.

## Computing The Next Generation

When you break up a problem into smaller sub-problems, and solve those, the “bigger” problem becomes easier to solve. That’s one of the miracles of computer programming. We’ve done all this work, only to make the core of Game of Life – computing the next generation – easier to code. Let’s get to it!

Add the following code to the `Grid` struct:

``````mutating func generation()
{
var nextCells = Array(repeating: Array(repeating: 0, count: size.height), count: size.width)

for x in 0..<size.width {
for y in 0..<size.height {
if staysAlive(x, y, isAlive: cells[x][y] == 1) {
nextCells[x][y] = 1
}
}
}

cells = nextCells
}
``````

Here’s what’s going on in the code:

1. Create an empty 2D array `nextCells` with a bunch of zeroes of size `width` × `height`. This is effectively the same as what we’re doing in the `init()` function of `Grid`. We’re starting the next generation with an empty grid.
2. Loop over the grid with an inner and outer loop. The outer loop runs from `0` to `size.width` (not including), and the inner loop from `0` to `size.height` (not including). Just as before, this gives us access to all grid cell coordinates `(x, y)` between the bounds of the grid.
3. Use the `staysAlive(_:_:isAlive:)` function to determine if a cell should survive to the next generation. Be mindful of the parameters here! We’re providing the `x` and `y` of the current cell, and the status of the current cell with `cells[x][y]`. If the cell is alive, `cells[x][y]` equals `1`.
4. Then, on the innermost line in the loop, if `staysAlive()` returns `true`, set the same `(x, y)` coordinate on `nextCells` to `1`. This cell is alive in the next generation. Yay!
5. Finally, overwrite `cells` with `nextCells`. This is the grid of the next generation, so the current generation is discarded.

What else is there to say about this function!? We’re looping over the grid, calculating every cell’s dead/alive status, and commit the next generation to the `cells` property of the grid. Awesome!

## Automating with a Timer

Last but not least, we’ll need some code to put all this together. We’ve created the `Grid`, the `GridView`, and some code to compute the next generations. You can essentially put that in a loop, and let it run forever.

That’s exactly what we’re going to do! Add the following code to the playground, below the existing code:

``````let timer = DispatchSource.makeTimerSource()
timer.setEventHandler(handler: {

gridView.grid.generation()

DispatchQueue.main.async {
gridView.setNeedsDisplay()
}
})

timer.activate()
``````

This code creates a timer that repeats some code every 500 milliseconds. You can see we’re calling the `generation()` function on the grid, and then call `setNeedsDisplay()` to redraw the view. On the last line, we’re activating the timer.

A problem with making Game of Life work is that the computation needs to take place in a serial queue. You can only calculate one generation, and then the next, and the next, and so on. What doesn’t work is a concurrent or parallel process.

The pace of the computation is also important, especially in an Xcode playground. The computation can potentially slow down as more cells are present in the grid, or when your Mac is doing something else. That’s why we’re only firing the `generation()` function once every 0.5 seconds.

Why didn’t we use simpler Timer component here? That `Timer` component uses a runloop to do work, and it works asynchronously. The `DispatchSourceTimer` that `makeTimerSource()` returns uses the default serial background queue, so we’re guaranteed that the work happens serially. Two generations cannot overlap, so to speak.

Inside the handler of the timer, after calling `generation()`, we’re jumping to the main thread and schedule `setNeedsDisplay()`, i.e. a redraw, there. This must happen asynchronously, but that also means that the computation in `generation()` can potentially run faster than the view can update. We’re avoiding this by setting a reasonable pace (500 ms) for the timer.

Quick Note: In the example code, I’ve included an example iOS app that you can run on your iPhone. Rendering views on an iPhone is much faster than doing the same in an Xcode playground. I’ve seen good performance with firing the timer every 50 milliseconds or so. That means you can simulate more generations in less time!

## Run Conway’s Game of Life!

That’s it! Fire up your Xcode playground or iPhone app and see Conway’s Game of Life come to life. Awesome!