logoEbreiny

Back to Home

showcase

Conway's Game of Life in Ruby 2D

11/1/2024

Ruby is a language that I wanted to learn. I decided it would be a good project to make Conway's Game of Life to get down the basics of the language.

Conway's Game of Life

If you haven't heard of Conway's Game of Life, it is a basic simulation of life represented by a grid of black and white cells. Each cell can be dead or alive depending on a certain subset of rules. Those rules are as follows:

  1. If a living cell has less than two living neighbors, it dies.
  2. If a living cell as more than three living neighbors, it dies.
  3. If a living cell has exactly two or three living neighbors, it will live on to the next generation.
  4. If a dead cell has exactly three living neighbors, it will become a living cell.

Creating a Program with Visuals in Ruby

While Ruby would be able to generate visuals inside of a terminal, I wanted to find a library that would allow me to create a window with 2D visuals. I stumbled upon a few different options while doing research, but the one that I decided on was Ruby 2D.

Ruby 2D is a simple graphics library that will allow me to create a window and draw squares to the screen. Creating a window would look like this:

require "ruby2d"

set width: 600, height: 600

show

Then say we want a square to move across the screen. That would be done like so:

require "ruby2d"

set width: 600, height: 600

square = Square.new
square.color = "blue"

update do
    square.x += 1
end

show

Putting it Together

Now, in Conway's Game of Life, we aren't going to be having any squares move. Instead, I will create a square for every grid position and then change the color of that cell to represent if it is dead or alive.

I keep track of each cells state in an array of booleans. I then have another array of booleans to keep track of the next generations state. We need to have two arrays here because as I loop through each cell to determine the number of living neighbors, if I change that cells state, it could affect the output of the cells around it once I get to checking those. To avoid this, I keep track of all of the changes in the nextGenCells array and clone it to the cells array after every cell has been checked.

Below you can see a screenshot of the result as well as the final script itself. If you have Ruby and Ruby 2D set up on your machine, you can try running it yourself!

Image

require "ruby2d"

WIDTH = 600
HEIGHT = 600

SQUARE_SIDE_LENGTH = 10

GRID_WIDTH = WIDTH / SQUARE_SIDE_LENGTH
GRID_HEIGHT = HEIGHT / SQUARE_SIDE_LENGTH

set width: WIDTH, height: HEIGHT, title: "Conway's Game of Life"

cells = []
nextGenCells = []
allSquares = []

for x in 0...GRID_WIDTH do
    for y in 0...GRID_HEIGHT do
        isCellOn = rand(2) == 1
        cells.push(isCellOn)
        nextGenCells.push(isCellOn)

        color = isCellOn ? "white" : "black"

        allSquares.push(Square.new(x: x * SQUARE_SIDE_LENGTH,
            y: y * SQUARE_SIDE_LENGTH,
            size: SQUARE_SIDE_LENGTH,
            color: color))
    end
end

update do
    for i in 0...cells.length do
        x, y = indexToXY(i)
        numberOfOnNeighbours = 0
        for j in (x - 1)..(x + 1) do
            for k in (y - 1)..(y + 1) do
                if j >= 0 && j < WIDTH && k >= 0 && k < HEIGHT
                    if j != x || k != y
                        if cells[(k * GRID_WIDTH) + j]
                            numberOfOnNeighbours += 1
                        end
                    end
                end
            end
        end

        if numberOfOnNeighbours < 2 || numberOfOnNeighbours > 3
            nextGenCells[i] = false
        elsif numberOfOnNeighbours == 3
            nextGenCells[i] = true
        else
            nextGenCells[i] = cells[i]
        end

    end
    
    cells = nextGenCells.clone
    for i in 0..(cells.length - 1) do
        allSquares[i].color = cells[i] ? "white" : "black"
    end
end

def indexToXY(index)
    x = index % GRID_WIDTH
    y = index / GRID_WIDTH
    return [x, y]
end

show

Back to Home