Shoubhit Dash
Posted on March 17, 2022
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
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"
)
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
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
}
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)
}
}
}
}
}
}
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
}
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)
}
}
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
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!
Posted on March 17, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.