From e3a25838db0ac392aa9e68379e167635f8e67a43 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 17 May 2006 14:56:04 +0000 Subject: Patch #1486962: Several bugs in the turtle Tk demo module were fixed and several features added, such as speed and geometry control. --- Lib/lib-tk/turtle.py | 583 +++++++++++++++++++++++++++++++++++++++++++++++++-- Misc/NEWS | 3 + 2 files changed, 568 insertions(+), 18 deletions(-) diff --git a/Lib/lib-tk/turtle.py b/Lib/lib-tk/turtle.py index a395613..ab36732 100644 --- a/Lib/lib-tk/turtle.py +++ b/Lib/lib-tk/turtle.py @@ -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 @@ class RawPen: 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 @@ class RawPen: 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 @@ class RawPen: 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 @@ class RawPen: 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 @@ class RawPen: 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 @@ class RawPen: {'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 @@ class RawPen: 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 @@ class RawPen: self._draw_turtle() def heading(self): + """ Return 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): + """ Return 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): + """ Return 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): + """ Set 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): + """ Set 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): + """Returs 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): + """ Go to 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 @@ class RawPen: 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 @@ class RawPen: 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,42 @@ class RawPen: 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): + """ Set 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 +652,17 @@ class RawPen: 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 +671,15 @@ class Pen(RawPen): 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 +691,18 @@ class Pen(RawPen): _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 +719,8 @@ 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 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 +730,106 @@ 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 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 +880,94 @@ def demo(): 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() diff --git a/Misc/NEWS b/Misc/NEWS index 4c7cb14..514c95d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -45,6 +45,9 @@ Extension Modules Library ------- +- Patch #1486962: Several bugs in the turtle Tk demo module were fixed + and several features added, such as speed and geometry control. + - Patch #1488881: add support for external file objects in bz2 compressed tarfiles. -- cgit v0.12