[Part 1: Hello, OpenGL][8]|[Part 2: Drawing the Game Board][9]|[Part 3: Implementing the Game][10]
The full source code of the tutorial is available on[GitHub][11].
Welcome back to theOpenGL & Go Tutorial!If you haven’t gone through[Part 1][12]and[Part 2][13]you’ll definitely want to take a step back and check them out.
At this point you should have a grid system created and a matrix ofcellsto represent each unit of the grid. Now it’s time to implementConway’s Game of Lifeusing the grid as the game board.
Let’s get started!
### Implement Conway’s Game
One of the keys to Conway’s game is that each cell must determine its next state based on the current state of the board, at the same time. This means that if Cell(X=3, Y=4)changes state during its calculation, its neighbor at(X=4, Y=4)must determine its own state based on what(X=3, Y=4)was, not what is has become. Basically, this means we must loop through the cells and determine their next state without modifying their current state before we draw, and then on the next loop of the game we apply the new state and repeat.
In order to accomplish this, we’ll add two booleans to thecellstruct:
```
type cell struct {
drawable uint32
alive bool
aliveNext bool
x int
y int
}
```
Now let’s add two functions that we’ll use to determine the cell’s state:
```
// checkState determines the state of the cell for the next tick of the game.
func (c *cell) checkState(cells [][]*cell) {
c.alive = c.aliveNext
c.aliveNext = c.alive
liveCount := c.liveNeighbors(cells)
if c.alive {
// 1\. Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
if liveCount <2{
c.aliveNext = false
}
// 2\. Any live cell with two or three live neighbours lives on to the next generation.
if liveCount == 2 || liveCount == 3 {
c.aliveNext = true
}
// 3\. Any live cell with more than three live neighbours dies, as if by overpopulation.
if liveCount > 3 {
c.aliveNext = false
}
} else {
// 4\. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
if liveCount == 3 {
c.aliveNext = true
}
}
}
// liveNeighbors returns the number of live neighbors for a cell.
func (c *cell) liveNeighbors(cells [][]*cell) int {
var liveCount int
add := func(x, y int) {
// If we're at an edge, check the other side of the board.
if x == len(cells) {
x = 0
} else if x == -1 {
x = len(cells) - 1
}
if y == len(cells[x]) {
y = 0
} else if y == -1 {
y = len(cells[x]) - 1
}
if cells[x][y].alive {
liveCount++
}
}
add(c.x-1, c.y) // To the left
add(c.x+1, c.y) // To the right
add(c.x, c.y+1) // up
add(c.x, c.y-1) // down
add(c.x-1, c.y+1) // top-left
add(c.x+1, c.y+1) // top-right
add(c.x-1, c.y-1) // bottom-left
add(c.x+1, c.y-1) // bottom-right
return liveCount
}
```
What’s more interesting is theliveNeighborsfunction where we return the number of neighbors to the current cell that are in analivestate. We define an inner function calledaddthat will do some repetitive validation on X and Y coordinates. What it does is check if we’ve passed a number that exceeds the bounds of the board - for example, if cell(X=0, Y=5)wants to check on its neighbor to the left, it has to wrap around to the other side of the board to cell(X=9, Y=5), and likewise for the Y-axis.
Below the inneraddfunction we calladdwith each of the cell’s eight neighbors, depicted below:
```
[
[-, -, -],
[N, N, N],
[N, C, N],
[N, N, N],
[-, -, -]
]
```
In this depiction, each cell labeledNis a neighbor toC.
Now in ourmainfunction, where we have our core game loop, let’s callcheckStateon each cell prior to drawing:
Let’s fix that. Back inmakeCellswe’ll use a random number between0.0and1.0to set the initial state of the game. We’ll define a constant threshold of0.15meaning that each cell has a 15% chance of starting in an alive state:
```
import (
"math/rand"
"time"
...
)
const (
...
threshold = 0.15
)
func makeCells() [][]*cell {
rand.Seed(time.Now().UnixNano())
cells := make([][]*cell, rows, rows)
for x := 0; x <rows;x++{
for y := 0; y <columns;y++{
c := newCell(x, y)
c.alive = rand.Float64() <threshold
c.aliveNext = c.alive
cells[x] = append(cells[x], c)
}
}
return cells
}
```
Next in the loop, after creating a cell with thenewCellfunction we set itsalivestate equal to the result of a random float, between0.0and1.0, being less thanthreshold(0.15). Again, this means each cell has a 15% chance of starting out alive. You can play with this number to increase or decrease the number of living cells at the outset of the game. We also setaliveNextequal toalive, otherwise we’ll get a massive die-off on the first iteration becausealiveNextwill always befalse!
Now go ahead and give it a run, and you’ll likely see a quick flash of cells that you can’t make heads or tails of. The reason is that your computer is probably way too fast and is running through (or even finishing) the simulation before you have a chance to really see it.
Let’s reduce the game speed by introducing a frames-per-second limitation in the main loop:
Now you should be able to see some patterns, albeit very slowly. Increase the FPS to 10 and the size of the grid to 100x100 and you should see some really cool simulations:
```
const (
...
rows = 100
columns = 100
fps = 10
...
)
```
![Conway's Game of Life in OpenGL and Golang Tutorial - Demo Game](https://kylewbanks.com/images/post/golang-opengl-conway-1.gif)
Try playing with the constants to see how they impact the simulation - cool right? Your very first OpenGL application with Go!
### What’s Next?
This concludes theOpenGL with Go Tutorial, but that doesn’t mean you should stop now. Here’s a few challenges to further improve your OpenGL (and Go) knowledge:
1. Give each cell a unique color.
2. Allow the user to specify, via command-line arguments, the grid size, frame rate, seed and threshold. You can see this one implemented on GitHub at[github.com/KyleBanks/conways-gol][4].
3. Change the shape of the cells into something more interesting, like a hexagon.
4. Use color to indicate the cell’s state - for example, make cells green on the first frame that they’re alive, and make them yellow if they’ve been alive more than three frames.
5. Automatically close the window if the simulation completes, meaning all cells are dead or no cells have changed state in the last two frames.
6. Move the shader source code out into their own files, rather than having them as string constants in the Go source code.
### Summary
Hopefully this tutorial has been helpful in gaining a foundation on OpenGL (and maybe even Go)! It was a lot of fun to make so I can only hope it was fun to go through and learn.
As I’ve mentioned, OpenGL can be very intimidating, but it’s really not so bad once you get started. You just want to break down your goals into small, achievable steps, and enjoy each victory because while OpenGL isn’t always as tough as it looks, it can certainly be very unforgiving. One thing that I have found helpful when stuck on OpenGL issues was to understand that the waygo-glis generated means you can always use C code as a reference, which is much more popular in tutorials around the internet. The only difference usually between the C and Go code is that functions in Go are prefixed withgl.instead ofgl, and constants are prefixed withglinstead ofGL_. This vastly increases the pool of knowledge you have to draw from!
[Part 1: Hello, OpenGL][14]|[Part 2: Drawing the Game Board][15]|[Part 3: Implementing the Game][16]
The full source code of the tutorial is available on[GitHub][17].