I've been going through the several suggestions for improvements for the 
turtle module. I'm sorry it's taken so long, but here is what I've come 
up with.

There were a lot of cool ideas, but I took a somewhat conservative 
approach. If something else needs to be added or removed, bring it up.

Please have a look, test it on your old turtle examples, etc. and report 
any problems (and their solutions!) back here.

Below is an executive summary of what has been added, and attached is 
the new file, called turtlenew.py for the moment, and turtlepatches.txt, 
a text file explaining the changes in more detail.

So let's see what you all think.

Cheers,
Vern Ceder

----

Several additions have been made to turtle.py:

To RawPen:

speed(speed=2)

   sets speed from 0 (very slow) to 4 (as fast as possible). 2 is 
equivalent to the old hard coded default

To Pen:

   changed __init__ to use specific window geometry from module variables

To module:

   setup((w,h,x,y))

     sets module window geometry variables, must be called before window 
is opened

   title(newtitle)

     sets module variable for title, must be called before window is opened

   done()

     starts an event loop (as the demo does at the end) to keep window 
from hanging if run in a program from IDLE. This is meant to be the last 
command of program, it doesn't do much for interactive sessions.

   speed(speed=2)

     sets default speed for Pens created after setting. See speed() 
under RawPen

   Turtle()

     added as an alias (subclass, actually) of Pen()

Bug fixes:

   * Removed self._canvas.lower(item) in fill, which caused filled 
polygons to move themselves to the back layer

   * removed local variable pen from _getpen()

-- 
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
-~----------~----~----~----~------~----~------~--~---
Changes to turtle.py
--------------------

These are mostly minor additions of functionality to make things more
convient when using turtle.py for introductory work. The intent is
to be compatible with all existing turtle examples. Further,
everything should work whether importing the module, importing Pen
or Turtle, or importing everything into the namespace with *.

1. remove self._canvas.lower(item) from fill()

   * this causes filled polygons to put themselves behind everything, while
     circles and lines do not

2. added a _delay variable to module and to RawPen to control the
   speed of drawing. Also added a speed() method to RawPen to map
   speeds from 0 (slowest) to 4 (fastest, _delay = 0). The default
   remains 10, the value hard-coded in the original

   * Being able to watch the turtle as it draws is good, but sometimes
     it moves too slowly for complex figures

   * the change also allows each turtle to move at a different
     speed. Not necessary, but cool anyway

3. added _width, _height, _startx, _starty and _title variables to
   module. Pen's __init__ now uses them to setup the geometry of the
   window when it opens. Also added a setup() function to set those
   variables, needed if you import * from turtle

   * by default the window opened is too small for interesting
     projects. 

   * the setup() function takes a tuple for width, height, startx and
     starty positions. There may be a better way to do this.

   * the default window geometry I chose - 50% of window width by 75%
     of height, starting at 0,0 - may not be the most convenient

4. Also set a default window title. Added a title() function to module
   to set initial title display.

5. add a done() function to start a main event loop (just as the demo
   does). Otherwise, the window is unresponsive if in a program run
   from IDLE. 

6. aliased (subclassed, actually) Pen() with Turtle() so that one may
   create Turtle objects after importing from turtle.py. Saying that
   they were turtles but calling the objects pens always seemed odd to
   some of us. 

7. removed the odd 3 way shuffle in _getpen(). Is there a reason for
   this that I'm missing?

Good ideas not implemented yet
------------------------------

There were several good suggestions that I haven't added yet, either
because they are problematic or because I'm not sure how best to do it:

1. the addition of a TurtleCanvas class to manage things like
   resizing, background color, etc. I'm not sure about the best way
   to do this and would prefer suggestions from someone who has a
   clear idea of how they might be used in teaching. It should also be
   dead simple to use...

2. any fixes for the "hanging turtle" problem, which seems to be a
   tkinter problem more than a turtle problem.

3. "getter" methods for Pen properties like line width (and now speed,
   I suppose) and many others. As Gregor suggests they should all be
   done the same way. But I'm not sure how much we need them for
   beginners.

4. improved documentation - Toby has offered to look at this

5. Chris Smith has some bug fixes and refactorings that he is going to
   submit via the patch manager. We can also add those if we want
   after we get through this step.
# LogoMation-like turtle graphics

from math import * # Also for export
import Tkinter

class Error(Exception):
    pass

_delay = 10

class RawPen:

    def __init__(self, canvas):
        self._canvas = canvas
        self._items = []
        self._tracing = 1
        self._arrow = 0
        self._delay = _delay
        self.degrees()
        self.reset()

    def degrees(self, fullcircle=360.0):
        self._fullcircle = fullcircle
        self._invradian = pi / (fullcircle * 0.5)

    def radians(self):
        self.degrees(2.0*pi)

    def reset(self):
        canvas = self._canvas
        self._canvas.update()
        width = canvas.winfo_width()
        height = canvas.winfo_height()
        if width <= 1:
            width = canvas['width']
        if height <= 1:
            height = canvas['height']
        self._origin = float(width)/2.0, float(height)/2.0
        self._position = self._origin
        self._angle = 0.0
        self._drawing = 1
        self._width = 1
        self._color = "black"
        self._filling = 0
        self._path = []
        self._tofill = []
        self.clear()
        canvas._root().tkraise()

    def clear(self):
        self.fill(0)
        canvas = self._canvas
        items = self._items
        self._items = []
        for item in items:
            canvas.delete(item)
        self._delete_turtle()
        self._draw_turtle()

    def tracer(self, flag):
        self._tracing = flag
        if not self._tracing:
            self._delete_turtle()
        self._draw_turtle()

    def forward(self, distance):
        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):
        self.forward(-distance)

    def left(self, angle):
        self._angle = (self._angle + angle) % self._fullcircle
        self._draw_turtle()

    def right(self, angle):
        self.left(-angle)

    def up(self):
        self._drawing = 0

    def down(self):
        self._drawing = 1

    def width(self, width):
        self._width = float(width)

    def color(self, *args):
        if not args:
            raise Error, "no color arguments"
        if len(args) == 1:
            color = args[0]
            if type(color) == type(""):
                # Test the color first
                try:
                    id = self._canvas.create_line(0, 0, 0, 0, fill=color)
                except Tkinter.TclError:
                    raise Error, "bad color string: %r" % (color,)
                self._set_color(color)
                return
            try:
                r, g, b = color
            except:
                raise Error, "bad color sequence: %r" % (color,)
        else:
            try:
                r, g, b = args
            except:
                raise Error, "bad color arguments: %r" % (args,)
        assert 0 <= r <= 1
        assert 0 <= g <= 1
        assert 0 <= b <= 1
        x = 255.0
        y = 0.5
        self._set_color("#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y)))

    def _set_color(self,color):
        self._color = color
        self._draw_turtle()

    def write(self, arg, move=0):
        x, y = start = self._position
        x = x-1 # correction -- calibrated for Windows
        item = self._canvas.create_text(x, y,
                                        text=str(arg), anchor="sw",
                                        fill=self._color)
        self._items.append(item)
        if move:
            x0, y0, x1, y1 = self._canvas.bbox(item)
            self._goto(x1, y1)
        self._draw_turtle()

    def fill(self, flag):
        if self._filling:
            path = tuple(self._path)
            smooth = self._filling < 0
            if len(path) > 2:
                item = self._canvas._create('polygon', path,
                                            {'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)
                        self._items.append(item)
        self._path = []
        self._tofill = []
        self._filling = flag
        if flag:
            self._path.append(self._position)
        self.forward(0)

    def circle(self, radius, extent=None):
        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
        else:
            start = self._angle + 90.0
            extent = -extent
        if self._filling:
            if abs(extent) >= self._fullcircle:
                item = self._canvas.create_oval(xc-radius, yc-radius,
                                                xc+radius, yc+radius,
                                                width=self._width,
                                                outline="")
                self._tofill.append(item)
            item = self._canvas.create_arc(xc-radius, yc-radius,
                                           xc+radius, yc+radius,
                                           style="chord",
                                           start=start,
                                           extent=extent,
                                           width=self._width,
                                           outline="")
            self._tofill.append(item)
        if self._drawing:
            if abs(extent) >= self._fullcircle:
                item = self._canvas.create_oval(xc-radius, yc-radius,
                                                xc+radius, yc+radius,
                                                width=self._width,
                                                outline=self._color)
                self._items.append(item)
            item = self._canvas.create_arc(xc-radius, yc-radius,
                                           xc+radius, yc+radius,
                                           style="arc",
                                           start=start,
                                           extent=extent,
                                           width=self._width,
                                           outline=self._color)
            self._items.append(item)
        angle = start + extent
        x1 = xc + abs(radius) * cos(angle * self._invradian)
        y1 = yc - abs(radius) * sin(angle * self._invradian)
        self._angle = (self._angle + extent) % self._fullcircle
        self._position = x1, y1
        if self._filling:
            self._path.append(self._position)
        self._draw_turtle()

    def heading(self):
        return self._angle

    def setheading(self, angle):
        self._angle = angle
        self._draw_turtle()

    def window_width(self):
        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):
        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):
        x0, y0 = self._origin
        x1, y1 = self._position
        return [x1-x0, -y1+y0]

    def setx(self, xpos):
        x0, y0 = self._origin
        x1, y1 = self._position
        self._goto(x0+xpos, y1)

    def sety(self, ypos):
        x0, y0 = self._origin
        x1, y1 = self._position
        self._goto(x1, y0-ypos)

    def goto(self, *args):
        if len(args) == 1:
            try:
                x, y = args[0]
            except:
                raise Error, "bad point argument: %r" % (args[0],)
        else:
            try:
                x, y = args
            except:
                raise Error, "bad coordinates: %r" % (args[0],)
        x0, y0 = self._origin
        self._goto(x0+x, y0-y)

    def _goto(self, x1, y1):
        x0, y0 = start = self._position
        self._position = map(float, (x1, y1))
        if self._filling:
            self._path.append(self._position)
        if self._drawing:
            if self._tracing:
                dx = float(x1 - x0)
                dy = float(y1 - y0)
                distance = hypot(dx, dy)
                nhops = int(distance)
                item = self._canvas.create_line(x0, y0, x0, y0,
                                                width=self._width,
                                                capstyle="round",
                                                fill=self._color)
                try:
                    for i in range(1, 1+nhops):
                        x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
                        self._canvas.coords(item, x0, y0, x, y)
                        self._draw_turtle((x,y))
                        self._canvas.update()
                        self._canvas.after(self._delay)
                    # in case nhops==0
                    self._canvas.coords(item, x0, y0, x1, y1)
                    self._canvas.itemconfigure(item, arrow="none")
                except Tkinter.TclError:
                    # Probably the window was closed!
                    return
            else:
                item = self._canvas.create_line(x0, y0, x1, y1,
                                                width=self._width,
                                                capstyle="round",
                                                fill=self._color)
            self._items.append(item)
        self._draw_turtle()

    def speed(self, speed=2):
        """ maps a speed in 4 to 0 to delay given _canvas.after() in
            _goto()

            4 is fastests (0 ms delay)
            0 is slowest (20 ms delay)"""

        if speed >=0 and speed <=4:    
            self._delay = 20 - (speed * 5)

    def _draw_turtle(self,position=[]):
        if not self._tracing:
            return
        if position == []:
            position = self._position
        x,y = position
        distance = 8
        dx = distance * cos(self._angle*self._invradian)
        dy = distance * sin(self._angle*self._invradian)
        self._delete_turtle()
        self._arrow = self._canvas.create_line(x-dx,y+dy,x,y,
                                          width=self._width,
                                          arrow="last",
                                          capstyle="round",
                                          fill=self._color)
        self._canvas.update()

    def _delete_turtle(self):
        if self._arrow != 0:
            self._canvas.delete(self._arrow)
        self._arrow = 0


_root = None
_canvas = None
_pen = None
_width = 0.50
_height = 0.75
_startx = 0
_starty = 0
_title = "Turtle Graphics"

class Pen(RawPen):

    def __init__(self):
        global _root, _canvas, _width, _height
        if _root is None:
            _root = Tkinter.Tk()
            _root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
            _root.title(_title)
            if _width <= 1:
                _width = _root.winfo_screenwidth() * _width
            if _height <= 1:
                _height = _root.winfo_screenheight() * _height
            _root.geometry("%dx%d+%d+%d" % (_width, _height,
                                            _startx, _starty))
        if _canvas is None:
            # XXX Should have scroll bars
            _canvas = Tkinter.Canvas(_root, background="white")
            _canvas.pack(expand=1, fill="both")
        RawPen.__init__(self, _canvas)

    def _destroy(self):
        global _root, _canvas, _pen
        root = self._canvas._root()
        if root is _root:
            _pen = None
            _root = None
            _canvas = None
        root.destroy()

def _getpen():
    global _pen
    pen = _pen
    if not pen:
        _pen = pen = Pen()
    return pen

class Turtle(Pen):
    pass

def degrees(): _getpen().degrees()
def radians(): _getpen().radians()
def reset(): _getpen().reset()
def clear(): _getpen().clear()
def tracer(flag): _getpen().tracer(flag)
def forward(distance): _getpen().forward(distance)
def backward(distance): _getpen().backward(distance)
def left(angle): _getpen().left(angle)
def right(angle): _getpen().right(angle)
def up(): _getpen().up()
def down(): _getpen().down()
def width(width): _getpen().width(width)
def color(*args): _getpen().color(*args)
def write(arg, move=0): _getpen().write(arg, move)
def fill(flag): _getpen().fill(flag)
def circle(radius, extent=None): _getpen().circle(radius, extent)
def goto(*args): _getpen().goto(*args)
def heading(): return _getpen().heading()
def setheading(angle): _getpen().setheading(angle)
def position(): return _getpen().position()
def window_width(): return _getpen().window_width()
def window_height(): return _getpen().window_height()
def setx(xpos): _getpen().setx(xpos)
def sety(ypos): _getpen().sety(ypos)
def done(): _root.mainloop()
def speed(speed=2): _getpen().speed(speed)
def setup(geometry=()):
    global _width, _height, _startx, _starty
    if len(geometry) == 4:
        _width, _height, _startx, _starty = geometry
def title(title):
    global _title
    _title = title

def demo():
    reset()
    tracer(1)
    up()
    backward(100)
    down()
    # draw 3 squares; the last filled
    width(3)
    for i in range(3):
        if i == 2:
            fill(1)
        for j in range(4):
            forward(20)
            left(90)
        if i == 2:
            color("maroon")
            fill(0)
        up()
        forward(30)
        down()
    width(1)
    color("black")
    # move out of the way
    tracer(0)
    up()
    right(90)
    forward(100)
    right(90)
    forward(100)
    right(180)
    down()
    # some text
    write("startstart", 1)
    write("start", 1)
    color("red")
    # staircase
    for i in range(5):
        forward(20)
        left(90)
        forward(20)
        right(90)
    # filled staircase
    fill(1)
    for i in range(5):
        forward(20)
        left(90)
        forward(20)
        right(90)
    fill(0)
    # more text
    write("end")
    if __name__ == '__main__':
        _root.mainloop()

if __name__ == '__main__':
    demo()

Reply via email to