Lesson 20. Capstone: A slice of life

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.

20.1. A new universe

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.

Note

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.

20.1.1. Looking at the universe

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.

20.1.2. Seeding live cells

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.

20.2. Implementing the game rules

The rules of Conway’s Game of Life are as follows:

  • A live cell with less than two live neighbors dies.
  • A live cell with two or three live neighbors lives on to the next generation.
  • A live cell with more than three live neighbors dies.
  • A dead cell with exactly three live neighbors becomes a live cell.

To implement the rules, break them down into three steps, each of which can be a method:

  • A way to determine whether a cell is alive
  • The ability to count the number of live neighbors
  • The logic to determine whether a cell should be alive or dead in the next generation

20.2.1. Dead or alive?

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.

20.2.2. Counting neighbors

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.

20.2.3. The game logic

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.

20.3. Parallel universe

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.

Note

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset