Geek Culture
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 WeakMaps 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 Cells.

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.

The Glider is a pattern that appears to always keep moving and never dies

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)

The Penta-decathlon oscillates in one place on the grid and never dies

Die-Hard

Die-hards eventually disappear rather than stabilizing.

Die-hards’ cells eventually all die

Still Lifes

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

Still lifes stay alive in their original shape, they never die, move or oscillate

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.

--

--

--

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

Recommended from Medium

Bootstrap Basics

What are Sylius plugins and how to use them properly?

How to Utilize io.TeeReader in Golang

Into the art of Binary Exploitation 0x000001 [Stack-Based Overflow]

Setting up a Go droplet in Digital Ocean

Asynchronous Programming

Asynchronues logo

Robot Framework as Web Service

What is a QR code? And why do you need for your restaurant business?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Dmitry Kostyuk

Dmitry Kostyuk

Full-time GAS developer, founder at Wurkspaces.dev

More from Medium

Quantum Computing and Healthcare

How to create a Twitter bot

Rust Guide: Evaluating multilinear extensions via Lagrange interpolation

Creating simple zero-knowledge verifier contract with ZoKrates (0.7.13) solidity (0.8.0)