I'm still working on a version that has fancy graphical output.
This is the ascii output version.

#
# Raj Sahae
# RubyQuiz #117
# Frost Simulation
#
# USAGE:  ruby frost.rb [height] [width] [vapor_percentage]

class Fixnum
  def even?
    self%2 == 0
  end
 
  def odd?
    not self.even?
  end
 
  def prev
    self -1
  end
 
end

#The order ROWxCOL is kept throughout the program
# for any type of matrix/grid format.
class Torus
  attr_reader :width, :height
  attr_accessor :grid
   
  def initialize(row, col)
    raise "Width and Height must be even integers" unless row.even? and 
col.even?
    @width = col
    @height = row
    @grid = Array.new(row){Array.new(col)}
  end
 
  def [](row)
    @grid[row]
  end
 
  def []=(row, value)
    @grid[row] = value
  end
 
  def next_row(row)
    row.next == @height ? 0 : row.next
  end
 
  def next_col(col)
    col.next == @width ? 0 : col.next
  end
 
  def prev_row(row)
    row == 0 ? @height.prev : row.prev
  end
 
  def prev_col(col)
    col == 0 ? @width.prev : col.prev
  end
end

class FrostSimulation
  #Initialize with the number of rows and columns
  # and the percentage of the grid(an Integer from 0-100)
  # that should be vapor
  def initialize(rows, cols, percentage)

    @torus = Torus.new(rows, cols)
    @torus.grid.each{|row| row.collect!{|n| rand(99) < percentage 
?(:vapor):(:vacuum)}}
    center = [rows/2, cols/2]
    @torus[center[0]][center[1]] = :ice

  end
 
  def display
    @torus.width.times{print '#'}; print "\n"
    @torus.grid.each do |row|
      row.each do |n|
        if n == :vapor        then print('.')
        elsif n == :vacuum then print(' ')
        elsif n == :ice       then print('*')
        end
      end
      print "\n"
    end
  end
 
  def extract_groups_at(tick)
    ptr = tick.even? ? [0, 0] : [1, 1]
    width, height = @torus.width/2, @torus.height/2
    #Neighborhood array is formatted counterclockwise from starting point
    #Eg. one element of neighborhood shows [top_left, bottom_left, 
bottom_right, top_right]
    groups = Array.new(width*height){Array.new(4)}
    groups.each_index do |index|
      groups[index][0] = @torus.grid[ptr[0]][ptr[1]] #set top_left
      ptr[0] = @torus.next_row(ptr[0])                #move pointer down 
a row
      groups[index][1] = @torus.grid[ptr[0]][ptr[1]] #set bottom_left
      ptr[1] = @torus.next_col(ptr[1])                 # move pointer 
over a col
      groups[index][2] = @torus.grid[ptr[0]][ptr[1]] # set bottom_right
      ptr[0] = @torus.prev_row(ptr[0])                # move pointer up 
a row
      groups[index][3] = @torus.grid[ptr[0]][ptr[1]] #set top_right
      ptr[1] = @torus.next_col(ptr[1])                 # move pointer 
over a col
      #if we are at the end of a row, move the pointer down 2 rows
      2.times{ptr[0] = @torus.next_row(ptr[0])} if index.next%width == 0
    end
  end
 
  def process_groups(groups)
    groups.each do |group|
      if group.include?(:ice)
        group.collect!{|n| n == :vapor ? :ice : n}
      else
        rand(100) < 51 ? group.unshift(group.pop) : group.push(group.shift)
      end
    end
  end
 
  def inject_groups(tick, groups)
    #this is the same algorithm as extraction
    ptr = tick.even? ? [0, 0] : [1, 1]
    width, height = @torus.width/2, @torus.height/2
    groups.each_index do |index|
      @torus.grid[ptr[0]][ptr[1]] = groups[index][0] #set top_left
      ptr[0] = @torus.next_row(ptr[0])                  #move pointer 
down a row
      @torus.grid[ptr[0]][ptr[1]] = groups[index][1]  #set bottom_left
      ptr[1] = @torus.next_col(ptr[1])                   # move pointer 
over a col
      @torus.grid[ptr[0]][ptr[1]] = groups[index][2]  # set bottom_right
      ptr[0] = @torus.prev_row(ptr[0])                  # move pointer 
up a row
      @torus.grid[ptr[0]][ptr[1]] = groups[index][3]  #set top_right
      ptr[1] = @torus.next_col(ptr[1])                   # move pointer 
over a col
      #if we are at the end of a row, move the pointer down 2 rows
      2.times{ptr[0] = @torus.next_row(ptr[0])} if index.next%width == 0
    end
  end
 
  def run
    tick = 0
    continue = true
    display
    while continue
      groups = inject_groups(tick, process_groups(extract_groups_at(tick)))
      display
      continue = @torus.grid.flatten.detect{|n| n == :vapor}
      tick = tick.next
      sleep(0.15)
    end
  end
end

if $0 == __FILE__
  rows = ARGV[0].nil? ? 24 : ARGV[0].to_i
  cols = ARGV[1].nil? ? 40 : ARGV[1].to_i
  percentage = ARGV[2].nil? ? 30 : ARGV[2].to_i
  sim = FrostSimulation.new(rows, cols, percentage)
  sim.run
end