Andreas Launila
8/12/2007 2:24:00 PM
Ruby Quiz wrote:
> This week's Ruby Quiz is to write a program that accepts up to three parameters:
> the rule as an integer in decimal, the number of steps to simulate, and the
> starting state of the cells as a String of ones and zeros.
Slightly different/renamed options and no graphics:
ruby cellular_automaton.rb -r 41 -s 20 -w 30 100100
X X
XXXXXXXXXXXXXXXXXXXXXXX X
X XXXX
XXXXXXXXXXXXXXXXXXXXX X X
X X X XX
X XXXXXXXXXXXXXXXXXX X X
X XX
XXX XXXXXXXXXXXXXXXX X XXXX
X X X X X
X X XXXXXXXXXXXXX X XX
X X X X X
XXXXX XXXXXXXXXXX X X
X X X X X X
X XXX X XXXXXXXXX XXXX
X X X X X
XXX XXXXX XXXXXXX X XX
X X X X X X X
X X XXX X XXXXXXXXX
X X X XXXX
XXXXX XXXXX XXXXXXX X
X X X X X X X XX
== Code
#!/usr/bin/env ruby
# == Usage
#
# cellular_automaton [OPTIONS] CELLS
#
# -h, --help:
# show help
#
# -r, --rule RULE:
# specified the rule to use as a decimal integer, defaults to 30
#
# -s, --steps STEPS:
# specifies the number of steps that should be shown, defaults to 20
#
# -w, --width WIDTH:
# specifies the number of cells that should be shown per step,
# defaults to 20
#
# CELLS: The initial cell state that should be used. Must be given as a
# string of 0 and 1.
require 'getoptlong'
require 'rdoc/usage'
require 'enumerator'
# Describes a state in a cellular automaton.
class CellularAutomatonState
attr :cells
private
# All the possible neighbourhoods of size 3.
NEIGHBOURHOODS = [[true, true, true], [true, true, false],
[true, false, true], [true, false, false], [false, true, true],
[false, true, false], [false, false, true], [false, false, false]]
public
# Creates a new state using the specified +rule+ given in decimal.
# +inital_state+ holds an array of booleans describing the initial
# state.
def initialize(rule, initial_state)
@cells = initial_state
# Decode the rule into a hash map. The map is then used when
# computing the next state.
booleans = rule.to_s(2).rjust(
NEIGHBOURHOODS.size, '0').split(//).map{ |x| x == '1' }
if booleans.size > NEIGHBOURHOODS.size
raise ArgumentError, 'The rule is too large.'
end
@rules = {}
NEIGHBOURHOODS.each_with_index do |neighbourhood, i|
@rules[neighbourhood] = booleans[i]
end
end
# Updates the automaton one step.
def step!
@new_cells = []
# Regard the endings as false.
([false] + @cells + [false]).each_cons(3) do |neighbourhood|
@new_cells << @rules[neighbourhood]
end
@cells = @new_cells
end
def to_s
@cells.map{ |x| x ? 'X' : ' ' }.join
end
end
# Defaults
rule = 30
steps = 20
width = 20
# Options
opts = GetoptLong.new(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--rule', '-r', GetoptLong::REQUIRED_ARGUMENT],
['--steps', '-s', GetoptLong::REQUIRED_ARGUMENT],
['--width', '-w', GetoptLong::REQUIRED_ARGUMENT])
opts.each do |opt, arg|
case opt
when '--help': RDoc::usage
when '--rule': rule = arg.to_i
when '--steps': steps = arg.to_i
when '--width': width = arg.to_i
end
end
if ARGV.size != 1
abort "Incorrect usage, see --help"
end
# Turn the provided state into an array of booleans, pad if needed.
cells = ARGV.shift.rjust(width,'0').split(//).map!{ |cell| cell == '1' }
# Create the initial state and then step the desired number of times.
state = CellularAutomatonState.new(rule, cells)
puts state.to_s
steps.times do
state.step!
puts state.to_s
end
__END__
--
Andreas Launila