Skip to content

Instantly share code, notes, and snippets.

/README.txt Secret

Created July 1, 2010 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/967c2235c85169f9673d to your computer and use it in GitHub Desktop.
Save anonymous/967c2235c85169f9673d to your computer and use it in GitHub Desktop.
## Ruby Programming Challenge: Game of Life ##
You can read in http://www.math.com/students/wonders/life/life.html about the Game of Life.
Yesterday I posted my first solution to this challenge (https://gist.github.com/f787d117565ed529c8a5)
in wich I didn't take into account a folded board, I didn't actually know how to do that, but after
some reading on this topic I discover that there are actually two main models to this game:
* torus model in which all boundaries have cyclic boundary conditions that wrap the universe back on itself as in a torus.
* box model in which each edges does not contribute any count to the living cell.
Those are the basic models, some variations do exists but this implementation of the game doesn't take them into account.
The important thing to note about this game is that for every cell you have to take into account just the 8 neighborhoods
of it, so, this implementation of the game creates a board that adds 2 more rows and 2 more columns to the initial board.
Example:
Initial seed visual board folded board
------------ ------------ ------------
0 0 0 0 0
* . . 1 0 0 0 1 0 0 1
* * . 1 1 0 0 1 1 0 1
. . . 0 0 0 0 0 0 0 0
0 1 0 0 1
The way going from the visual board to the folded board is as follows:
* copy on the first row of the folded board the last row of the visual board
* copy on the last row of the folded board the first row of the visual board
* copy on the first column of the folded board the last column of the visual board
* copy on the last column of the folded board the first column of the visual board
* copy on the north-left corner of the folded board the south-east corner of the visual board
* copy on the north-right corner of the folded board the south-west corner of the visual board
* copy on the south-west corner of the folded board the north-east corner of the visual board
* copy on the south-east corner of the folded board the north-west corner of the visual board
In this manner, the game is fully specified because for every cell in the visual board (effectively the work board)
there are 8-neighborhoods in the folded board, so we can work our way through the game easily, everytime life evolves
the folded board is updated and the neighborhoods are calculated from there, in this manner we can simulate the cyclic
boundaries condition of the torus model.
Anyway, this implementation gives you the possibility of choosing your model (one of box or torus), the image above
explain how the folded board is constructed in the case of a torus model, for a box model this is not required and in
the implementation I just put 0s in the respective positions of the folded board.
Files of this implementation:
README => This file you are reading
game_of_life.rb => This is the implementation of the game of life, is where we construct our boar, calculate
neighborhoods and evolve.
play_game.rb => This is a simple class to play the game, it asks all the data needed (rows, columns, initial seed and model)
and starts a new game in the console.
seed_data.rb => This is a module that PlayGame includes to read seed data from a file.
You can find seed data in http://www.radicaleye.com/lifepage/glossary.html
game_of_life_script => This puts the game in motion
Thanks to rubylearning.com for giving this to the community, had a lot of fun doing this, so, keep up the good work.
class GameOfLife
attr_reader :rows, :columns, :model
def initialize(args)
w = args[:rows] || 5
h = args[:columns] || 5
s = args[:seed] || random_seed(w, h)
m = args[:model] || :torus
@rows, @columns = w + 2, h + 2
@model = m
@board = new_board
s.each { |first, second| @board[first + 1][second + 1] = true }
end
def evolve
board = new_board
fold_board if @model == :torus
@rows.times do |row|
next if row == 0 or row == @rows - 1
@columns.times do |col|
next if col == 0 or col == @columns - 1
neighbours = neighbours_number(row, col)
board[row][col] =
if ((neighbours < 2 || neighbours > 3) and (@board[row][col] == true))
false
elsif ((neighbours == 3) and (@board[row][col] == false))
true
else
@board[row][col]
end
end
end
@board = board
end
def to_s
result = ''
@board[1..-2].each do |row|
row[1..-2].each do |value|
result << (value ? ' #' : ' .')
end
result << "\n"
end
result
end
private
def new_board
Array.new(@rows) { Array.new(@columns, false) }
end
def random_seed(rows, columns)
seed = []
rows.times do |i|
columns.times do |j|
seed << [i, j]
end
end
seed.shuffle[0, rand(rows * columns)]
end
def neighbours_number(row, col)
count = 0
Range.new(row - 1, row + 1).each do |i|
Range.new(col - 1, col + 1).each do |j|
count = count.succ if ((i != row || j != col) and (@board[i][j] == true))
end
end
count
end
def fold_board
@rows.times do |row|
next if row == 0 or row == @rows - 1
@board[row][0] = @board[row][-2]
@board[row][-1] = @board[row][1]
end
@columns.times do |col|
next if col == 0 or col == @columns - 1
@board[0][col] = @board[-2][col]
@board[-1][col] = @board[1][col]
end
@board[0][0] = @board[-2][-2]
@board[0][-1] = @board[-2][1]
@board[-1][0] = @board[1][-2]
@board[-1][-1] = @board[1][1]
end
end
require File.join(File.dirname(__FILE__), 'play_game')
game = PlayGame.new
game.play
require File.join(File.dirname(__FILE__), 'seed_data')
require File.join(File.dirname(__FILE__), 'game_of_life')
class Array
def second; self[1]; end
def third; self[2]; end
end
class PlayGame
include SeedData
def initialize
model = ask_model
seed = ask_seed
@game = GameOfLife.new(:rows => seed.first,
:columns => seed.second, :seed => seed.third, :model => model)
end
def play
print_title
puts @game
iterations = ask_number('Enter number of iterations: ')
iterations.times do |iteration|
clear_screen
@game.evolve
print_title(iteration.succ)
puts @game
sleep 1
end
end
private
def print_title(iteration = 0)
if iteration == 0
puts "Game of Life: (Initial Seed)(#{@game.rows - 2}x#{@game.columns - 2} board)"\
"(#{@game.model} model)"
else
puts "Game of Life: (Generation #{iteration})"\
"(#{@game.rows - 2}x#{@game.columns - 2} board)(#{@game.model} model)"
end
end
def ask(question)
print question
$stdout.flush
answer = gets
exit if answer == nil
answer.chomp
end
def ask_number(question)
answer = ask(question)
Integer(answer)
rescue
puts "#{answer} is not a valid integer value"
ask_number(question)
end
def ask_seed
answer = ask('Enter seed (filename or random for random seed): ')
if File.file?(answer)
seed = SeedData.read(answer)
[seed.shift, seed.shift, seed]
elsif answer =~ /^random/
w = ask_number('Enter number of rows: ')
h = ask_number('Enter number of columns: ')
[w, h, nil]
else
puts "#{answer} is not a valid seed"
ask_seed
end
end
def ask_model
answer = ask('Enter model name (box or torus): ').to_sym
if not [:box, :torus].include?(answer)
puts "#{answer} is not a valid model"
ask_model
else
answer
end
end
def clear_screen
if (RUBY_PLATFORM =~ /win/)
system('cls')
else
system('clear')
end
end
end
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . # # # # # # # # # # # # # # # # # . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . # . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
....**......**....
...*.*......*.*...
...*..........*...
**.*..........*.**
**.*.*..**..*.*.**
...*.*.*..*.*.*...
...*.*.*..*.*.*...
**.*.*..**..*.*.**
**.*..........*.**
...*..........*...
...*.*......*.*...
....**......**....
module SeedData
class << self
def read(file_name)
raise "#{file_name} is not a file" unless File.file?(file_name)
raise "#{file_name} is not readable" unless File.readable?(file_name)
lines = IO.readlines(file_name)
lines = lines.reject { |line| line =~ /^\s+$/ }
lines.each {|line| line.gsub!(/\s+/, '')}
rows = lines.size
columns = lines[0].size
result = [rows, columns]
rows.times do |i|
columns.times do |j|
result << [i, j] if lines[i][j] == '#' or lines[i][j] == '*'
end
end
result
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment