DEV Community

Shoubhit Dash
Shoubhit Dash

Posted on

Implementing Conway's Game of Life in Go⚡

demo

I recently started learning Go and I absolutely love it. I think implementing Conway's Game of Life in a language is like a more sophisticated 'Hello World' program. Its pretty easy to do it and you learn a lot of stuff about that language. So that's what I did.

Walk-through

So first let's initialize a Go project:

go mod init github.com/nexxeln/go-gol
Enter fullscreen mode Exit fullscreen mode

Now make a gol.go file. This is where all our code will go.

package main

import (
    "image/color"
    "log"
    "math/rand"

    "github.com/hajimehoshi/ebiten"
)
Enter fullscreen mode Exit fullscreen mode

So we start by declaring this as the main package and importing the tools we need. The last one called ebiten is a very simple 2-D engine written in Go, and we import it straight from GitHub which is pretty cool.

const scale int = 2
const width = 300
const height = 300

var blue color.Color = color.RGBA{69, 145, 196, 255}
var yellow color.Color = color.RGBA{255, 230, 120, 255}
var grid [width][height]uint8 = [width][height]uint8{}
var buffer [width][height]uint8 = [width][height]uint8{}
var count int = 0
Enter fullscreen mode Exit fullscreen mode

Here we have declared some global variables. The scale is the density of the pixels. width and height are the width and height of the window. Next we declare some colors namely blue and yellow. The grid is where everything is going to be displayed and the buffer is the next iteration of the grid.

func update() error {
    for x := 1; x < width-1; x++ {
        for y := 1; y < height-1; y++ {
            buffer[x][y] = 0
            n := grid[x-1][y-1] + grid[x-1][y+0] + grid[x-1][y+1] + grid[x+0][y-1] + grid[x+0][y+1] + grid[x+1][y-1] + grid[x+1][y+0] + grid[x+1][y+1]

            if grid[x][y] == 0 && n == 3 {
                buffer[x][y] = 1
            } else if n < 2 || n > 3 {
                buffer[x][y] = 0
            } else {
                buffer[x][y] = grid[x][y]
            }
        }
    }

    temp := buffer
    buffer = grid
    grid = temp
    return nil
}
Enter fullscreen mode Exit fullscreen mode

This is the update function. Here we first get all the neighbors of the cell and store it in n. Next we check if the cells around it are dead, if yes we make it alive. If there's overpopulation or under population we make it dead, otherwise we just copy it to the next iteration. After that we just return nil.

func display(window *ebiten.Image) {
    window.Fill(blue)

    for x := 0; x < width; x++ {
        for y := 0; y < height; y++ {
            for i := 0; i < scale; i++ {
                for j := 0; j < scale; j++ {
                    if grid[x][y] == 1 {
                        window.Set(x*scale+i, y*scale+j, yellow)
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This is the display function which is going to render it on the screen. This is where ebiten comes in. We start by defining window as an ebiten.Image and fill it with a nice blue color. Next for every pixel we're going to set that pixel as yellow.

func frame(window *ebiten.Image) error {
    count++
    var err error = nil
    if count == 20 {
        err = update()
        count = 0
    }
    if !ebiten.IsDrawingSkipped() {
        display(window)
    }

    return err
}
Enter fullscreen mode Exit fullscreen mode

This is the frame function. First we increment the count variable, and once it reaches 20 we're going to call the update function and reset the count variable. We also check if ebiten is not going to skip the rendering phase, then we're just going to call the display function and render it to the screen.

We're almost finished, let's set up our main function.

func main() {
    for x := 1; x < width-1; x++ {
        for y := 1; y < height-1; y++ {
            if rand.Float32() < 0.5 {
                grid[x][y] = 1
            }
        }
    }

    if err := ebiten.Run(frame, width, height, 2, "Game of Life"); err != nil {
        log.Fatal(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

So we start off by setting up the grid and randomly populating it with cells using rand.Float32(). Next we use ebiten.Run to run the program with the name "Game of Life". If ebiten.Run returns an error we're just going to log it to the terminal. This is reminiscent of pattern matching in Rust.

Well that's pretty much it! Now just open up the terminal and run the program:

go run ./main.go
Enter fullscreen mode Exit fullscreen mode

All the code can be found here.

This was my first blog ever, so please tell me if I did something wrong. If you read till here you're very cool. Okay bye!

Latest comments (0)