Here's my solution. I only provide text console output, which is
pretty effective when you pause a little bit between "frames".
Glancing over some of the other solutions, the one thing I may have
done differently is pre-compute the two grid overlays.
Eric
----
Are you interested in on-site Ruby training that's been highly
reviewed by former students? http://LearnRuby.com
====
class SimFrost
# A Cell keeps track of its contents. It is essentially a mutable
# Symbol with some extra knowledge to convert into a string.
class Cell
attr_accessor :contents
@@strings = { :space => ' ', :ice => '*', :vapor => '-' }
def initialize(contents)
@contents = contents
end
def to_s
@@strings[@contents]
end
end # class SimFrost::Cell
# A Grid overlays the space dividing it up into 2-by-2 Boxes.
# Different Grids can cover the same space if the offsets are
# different.
class Grid
# A Box is a 2-by-2 slice of the space containing 4 cells, and a
# Grid contains a set of Boxes that cover the entire space.
class Box
def initialize
@cells = []
end
# Appends a cell to this box
def <<(cell)
@cells << cell
end
# Adjust the cell contents by the following rules: if any cell
# contains Ice then all vapor in the Box will be transformed to
# ice. Otherwise rotate the four cells clockwise or
# counter-clockwise with a 50/50 chance.
def tick
if @cells.any? { |cell| cell.contents == :ice }
@cells.each do
|cell| cell.contents = :ice if cell.contents == :vapor
end
else
if rand(2) == 0 # rotate counter-clockwise
@cells[0].contents, @cells[1].contents,
@cells[2].contents, @cells[3].contents =
@cells[1].contents, @cells[3].contents,
@cells[0].contents, @cells[2].contents
else # rotate clockwise
@cells[0].contents, @cells[1].contents,
@cells[2].contents, @cells[3].contents =
@cells[2].contents, @cells[0].contents,
@cells[3].contents, @cells[1].contents
end
end
end
end # class SimFrost::Grid::Box
# Creates a Grid over the space provided with the given offset.
# Offset should be either 0 or 1.
def initialize(space, offset)
@boxes = []
rows = space.size
cols = space[0].size
# move across the space Box by Box
(rows / 2).times do |row0|
(cols / 2).times do |col0|
# create a Box and add it to the list
box = Box.new
@boxes << box
# add the four neighboring Cells to the Box
(0..1).each do |row1|
(0..1).each do |col1|
# compute the indexes and wrap around at the far edges
row_index = (2*row0 + row1 + offset) % rows
col_index = (2*col0 + col1 + offset) % cols
# add the indexed Cell to the Box
box << space[row_index][col_index]
end
end
end
end
end
# Tick each box in this Grid.
def tick()
@boxes.each { |box| box.tick }
end
end # class SimFrost::Grid
# Creates the space and the two alternate Grids and initializes the
# time counter to 0.
def initialize(rows, columns, vapor_rate)
# argument checks
raise ArgumentError, "rows and columns must be positive" unless
rows > 0 && columns > 0
raise ArgumentError, "rows and columns must be even" unless
rows % 2 == 0 && columns % 2 == 0
raise ArgumentError, "vapor rate must be from 0.0 to 1.0" unless
vapor_rate >= 0.0 && vapor_rate <= 1.0
# Create the space with the proper vapor ratio.
@space = Array.new(rows) do
Array.new(columns) do
Cell.new(rand <= vapor_rate ? :vapor : :space)
end
end
# Put one ice crystal in the middle.
@space[rows/2][columns/2].contents = :ice
# Create the two Grids by using different offsets.
@grids = [Grid.new(@space, 0), Grid.new(@space, 1)]
@time = 0
end
# Returns true if there's any vapor left in @space
def contains_vapor?
@space.flatten.any? { |cell| cell.contents == :vapor }
end
# Alternates which Grid is used during each tick and adjust the
# Cells in each Box.
def tick
@grids[@time % 2].tick
@time += 1
end
def to_s
@space.map do |row|
row.map { |cell| cell.to_s }.join('')
end.join("\n")
end
end # class SimFrost
if __FILE__ == $0
# choose command-line arguments or default values
rows = ARGV[0] && ARGV[0].to_i || 30
columns = ARGV[1] && ARGV[1].to_i || 60
vapor_rate = ARGV[2] && ARGV[2].to_f || 0.15
pause = ARGV[3] && ARGV[3].to_f || 0.025
s = SimFrost.new(rows, columns, vapor_rate)
puts s.to_s
while s.contains_vapor?
sleep(pause)
s.tick
puts "=" * columns # separator
puts s.to_s
end
end