Going through the Awesome Go list, I came across a new library under the Game Development section named Pixel. Since I have been looking for a simple game development library in Go for visualization of cellular automata and evolutionary algorithms simulations.

After spending a few minutes with the Pixel’s documentation, I was impressed with the library and decided to develop a simple interactive demo for exploring Conway’s Game of Life.

Game of Life is a 2D cellular automata and a zero-player game whose evolution only depends on the initial configuration. The rules of the game as per its Wikipedia page are as follows:

The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, alive or dead, or “populated” or “unpopulated”. Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

  1. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.

Even with these simple and local rules, the Game of Life is known to produce some very interesting static, oscillating and repeating patterns.

I wanted to see how easy it would be to develop the following features:

  1. Draw individual pixels and primitives (lines, circles, polygons, etc.)
  2. Keyboard interaction (press, hold, release)
  3. Mouse interaction (press, hold, release, double-click)

With a few hours of copy-pasting from the Pixel examples and some own coding, I was able to create a demo with the following features:

  1. Store the state of the world in a 2D array
  2. Scale the world to 4x to make the cells visibly distinguishable
  3. Draw the cells as filled rectangles (also tried circles)
  4. Left-click to set a cell alive
  5. Alt+Left-click to set a cell to dead
  6. Hold the click (and Alt variant) to continuous set cell states
  7. Ctrl+Left-click to reinitialize a fixed 11x11 area around the mouse position
  8. Press c and r to clear and reinitialize the entire world, respectively
  9. Press Space to toggle pausing the simulation

Except for double-click, which I could not figure out, all other features were relatively easy to figure out as well as implement.

The resulting code is available at https://github.com/shivakar/go-game-of-life. Here’s a screencast of one run of the simulation.

Conway's Game of Life demo

Additional criteria that I am interested in include:

  1. Ease of installation of the library

    As with most Go packages installation was easy:

    go get -u github.com/faiface/pixel
    
  2. Dependencies of the library

    The number and quality of dependencies of a library are something that I always look at when selecting a library. I find that too many dependencies or low-quality dependencies tend to create usability issues down the line.

    Here’s the dependency graph for github.com/faiface/pixelgl generated using goviz Pixel library
dependencies

    As evident from the dependency graph, the only dependencies outside the standard library are the excellent go-gl (OpenGL with Golang) and golang.org/x packages.

  3. Performance

    I ran these experiments on my MacBook Air (mid-2012) running macOS Sierra (10.12) and Go 1.8.1.

    With cells being drawn as filled rectangles and VSync set to false, I saw frame rates between 250-500 fps depending on how many cells in the world were alive. With VSync set to true, the frame rate was synchronized to 60fps monitor refresh rate.

    However when the cells were drawn as filled circles, the frame rate dropped to 5-50 fps.

    Profiling with pprof I noticed that the top CPU consuming functions went from

    flat%  cum%
    37.76% 37.76%  main.(*GameOfLife).NeighborsAlive
     6.98% 17.73%  main.(*GameOfLife).Draw
     4.37% 42.13%  main.(*GameOfLife).Update
     2.23%  2.92%  github.com/faiface/pixel/imdraw.(*IMDraw).applyMatrixAndMask
     1.23%  6.60%  github.com/faiface/pixel/imdraw.(*IMDraw).fillRectangle
    

    to

    flat%  cum%
    20.92% 24.35%  github.com/faiface/pixel/imdraw.(*IMDraw).applyMatrixAndMask
    15.07% 15.07%  github.com/faiface/pixel/pixelgl.(*GLTriangles).updateData
    10.82% 10.82%  math.Sincos
     3.72%  3.72%  main.(*GameOfLife).NeighborsAlive
     3.70% 50.00%  github.com/faiface/pixel/imdraw.(*IMDraw).fillEllipseArc
     2.15% 11.09%  github.com/faiface/pixel.(*TrianglesData).SetLen
    

    SVGs of the full CPU profile graphs are available for drawing cells as rectangles and circles.

    I haven’t looked (and most probably won’t) into the underlying causes but it appears that the library is just slow at drawing circles than rectangles. While not a deal breaker, I will have to develop a few more sample programs to get a better understanding of the strengths and weaknesses of the library.

All things considered, I am excited to see new packages like Pixel address the lack of good and easy-to-use GUI, visualization and game development libraries for Go. Although this does not end my search for a game development library in Go, I will certainly keep track of Pixel development.