How to resolve the algorithm Conway's Game of Life step by step in the Ruby programming language

Published on 12 May 2024 09:40 PM

How to resolve the algorithm Conway's Game of Life step by step in the Ruby programming language

Table of Contents

Problem Statement

The Game of Life is a   cellular automaton   devised by the British mathematician   John Horton Conway   in 1970.   It is the best-known example of a cellular automaton. Conway's game of life is described   here: A cell   C   is represented by a   1   when alive,   or   0   when dead,   in an   m-by-m   (or m×m)   square array of cells. We calculate   N   - the sum of live cells in C's   eight-location neighbourhood,   then cell   C   is alive or dead in the next generation based on the following table: Assume cells beyond the boundary are always dead. The "game" is actually a zero-player game, meaning that its evolution is determined by its initial state, needing no input from human players.   One interacts with the Game of Life by creating an initial configuration and observing how it evolves.

Although you should test your implementation on more complex examples such as the   glider   in a larger universe,   show the action of the blinker   (three adjoining cells in a row all alive),   over three generations, in a 3 by 3 grid.

Let's start with the solution:

Step by Step solution about How to resolve the algorithm Conway's Game of Life step by step in the Ruby programming language

This Ruby code provides an implementation of John Conway's Game of Life, a cellular automation simulation. It defines two classes, Game and GameBoard, to model the game and its board, respectively. The Cell class represents individual cells on the board.

  1. game_of_life Function:

    • Defines a method to run the Game of Life simulation.
    • Takes parameters such as name (board name), size (board dimensions), generations (number of simulation steps), and initial_life (initial seed points).
  2. new_board Function:

    • Generates a new rectangular board of zeros with specified dimensions.
  3. seed Function:

    • Randomly or manually seeds the board with living cells (value set to 1).
  4. evolve Function:

    • Evolves the board according to the Game of Life rules.
    • Iterates over each cell and calculates its fate based on its neighbors.
  5. fate Function:

    • Determines the fate of a cell based on the number of living neighbors.
    • A cell becomes alive if it has 3 living neighbors or if it's currently alive and has 2 living neighbors.
  6. barren? Function:

    • Checks if the board has any living cells.
  7. print_board Function:

    • Prints the current state of the board, showing living cells as '#' and dead cells as '.'.
  8. Game Class:

    • Encapsulates the game logic and board state.
    • Initializes with parameters similar to the game_of_life function.
  9. evolve Method (in Game Class:

    • Evolves the board and returns a new GameBoard instance.
  10. cell_fate Method (in Game Class:

  • Calculates the fate of a cell based on its neighbors.
  1. GameBoard Class:

    • Represents the game board.
    • Contains an array of Cell objects.
  2. seed_board Method (in GameBoard Class:

    • Seeds the board with living cells.
  3. each_index Method (in GameBoard Class:

    • Iterates over the indices of the board.
  4. == Method (in GameBoard Class:

    • Compares two GameBoard instances for equality based on their living cells.
  5. barren? Method (in GameBoard Class:

    • Checks if the board has any living cells.
  6. life Method (in GameBoard Class:

    • Returns an array of the coordinates of living cells.
  7. display Method (in GameBoard Class:

    • Prints the board state, similar to the print_board function.
  8. apocalypse Method (in GameBoard Class:

    • Clears the board by setting all cells to dead.
  9. Cell Class:

    • Represents an individual cell on the board.
    • Has properties like alive? and value (0 for dead, 1 for alive).

The code provides two ways to play the game: the original game_of_life function and the object-oriented approach using the Game class. It supports different board configurations, including "blinker," "glider," and "random." The program simulates the game for a specified number of generations or until the board becomes static or barren.

Source code in the ruby programming language

def game_of_life(name, size, generations, initial_life=nil)
  board = new_board size
  seed board, size, initial_life
  print_board board, name, 0
  reason = generations.times do |gen|
    new = evolve board, size
    print_board new, name, gen+1
    break :all_dead if barren? new, size
    break :static   if board == new
    board = new
  end
  if    reason == :all_dead then puts "no more life."
  elsif reason == :static   then puts "no movement"
  else                           puts "specified lifetime ended"
  end
  puts
end

def new_board(n)
  Array.new(n) {Array.new(n, 0)}
end

def seed(board, n, points=nil)
  if points.nil?
    # randomly seed board
    indices = []
    n.times {|x| n.times {|y| indices << [x,y] }}
    indices.shuffle[0,10].each {|x,y| board[y][x] = 1}
  else
    points.each {|x, y| board[y][x] = 1}
  end
end

def evolve(board, n)
  new = new_board n
  n.times {|i| n.times {|j| new[i][j] = fate board, i, j, n}}
  new
end

def fate(board, i, j, n)
  i1 = [0, i-1].max; i2 = [i+1, n-1].min
  j1 = [0, j-1].max; j2 = [j+1, n-1].min
  sum = 0
  for ii in (i1..i2)
    for jj in (j1..j2)
      sum += board[ii][jj] if not (ii == i and jj == j)
    end
  end
  (sum == 3 or (sum == 2 and board[i][j] == 1)) ? 1 : 0
end

def barren?(board, n)
  n.times {|i| n.times {|j| return false if board[i][j] == 1}}
  true
end

def print_board(m, name, generation)
  puts "#{name}: generation #{generation}"
  m.each {|row| row.each {|val| print "#{val == 1 ? '#' : '.'} "}; puts}
end

game_of_life "blinker", 3, 2, [[1,0],[1,1],[1,2]]
game_of_life "glider", 4, 4, [[1,0],[2,1],[0,2],[1,2],[2,2]]
game_of_life "random", 5, 10

class Game
  def initialize(name, size, generations, initial_life=nil)
    @size = size
    @board = GameBoard.new size, initial_life
    @board.display name, 0
    
    reason = generations.times do |gen|
      new_board = evolve
      new_board.display name, gen+1
      break :all_dead if new_board.barren?
      break :static   if @board == new_board
      @board = new_board
    end
    
    case reason
    when :all_dead  then puts "No more life."
    when :static    then puts "No movement."
    else                 puts "Specified lifetime ended."
    end
    puts
  end
  
  def evolve
    life = @board.each_index.select {|i,j| cell_fate(i,j)}
    GameBoard.new @size, life
  end
  
  def cell_fate(i, j)
    left_right = [0, i-1].max .. [i+1, @size-1].min
    top_bottom = [0, j-1].max .. [j+1, @size-1].min
    sum = 0
    for x in left_right
      for y in top_bottom
        sum += @board[x,y].value if x != i or y != j
      end
    end
    sum == 3 or (sum == 2 and @board[i,j].alive?)
  end
end

class GameBoard
  include Enumerable
  
  def initialize(size, initial_life=nil)
    @size = size
    @board = Array.new(size) {Array.new(size) {Cell.new false}}
    seed_board initial_life
  end
  
  def seed_board(life)
    if life.nil?
      # randomly seed board
      each_index.to_a.sample(10).each {|x,y| @board[y][x].live}
    else
      life.each {|x,y| @board[y][x].live}
    end
  end
  
  def each
    @size.times {|x| @size.times {|y| yield @board[y][x] }}
  end
  
  def each_index
    return to_enum(__method__) unless block_given?
    @size.times {|x| @size.times {|y| yield x,y }}
  end
  
  def [](x, y)
    @board[y][x]
  end
  
  def ==(board)
    self.life == board.life
  end
  
  def barren?
    none? {|cell| cell.alive?}
  end
  
  def life
    each_index.select {|x,y| @board[y][x].alive?}
  end
  
  def display(name, generation)
    puts "#{name}: generation #{generation}"
    puts @board.map {|row| row.map {|cell| cell.alive? ? '#' : '.'}.join(' ')}
  end
  
  def apocalypse
    # utility function to entirely clear the game board
    each {|cell| cell.die}
  end
end

class Cell
  def initialize(alive) @alive = alive  end
  def alive?;           @alive          end
  def value;            @alive ? 1 : 0  end
  def live;             @alive = true   end
  def die;              @alive = false  end
end

Game.new "blinker", 3, 2, [[1,0],[1,1],[1,2]]
Game.new "glider", 4, 4, [[1,0],[2,1],[0,2],[1,2],[2,2]]
Game.new "random", 5, 10

  

You may also check:How to resolve the algorithm Balanced brackets step by step in the Seed7 programming language
You may also check:How to resolve the algorithm CUSIP step by step in the Factor programming language
You may also check:How to resolve the algorithm Perfect numbers step by step in the COBOL programming language
You may also check:How to resolve the algorithm Hamming numbers step by step in the jq programming language
You may also check:How to resolve the algorithm Pangram checker step by step in the Arturo programming language