On Wed, 16 May 2001, meredith wrote:
> I've been trying to persuade my teacher friends to use Ruby to teach 
> their kids. The bottom line for most of them is that they need to have 
> turtle graphics. If I knew TK I might attempt it myself, but I don't and 
> so the learning curve would be way too big. Anyone out there want to 
> take it on?
> Getting it into the schools is a really good way to help a language 
> catch on.



Three parts:

	* a sample program using the turtle

	* the turtle package (yellow window)
		commands typed in the red box may be
		"forward", "back", "right", "left", or any
		ruby expression.

	* the listener package (type commands in the red box)
		I originally wrote this last part for another project;
		it has a command history.

----------------8<----------------cut-here----------------8<----------------

# this draws a recursive tree.

# unfortunately you'll have to put the whole def on one line to feed it to
# the listener, unless you wrap it in $listener.instance_eval { } just
# before the mainloop.

def tree(d,n)
	return if n<1
	forward d
	left 45
	tree d*3/4,n-1
	right 90
	tree d*3/4,n-1
	left 45
	back d
end

back 50
tree 50,6

--------8<----------------cut-here----------------8<-------- TkTurtle.rb

#       Ruby/Tk Turtle Graphics
#       Copyright (c) 2001 by Mathieu Bouchard
#       under the same license as Ruby itself

require 'tk'
require 'TkRubyListener'

$root = TkRoot.new {
	title "Tortue"
}
$canvas = TkCanvas.new($root) {
	background "#888800" # dark yellow
	width 512
	height 384
}.pack
$listener = TkRubyListener.new($root,60,8)
$listener.frame.pack("fill"=>"both")

module Turtle
	def turtle_init
		@pos_x = $canvas.width/2
		@pos_y = $canvas.height/2
		@angle = 0
		@turtle_lines ||= (0...3).map {|i|
			l = TkcLine.new($canvas,0,0,0,0)
			l.fill "white"
			l
		}
		turtle_draw
	end
	def forward(distance)
		nx = @pos_x + distance * Math.sin(@angle)
		ny = @pos_y - distance * Math.cos(@angle)
		TkcLine.new($canvas,@pos_x,@pos_y,nx,ny).fill "black"
		@pos_x = nx
		@pos_y = ny
		turtle_draw
		return distance
	end
	def right(angle)
		@angle += angle * Math::PI / 180
		turtle_draw
		return angle
	end
	def back(distance); forward -distance; end
	def left(angle); right(-angle); angle; end

	def turtle_draw
		@turtle_points = 
		[[8,0],[-8,0],[0,20]].map {|x,y| [
			@pos_x + y * Math.sin(@angle) + x * Math.cos(@angle),
			@pos_y - y * Math.cos(@angle) + x * Math.sin(@angle)
		]}
		(0...3).each {|i| @turtle_lines[i].coords(*(
			@turtle_points[i] + @turtle_points[(i+1)%3] ))
		}
	end
end

$listener.extend Turtle
$listener.turtle_init
# $listener.instance_eval { }
Tk.mainloop

----8<----------------cut-here----------------8<---- TkRubyListener.rb
#       Interactive Ruby/Tk
#       Copyright (c) 2000,2001 by Mathieu Bouchard
#       under the same license as Ruby itself

require "tk"

class TkRubyListener
	attr_reader :entry
	attr_reader :text
	attr_reader :hist
	attr_reader :frame

	def initialize(parent,w=80,h=10)
		@frame = TkFrame.new(parent)

		@hist = []
		@histn = 0
		_line = @line = TkVariable.new

		@text = TkText.new(frame) {
			background "#006090"
			foreground "#ffffff"
			highlightbackground "#000000"
			highlightcolor "#ffffff"
			width w
			height h
		}.pack("fill"=>"both")

		@entry = TkEntry.new(frame) {
			background "#900060"
			foreground "#ffffff"
			highlightbackground "#000000"
			highlightcolor "#ffffff"
			width w
			textvariable _line
		}.pack("fill"=>"x")

		@entry.bind("Return") { self.eval_entry   }
		@entry.bind("Up")     { self.history_up   }
		@entry.bind("Down")   { self.history_down }
	end

	def line=(value)
		# bug?
		# @line.value = value

		@entry.delete("0","end")
		@entry.insert("0",value)
	end
	def line()
		@line.value
	end

	def hist_peek(i)
		hist[i]
	end

	def histn=(v)
		@histn = v > hist.length ? hist.length : v < 0 ? 0 : v
	end
	attr_reader :histn

	def add_history(v)
		@hist.push v
		self.histn = hist.length
	end

	# there is a bug that causes spurious '(undef)' to appear (and possibly
	# not even executing the code) but i don't know what it is
	# this was in perl, may not occur in ruby.
	def eval_entry
		@text.delete("1.0", "end")
		@text.insert("end","\n<< #{self.line}\n")
		_result_,_err_ = nil
		begin
			_result_ = eval line
		rescue Exception => _err_
			text.insert("end","!> #{_err_}\n")
		end
		if not _err_
			@text.insert("end",">> #{_result_.inspect}\n")
		end

		add_history(line)
		self.line = ""
		text.yview("moveto",1)
	end

	def history_up
		self.histn -= 1
		self.line = hist_peek(histn)
		entry.cursor = 'end'
	end

	def history_down
		self.histn += 1
		self.line = hist_peek(histn)
		entry.cursor = 'end'
	end
end

----------------8<----------------cut-here----------------8<----------------

matju