Published in

Geek Culture

# How I Programmed the Game of Life in a Google Sheet with Google Apps Script

A Google Sheet, being essentially a two-dimensional array, is perfect for this task!

# What Is the Game of Life?

According to Wikipedia, the Game of Life “is a cellular automaton devised by the British mathematician John Horton Conway in 1970.”

It begins on a two-dimensional grid of square cells. Each cell can be either alive or dead. Every cell interacts with its eight immediate neighbors. A live cell only remains alive if it has two or three living neighbors. If it has fewer than two living neighbors, it dies as if by underpopulation. Conversely, if it has more than three, it dies as if by overpopulation. A dead cell remains dead unless it has exactly three living neighbors; otherwise, it becomes a live cell, as if by reproduction.

By simultaneously recalculating the state of the cells, we can create a new generation, and another one, and so on. In some cases, the cells all ultimately die, whereas in other cases, they do not. Along the way, they might even form some interesting patterns (more on them later).

With this simple set of rules, we are looking at a simulation that plays out like this:

There is no immediate practical use for the Game of Life in a spreadsheet; however, it is a fun algorithmic challenge. Moreover, Google Sheets natively provides us with the perfect data structure: a two-dimensional array. This is all the more reason to work on those array skills!

As usual, there is a GitHub repo with the full source code. Alternatively, you can just make a copy of this spreadsheet.

# The Approach

We will be using the OOP style and ES6 syntax extensively. Although I did try FP, it seemed to lend itself a little less well than OOP in this specific challenge. I am also a fan of using `WeakMap`s as private or read-only properties for JavaScript classes. I recognize that the latest version of JS supports private properties directly, but hey, one step at time, right? I also like to enclose my classes in IIFEs that return the actual class and hide away all of the private properties and methods.

Our classes diagram looks like this:

You will notice that our main class `GameOfLife` contains a `Grid` that is composed of `Cell`s.

We will also be using a 25x25 grid that we will need to set up manually. Although you can automate this on your own if you prefer, it is not the objective of this article. We will use the `setBackgrounds()` method to color the alive and dead cells.

Now, let’s jump into the code.

# The Code

We will look into the classes in the order in which they appear: first `GameOfLife`, then `Grid`, then `Cell`. Ultimately, we will specifically look into our `render()` function that displays the results of our app’s calculations.

# GameOfLife Class

The constructor of `GameOfLife` accepts two parameters: `renderCallback` and `options`.

The former is, as the name suggests, a callback that will be used to render a generation of cells on the grid. Having it passed as a callback rather than building it in has the advantage of making this library extensible, meaning that it respects the open/closed principle. This allows any other developers to use their own rendering logic (e.g., inserting values rather than changing cell backgrounds).

The `options` parameter is an object that contains the following optional properties:

• `width`: number, the width of the grid, defaults to 25
• `height`: number, the height of the grid, defaults to 25
• `patternName`: string, in case we want to initiate the grid with a specific pattern
• `frameMs`: number, how long to pause in milliseconds before rendering a new generation, defaults to 100
• `numIterations`: number, how many generations to calculate and display on the grid (you probably want to stay within the six-minute or thirty-minute execution threshold, depending on the type of your account)

The private `_grid` property is an instance of the `Grid` class that we will examine next. Some of the options, namely `width`, `height`, and `patternName`, which we pass to `GameOfLife`, are passed on to the `Grid` instance.

The game starts when the `play()` method is called. It’s a recursive method that pauses the game a predefined number of milliseconds. It then asks the `Grid` to create a new generation of cells via the `Grid.nextGen()` method and renders them. It stops after a predefined number of times. Additionally, `Grid.nextGen()` can return `null` in case there are no changes between generations. In this case, the game stops as there is no longer a need to run any more calculations. It also calls the `render()` method that calls the `render` callback.

The full source code for the class is provided below.

# Grid Class

The `Grid` class’ constructor accepts the `options` object as a parameter that contains `width`, `height`, and `patternName` entries and stores them as read-only properties. It also has the `grid` property that is a two-dimensional array of instances of the `Cell` class. Granted, I recognize that `Grid.grid` is not the best name, so if you have a better idea of what this property can be renamed to, let me know in the comments. I’m all ears!

The class has a few methods:

`setCell()` sets the value of a specific cell in the `grid` property to a given `Cell` instance. It is used when initiating or calculating a new generation of cells and setting new values to the grid.

`init()` creates a new `grid` that is a two-dimensional array with the private `_make2DArray()` method with random alive and dead cells. Later, we will modify this method to allow setting specific patterns. From the start, we’re building a two-dimensional array and will be storing our cell data there. This will allow us to render the grid easily.

`nextGen()` calculates a new generation of cells. It loops through the whole grid, calls the `getNewIsAlive()` method of the cell in question, and sets that value to a new `Grid` instance. Once all cells have been covered, that new `Grid` instance is returned.

Here is the full source code of the `Grid` class:

# Cell Class

The `Cell`’s constructor is called by the `Grid` class when a grid is built. The constructor accepts the following parameters: `Grid`, `row`, `column`, and `isAlive`. It is the `Grid` class that sets the value of `isAlive` to `true` or `false`; however, it also passes its own reference as well as the row and column index to the cell. This is necessary for the cell to count its alive neighbors and return its new state. Then, the grid can create a new `Cell` based on the current cell’s future state.

The `neighborsAlive` property and `getNewIsAlive()` method are what implement the actual rules of the Game of Life. `neighborsAlive` returns the number of alive neighbor cells for the current cell. If a cell is on the edge of the grid, it wraps around to check the cells on the opposite side of the grid. This property is then used in `getNewIsAlive()` to calculate the future state of the cell depending on whether it is currently alive or dead. If you remember, this method is used in `Grid.nextGen()` iteratively to create a whole new generation.

The full source code for the `Cell` class is provided below:

# render Callback

The `render` callback accepts the `Grid.grid` property, which, if you remember, is a two-dimensional array of `Cells` and replaces them with hex color codes depending on whether `Cell.isAlive` returns `true` or `false`. Then, it applies the new two-dimensional arrays to the spreadsheet as background colors with `setBackgrounds()`.

Notice that I am storing the `Range` reference in a Singleton to avoid calling `SpreadsheetApp.getActive().getRange()` on every iteration. This improves the function’s performance.

# The Different Patterns

As previously mentioned, there are several types of patterns depending on how they behave. Let’s review a few different types and then implement them in our code.

# Spaceships

Spaceships move across the grid and never die. Below is a glider, which is a one example of a spaceship.

# Oscillators

Oscillators return to their initial state after a finite number of generations. Below is an example of an oscillator called Penta-decathlon (period 15)

# Die-Hard

Die-hards eventually disappear rather than stabilizing.

# Still Lifes

Still lifes do not change from one generation to the next.

# Pattern Implementation

First, let’s define a `pattern` object and create a few patterns with zeros and ones in a two-dimensional array as follows:

Of course, ones represent alive cells and zeros represent dead ones. Now that we have a simple way to describe our patterns, let’s add a new private method to our `Grid` that will insert them into the defined grid size. This method should read the width and height of the grid and insert the pattern in the middle. This is what I came up with:

Now, we just need to add one line to our `Grid.init()` method to return the pattern if there is a `patternName` present in the `options` object instead of randomizing, as follows:

# Running the Game of Life

Running the Game of Life in our spreadsheet is now simply a matter of creating an instance of `GameOfLife` with the right parameters and calling its `play()` method:

# Conclusion

If you have read until here, thank you very much. I certainly hope that you found the information useful! This is just another example of how versatile spreadsheets are and the multitude of things you can do with with a two-dimensional array data structure and the OOP approach.

If you enjoyed this article, feel free to buy me a coffee — it’s what fuels my code. :)

# About the Author

Dmitry Kostyuk is a full-time GAS developer and the Founder of Wurkspaces.dev.

--

--

--

## More from Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

## Dmitry Kostyuk

Full-time GAS developer, founder at Wurkspaces.dev