I thought some of you would like to see what showed up at 
http://svn.python.org/view/python/trunk/Misc/NEWS a few hours ago...

================================================================
Revision 46028
Modified Wed May 17 14:56:04 2006 UTC (11 hours, 10 minutes ago) by 
georg.brandl
File length: 53841 byte(s)

Patch #1486962: Several bugs in the turtle Tk demo module were fixed
and several features added, such as speed and geometry control.
================================================================

So while it may not be everything we discussed or exactly what everyone 
would want, we do have some fixes and enhancements to turtle.py 
committed for the upcoming Python 2.5 release.

My thanks to all, especially Atanas, Toby, Chris and Gregor for  their 
fixes and suggestions.

I expect this list might go dormant for a while now, but perhaps the 
turtle will rise again. ;) And of course it's available for any other 
group or project.

Cheers,

Vern

P.S. attached is the patch that I sent in (as described below)
=================================================================
Several bugfixes and enhancements (from several
teachers who use Python in secondary and post-secondary
classes) to improve usability in the classroom:

* docstrings added to methods (Toby Donaldson)

* added methods to control speed, window geometry and
window title. (Vern Ceder)

* added Turtle as alias for Pen - students can now
create Turtle objects (Toby Donaldson)

* default window now larger and centered (Vern Ceder)

* added done() function to start main event loop after
drawing (handy when running programs in IDLE) (Vern
Ceder/Chris Smith)

* fixed bug where filled polygons are lowered (Atanas
Radenski)

* fixed bug in circle() method to use self._fullcircle
/ 4.0 instead of 90.0 to determine start (Chris Smith)

* removed several redundant assignments (Chris Smith)

* added second demo which uses new features (Gregor Lindl)

-- 
This time for sure!
    -Bullwinkle J. Moose
-----------------------------
Vern Ceder, Director of Technology
Canterbury School, 3210 Smith Road, Ft Wayne, IN 46804
[EMAIL PROTECTED]; 260-436-0746; FAX: 260-436-5137


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"edupython" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at http://groups.google.com/group/edupython
-~----------~----~----~----~------~----~------~--~---
--- /usr/lib/python2.4/lib-tk/turtle.py	2006-05-10 23:07:46.000000000 -0400
+++ turtle.py	2006-05-11 19:41:35.000000000 -0400
@@ -1,8 +1,24 @@
 # LogoMation-like turtle graphics
 
+"""
+Turtle graphics is a popular way for introducing programming to
+kids. It was part of the original Logo programming language developed
+by Wally Feurzeig and Seymour Papert in 1966.
+
+Imagine a robotic turtle starting at (0, 0) in the x-y plane. Give it
+the command turtle.forward(15), and it moves (on-screen!) 15 pixels in
+the direction it is facing, drawing a line as it moves. Give it the
+command turtle.left(25), and it rotates in-place 25 degrees clockwise.
+
+By combining together these and similar commands, intricate shapes and
+pictures can easily be drawn.
+"""
+
 from math import * # Also for export
 import Tkinter
 
+speeds = ['fastest', 'fast', 'normal', 'slow', 'slowest']
+
 class Error(Exception):
     pass
 
@@ -13,17 +29,42 @@
         self._items = []
         self._tracing = 1
         self._arrow = 0
+        self._delay = 10     # default delay for drawing
         self.degrees()
         self.reset()
 
     def degrees(self, fullcircle=360.0):
+        """ Set angle measurement units to degrees.
+
+        Example:
+        >>> turtle.degrees()
+        """
         self._fullcircle = fullcircle
         self._invradian = pi / (fullcircle * 0.5)
 
     def radians(self):
+        """ Set the angle measurement units to radians.
+
+        Example:
+        >>> turtle.radians()
+        """
         self.degrees(2.0*pi)
 
     def reset(self):
+        """Clear the screen, re-center the pen, and set variables to
+        the default values.
+
+        Example:
+        >>> turtle.position()
+        [0.0, -22.0]
+        >>> turtle.heading()
+        100.0
+        >>> turtle.reset()
+        >>> turtle.position()
+        [0.0, 0.0]
+        >>> turtle.heading()
+        0.0
+        """
         canvas = self._canvas
         self._canvas.update()
         width = canvas.winfo_width()
@@ -45,6 +86,11 @@
         canvas._root().tkraise()
 
     def clear(self):
+        """ Clear the screen. The turtle does not move.
+
+        Example:
+        >>> turtle.clear()
+        """
         self.fill(0)
         canvas = self._canvas
         items = self._items
@@ -55,37 +101,130 @@
         self._draw_turtle()
 
     def tracer(self, flag):
+        """Set tracing on if flag is True, and off if it is False.
+        Tracing means line are drawn more slowly, with an
+        animation of an arrow along the line.
+
+        Example:
+        >>> turtle.tracer(False)   # turns off Tracer
+        """
         self._tracing = flag
         if not self._tracing:
             self._delete_turtle()
         self._draw_turtle()
 
     def forward(self, distance):
+        """ Go forward distance steps.
+
+        Example:
+        >>> turtle.position()
+        [0.0, 0.0]
+        >>> turtle.forward(25)
+        >>> turtle.position()
+        [25.0, 0.0]
+        >>> turtle.forward(-75)
+        >>> turtle.position()
+        [-50.0, 0.0]
+        """
         x0, y0 = start = self._position
         x1 = x0 + distance * cos(self._angle*self._invradian)
         y1 = y0 - distance * sin(self._angle*self._invradian)
         self._goto(x1, y1)
 
     def backward(self, distance):
+        """ Go backwards distance steps.
+
+        The turtle's heading does not change.
+
+        Example:
+        >>> turtle.position()
+        [0.0, 0.0]
+        >>> turtle.backward(30)
+        >>> turtle.position()
+        [-30.0, 0.0]
+        """
         self.forward(-distance)
 
     def left(self, angle):
+        """ Turn left angle units (units are by default degrees,
+        but can be set via the degrees() and radians() functions.)
+
+        When viewed from above, the turning happens in-place around
+        its front tip.
+
+        Example:
+        >>> turtle.heading()
+        22
+        >>> turtle.left(45)
+        >>> turtle.heading()
+        67.0
+        """
         self._angle = (self._angle + angle) % self._fullcircle
         self._draw_turtle()
 
     def right(self, angle):
+        """ Turn right angle units (units are by default degrees,
+        but can be set via the degrees() and radians() functions.)
+
+        When viewed from above, the turning happens in-place around
+        its front tip.
+
+        Example:
+        >>> turtle.heading()
+        22
+        >>> turtle.right(45)
+        >>> turtle.heading()
+        337.0
+        """
         self.left(-angle)
 
     def up(self):
+        """Pull the pen up -- no drawing when moving.
+
+        Example:
+        >>> turtle.up()
+        """
         self._drawing = 0
 
     def down(self):
+        """Put the pen down -- draw when moving.
+
+        Example:
+        >>> turtle.down()
+        """
         self._drawing = 1
 
     def width(self, width):
+        """ Set the line to thickness to width.
+
+        Example:
+        >>> turtle.width(10)
+        """
         self._width = float(width)
 
     def color(self, *args):
+        """ Set the pen color.
+
+        Three input formats are allowed:
+
+            color(s)
+            s is a Tk specification string, such as "red" or "yellow"
+
+            color((r, g, b))
+            *a tuple* of r, g, and b, which represent, an RGB color,
+            and each of r, g, and b are in the range [0..1]
+
+            color(r, g, b)
+            r, g, and b represent an RGB color, and each of r, g, and b
+            are in the range [0..1]
+
+        Example:
+
+        >>> turtle.color('brown')
+        >>> tup = (0.2, 0.8, 0.55)
+        >>> turtle.color(tup)
+        >>> turtle.color(0, .5, 0)
+        """
         if not args:
             raise Error, "no color arguments"
         if len(args) == 1:
@@ -118,11 +257,20 @@
         self._color = color
         self._draw_turtle()
 
-    def write(self, arg, move=0):
-        x, y = start = self._position
+    def write(self, text, move=False):
+        """ Write text at the current pen position.
+
+        If move is true, the pen is moved to the bottom-right corner
+        of the text. By default, move is False.
+
+        Example:
+        >>> turtle.write('The race is on!')
+        >>> turtle.write('Home = (0, 0)', True)
+        """
+        x, y  = self._position
         x = x-1 # correction -- calibrated for Windows
         item = self._canvas.create_text(x, y,
-                                        text=str(arg), anchor="sw",
+                                        text=str(text), anchor="sw",
                                         fill=self._color)
         self._items.append(item)
         if move:
@@ -131,6 +279,20 @@
         self._draw_turtle()
 
     def fill(self, flag):
+        """ Call fill(1) before drawing the shape you want to fill, and
+        fill(0) when done.
+
+        Example:
+        >>> turtle.fill(1)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.fill(0)
+        """
         if self._filling:
             path = tuple(self._path)
             smooth = self._filling < 0
@@ -139,7 +301,6 @@
                                             {'fill': self._color,
                                              'smooth': smooth})
                 self._items.append(item)
-                self._canvas.lower(item)
                 if self._tofill:
                     for item in self._tofill:
                         self._canvas.itemconfigure(item, fill=self._color)
@@ -151,16 +312,62 @@
             self._path.append(self._position)
         self.forward(0)
 
+    def begin_fill(self):
+        """ Called just before drawing a shape to be filled.
+
+        Example:
+        >>> turtle.begin_fill()
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.end_fill()
+        """
+        self.fill(1)
+
+    def end_fill(self):
+        """ Called after drawing a shape to be filled.
+
+        Example:
+        >>> turtle.begin_fill()
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.left(90)
+        >>> turtle.forward(100)
+        >>> turtle.end_fill()
+        """
+        self.fill(0)
+
     def circle(self, radius, extent=None):
+        """ Draw a circle with given radius.
+        The center is radius units left of the turtle; extent
+        determines which part of the circle is drawn. If not given,
+        the entire circle is drawn.
+
+        If extent is not a full circle, one endpoint of the arc is the
+        current pen position. The arc is drawn in a counter clockwise
+        direction if radius is positive, otherwise in a clockwise
+        direction. In the process, the direction of the turtle is
+        changed by the amount of the extent.
+
+        >>> turtle.circle(50)
+        >>> turtle.circle(120, 180)  # half a circle
+        """
         if extent is None:
             extent = self._fullcircle
         x0, y0 = self._position
         xc = x0 - radius * sin(self._angle * self._invradian)
         yc = y0 - radius * cos(self._angle * self._invradian)
         if radius >= 0.0:
-            start = self._angle - 90.0
+            start = self._angle - (self._fullcircle / 4.0)
         else:
-            start = self._angle + 90.0
+            start = self._angle + (self._fullcircle / 4.0)
             extent = -extent
         if self._filling:
             if abs(extent) >= self._fullcircle:
@@ -202,40 +409,145 @@
         self._draw_turtle()
 
     def heading(self):
+        """ Returns the turtle's current heading.
+
+        Example:
+        >>> turtle.heading()
+        67.0
+        """
         return self._angle
 
     def setheading(self, angle):
+        """ Set the turtle facing the given angle.
+
+        Here are some common directions in degrees:
+
+           0 - east
+          90 - north
+         180 - west
+         270 - south
+
+        Example:
+        >>> turtle.setheading(90)
+        >>> turtle.heading()
+        90
+        >>> turtle.setheading(128)
+        >>> turtle.heading()
+        128
+        """
         self._angle = angle
         self._draw_turtle()
 
     def window_width(self):
+        """ Returns the width of the turtle window.
+
+        Example:
+        >>> turtle.window_width()
+        640
+        """
         width = self._canvas.winfo_width()
         if width <= 1:  # the window isn't managed by a geometry manager
             width = self._canvas['width']
         return width
 
     def window_height(self):
+        """ Returns the height of the turtle window.
+
+        Example:
+        >>> turtle.window_height()
+        768
+        """
         height = self._canvas.winfo_height()
         if height <= 1: # the window isn't managed by a geometry manager
             height = self._canvas['height']
         return height
 
     def position(self):
+        """ Returns the current (x, y) location of the turtle.
+
+        Example:
+        >>> turtle.position()
+        [0.0, 240.0]
+        """
         x0, y0 = self._origin
         x1, y1 = self._position
         return [x1-x0, -y1+y0]
 
     def setx(self, xpos):
+        """ Sets the turtle's x coordinate to be xpos.
+
+        Example:
+        >>> turtle.position()
+        [10.0, 240.0]
+        >>> turtle.setx(10)
+        >>> turtle.position()
+        [10.0, 240.0]
+        """
         x0, y0 = self._origin
         x1, y1 = self._position
         self._goto(x0+xpos, y1)
 
     def sety(self, ypos):
+        """ Sets the turtle's y coordinate to be ypos.
+
+        Example:
+        >>> turtle.position()
+        [0.0, 0.0]
+        >>> turtle.sety(-22)
+        >>> turtle.position()
+        [0.0, -22.0]
+        """
         x0, y0 = self._origin
         x1, y1 = self._position
         self._goto(x1, y0-ypos)
 
+    def towards(self, *args):
+        """Returns the angle, which corresponds to the line
+        from turtle-position to point (x,y).
+
+        Argument can be two coordinates or one pair of coordinates
+        or a RawPen/Pen instance.
+
+        Example:
+        >>> turtle.position()
+        [10.0, 10.0]
+        >>> turtle.towards(0,0)
+        225.0
+        """
+        if len(args) == 2:
+            x, y = args
+        else:
+            arg = args[0]
+            if isinstance(arg, RawPen):
+                x, y = arg.position()
+            else:
+                x, y = arg
+        x0, y0 = self.position()
+        dx = x - x0
+        dy = y - y0
+        return (atan2(dy,dx) / self._invradian) % self._fullcircle
+
     def goto(self, *args):
+        """ Goto the given point.
+
+        If the pen is down, then a line will be drawn. The turtle's
+        orientation does not change.
+
+        Two input formats are accepted:
+
+           goto(x, y)
+           go to point (x, y)
+
+           goto((x, y))
+           go to point (x, y)
+
+        Example:
+        >>> turtle.position()
+        [0.0, 0.0]
+        >>> turtle.goto(50, -45)
+        >>> turtle.position()
+        [50.0, -45.0]
+        """
         if len(args) == 1:
             try:
                 x, y = args[0]
@@ -250,7 +562,7 @@
         self._goto(x0+x, y0-y)
 
     def _goto(self, x1, y1):
-        x0, y0 = start = self._position
+        x0, y0 = self._position
         self._position = map(float, (x1, y1))
         if self._filling:
             self._path.append(self._position)
@@ -270,7 +582,7 @@
                         self._canvas.coords(item, x0, y0, x, y)
                         self._draw_turtle((x,y))
                         self._canvas.update()
-                        self._canvas.after(10)
+                        self._canvas.after(self._delay)
                     # in case nhops==0
                     self._canvas.coords(item, x0, y0, x1, y1)
                     self._canvas.itemconfigure(item, arrow="none")
@@ -285,7 +597,43 @@
             self._items.append(item)
         self._draw_turtle()
 
-    def _draw_turtle(self,position=[]):
+    def speed(self, speed):
+        """ Set the turtle's speed.
+
+        speed must one of these five strings:
+
+            'fastest' is a 0 ms delay
+            'fast' is a 5 ms delay
+            'normal' is a 10 ms delay
+            'slow' is a 15 ms delay
+            'slowest' is a 20 ms delay
+
+         Example:
+         >>> turtle.speed('slow')
+        """
+        try:
+            speed = speed.strip().lower()
+            self._delay = speeds.index(speed) * 5
+        except:
+            raise ValueError, \
+               "%r is not a valid speed. speed must be one of %s" \
+               % (speed, speeds)
+
+
+    def delay(self, delay):
+        """ Sets the drawing delay in milliseconds.
+
+        This is intended to allow finer control of the drawing speed
+        than the speed() method
+
+        Example:
+        >>> turtle.delay(15)
+        """
+        if int(delay) < 0:
+            raise ValueError, "delay must be greater than or equal to 0"
+        self._delay = int(delay)
+
+    def _draw_turtle(self, position=[]):
         if not self._tracing:
             return
         if position == []:
@@ -305,13 +653,17 @@
     def _delete_turtle(self):
         if self._arrow != 0:
             self._canvas.delete(self._arrow)
-        self._arrow = 0
-
+            self._arrow = 0
 
 
 _root = None
 _canvas = None
 _pen = None
+_width = 0.50                  # 50% of window width
+_height = 0.75                 # 75% of window height
+_startx = None
+_starty = None
+_title = "Turtle Graphics"     # default title
 
 class Pen(RawPen):
 
@@ -320,10 +672,15 @@
         if _root is None:
             _root = Tkinter.Tk()
             _root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
+            _root.title(_title)
+
         if _canvas is None:
             # XXX Should have scroll bars
             _canvas = Tkinter.Canvas(_root, background="white")
             _canvas.pack(expand=1, fill="both")
+
+        setup(width=_width, height= _height, startx=_startx, starty=_starty)
+
         RawPen.__init__(self, _canvas)
 
     def _destroy(self):
@@ -335,13 +692,18 @@
             _canvas = None
         root.destroy()
 
-
 def _getpen():
     global _pen
-    pen = _pen
-    if not pen:
-        _pen = pen = Pen()
-    return pen
+    if not _pen:
+        _pen = Pen()
+    return _pen
+
+class Turtle(Pen):
+    pass
+
+"""For documentation of the following functions see
+   the RawPen methods with the same names
+"""
 
 def degrees(): _getpen().degrees()
 def radians(): _getpen().radians()
@@ -358,6 +720,8 @@
 def color(*args): _getpen().color(*args)
 def write(arg, move=0): _getpen().write(arg, move)
 def fill(flag): _getpen().fill(flag)
+def begin_fill(): _getpen().begin_fill()
+def end_fill(): _getpen.end_fill()
 def circle(radius, extent=None): _getpen().circle(radius, extent)
 def goto(*args): _getpen().goto(*args)
 def heading(): return _getpen().heading()
@@ -367,6 +731,106 @@
 def window_height(): return _getpen().window_height()
 def setx(xpos): _getpen().setx(xpos)
 def sety(ypos): _getpen().sety(ypos)
+def towards(*args): return _getpen().towards(*args)
+
+def done(): _root.mainloop()
+def delay(delay): return _getpen().delay(delay)
+def speed(speed): return _getpen().speed(speed)
+
+for methodname in dir(RawPen):
+    """ copies RawPen docstrings to module functions of same name """
+    if not methodname.startswith("_"):
+        eval(methodname).__doc__ = RawPen.__dict__[methodname].__doc__
+
+
+def setup(**geometry):
+    """ Sets the size and position of the main window.
+
+    Keywords are width, height, startx and starty
+
+    width: either a size in pixels or a fraction of the screen.
+      Default is 50% of screen.
+    height: either the height in pixels or a fraction of the screen.
+      Default is 75% of screen.
+
+    Setting either width or height to None before drawing will force
+      use of default geometry as in older versions of turtle.py
+    
+    startx: starting position in pixels from the left edge of the screen.
+      Default is to center window. Setting startx to None is the default
+      and centers window horizontally on screen.
+    
+    starty: starting position in pixels from the top edge of the screen.
+      Default is to center window. Setting starty to None is the default
+      and centers window vertically on screen.
+
+    Examples:
+    >>> setup (width=200, height=200, startx=0, starty=0)
+
+    sets window to 200x200 pixels, in upper left of screen
+
+    >>> setup(width=.75, height=0.5, startx=None, starty=None)
+
+    sets window to 75% of screen by 50% of screen and centers
+
+    >>> setup(width=None)
+
+    forces use of default geometry as in older versions of turtle.py
+    """
+    
+    global _width, _height, _startx, _starty
+
+    width = geometry.get('width',_width)
+    if width >= 0 or width == None:
+        _width = width
+    else:
+        raise ValueError, "width can not be less than 0"
+
+    height = geometry.get('height',_height)
+    if height >= 0 or height == None:
+        _height = height
+    else:
+        raise ValueError, "height can not be less than 0"
+        
+    startx = geometry.get('startx', _startx)
+    if startx >= 0 or startx == None:
+        _startx = _startx
+    else:
+        raise ValueError, "startx can not be less than 0"
+
+    starty = geometry.get('starty', _starty)
+    if starty >= 0 or starty == None:
+        _starty = starty
+    else:
+        raise ValueError, "startx can not be less than 0"
+
+
+    if _root and _width and _height:
+        if 0 < _width <= 1:
+            _width = _root.winfo_screenwidth() * +width
+        if 0 < _height <= 1:
+            _height = _root.winfo_screenheight() * _height
+
+        # center window on screen
+        if _startx is None:
+            _startx = (_root.winfo_screenwidth() - _width) / 2
+ 
+        if _starty is None:
+            _starty = (_root.winfo_screenheight() - _height) / 2
+
+        _root.geometry("%dx%d+%d+%d" % (_width, _height, _startx, _starty))
+
+def title(title):
+    """ set the window title.
+
+    By default this is set to 'Turtle Graphics'
+
+    Example:
+    >>> title("My Window")
+    """
+    
+    global _title
+    _title = title
 
 def demo():
     reset()
@@ -417,10 +881,94 @@
         forward(20)
         right(90)
     fill(0)
+    tracer(1)
     # more text
     write("end")
-    if __name__ == '__main__':
-        _root.mainloop()
+
+def demo2():
+    # exercises some new and improved features
+    speed('fast')
+    width(3)
+
+    # draw a segmented half-circle
+    setheading(towards(0,0))
+    x,y = position()
+    r = (x**2+y**2)**.5/2.0
+    right(90)
+    pendown = True
+    for i in range(18):
+        if pendown:
+            up()
+            pendown = False
+        else:
+            down()
+            pendown = True
+        circle(r,10)
+    sleep(2)
+   
+    reset() 
+    left(90)
+    
+    # draw a series of triangles
+    l = 10
+    color("green")
+    width(3)
+    left(180)
+    sp = 5
+    for i in range(-2,16):
+        if i > 0:
+            color(1.0-0.05*i,0,0.05*i)
+            fill(1)
+            color("green")
+        for j in range(3):
+            forward(l)
+            left(120)
+        l += 10
+        left(15)
+        if sp > 0:
+            sp = sp-1
+            speed(speeds[sp])
+    color(0.25,0,0.75)
+    fill(0)
+    color("green")
+
+    left(130)
+    up()
+    forward(90)
+    color("red")
+    speed('fastest')
+    down(); 
+
+    # create a second turtle and make the original pursue and catch it
+    turtle=Turtle()
+    turtle.reset()
+    turtle.left(90)
+    turtle.speed('normal')
+    turtle.up()
+    turtle.goto(280,40)
+    turtle.left(24)
+    turtle.down()
+    turtle.speed('fast')
+    turtle.color("blue")
+    turtle.width(2)
+    speed('fastest')
+
+    # turn default turtle towards new turtle object
+    setheading(towards(turtle))
+    while ( abs(position()[0]-turtle.position()[0])>4 or
+            abs(position()[1]-turtle.position()[1])>4):
+        turtle.forward(3.5)
+        turtle.left(0.6)
+        # turn default turtle towards new turtle object
+        setheading(towards(turtle))
+        forward(4)
+    write("CAUGHT! ", move=True)
+
+    
 
 if __name__ == '__main__':
+    from time import sleep
     demo()
+    sleep(3)
+    demo2()
+    done()

Reply via email to