James G. wrote:
The three rules of Ruby Q.:
Woah, this quiz was very entertaining. I enjoyed a lot doing it, and
still enjoy watching it every time 
As the console version wouldn’t let me be happy, I tried to do it using
OpenGL. I got it at the end, although it’s pretty slow (runs at decent
speed for size of <200*200, obviously the greater values the slowest),
but I’m happy with it for being my first try using GL.
I’ll probably spend another little time tuning it up (as I stated again
that premature optimization is evil), and perhaps designing the 3D view
someone suggested :O.
Thanks again for the great quizes!
Quiz 117 : SimFrost
Usage> ruby quiz117.rb width height vapor_density
#Based on OpenGL
require ‘opengl’
require ‘glut’
Each pixel represents an element.
class Element
attr_accessor :element
def initialize(element)
@element = element
end
#Just a change of state. Don’t forget to decrement the vapor number.
def freeze
if @element == :vapor
$VAPOR -= 1
@element = :ice
end
end
end
Main class
class Freeze_Simulator
#Standard initializer. It prepares the windows to be called.
def initialize
$WIDTH = ARGV[0] && ARGV[0].to_i || 100
$HEIGHT = ARGV[1] && ARGV[1].to_i || 100
$WIDTH += 1 if $WIDTH % 2 != 0
$HEIGHT += 1 if $HEIGHT % 2 != 0
$DENSITY = ARGV[2] && ARGV[2].to_i || 30
$VAPOR = 0
# We create a matrix, assigning randomly (according to the
# percentage) the density of vapor. Vacuum is represented by nil.
$GRID = Array.new($HEIGHT) do
Array.new($WIDTH) do
if rand(100) > $DENSITY
nil
else
# We need this counter if we want a nice quick
# checker for all_frozen? method
$VAPOR += 1
Element.new(:vapor)
end
end
end
#We set the center to be frozen
($GRID[$HEIGHT/2][$WIDTH/2] = Element.new(:vapor)).freeze
$TICK_COUNT = 0
$PIXELS = []
#Standard GL methods
GLUT.Init
GLUT.InitDisplayMode(GLUT::SINGLE)
GLUT.InitWindowSize($WIDTH, $HEIGHT)
GLUT.InitWindowPosition(100, 100)
GLUT.CreateWindow('SimFrost : Quiz #117 - by CHubas')
GL.ShadeModel(GL::FLAT)
make_matrix
GLUT.DisplayFunc(method(:display).to_proc)
GLUT.KeyboardFunc(Proc.new{|k, x, y| exit if k == 27})
GLUT.ReshapeFunc(method(:reshape).to_proc)
# IdleFunc takes a proc object and calls it continously whenever it
can.
GLUT.IdleFunc(method(:tick).to_proc)
end
Here we create the pixel information.
Open GL takes an array of integers and splits it in groups of three
that represent one color component each.
def make_matrix
for i in 0…$HEIGHT-1
for j in 0…$WIDTH-1
index = (i * $WIDTH + j) * 3
if particle = $GRID[i][j]
case particle.element
when :vapor
# A blue-ish color
$PIXELS[index] = 50
$PIXELS[index+1] = 100
$PIXELS[index+2] = 255
when :ice
# White ice
$PIXELS[index] = 255
$PIXELS[index+1] = 255
$PIXELS[index+2] = 255
end
else
# Black
$PIXELS[index] = 0
$PIXELS[index+1] = 0
$PIXELS[index+2] = 0
end
end
end
end
Some basic window behavior
def reshape(width, height)
GL.PixelZoom(width / $WIDTH.to_f, height / $HEIGHT.to_f)
display
end
Draws the pixel bitmap
def display
GL.DrawPixels($WIDTH, $HEIGHT, GL::RGB, GL::UNSIGNED_BYTE,
$PIXELS.pack(“C*”))
GL.Flush
end
We split the board into 2*2 squares with this method
def each_square( tick_number )
start = 0
w_end = $WIDTH
h_end = $HEIGHT
if tick_number % 2 != 0 #odd
start -= 1
w_end -= 1
h_end -= 1
end
(start...h_end).step(2) do |row|
(start...w_end).step(2) do |column|
s = yield *get_square_at(row, column)
set_square_at(row, column, s)
end
end
end
Checks for each 2*2 square and does the proper transformation
def tick
each_square( ($TICK_COUNT += 1) ) do |*square|
if square.any?{|e| e != nil && e.element == :ice}
for e in square
e.freeze
end
square
else
rotate(square)
end
end
# Having modified the matrix, now we have to rebuild the pixel map
make_matrix
GLUT.PostRedisplay
GLUT.SwapBuffers
#Stop doing this if everything is frozen already
GLUT.IdleFunc(nil) if all_frozen?
end
Some dirty methods
def get_square_at(row, column)
[$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]]
end
def set_square_at(row, column, new_square)
$GRID[row][column],$GRID[row][column+1],$GRID[row+1][column],$GRID[row+1][column+1]
= new_square
end
Rotates elements in
| 0 1 |
| 2 3 |
def rotate(square)
if rand(2) == 0
square.values_at(1,3,0,2)
else
square.values_at(2,0,3,1)
end
end
Validates if there is any vapor particle
def all_frozen?
if $VAPOR > 0
return false
else
puts “Welcome to the ice age!”
puts “All frozen in #{$TICK_COUNT} thicks”
return true
end
end
Starts the main loop
def start
GLUT.MainLoop
end
end
#Let the fun begin
Freeze_Simulator.new.start
Now I wonder how to make a movie 