diff options
Diffstat (limited to 'Lib/tkinter/turtle.py')
-rw-r--r-- | Lib/tkinter/turtle.py | 956 |
1 files changed, 956 insertions, 0 deletions
diff --git a/Lib/tkinter/turtle.py b/Lib/tkinter/turtle.py new file mode 100644 index 0000000..960a9f6 --- /dev/null +++ b/Lib/tkinter/turtle.py @@ -0,0 +1,956 @@ +# 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 +from time import sleep +import Tkinter + +speeds = ['fastest', 'fast', 'normal', 'slow', 'slowest'] + +class Error(Exception): + pass + +class RawPen: + + def __init__(self, canvas): + self._canvas = canvas + self._items = [] + self._tracing = 1 + self._arrow = 0 + self._delay = 10 # default delay for drawing + self._angle = 0.0 + self.degrees() + self.reset() + + def degrees(self, fullcircle=360.0): + """ Set angle measurement units to degrees. + + Example: + >>> turtle.degrees() + """ + # Don't try to change _angle if it is 0, because + # _fullcircle might not be set, yet + if self._angle: + self._angle = (self._angle / self._fullcircle) * fullcircle + 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() + 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.clear() + 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 + self._items = [] + for item in items: + canvas.delete(item) + self._delete_turtle() + 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: + 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, 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(text), 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): + """ 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 + if len(path) > 2: + item = self._canvas._create('polygon', path, + {'fill': self._color, + 'smooth': smooth}) + self._items.append(item) + self._path = [] + self._filling = flag + if flag: + self._path.append(self._position) + + def begin_fill(self): + """ Called just before drawing a shape to be filled. + Must eventually be followed by a corresponding end_fill() call. + Otherwise it will be ignored. + + 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._path = [self._position] + self._filling = 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 + frac = abs(extent)/self._fullcircle + steps = 1+int(min(11+abs(radius)/6.0, 59.0)*frac) + w = 1.0 * extent / steps + w2 = 0.5 * w + l = 2.0 * radius * sin(w2*self._invradian) + if radius < 0: + l, w, w2 = -l, -w, -w2 + self.left(w2) + for i in range(steps): + self.forward(l) + self.left(w) + self.right(w2) + + 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] + 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 = self._position + self._position = (float(x1), float(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): + """ 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: + self._canvas.update() + 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 # 50% of window width +_height = 0.75 # 75% of window height +_startx = None +_starty = None +_title = "Turtle Graphics" # default title + +class Pen(RawPen): + + def __init__(self): + global _root, _canvas + 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): + global _root, _canvas, _pen + root = self._canvas._root() + if root is _root: + _pen = None + _root = None + _canvas = None + root.destroy() + +def _getpen(): + global _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() +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 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() +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 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 is None: + _width = width + else: + raise ValueError("width can not be less than 0") + + height = geometry.get('height',_height) + if height >= 0 or height is None: + _height = height + else: + raise ValueError("height can not be less than 0") + + startx = geometry.get('startx', _startx) + if startx >= 0 or startx is None: + _startx = _startx + else: + raise ValueError("startx can not be less than 0") + + starty = geometry.get('starty', _starty) + if starty >= 0 or starty is 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() + 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) + tracer(1) + # more text + write("end") + +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) + + # draw and fill a concave shape + left(120) + up() + forward(70) + right(30) + down() + color("red") + speed("fastest") + fill(1) + for i in range(4): + circle(50,90) + right(90) + forward(30) + right(90) + color("yellow") + fill(0) + left(90) + up() + forward(30) + down(); + + color("red") + + # 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__': + demo() + sleep(3) + demo2() + done() |