Conway’s Game of Life in 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.
Table of Contents
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:
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:
- A live cell with 2 or 3 live neighbours survives.
- A dead cell with 3 live neighbours becomes a live cell.
- 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:
- Game of Life with arrays: A playground with the code in this tutorial
- Game of Life with Sets: An alternative implementation based on Set
- 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:
- The Grid struct, which represents the Game of Life’s cells in a 2D array. It’s responsible for calculating the next generation.
- 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.
Next, add the following code:
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.
Add the following code to your playground:
struct Factory
{
static func glider() -> [[Int]]
{
return [
[0, 1, 0],
[0, 0, 1],
[1, 1, 1],
]
}
static func blinker() -> [[Int]]
{
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.. 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. 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 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: Let’s go! First, add the following code to the draw(_:) function: guard let context = UIGraphicsGetCurrentContext() else { context.clear(CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height)) Here’s what’s happening: Next, add this code to the draw(_:) function: context.setFillColor(UIColor.white.cgColor) That does this: We now have drawn a completely white view. 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)) 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.. Awesome! 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)) gridView.grid.insertCells(Factory.glider(), at: (x: 2, y: 2)) gridView.setNeedsDisplay() Here’s what the code does: Here’s what you should see on your screen now: 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: 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 = [ 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 } 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: Next, add the following code inside the for in loop, below the existing code: if xd >= 0 && yd >= 0 && 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 false Ah, what’s that!? This looks like the rules for Game of Life, right? Who knew that could be so simple… Awesome! This concludes the work on the function staysAlive(). We’re ready to apply that to the grid now, and calculate 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() for x in 0.. 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! 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() gridView.grid.generation() DispatchQueue.main.async { 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! That’s it! Fire up your Xcode playground or iPhone app and see Conway’s Game of Life come to life. Awesome!Drawing The Grid with GridView
{
var grid = Grid()
}
{Setting Up The Canvas
return
}Filling White Background
context.addRect(CGRect(x: 0.0, y: 0.0, width: bounds.width, height: bounds.height))
context.fillPath()Drawing The Cells
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 Creating The Game of Life Environment
PlaygroundPage.current.liveView = gridView
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))Which Cells Stay Alive?
{
[-1,-1], [0,-1], [1,-1],
[-1, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]
]
{
let xd = x + pair[0]
let yd = y + pair[1]
xd < size.width && yd < size.height && cells[xd][yd] == 1 { count += 1
}
What’s going on here? You’re looking at 4 steps:
return true
} else if !isAlive && count == 3 {
return true
}Computing The Next Generation
{
var nextCells = Array(repeating: Array(repeating: 0, count: size.height), count: size.width)Automating with a Timer
timer.schedule(deadline: .now(), repeating: .milliseconds(500))
timer.setEventHandler(handler: {
gridView.setNeedsDisplay()
}
})Run Conway’s Game of Life!
Related Articles
Most Popular Posts
Shopify integration with its top 10 apps to drive the maximum sales in 2023
By Abhinav Girdhar | October 31, 2022
How to Make an App Like TikTok in 2022?
By Snigdha | August 4, 2022
Mobile Affiliate Marketing SEO: What You Can Do?
By Abhinav Girdhar | August 3, 2020
How to make a WordPress website for free?
By Abhinav Girdhar | January 15, 2020
Diwali: The Festival of Lights and Happiness
By Neeraj Shukla | August 10, 2022