For this challenge, you will build a simulation of underpopulation, overpopulation, and reproduction called Conway’s Game of Life (see mng.bz/xOyY). The simulation is played out on a two-dimensional grid of cells. As such, this challenge focuses on slices.
Each cell has eight adjacent cells in the horizontal, vertical, and diagonal directions. In each generation, cells live or die based on the number of living neighbors.
For your first implementation of the Game of Life, limit the universe to a fixed size. Decide on the dimensions of the grid and define some constants:
const ( width = 80 height = 15 )
Next, define a Universe type to hold a two-dimensional field of cells. With a Boolean type, each cell will be either dead (false) or alive (true):
type Universe [][]bool
Uses slices rather than arrays so that a universe can be shared with, and modified by, functions or methods.
Lesson 26 introduces pointers, an alternative that allows you to directly share arrays with functions and methods.
Write a NewUniverse function that uses make to allocate and return a Universe with height rows and width columns per row:
func NewUniverse() Universe
Freshly allocated slices will default to the zero value, which is false, so the universe begins empty.
Write a method to print a universe to the screen using the fmt package. Represent live cells with an asterisk and dead cells with a space. Be sure to move to a new line after printing each row:
func (u Universe) Show()
Write a main function to create a NewUniverse and Show it. Before continuing, be sure that you can run your program, even though the universe is empty.
Write a Seed method that randomly sets approximately 25% of the cells to alive (true):
func (u Universe) Seed()
Remember to import math/rand to use the Intn function. When you’re done, update main to populate the universe with Seed and display your handiwork with Show.
The rules of Conway’s Game of Life are as follows:
To implement the rules, break them down into three steps, each of which can be a method:
It should be easy to determine whether a cell is dead or alive. Just look up a cell in the Universe slice. If the Boolean is true, the cell is alive.
Write an Alive method on the Universe type with the following signature:
func (u Universe) Alive(x, y int) bool
A complication arises when the cell is outside of the universe. Is (–1,–1) dead or alive? On an 80 × 15 grid, is (80,15) dead or alive?
To address this, make the universe wrap around. The neighbor above (0,0) will be (0,14) instead of (0,–1), which can be calculated by adding height to y. If y exceeds the height of the grid, you can turn to the modulus operator (%) that we used for leap year calculations. Use % to divide y by height and keep the remainder. The same goes for x and width.
Write a method to count the number of live neighbors for a given cell, from 0 to 8. Rather than access the universe data directly, use the Alive method so that the universe wraps around:
func (u Universe) Neighbors(x, y int) int
Be sure to only count adjacent neighbors and not the cell in question.
Now that you can determine whether a cell has two, three, or more neighbors, you can implement the rules shown at the beginning of this section. Write a Next method to do this:
func (u Universe) Next(x, y int) bool
Don’t modify the universe directly. Instead, return whether the cell should be dead or alive in the next generation.
To complete the simulation, you need to step through each cell in the universe and determine what its Next state should be.
There’s one catch. When counting neighbors, your count should be based on the previous state of the universe. If you modify the universe directly, those changes will influence the neighbor counts for the surrounding cells.
A simple solution is to create two universes of the same size. Read through universe A while setting cells in universe B. Write a Step function to perform this operation:
func Step(a, b Universe)
Once universe B holds the next generation, you can swap universes and repeat:
a, b = b, a
To clear the screen before displaying a new generation, print "x0c", which is a special ANSI escape sequence. Then display the universe and use the Sleep function from the time package to slow down the animation.
Outside of the Go Playground, you may need another mechanism to clear the screen, such as " 33[H" on macOS.
Now you should have everything you need to write a complete Game of Life simulation and run it in the Go Playground.
When you’re done, share a Playground link to your solution in the Manning forums at forums.manning.com/forums/get-programming-with-go.