This Ad helps me put food on the table. Thanks for not blocking! You can also show your support using one of my affiliate links.

Welcome back! Or if you landed here for some reason, read the first entry in the series to see what this is all about.

This is the episode where we actually start doing some programming.

In the first episode, we picked Ruby as our first programming language. This was because Ruby is an object-oriented, imperative programming language with an easy to read syntax to make life (ha ha) easy.

First things first

Before we start, we need to do some thinking. Or scaffolding, if you want. We discussed some of the details of Game of Life in the previous post and from this, we can identify the following building blocks:

  • the game itself
  • a two-dimensional grid
  • cells
  • life or death
  • time (iterations, steps)

Translating this into object-oriented programming we will have:

  • Game (object)
  • Grid (object)
  • Cell (object)
  • state (property of Cell object)
  • current iteration (property of Game object)

We also need a folder to keep stuff in, let's create one and call it ruby-life.

Scaffolding

All is well and we can start sketching things out. First up, the Game class. Ruby does not use any semi-colons or {}, instead you use indentation and keywords to define stuff. So let's the Game object in a file called game.rb. When you create a new object type in Ruby, it's going to be called a class so we put that in our file:

class Game

end

That's it for now. Next up then, we'll create Grid and Cell in the same manner:

class Grid

end
class Cell

end

Okay, we identified that the Cell object should have a state (living or dead) and that the Game object should have a current iteration (moment in time), so we're going to modify Grid and Cell to add those.

Refinement

In Ruby, you can use a shorthand to add properties to objects using something called attr_accessor, attr_reader and attr_writer, we'll use one of those to keep things simple. These properties will be available inside our code as variables beginning with @ so the a Game object has an iteration variable inside available to the code as @iteration.

class Game
  attr_accessor :iteration
end
class Cell
  attr_accessor :state
end

So now we have three basic objects defined, we should check these out a bit. When we installed Ruby, we got something called irb in the package. It stands for Interactive Ruby and is just that, a way to play with ruby. We can use this to look at our objects so we're going to do that.

You start irb in your terminal or command prompt by typing irb and pressing enter, but in order to enable it to load our objects we have to pass the argument -I . (you can see the full list of arguments accepted by irb by typing irb -h):

$ irb -I .
irb(main):001:0>

Now we have a Ruby shell! Let's load our objects. This is done with the keyword require:

irb(main):001:0> require 'game'
=> true
irb(main):002:0> require 'grid'
=> true
irb(main):003:0> require 'cell'
=> true
irb(main):004:0>

And now we can create objects of the types we've just defined:

irb(main):004:0> game = Game.new
=> #<Game:0x007fc869a145a8>
irb(main):005:0> grid = Grid.new
=> #<Grid:0x007fc86b039400>
irb(main):006:0> cell = Cell.new
=> #<Cell:0x007fc86a872258>
irb(main):007:0>

And we can use the properties we defined:

irb(main):007:0> game.iteration
=> nil
irb(main):008:0> game.iteration = 1
=> 1
irb(main):009:0> cell.state
=> nil
irb(main):010:0> cell.state = "dead"
=> "dead"
irb(main):011:0> cell.state
=> "dead"
irb(main):012:0>

That should cover the basics, let's make a few more assumptions.

  • The Game object contains a Grid object
  • A Grid object contains a list or matrix of Cell objects and the size of the grid

We should probably add a constructor to initialise our objects too.

Let's modify our files again:

class Game
  attr_accessor :iteration, :grid

  def initialize
    @iteration = 0
    @grid = Grid.new
  end
end

For the Grid, I realised I probably should have added some more, like the width and height of the grid. You know, because it's a grid and should have some limits since we're not Excel (ha... funny).

But seriously, we're going to add these to the Grid and initiate them to something sensible, like 32 (really just a guess that I didn't want to keep to large). Here, I'm also using the mostly awesome method called #nil? that every Ruby object inherits from somewhere deep down the standard library or the core. It will say true if something is nil, empty. Also, I'm using a default assignment in the initialize method so that we can do stuff like Cell.new without the need of passing arguments. Handy!

class Grid
  attr_accessor :cells, :width, :height

  def initialize(args=nil)
    @cells = []
    unless args.nil?
      @width = args[:width].nil? ? 32 : args[:width]
      @height = args[:height].nil? ? 32 : args[:height]
    end
  end
end

Worth noting here also is that the argument args that is available to initialize is a Hash, Ruby have this basic type that is a bit like a list but with named keys. The Hash that we'll expect as an argument to the Grid constructor will look like this:

{
  width: 32,
  height: 32
}

And the contents can then be accessed by using [] and the name of the key we're looking for as a symbol (a string beginning with a colon, :width), like this:

irb(main):001:0> args = { width: 32, height: 32 }
=> {:width=>32, :height=>32}
irb(main):002:0> args[:width]
=> 32
irb(main):003:0>
Notes on methods and notation

You might have noticed how I refer to methods by calling them #method, Ruby uses this notation to point out that this is a method, more precisely an instance method. Instance methods are methods that are available on an instance of an object, in cell = Cell.new the instance is cell which means we can do stuff like cell.nil? since #nil? is an instance method.

There are also class methods which is methods that can be called on a class, most Ruby documentation refers to these with .method to difference them from instance methods. A class method on our Cell class would be something called like cell2 = Cell.dead() or something like that. I guess you could say that .new is a special class method that we call when creating an instance of something.

If you want to check out some real world examples you can have a look at the Ruby documentation for something, like the Time class for instance: http://www.rubydoc.info/stdlib/time/Time

Preparing some actions

Fine! Now we have something to start with. We have a Game object that contains a Grid, and that Grid can hold Cells.

We should take a look at the mechanics of the game:

  • Time moves forward, one step at a time
  • Every Cell on the Grid is modified according to the set of rules

Let's start with sketching out our methods. First, the Game class should have a way to run the game, I'm going to add a #start method:

class Game
  attr_accessor :iteration, :grid

  def initialize
    @iteration = 0
    @grid = Grid.new
  end

  def start
    # Run the game
  end
end

For now, it only has a comment and doesn't do anything. So, what's next? The cell should have a method to modify it for the next step, we'll add a #step method to it, while we're there I'm adding a constructor method too for good measure. The Grid should also have a method to process all cells according to the rules so we'll add a #step method there too:

class Cell
  attr_accessor :state

  def initialize(state=:dead)
    @state = state
  end

  # Set a new state for the cell
  def step(newstate)
    @state = newstate
  end
end
class Grid
  attr_accessor :cells, :width, :height

  def initialize(args=nil)
    @cells = []
    unless args.nil?
      @width = args[:width].nil? ? 32 : args[:width]
      @height = args[:height].nil? ? 32 : args[:height]
    end
  end

  def step
    # Process all cells to find the next state, then move forward.
  end

end

Good! The Cell class is probably done now. Shouldn't need much more. So, maybe the next step should be to build that #step method in Grid? Sounds good to me.

So what do we need it to do? We need to implement the rules we identified earlier on, then apply these to each cell to find the next state of each cell and finally apply this to the cells in the grid. We should probably think about the case where a cell is at the edge of the grid too, this will be an edge case (no pun intended) since there will be missing neighbours in comparison to cells in the middle of the grid.

Here goes.

  • Each cell has eight neighbours
    • except when the cell is in the first or last row or column
x-1,y-1 x,y-1 x+1,y-1
x-1,y Cell x+1,y
x-1,y+1 x,y+1 x+1,y+1

It's starting to look like we have to have some other way to store these cells in the Grid object, a simple array isn't quite satisfactory when we're thinking about x and y coordinates. Since Ruby arrays can store anything, we can modify the Cell class to create an initial storage that better suits what we're trying to do. Ruby has an awesome thing called .. we can use to make our @cells variable into a grid. We can try it out in irb:

irb(main):001:0> @cells = []
=> []
irb(main):002:0> (1..32).each do |y|
irb(main):003:1* @cells[y-1]=[]
irb(main):004:1> end
=> 1..32
irb(main):005:0> @cells
=> [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
irb(main):006:0>

Awesome! We'll use this in the constructor method for Cell and then we have a grid instead of an array to store our cells in. While there, we'll initiate each cell in the grid to a new Cell object. I'm also going to initiate each cell to a random value by passing an argument to the constructor in Cell that we prepared earlier:

class Grid
  attr_accessor :cells, :width, :height

  def initialize(args=nil)
    @cells = []
    @width = 32
    @height = 32
    unless args.nil?
      @width = args[:width] unless args[:width].nil?
      @height = args[:height] unless args[:height].nil?
    end

    (1..@height).each do |y|
      @cells[y-1] = []
      (1..@width).each do |x|
        @cells[y-1][x-1] = Cell.new [:dead, :living][(rand*2).to_i]
      end
    end
  end

  def step
    # Process all cells to find the next state, then move forward.
  end

end

So now we have working Cell objects, and a Grid object initialised with Cell objects that have a random state from the beginning. Nice! That means we can go back to what we were actually doing, building the #step method in Grid.

Since we now have a proper grid inside our Grid class, we can actually access the contents of it using calls like @cells[y-1][x-1], at least for cells that doesn't fall into our edge case. So what about the edge case? We have to decide on how to handle it. I'm going to say that we have two options:

  1. Just skip the cells that are missing
  2. Wrap around and fetch cells from the opposite side of the grid

To keep things simple, we're going to go with option 2. This is because Ruby allows for negative indices, meaning that array[-1] returns the last element in the array. Sweet! This means we don't need any special logic to count indices or anything, we'll just go and get the cells we need and Ruby will make sure we're not going outside our arrays.

First, we'll make a private helper function called #neighbours that returns an array with the neighbours of a cell, the Grid class now looks like this:

require 'cell'

class Grid
  attr_accessor :cells, :width, :height

  def initialize(args=nil)
    @cells = []
    @width = 32
    @height = 32
    unless args.nil?
      @width = args[:width] unless args[:width].nil?
      @height = args[:height] unless args[:height].nil?
    end

    (1..@height).each do |y|
      @cells[y-1] = []
      (1..@width).each do |x|
        @cells[y-1][x-1] = Cell.new [:dead, :living][(rand*2).to_i]
      end
    end
  end

  def step
    # Process all cells to find the next state, then move forward.
  end

  private

  def neighbours(x,y)
    neighbours = []

    (y-1..y+1).each do |y1|
      (x-1..x+1).each do |x1|
        neighbours << @cells[y1][x1]
      end
    end

    neighbours
  end
end

In Ruby, you don't have to specifically return something from a method, whatever was the last result of an operation will be returned. Another handy thing is the << operator which means push the rightmost thing to the end of the array on the left. We should try this out in irb to see that it works. Start by temporarily removing the line that says private, then load everything into irb as we did before:

irb(main):001:0> require 'cell'
=> true
irb(main):002:0> require 'grid'
=> true
irb(main):003:0> grid = Grid.new
=> #<Grid:0x007fa0d3a4e9f0 @cells=[[#<Cell:0x007fa0d3a4e8d8 @state=:dead>,...
irb(main):004:0> grid.neighbours 0,0
=> [#<Cell:0x007fa0d3a4e888 @state=:dead>, #<Cell:0x007fa0d3a4de88 @state=:living>, #<Cell:0x007fa0d3a4de38 @state=:living>]
irb(main):005:0> grid.neighbours 10,31
=> [#<Cell:0x007fa0d3975448 @state=:living>, #<Cell:0x007fa0d3975358 @state=:living>, #<Cell:0x007fa0d39767f8 @state=:living>, #<Cell:0x007fa0d39768c0 @state=:dead>, #<Cell:0x007fa0d3976780 @state=:living>]
irb(main):006:0> grid.neighbours 21,22
=> [#<Cell:0x007fa0d502b0e8 @state=:dead>, #<Cell:0x007fa0d502aff8 @state=:living>, #<Cell:0x007fa0d39cc3b0 @state=:living>, #<Cell:0x007fa0d5029e78 @state=:living>, #<Cell:0x007fa0d39cc478 @state=:living>, #<Cell:0x007fa0d39cc338 @state=:living>, #<Cell:0x007fa0d5029ec8 @state=:dead>, #<Cell:0x007fa0d5029db0 @state=:living>]
irb(main):007:0>

Seems to be working! Put back the line with private again, we don't need to export this method. In Ruby, anything defined below the private keyword is made private. This means you cannot run methods placed here anywhere else than inside the class. Good place to keep helpers and internal stuff!

I'm also going to add something to make sure requirements are available where they need to be. We've been doing this ourselves in irb this far, the require keyword. In Grid, I'm adding require 'cell' at the top, this way the Cell class is imported each time the Grid class is. Also, I'm adding require 'grid' at the top of Game, since the Game object has a Grid object.

So, back to the problem, now we can figure out the next state of the grid since we can find all neighbours of a cell in the grid. To make it easy for us, let's iterate through all cells in the grid to calculate the next state. Also, we cannot apply the next state to the objects in the grid, this would make the calculations of the state for one cell wrong since part of the neighbouring cells already have a new state. We'll have to keep a local copy of the next state while we do the calculations, then apply them to all cells.

We can make a new local grid and only store the new states in it, then we iterate again and apply the new states to the objects in the grid.

Processing the rules of life

The rules are only concerned with living neighbours so we can limit our logic to check for this. In Ruby, we can use something called #map on the array of neighbours we get from our #neighbours method and then #reject to remove all dead cells to filter out only cells with the state :living:

def step
  states = []
  (1..@height).each do |y|
    states[y-1] = []
    (1..@width).each do |x|
      current_state = cells[y-1][x-1].state
      states[y-1][x-1] = current_state
      n = neighbours(x-1, y-1)
      n_states = n.map{ |cell| cell.state }
      alive = n_states.reject{ |state| state == :dead }
      living_neighbours = alive.count
      next_state = Cell.next_state_based_on current_state, living_neighbours
    end
  end
end

This is probably a good time to mention something about that these two (#map and #reject) take a block as argument, basically this block is an anonymous method that will be applied to each element in the array, returning a modified array. It's pretty much the same as what we were already doing with our #each but we used a slightly different syntax:

# this syntax:
array.each do |element|
  do_stuff(element)
end

# means the same as
array.each{ |element| do_stuff(element) }

Oh, and I made a helper to keep the #step method small, all the business logic is performed in a method in Cell called .next_state_based_on instead that looks like this:

def self.next_state_based_on(current_state, living_neighbours)
  next_state = nil

  if current_state == :living
    if living_neighbours < 2 # Rule number 1
      next_state = :dead
    elsif living_neighbours == 2 or living_neighbours == 3 # Rule number 2
      next_state = :living
    elsif living_neighbours > 3 # Rule number 3
      next_state = :dead
    end
  elsif current_state == :dead
    if living_neighbours == 3 # Rule number 4
      next_state = :living
    else
      next_state = :dead
    end
  end

  next_state
end

Going over this, we can make it a little simpler by noticing that there are ony two cases when the next state will be :living. I'm also going to use a case statement, since that's usually a bit faster than an if ... else statement, at least in some other programming languages. Problably doesn't make much difference but I'm using it anyway. So now the method looks like this:

def self.next_state_based_on(current_state, living_neighbours)
  next_state = :dead

  case living_neighbours
  when 2
    next_state = :living if current_state == :living
  when 3
    next_state = :living
  end

  next_state
end

Much simpler! Also, I made it a class method by adding self. to the method name. This way, it's called from the Cell class instead of on an object instance: Cell.next_state_based_on(current_state,living_neighbours).

So now, we have two new methods in Cell and Grid, the complete files now look like this:

class Cell
  attr_accessor :state

  def initialize(state=:dead)
    @state = state
  end

  # Set a new state for the cell
  def step(newstate)
    @state = newstate
  end

  def self.next_state_based_on(current_state, living_neighbours)
    next_state = :dead

    case living_neighbours
    when 2
      next_state = :living if current_state == :living
    when 3
      next_state = :living
    end

    next_state
  end
end
require 'cell'

class Grid
  attr_accessor :cells, :width, :height

  def initialize(args=nil)
    @cells = []
    @width = 32
    @height = 32
    unless args.nil?
      @width = args[:width] unless args[:width].nil?
      @height = args[:height] unless args[:height].nil?
    end

    (1..@height).each do |y|
      @cells[y-1] = []
      (1..@width).each do |x|
        @cells[y-1][x-1] = Cell.new [:dead, :living][(rand*2).to_i]
      end
    end
  end

  def step
    states = []
    # Calculate next state for all cells in the grid
    (1..@height).each do |y|
      states[y-1] = []
      (1..@width).each do |x|
        current_state = cells[y-1][x-1].state
        states[y-1][x-1] = current_state
        n = neighbours(x-1, y-1)
        n_states = n.map{ |cell| cell.state }
        alive = n_states.reject{ |state| state == :dead }
        living_neighbours = alive.count
        next_state = Cell.next_state_based_on current_state, living_neighbours

        states[y-1][x-1] = next_state
      end
    end
    # Apply the new state to each cell in the grid
    (1..@height).each do |y|
      (1..@width).each do |x|
        @cells[y-1][x-1].state = states[y-1][x-1]
      end
    end
  end

  private

  def neighbours(x,y)
    neighbours = []

    (y-1..y+1).each do |y1|
      yc = (y1 == @cells.count) ? 0 : y1
      (x-1..x+1).each do |x1|
        xc = (x1 == @cells[yc].count) ? 0 : x1
        neighbours << @cells[yc][xc]
      end
    end

    neighbours
  end
end

That should do it for stepping the forward in time. All we need now is two things:

  • Run the game by stepping forward, a given number of times
  • Print the grid to let us see the cells in the grid

Printing sounds more fun to me so we're going to start with that.

Printing the grid

To keep things simple, we're just going to output the grid in our terminal, printing a space for dead cells and an asterisk (*) for living cells. We will have to loop through the grid again to get the cells some way but it should be enough to get the rows and then somehow print each row.

For good measure, I also added a #to_s method to Grid, well, just because. So this is what we're adding:

def print
  puts to_s
end

def to_s
  str = ""
  @cells.each do |row|
    str += row.map{ |cell|
      case cell.state
      when :living then '*'
      when :dead then ' '
      end
    }.join("") + "\n"
  end
  str
end

Trying it out:

irb(main):001:0> require 'grid'
=> true
irb(main):002:0> grid = Grid.new({width: 8, height: 8})
=> #<Grid:0x007fabf1300f30 @cells=[[#<Cell:0x007fabf1300e40 @state=:living>, ...]], @width=8, @height=8>
irb(main):003:0> grid.print
***  **
 ** ** *
* * ** *
***    *
*  * * *
*    ***
**   **
***  *  
=> nil
irb(main):004:0> grid.step
=> 1..8
irb(main):005:0> grid.print



     *  
  ****  

  * *
    *
=> nil
irb(main):006:0>

Looks nice! Now for the final part then, the #start method in the Game class!

This should be a piece of cake by now, we've done quite a lot of iterating and such already. First of all, we should decide on a number of iterations, to make sure we won't loop indefinitely. Let's say 200. Now we can build the #start method to perform #print and #step on the grid for a given number of iterations. Adding this, our Game class now looks like this:

require 'grid'

class Game
  attr_accessor :iteration, :grid

  def initialize
    @iteration = 0
    @grid = Grid.new
  end

  def start(iterations=200)
    while @iteration < iterations
      grid.print
      grid.step
      puts "-" * @grid.width
      @iteration += 1
    end
  end
end

Great! I just want to do one more thing, I want to add a shell command that runs the game. First, we need to create a new file, let's call it gameoflife (no extension), then make it executable:

$ touch gameoflife
$ chmod +x gameoflife

Just a quick disclaimer though, this won't run on Windows (Windows only runs .cmd or .bat files that require their own language that ain't Ruby).

Let's add some of what we've done in irb to it:

#!/usr/bin/env ruby
$LOAD_PATH.unshift(File.expand_path('..', __FILE__))

require 'game'

game = Game.new

unless ARGV.empty?
  game.start ARGV.first.to_i
else
  game.start
end

And it works! That ARGV variable is special, it holds an array with any arguments passed to the script. If it's empty, we won't use it, otherwise we'll just take the first element and assume it holds a number that is the number of iterations to run.

The first line performs the same thing as what we did when we passed -I to irb, it adds the folder to the array of paths where Ruby will look for files. Without it, we cannot require 'game'. The part in it with File.expand_path is a way to get the full file path to the folder containing the file we are running, denoted by the special variable __FILE__.

Now we have something we can run pretty much like any other application:

./gameoflife 100

Recap

That's it then, Ruby! I think we covered some ground:

  • Attribute accessors (attr_accessor)
  • Instance and class methods
  • Instance and local variables (@var and var)
  • Ranges (1..@width.count)
  • Blocks (array.map{ |element| do_stuff(element) })
  • Case blocks, if statements and while clauses

To be honest, I know Ruby already but I wanted to start with something that was familiar to me. This implementation is admittedly very rough and we never got in to some of the most awesome things about Ruby, most notably the awesome community support, Rails and gems. You can read more about Ruby yourself at the following resources if you want to get into it:

For good measure, I've also made a gist with the full implementation: https://gist.github.com/janlindblom/511648cf8cc58c367f34

And you can download the source code as a zip from here: https://bitbucket.org/janlindblom/gameoflife/get/ruby_finished.zip (the full repository is available here: https://bitbucket.org/janlindblom/gameoflife/src).

Next up

Ok, so that was Ruby. I'm going to pick a new programming language to do next. This time, I think I'm going to choose something I haven't worked with before.

There is one that I've been curious about so I'm going to pick that one: Elixir!