Game of Life in Swift Playgrounds - Part 1

Game of Life in Swift Playgrounds - Part 1

This Game of Life series is an adapted chapter from my book titled “Simulations in Swift”. Because I already cover the basics in my book, this post assumes you already have some working knowledge of Swift Playgrounds.
If you’re interested in learning more about writing simulations using the Swift programming language, or struggling with with Playgrounds, you can purchase my book from most popular online retailers in paperback or eBook format.
Buy Simulations in Swift

What is the Game of Life?

Developed by John Conway in 1970, the Game of Life is a cellular-automaton, zero-player game. A game in the lightest sense as there is no user interaction beyond the initial starting conditions and neither is there an end.

The game takes place on a grid of cells. Each cell can either be alive or dead. Four simple rules determine the vitality of each cell.

  1. Any live cell with fewer than two live neighbors will die.
  2. Any live cell with two or three live neighbors will live on to the next generation.
  3. Any live cell with more than three live neighbors will die.
  4. Any dead cell with exactly three live neighbors will become a live cell.

If you’re unfamiliar with the game and its rules, I highly recommend finding an online version of the game to play around with before continuing.

New Playground

Create a new playground and name it “GameOfLife”. From the above description of the game, you might already have an idea of the kind of elements we’ll need to create. A cell and world object is a great place to start.

So let’s create two new files in the source folder of our playground and call them Cell.swift and World.swift.

We’re also going to be making use of Swift Playgrounds live views. While it might be ok to print out results to the console, being able to see them is even better. So create another file and call it WorldView.swift.

Cells

Cells represent each of the little lives in the simulation. They can be in one of two states — dead or alive — they also know their position in the world. These are the properties we’ll add to our cell.

Open up Cell.swift and under import Foundation create a new public enum and name it State.

public enum State {
    case alive
    case dead
}

Below the enum is where we will create our cell struct.

public struct Cell {
    public let x: Int
    public let y: Int
    public var state: State
}


Usually, we’d take advantage of the fact that structures give us an initializer without doing any further work. We’re using playgrounds though, and that means we need to have a public initializer to create our cells inside the main playground file. Unfortunately, we don’t get a public initializer for free, so let’s add that real quick.

public init(x: Int, y: Int, state: State) {
    self.x = x
    self.y = y
    self.state = state
}

We’ll also give a cell the ability to tell us if it is the neighbor to a given cell. This will help when enforcing our game rules where we determine if the cell should be dead or alive based on those surrounding it.

Inside the cell struct, below the properties, we’ll add a new function and make it public.


public func isNeighbor(to cell: Cell) -> Bool {

    let xDelta = abs(self.x - cell.x)
    let yDelta = abs(self.y - cell.y)

    switch (xDelta, yDelta) {
    case (1, 1), (0, 1), (1, 0):
        return true
    default:
        return false
    }

}

So the calculations performed here will take the coordinates of both cells and check if they are situated right next to each other. It’s quite simple:

If one cell is at x position of 10 and the other cell is at x position of 11; 11–10 = 1 or 10–11 = -1. Wrapping the result in abs() gives us an “absolute value”. So -1 becomes 1. Whether the subtraction is 10–11 or 11–10, we get the same result either way. This indicates that the cells are neighbors on the x axis. For cells to be neighbors, they cannot be any further than1 point away from each other on either axis.

We can test that this works by going back to our main playground file and creating a few cells.

let firstCell = Cell(x: 10, y: 10, state: .dead)
let secondCell = Cell(x: 11, y: 11, state: .dead)

print(firstCell.isNeighbor(to: secondCell))
// true - Adjacent row, adjacent column.

This code will print true to the console as secondCell is one step away on both axes from firstCell. Let’s try some more combinations.

let firstCell = Cell(x: 10, y: 10, state: .dead)
let secondCell = Cell(x: 11, y: 10, state: .dead)
print(firstCell.isNeighbor(to: secondCell))
// true - Same row, adjacent column.

let firstCell = Cell(x: 10, y: 10, state: .dead)
let secondCell = Cell(x: 20, y: 10, state: .dead)
print(firstCell.isNeighbor(to: secondCell))
// false - Same row, vastly different column.

let firstCell = Cell(x: 10, y: 10, state: .dead)
let secondCell = Cell(x: 10, y: 10, state: .dead)
print(firstCell.isNeighbor(to: secondCell))
// false - Same row, same column. They're identical cells!

Okay so the last one probably won’t happen in our simulation, but it’s worth testing out anyway.

In part 2 we’ll look at filling our world with the cells we’ve just created.