require 'strscan'
class LSystem
attr_reader :output
def initialize( in_axiom, in_rules, in_iterations = 0 )
@axiom = in_axiom
@output = in_axiom
@rules = in_rules
in_iterations.times do iterate end
return @output
end
def iterate
temp_string = ""
@output.scan( /./ ) do |letter|
rule_hit = false
@rules.each do |rule|
if( letter[ rule[0] ] )
rule_hit = true
temp_string << rule[1]
end
end
if( not rule_hit )
temp_string << letter
end
end
@output = temp_string
end
end
the_rules = [
[ /F/, "" ],
[ /Y/, "+FX--FY+" ],
[ /X/, "-FX++FY-" ]
]
the_system = LSystem.new( "FX", the_rules, 10 )
class Pen
attr_accessor :position_stack
attr_reader :position
attr_reader :heading
attr_accessor :theta
attr_accessor :speed #coefficent
DEG2RAD = Math::PI/180
def initialize( in_position, in_shoes )
@shoes = in_shoes
@position = in_position
@up = false #starts out down
@position_stack = []
@heading = 90
@theta = 60
@unit_move = 10
@speed = 1
@reverse_coef = 1
end
def is_up?
return @up
end
def pen_down
@up = false
end
def pen_up
@up = true
end
def position=(new_position)
#p new_position
old_position = @position
@position = new_position
@shoes.append { @shoes.line old_position[0], old_position[1], new_position[0], new_position[1] }
end
def heading=(in_value)
in_value = in_value.to_f
while in_value < 0
in_value += 360
end
@heading = (in_value % 360)
end
def forward( in_amount )
if( in_amount )
in_amount = in_amount.to_f
dx = in_amount * Math.sin(@heading*DEG2RAD)
dy = in_amount * Math.cos(@heading*DEG2RAD)
self.position = [@position[0]+dx, @position[1]+dy]
else
pen_down
dx = @unit_move * @speed * Math.sin(@heading*DEG2RAD)
dy = @unit_move * @speed * Math.cos(@heading*DEG2RAD)
self.position = [@position[0]+dx, @position[1]+dy]
end
end
def back( in_amount )
if( in_amount )
in_amount = in_amount.to_f
dx = in_amount * Math.sin(@heading*DEG2RAD)
dy = in_amount * Math.cos(@heading*DEG2RAD)
self.position = [@position[0]-dx, @position[1]-dy]
else
pen_down
dx = @unit_move * @speed * Math.sin(@heading*DEG2RAD)
dy = @unit_move * @speed * Math.cos(@heading*DEG2RAD)
self.position = [@position[0]-dx, @position[1]-dy]
end
end
def go
pen_up
dx = @unit_move * @speed * Math.sin(@heading*DEG2RAD)
dy = @unit_move * @speed * Math.cos(@heading*DEG2RAD)
self.position = [@position[0]+dx, @position[1]+dy]
end
def left
@heading += @theta * @reverse_coef
end
def right
@heading -= @theta * @reverse_coef
end
def about_face
if( @heading >= 180 )
@heading -= 180
else
@heading += 180
end
end
def up_theta( in_amount )
@theta += in_amount.to_f * @reverse_coef
end
def down_theta( in_amount )
@theta -= in_amount.to_f * @reverse_coef
end
def push_position
@position_stack.push( [@position, @heading] )
end
def pop_position
pen_up
self.position, @heading = @position_stack.pop
pen_down
end
def go_reverse
@reverse_coef = -@reverse_coef
end
def set_speed( in_inverse, in_sqrt, in_value )
@speed = in_value.to_f
@speed = 1 / @speed if in_inverse
@speed = Math.sqrt( @speed ) if in_sqrt
end
def set_color( in_r, in_g, in_b )
stroke in_r, in_g, in_b
end
def follow_instructions( in_instructions )
scanner = StringScanner.new( in_instructions )
while inst = scanner.getch do
case inst
###
# Fractint compatibility
when "F", "D"
self.forward( scanner.scan( /[+-]?\d+\.?\d*/ ) )
when "G", "M"
self.go
when "+"
self.left
when "-"
self.right
when "|"
self.about_face
when "\\"
the_value = scanner..scan( /[+-]?\d+\.?\d*/ )
self.up_theta( the_value )
when "/"
the_value = scanner..scan( /[+-]?\d+\.?\d*/ )
self.down_theta( the_value )
when "["
self.push_position
when "]"
self.pop_position
when "!"
self.go_reverse
when "@"
the_value = scanner.scan( /I?Q?[+-]?\d+\.?\d*/ )
self.set_speed( the_value[/I/], the_value[/Q/], the_value[/[+-]?\d+\.?\d*/] )
###
# Turtle compatibility
when "^"
self.pen_up
when "#"
self.pen_down
when "S"
case scanner.getch
when "P"
x = scanner..scan( /[+-]?\d+\.?\d*/ )
scanner.getch
y = scanner..scan( /[+-]?\d+\.?\d*/ )
x = x.to_i + 250
y = -y.to_i + 250
self.position = [ x, y ]
when "H"
theta = scanner..scan( /[+-]?\d+\.?\d*/ )
self.heading = theta
when "T"
self.theta = scanner..scan( /[+-]?\d+\.?\d*/ ).to_f
end
when "B"
self.back( scanner..scan( /[+-]?\d+\.?\d*/ ) )
when "T"
case scanner.getch
when "R"
theta = scanner..scan( /[+-]?\d+\.?\d*/ )
theta = theta.to_f
self.heading -= theta
when "L"
theta = scanner..scan( /[+-]?\d+\.?\d*/ )
theta = theta.to_f
self.heading += theta
end
when "C"
r = scanner..scan( /[+-]?\d+\.?\d*/ )
scanner.getch
g = scanner..scan( /[+-]?\d+\.?\d*/ )
scanner.getch
b = scanner..scan( /[+-]?\d+\.?\d*/ )
self.set_color( r.to_f, g.to_f, b.to_f )
end
end
end
end
Shoes.app :width=>800, :height=>450 do
strokewidth 2
button "Night Night" do
the_pen = Pen.new([400,225], self)
the_pen.down_theta( 15 )
the_pen.follow_instructions( the_system.output )
end
end