diff --git a/Demo/turtle/tdemo_nim.py b/Demo/turtle/tdemo_nim.py new file mode 100644 index 00000000000..b0edf447142 --- /dev/null +++ b/Demo/turtle/tdemo_nim.py @@ -0,0 +1,227 @@ +""" turtle-example-suite: + + tdemo_nim.py + +Play nim against the computer. The player +who takes the last stick is the winner. + +Implements the model-view-controller +design pattern. +""" + + +import turtle +import random +import time + +SCREENWIDTH = 640 +SCREENHEIGHT = 480 + +MINSTICKS = 7 +MAXSTICKS = 31 + +HUNIT = SCREENHEIGHT // 12 +WUNIT = SCREENWIDTH // ((MAXSTICKS // 5) * 11 + (MAXSTICKS % 5) * 2) + +SCOLOR = (63, 63, 31) +HCOLOR = (255, 204, 204) +COLOR = (204, 204, 255) + +def randomrow(): + return random.randint(MINSTICKS, MAXSTICKS) + +def computerzug(state): + xored = state[0] ^ state[1] ^ state[2] + if xored == 0: + return randommove(state) + for z in range(3): + s = state[z] ^ xored + if s <= state[z]: + move = (z, s) + return move + +def randommove(state): + m = max(state) + while True: + z = random.randint(0,2) + if state[z] > (m > 1): + break + rand = random.randint(m > 1, state[z]-1) + return z, rand + + +class NimModel(object): + def __init__(self, game): + self.game = game + + def setup(self): + if self.game.state not in [Nim.CREATED, Nim.OVER]: + return + self.sticks = [randomrow(), randomrow(), randomrow()] + self.player = 0 + self.winner = None + self.game.view.setup() + self.game.state = Nim.RUNNING + + def move(self, row, col): + maxspalte = self.sticks[row] + self.sticks[row] = col + self.game.view.notify_move(row, col, maxspalte, self.player) + if self.game_over(): + self.game.state = Nim.OVER + self.winner = self.player + self.game.view.notify_over() + elif self.player == 0: + self.player = 1 + row, col = computerzug(self.sticks) + self.move(row, col) + self.player = 0 + + def game_over(self): + return self.sticks == [0, 0, 0] + + def notify_move(self, row, col): + if self.sticks[row] <= col: + return + self.move(row, col) + + +class Stick(turtle.Turtle): + def __init__(self, row, col, game): + turtle.Turtle.__init__(self, visible=False) + self.row = row + self.col = col + self.game = game + x, y = self.coords(row, col) + self.shape("square") + self.shapesize(HUNIT/10.0, WUNIT/20.0) + self.speed(0) + self.pu() + self.goto(x,y) + self.color("white") + self.showturtle() + + def coords(self, row, col): + packet, remainder = divmod(col, 5) + x = (3 + 11 * packet + 2 * remainder) * WUNIT + y = (2 + 3 * row) * HUNIT + return x - SCREENWIDTH // 2 + WUNIT // 2, SCREENHEIGHT // 2 - y - HUNIT // 2 + + def makemove(self, x, y): + if self.game.state != Nim.RUNNING: + return + self.game.controller.notify_move(self.row, self.col) + + +class NimView(object): + def __init__(self, game): + self.game = game + self.screen = game.screen + self.model = game.model + self.screen.colormode(255) + self.screen.tracer(False) + self.screen.bgcolor((240, 240, 255)) + self.writer = turtle.Turtle(visible=False) + self.writer.pu() + self.writer.speed(0) + self.sticks = {} + for row in range(3): + for col in range(MAXSTICKS): + self.sticks[(row, col)] = Stick(row, col, game) + self.display("... a moment please ...") + self.screen.tracer(True) + + def display(self, msg1, msg2=None): + self.screen.tracer(False) + self.writer.clear() + if msg2 is not None: + self.writer.goto(0, - SCREENHEIGHT // 2 + 48) + self.writer.pencolor("red") + self.writer.write(msg2, align="center", font=("Courier",18,"bold")) + self.writer.goto(0, - SCREENHEIGHT // 2 + 20) + self.writer.pencolor("black") + self.writer.write(msg1, align="center", font=("Courier",14,"bold")) + self.screen.tracer(True) + + + def setup(self): + self.screen.tracer(False) + for row in range(3): + for col in range(self.model.sticks[row]): + self.sticks[(row, col)].color(SCOLOR) + for row in range(3): + for col in range(self.model.sticks[row], MAXSTICKS): + self.sticks[(row, col)].color("white") + self.display("Your turn! Click leftmost stick to remove.") + self.screen.tracer(True) + + def notify_move(self, row, col, maxspalte, player): + if player == 0: + farbe = HCOLOR + for s in range(col, maxspalte): + self.sticks[(row, s)].color(farbe) + else: + self.display(" ... thinking ... ") + time.sleep(0.5) + self.display(" ... thinking ... aaah ...") + farbe = COLOR + for s in range(maxspalte-1, col-1, -1): + time.sleep(0.2) + self.sticks[(row, s)].color(farbe) + self.display("Your turn! Click leftmost stick to remove.") + + def notify_over(self): + if self.game.model.winner == 0: + msg2 = "Congrats. You're the winner!!!" + else: + msg2 = "Sorry, the computer is the winner." + self.display("To play again press space bar. To leave press ESC.", msg2) + + def clear(self): + if self.game.state == Nim.OVER: + self.screen.clear() + +class NimController(object): + + def __init__(self, game): + self.game = game + self.sticks = game.view.sticks + self.BUSY = False + for stick in self.sticks.values(): + stick.onclick(stick.makemove) + self.game.screen.onkey(self.game.model.setup, "space") + self.game.screen.onkey(self.game.view.clear, "Escape") + self.game.view.display("Press space bar to start game") + self.game.screen.listen() + + def notify_move(self, row, col): + if self.BUSY: + return + self.BUSY = True + self.game.model.notify_move(row, col) + self.BUSY = False + +class Nim(object): + CREATED = 0 + RUNNING = 1 + OVER = 2 + def __init__(self, screen): + self.state = Nim.CREATED + self.screen = screen + self.model = NimModel(self) + self.view = NimView(self) + self.controller = NimController(self) + + +mainscreen = turtle.Screen() +mainscreen.mode("standard") +mainscreen.setup(SCREENWIDTH, SCREENHEIGHT) + +def main(): + nim = Nim(mainscreen) + return "EVENTLOOP!" + +if __name__ == "__main__": + main() + turtle.mainloop() + diff --git a/Demo/turtle/tdemo_round_dance.py b/Demo/turtle/tdemo_round_dance.py new file mode 100644 index 00000000000..67676d055ce --- /dev/null +++ b/Demo/turtle/tdemo_round_dance.py @@ -0,0 +1,90 @@ +""" turtle-example-suite: + + tdemo_round_dance.py + +(Needs version 1.1 of the turtle module that +comes with Python 3.1) + +Dancing turtles have a compound shape +consisting of a series of triangles of +decreasing size. + +Turtles march along a circle while rotating +pairwise in opposite direction, with one +exception. Does that breaking of symmetry +enhance the attractiveness of the example? + +Press any key to stop the animation. + +Technically: demonstrates use of compound +shapes, transformation of shapes as well as +cloning turtles. The animation is +controlled through update(). +""" + +from turtle import * + +def stop(): + global running + running = False + +def main(): + global running + clearscreen() + bgcolor("gray10") + tracer(False) + shape("triangle") + f = 0.793402 + phi = 9.064678 + s = 5 + c = 1 + # create compound shape + sh = Shape("compound") + for i in range(10): + shapesize(s) + p =get_shapepoly() + s *= f + c *= f + tilt(-phi) + sh.addcomponent(p, (c, 0.25, 1-c), "black") + register_shape("multitri", sh) + # create dancers + shapesize(1) + shape("multitri") + pu() + setpos(0, -200) + dancers = [] + for i in range(180): + fd(7) + tilt(-4) + lt(2) + update() + if i % 12 == 0: + dancers.append(clone()) + home() + # dance + running = True + onkeypress(stop) + listen() + cs = 1 + while running: + ta = -4 + for dancer in dancers: + dancer.fd(7) + dancer.lt(2) + dancer.tilt(ta) + ta = -4 if ta > 0 else 2 + if cs < 180: + right(4) + shapesize(cs) + cs *= 1.005 + update() + return "DONE!" + +if __name__=='__main__': + print(main()) + mainloop() + + + + diff --git a/Doc/library/turtle.rst b/Doc/library/turtle.rst index 001f349fec8..b3387b6d88a 100644 --- a/Doc/library/turtle.rst +++ b/Doc/library/turtle.rst @@ -149,9 +149,12 @@ Turtle state | :func:`shape` | :func:`resizemode` | :func:`shapesize` | :func:`turtlesize` + | :func:`shearfactor` | :func:`settiltangle` | :func:`tiltangle` | :func:`tilt` + | :func:`shapetransform` + | :func:`get_shapepoly` Using events | :func:`onclick` @@ -187,9 +190,11 @@ Animation control Using screen events | :func:`listen` - | :func:`onkey` + | :func:`onkey` | :func:`onkeyrelease` + | :func:`onkeypress` | :func:`onclick` | :func:`onscreenclick` | :func:`ontimer` + | :func:`mainloop` Settings and special methods | :func:`mode` @@ -201,6 +206,10 @@ Settings and special methods | :func:`window_height` | :func:`window_width` +Input methods + | :func:`textinput` + | :func:`numinput` + Methods specific to Screen | :func:`bye` | :func:`exitonclick` @@ -1157,6 +1166,26 @@ Appearance (5, 5, 8) +.. function:: shearfactor(self, shear=None): + + :param shear: number (optional) + + Set or return the current shearfactor. Shear the turtleshape according to + the given shearfactor shear, which is the tangent of the shear angle. + Do *not* change the turtle's heading (direction of movement). + If shear is not given: return the current shearfactor, i. e. the + tangent of the shear angle, by which lines parallel to the + heading of the turtle are sheared. + + .. doctest:: + + >>> turtle.shape("circle") + >>> turtle.shapesize(5,2) + >>> turtle.shearfactor(0.5) + >>> turtle.shearfactor() + >>> 0.5 + + .. function:: tilt(angle) :param angle: a number @@ -1194,10 +1223,19 @@ Appearance >>> turtle.fd(50) -.. function:: tiltangle() +.. function:: tiltangle(angle=None) - Return the current tilt-angle, i.e. the angle between the orientation of the - turtleshape and the heading of the turtle (its direction of movement). + :param angle: a number (optional) + + Set or return the current tilt-angle. If angle is given, rotate the + turtleshape to point in the direction specified by angle, + regardless of its current tilt-angle. Do *not* change the turtle's + heading (direction of movement). + If angle is not given: return the current tilt-angle, i. e. the angle + between the orientation of the turtleshape and the heading of the + turtle (its direction of movement). + + Deprecated since Python 3.1 .. doctest:: @@ -1209,6 +1247,46 @@ Appearance 45.0 +.. function:: shapetransform(t11=None, t12=None, t21=None, t22=None) + + :param t11: a number (optional) + :param t12: a number (optional) + :param t21: a number (optional) + :param t12: a number (optional) + + Set or return the current transformation matrix of the turtle shape. + + If none of the matrix elements are given, return the transformation + matrix as a tuple of 4 elements. + Otherwise set the given elements and transform the turtleshape + according to the matrix consisting of first row t11, t12 and + second row t21, 22. The determinant t11 * t22 - t12 * t21 must not be + zero, otherwise an error is raised. + Modify stretchfactor, shearfactor and tiltangle according to the + given matrix. + + .. doctest:: + + >>> turtle.shape("square") + >>> turtle.shapesize(4,2) + >>> turtle.shearfactor(-0.5) + >>> turtle.shapetransform() + >>> (4.0, -1.0, -0.0, 2.0) + + +.. function:: get_shapepoly(): + + Return the current shape polygon as tuple of coordinate pairs. This + can be used to define a new shape or components of a compound shape. + + .. doctest:: + + >>> turtle.shape("square") + >>> turtle.shapetransform(4, -1, 0, 2) + >>> turtle.get_shapepoly() + ((50, -20), (30, 20), (-50, 20), (-30, -20)) + + Using events ------------ @@ -1595,6 +1673,7 @@ Using screen events .. function:: onkey(fun, key) + onkeyrelease(fun, key) :param fun: a function with no arguments or ``None`` :param key: a string: key (e.g. "a") or key-symbol (e.g. "space") @@ -1613,6 +1692,25 @@ Using screen events >>> screen.listen() +.. function:: onkeypress(fun, key=None): + + :param fun: a function with no arguments or ``None`` + :param key: a string: key (e.g. "a") or key-symbol (e.g. "space") + + Bind *fun* to key-press event of key if key is given, + or to any key-press-event if no key is given. + Remark: in order to be able to register key-events, TurtleScreen + must have focus. (See method :func:`listen`.) + + .. doctest:: + + >>> def f(): + ... fd(50) + ... + >>> screen.onkey(f, "Up") + >>> screen.listen() + + .. function:: onclick(fun, btn=1, add=None) onscreenclick(fun, btn=1, add=None) @@ -1659,6 +1757,53 @@ Using screen events >>> running = False +.. function:: mainloop() + + Starts event loop - calling Tkinter's mainloop function. + Must be the last statement in a turtle graphics program. + Must *not* be used if a script is run from within IDLE in -n mode + (No subprocess) - for interactive use of turtle graphics. :: + + >>> screen.mainloop() + + +Input methods +------------- + +.. function:: textinput(title, prompt) + + :param title: string + :param prompt: string + + Pop up a dialog window for input of a string. Parameter title is + the title of the dialog window, propmt is a text mostly describing + what information to input. + Return the string input. If the dialog is canceled, return None. :: + + >>> screen.textinput("NIM", "Name of first player:") + + +.. function:: numinput(self, title, prompt, + default=None, minval=None, maxval=None): + + :param title: string + :param prompt: string + :param default: number (optional) + :param prompt: number (optional) + :param prompt: number (optional) + + Pop up a dialog window for input of a number. title is the title of the + dialog window, prompt is a text mostly describing what numerical information + to input. default: default value, minval: minimum value for imput, + maxval: maximum value for input + The number input must be in the range minval .. maxval if these are + given. If not, a hint is issued and the dialog remains open for + correction. + Return the number input. If the dialog is canceled, return None. :: + + >>> screen.numinput("Poker", "Your stakes:", 1000, minval=10, maxval=10000) + + Settings and special methods ---------------------------- @@ -2159,6 +2304,10 @@ The demoscripts are: | | | as Hanoi discs | | | | (shape, shapesize) | +----------------+------------------------------+-----------------------+ +| nim | play the classical nim game | turtles as nimsticks, | +| | with three heaps of sticks | event driven (mouse, | +| | against the computer. | keyboard) | ++----------------+------------------------------+-----------------------+ | paint | super minimalistic | :func:`onclick` | | | drawing program | | +----------------+------------------------------+-----------------------+ @@ -2171,6 +2320,10 @@ The demoscripts are: | planet_and_moon| simulation of | compound shapes, | | | gravitational system | :class:`Vec2D` | +----------------+------------------------------+-----------------------+ +| round_dance | dancing turtles rotating | compound shapes, clone| +| | pairwise in opposite | shapesize, tilt, | +| | direction | get_polyshape, update | ++----------------+------------------------------+-----------------------+ | tree | a (graphical) breadth | :func:`clone` | | | first tree (using generators)| | +----------------+------------------------------+-----------------------+ @@ -2204,6 +2357,32 @@ Changes since Python 2.6 This behaviour corresponds to a ``fill()`` call without arguments in Python 2.6. +Changes since Python 3.0 +======================== + +- The methods :meth:`Turtle.shearfactor`, :meth:`Turtle.shapetransform` and + :meth:`Turtle.get_shapepoly` have been added. Thus the full range of + regular linear transforms is now available for transforming turtle shapes. + :meth:`Turtle.tiltangle` has been enhanced in functionality: it now can + be used to get or set the tiltangle. :meth:`Turtle.settiltangle` has been + deprecated. + +- The method :meth:`Screen.onkeypress` has been added as a complement to + :meth:`Screen.onkey` which in fact binds actions to the keyrelease event. + Accordingly the latter has got an alias: :meth:`Screen.onkeyrelease`. + +- The method :meth:`Screen.mainloop` has been added. So when working only + with Screen and Turtle objects one must not additonally import + :func:`mainloop` anymore. + +- Two input methods has been added :meth:`Screen.textinput` and + :meth:`Screen.numinput`. These popup input dialogs and return + strings and numbers respectively. + +- Two example scripts :file:`tdemo_nim.py` and :file:`tdemo_round_dance.py` + have been added to the Demo directory (source distribution only). As usual + they can be viewed and executed within the demo viewer :file:`turtleDemo.py`. + .. doctest:: :hide: diff --git a/Lib/turtle.py b/Lib/turtle.py index ded2b2712b8..f0e4712565c 100644 --- a/Lib/turtle.py +++ b/Lib/turtle.py @@ -1,8 +1,8 @@ # # turtle.py: a Tkinter based turtle graphics module for Python -# Version 1.0b1 - 31. 5. 2008 +# Version 1.1b - 4. 5. 2009 # -# Copyright (C) 2006 - 2008 Gregor Lingl +# Copyright (C) 2006 - 2009 Gregor Lingl # email: glingl@aon.at # # This software is provided 'as-is', without any express or implied @@ -100,7 +100,7 @@ extensions in in mind. These will be commented and documented elsewhere. """ -_ver = "turtle 1.0b1- - for Python 3.0 - 9. 6. 2008, 01:15" +_ver = "turtle 1.1b- - for Python 3.1 - 4. 5. 2009" # print(_ver) @@ -112,36 +112,31 @@ import os from os.path import isfile, split, join from copy import deepcopy - -#from math import * ## for compatibility with old turtle module +from tkinter import simpledialog _tg_classes = ['ScrolledCanvas', 'TurtleScreen', 'Screen', 'RawTurtle', 'Turtle', 'RawPen', 'Pen', 'Shape', 'Vec2D'] _tg_screen_functions = ['addshape', 'bgcolor', 'bgpic', 'bye', 'clearscreen', 'colormode', 'delay', 'exitonclick', 'getcanvas', - 'getshapes', 'listen', 'mode', 'onkey', 'onscreenclick', 'ontimer', + 'getshapes', 'listen', 'mainloop', 'mode', 'numinput', + 'onkey', 'onkeypress', 'onkeyrelease', 'onscreenclick', 'ontimer', 'register_shape', 'resetscreen', 'screensize', 'setup', - 'setworldcoordinates', 'title', 'tracer', 'turtles', 'update', + 'setworldcoordinates', 'textinput', 'title', 'tracer', 'turtles', 'update', 'window_height', 'window_width'] _tg_turtle_functions = ['back', 'backward', 'begin_fill', 'begin_poly', 'bk', 'circle', 'clear', 'clearstamp', 'clearstamps', 'clone', 'color', 'degrees', 'distance', 'dot', 'down', 'end_fill', 'end_poly', 'fd', - #'fill', - 'fillcolor', 'forward', 'get_poly', 'getpen', 'getscreen', + 'fillcolor', 'forward', 'get_poly', 'getpen', 'getscreen', 'get_shapepoly', 'getturtle', 'goto', 'heading', 'hideturtle', 'home', 'ht', 'isdown', 'isvisible', 'left', 'lt', 'onclick', 'ondrag', 'onrelease', 'pd', 'pen', 'pencolor', 'pendown', 'pensize', 'penup', 'pos', 'position', 'pu', 'radians', 'right', 'reset', 'resizemode', 'rt', 'seth', 'setheading', 'setpos', 'setposition', 'settiltangle', - 'setundobuffer', 'setx', 'sety', 'shape', 'shapesize', 'showturtle', - 'speed', 'st', 'stamp', 'tilt', 'tiltangle', 'towards', #'tracer', + 'setundobuffer', 'setx', 'sety', 'shape', 'shapesize', 'shapetransform', 'shearfactor', 'showturtle', + 'speed', 'st', 'stamp', 'tilt', 'tiltangle', 'towards', 'turtlesize', 'undo', 'undobufferentries', 'up', 'width', - #'window_height', 'window_width', 'write', 'xcor', 'ycor'] -_tg_utilities = ['write_docstringdict', 'done', 'mainloop'] -##_math_functions = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', -## 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', -## 'log10', 'modf', 'pi', 'pow', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'] +_tg_utilities = ['write_docstringdict', 'done'] __all__ = (_tg_classes + _tg_screen_functions + _tg_turtle_functions + _tg_utilities) # + _math_functions) @@ -172,16 +167,6 @@ _CFG = {"width" : 0.5, # Screen "using_IDLE": False } -##print "cwd:", os.getcwd() -##print "__file__:", __file__ -## -##def show(dictionary): -## print "==========================" -## for key in sorted(dictionary.keys()): -## print key, ":", dictionary[key] -## print "==========================" -## print - def config_dict(filename): """Convert content of config-file into dictionary.""" f = open(filename, "r") @@ -230,7 +215,6 @@ def readconfig(cfgdict): cfgdict2 = {} if isfile(default_cfg): cfgdict1 = config_dict(default_cfg) - #print "1. Loading config-file %s from: %s" % (default_cfg, os.getcwd()) if "importconfig" in cfgdict1: default_cfg = "turtle_%s.cfg" % cfgdict1["importconfig"] try: @@ -239,15 +223,9 @@ def readconfig(cfgdict): except: cfg_file2 = "" if isfile(cfg_file2): - #print "2. Loading config-file %s:" % cfg_file2 cfgdict2 = config_dict(cfg_file2) -## show(_CFG) -## show(cfgdict2) _CFG.update(cfgdict2) -## show(_CFG) -## show(cfgdict1) _CFG.update(cfgdict1) -## show(_CFG) try: readconfig(_CFG) @@ -697,7 +675,7 @@ class TurtleScreenBase(object): fun(x, y) self.cv.bind("" % num, eventfun, add) - def _onkey(self, fun, key): + def _onkeyrelease(self, fun, key): """Bind fun to key-release event of key. Canvas must have focus. See method listen """ @@ -708,6 +686,24 @@ class TurtleScreenBase(object): fun() self.cv.bind("" % key, eventfun) + def _onkeypress(self, fun, key=None): + """If key is given, bind fun to key-press event of key. + Otherwise bind fun to any key-press. + Canvas must have focus. See method listen. + """ + if fun is None: + if key is None: + self.cv.unbind("", None) + else: + self.cv.unbind("" % key, None) + else: + def eventfun(event): + fun() + if key is None: + self.cv.bind("", eventfun) + else: + self.cv.bind("" % key, eventfun) + def _listen(self): """Set focus on canvas (in order to collect key-events) """ @@ -801,6 +797,57 @@ class TurtleScreenBase(object): height = self.cv['height'] return width, height + def mainloop(self): + """Starts event loop - calling Tkinter's mainloop function. + + No argument. + + Must be last statement in a turtle graphics program. + Must NOT be used if a script is run from within IDLE in -n mode + (No subprocess) - for interactive use of turtle graphics. + + Example (for a TurtleScreen instance named screen): + >>> screen.mainloop() + + """ + TK.mainloop() + + def textinput(self, title, prompt): + """Pop up a dialog window for input of a string. + + Arguments: title is the title of the dialog window, + prompt is a text mostly describing what information to input. + + Return the string input + If the dialog is canceled, return None. + + Example (for a TurtleScreen instance named screen): + >>> screen.textinput("NIM", "Name of first player:") + + """ + return simpledialog.askstring(title, prompt) + + def numinput(self, title, prompt, default=None, minval=None, maxval=None): + """Pop up a dialog window for input of a number. + + Arguments: title is the title of the dialog window, + prompt is a text mostly describing what numerical information to input. + default: default value + minval: minimum value for imput + maxval: maximum value for input + + The number input must be in the range minval .. maxval if these are + given. If not, a hint is issued and the dialog remains open for + correction. Return the number input. + If the dialog is canceled, return None. + + Example (for a TurtleScreen instance named screen): + >>> screen.numinput("Poker", "Your stakes:", 1000, minval=10, maxval=10000) + + """ + return simpledialog.askfloat(title, prompt, initialvalue=default, + minvalue=minval, maxvalue=maxval) + ############################################################################## ### End of Tkinter - interface ### @@ -913,7 +960,6 @@ class TurtleScreen(TurtleScreenBase): upon components of the underlying graphics toolkit - which is Tkinter in this case. """ -# _STANDARD_DELAY = 5 _RUNNING = True def __init__(self, cv, mode=_CFG["mode"], @@ -951,11 +997,11 @@ class TurtleScreen(TurtleScreenBase): def clear(self): """Delete all drawings and all turtles from the TurtleScreen. + No argument. + Reset empty TurtleScreen to its initial state: white background, no backgroundimage, no eventbindings and tracing on. - No argument. - Example (for a TurtleScreen instance named screen): screen.clear() @@ -972,8 +1018,10 @@ class TurtleScreen(TurtleScreenBase): self.bgcolor("white") for btn in 1, 2, 3: self.onclick(None, btn) + self.onkeypress(None) for key in self._keys[:]: self.onkey(None, key) + self.onkeypress(None, key) Turtle._pen = None def mode(self, mode=None): @@ -1083,7 +1131,6 @@ class TurtleScreen(TurtleScreenBase): shape = Shape("polygon", shape) ## else shape assumed to be Shape-instance self._shapes[name] = shape - # print "shape added:" , self._shapes def _colorstr(self, color): """Return color string corresponding to args. @@ -1243,9 +1290,12 @@ class TurtleScreen(TurtleScreenBase): def update(self): """Perform a TurtleScreen update. """ + tracing = self._tracing + self._tracing = True for t in self.turtles(): t._update_data() t._drawturtle() + self._tracing = tracing self._update() def window_width(self): @@ -1336,10 +1386,44 @@ class TurtleScreen(TurtleScreenBase): ### consequently drawing a hexagon """ if fun == None: - self._keys.remove(key) + if key in self._keys: + self._keys.remove(key) elif key not in self._keys: self._keys.append(key) - self._onkey(fun, key) + self._onkeyrelease(fun, key) + + def onkeypress(self, fun, key=None): + """Bind fun to key-press event of key if key is given, + or to any key-press-event if no key is given. + + Arguments: + fun -- a function with no arguments + key -- a string: key (e.g. "a") or key-symbol (e.g. "space") + + In order to be able to register key-events, TurtleScreen + must have focus. (See method listen.) + + Example (for a TurtleScreen instance named screen + and a Turtle instance named turtle): + + >>> def f(): + fd(50) + + + >>> screen.onkey(f, "Up") + >>> screen.listen() + + ### Subsequently the turtle can be moved by + ### repeatedly pressing the up-arrow key, + ### or by keeping pressed the up-arrow key. + ### consequently drawing a hexagon. + """ + if fun == None: + if key in self._keys: + self._keys.remove(key) + elif key is not None and key not in self._keys: + self._keys.append(key) + self._onkeypress(fun, key) def listen(self, xdummy=None, ydummy=None): """Set focus on TurtleScreen (in order to collect key-events) @@ -1421,6 +1505,7 @@ class TurtleScreen(TurtleScreenBase): resetscreen = reset clearscreen = clear addshape = register_shape + onkeyrelease = onkey class TNavigator(object): """Navigation part of the RawTurtle. @@ -1947,10 +2032,11 @@ class TPen(object): self._fillcolor = fillcolor self._drawing = True self._speed = 3 - self._stretchfactor = (1, 1) - self._tilt = 0 + self._stretchfactor = (1., 1.) + self._shearfactor = 0. + self._tilt = 0. + self._shapetrafo = (1., 0., 0., 1.) self._outlinewidth = 1 - ### self.screen = None # to override by child class def resizemode(self, rmode=None): """Set resizemode to one of the values: "auto", "user", "noresize". @@ -2262,6 +2348,7 @@ class TPen(object): "speed" : number in range 0..10 "resizemode" : "auto" or "user" or "noresize" "stretchfactor": (positive number, positive number) + "shearfactor": number "outline" : positive number "tilt" : number @@ -2276,19 +2363,19 @@ class TPen(object): >>> turtle.pen() {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, 'pencolor': 'red', 'pendown': True, 'fillcolor': 'black', - 'stretchfactor': (1,1), 'speed': 3} + 'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} >>> penstate=turtle.pen() >>> turtle.color("yellow","") >>> turtle.penup() >>> turtle.pen() {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, 'pencolor': 'yellow', 'pendown': False, 'fillcolor': '', - 'stretchfactor': (1,1), 'speed': 3} + 'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} >>> p.pen(penstate, fillcolor="green") >>> p.pen() {'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1, 'pencolor': 'red', 'pendown': True, 'fillcolor': 'green', - 'stretchfactor': (1,1), 'speed': 3} + 'stretchfactor': (1,1), 'speed': 3, 'shearfactor': 0.0} """ _pd = {"shown" : self._shown, "pendown" : self._drawing, @@ -2298,6 +2385,7 @@ class TPen(object): "speed" : self._speed, "resizemode" : self._resizemode, "stretchfactor" : self._stretchfactor, + "shearfactor" : self._shearfactor, "outline" : self._outlinewidth, "tilt" : self._tilt } @@ -2351,12 +2439,20 @@ class TPen(object): if isinstance(sf, (int, float)): sf = (sf, sf) self._stretchfactor = sf + if "shearfactor" in p: + self._shearfactor = p["shearfactor"] if "outline" in p: self._outlinewidth = p["outline"] if "shown" in p: self._shown = p["shown"] if "tilt" in p: self._tilt = p["tilt"] + if "stretchfactor" in p or "tilt" in p or "shearfactor" in p: + scx, scy = self._stretchfactor + shf = self._shearfactor + sa, ca = math.sin(self._tilt), math.cos(self._tilt) + self._shapetrafo = ( scx*ca, scy*(shf*ca + sa), + -scx*sa, scy*(ca - shf*sa)) self._update() ## three dummy methods to be implemented by child class: @@ -2389,7 +2485,7 @@ class _TurtleImage(object): self._setshape(shapeIndex) def _setshape(self, shapeIndex): - screen = self.screen # RawTurtle.screens[self.screenIndex] + screen = self.screen self.shapeIndex = shapeIndex if self._type == "polygon" == screen._shapes[shapeIndex]._type: return @@ -2699,9 +2795,11 @@ class RawTurtle(TPen, TNavigator): >>> turtle.shapesize(5, 5, 12) >>> turtle.shapesize(outline=8) """ - if stretch_wid is None and stretch_len is None and outline == None: + if stretch_wid is stretch_len is outline == None: stretch_wid, stretch_len = self._stretchfactor return stretch_wid, stretch_len, self._outlinewidth + if stretch_wid == 0 or stretch_len == 0: + raise TurtleGraphicsError("stretch_wid/stretch_len must not be zero") if stretch_wid is not None: if stretch_len is None: stretchfactor = stretch_wid, stretch_wid @@ -2716,11 +2814,33 @@ class RawTurtle(TPen, TNavigator): self.pen(resizemode="user", stretchfactor=stretchfactor, outline=outline) + def shearfactor(self, shear=None): + """Set or return the current shearfactor. + + Optional argument: shear -- number, tangent of the shear angle + + Shear the turtleshape according to the given shearfactor shear, + which is the tangent of the shear angle. DO NOT change the + turtle's heading (direction of movement). + If shear is not given: return the current shearfactor, i. e. the + tangent of the shear angle, by which lines parallel to the + heading of the turtle are sheared. + + Examples (for a Turtle instance named turtle): + >>> turtle.shape("circle") + >>> turtle.shapesize(5,2) + >>> turtle.shearfactor(0.5) + >>> turtle.shearfactor() + >>> 0.5 + """ + if shear is None: + return self._shearfactor + self.pen(resizemode="user", shearfactor=shear) + def settiltangle(self, angle): """Rotate the turtleshape to point in the specified direction - Optional argument: - angle -- number + Argument: angle -- number Rotate the turtleshape to point in the direction specified by angle, regardless of its current tilt-angle. DO NOT change the turtle's @@ -2741,14 +2861,19 @@ class RawTurtle(TPen, TNavigator): tilt = (tilt * math.pi / 180.0) % (2*math.pi) self.pen(resizemode="user", tilt=tilt) - def tiltangle(self): - """Return the current tilt-angle. + def tiltangle(self, angle=None): + """Set or return the current tilt-angle. - No argument. + Optional argument: angle -- number - Return the current tilt-angle, i. e. the angle between the - orientation of the turtleshape and the heading of the turtle - (its direction of movement). + Rotate the turtleshape to point in the direction specified by angle, + regardless of its current tilt-angle. DO NOT change the turtle's + heading (direction of movement). + If angle is not given: return the current tilt-angle, i. e. the angle + between the orientation of the turtleshape and the heading of the + turtle (its direction of movement). + + Deprecated since Python 3.1 Examples (for a Turtle instance named turtle): >>> turtle.shape("circle") @@ -2757,8 +2882,11 @@ class RawTurtle(TPen, TNavigator): >>> turtle.tiltangle() >>> """ - tilt = -self._tilt * (180.0/math.pi) * self._angleOrient - return (tilt / self._degreesPerAU) % self._fullcircle + if angle is None: + tilt = -self._tilt * (180.0/math.pi) * self._angleOrient + return (tilt / self._degreesPerAU) % self._fullcircle + else: + self.settiltangle(angle) def tilt(self, angle): """Rotate the turtleshape by angle. @@ -2779,6 +2907,46 @@ class RawTurtle(TPen, TNavigator): """ self.settiltangle(angle + self.tiltangle()) + def shapetransform(self, t11=None, t12=None, t21=None, t22=None): + """Set or return the current transformation matrix of the turtle shape. + + Optional arguments: t11, t12, t21, t22 -- numbers. + + If none of the matrix elements are given, return the transformation + matrix. + Otherwise set the given elements and transform the turtleshape + according to the matrix consisting of first row t11, t12 and + second row t21, 22. + Modify stretchfactor, shearfactor and tiltangle according to the + given matrix. + + Examples (for a Turtle instance named turtle): + >>> turtle.shape("square") + >>> turtle.shapesize(4,2) + >>> turtle.shearfactor(-0.5) + >>> turtle.shapetransform() + >>> (4.0, -1.0, -0.0, 2.0) + """ + if t11 is t12 is t21 is t22 is None: + return self._shapetrafo + m11, m12, m21, m22 = self._shapetrafo + if t11 is not None: m11 = t11 + if t12 is not None: m12 = t12 + if t21 is not None: m21 = t21 + if t22 is not None: m22 = t22 + if t11 * t22 - t12 * t21 == 0: + raise TurtleGraphicsError("Bad shape transform matrix: must not be singular") + self._shapetrafo = (m11, m12, m21, m22) + alfa = math.atan2(-m21, m11) % (2 * math.pi) + sa, ca = math.sin(alfa), math.cos(alfa) + a11, a12, a21, a22 = (ca*m11 - sa*m21, ca*m12 - sa*m22, + sa*m11 + ca*m21, sa*m12 + ca*m22) + self._stretchfactor = a11, a22 + self._shearfactor = a12/a22 + self._tilt = alfa + self._update() + + def _polytrafo(self, poly): """Computes transformed polygon shapes from a shape according to current position and heading. @@ -2791,6 +2959,36 @@ class RawTurtle(TPen, TNavigator): return [(p0+(e1*x+e0*y)/screen.xscale, p1+(-e0*x+e1*y)/screen.yscale) for (x, y) in poly] + def get_shapepoly(self): + """Return the current shape polygon as tuple of coordinate pairs. + + No argument. + + Examples (for a Turtle instance named turtle): + >>> turtle.shape("square") + >>> turtle.shapetransform(4, -1, 0, 2) + >>> turtle.get_shapepoly() + ((50, -20), (30, 20), (-50, 20), (-30, -20)) + + """ + shape = self.screen._shapes[self.turtle.shapeIndex] + if shape._type == "polygon": + return self._getshapepoly(shape._data, shape._type == "compound") + # else return None + + def _getshapepoly(self, polygon, compound=False): + """Calculate transformed shape polygon according to resizemode + and shapetransform. + """ + if self._resizemode == "user" or compound: + t11, t12, t21, t22 = self._shapetrafo + elif self._resizemode == "auto": + l = max(1, self._pensize/5.0) + t11, t12, t21, t22 = l, 0, 0, l + elif self._resizemode == "noresize": + return polygon + return tuple([(t11*x + t12*y, t21*x + t22*y) for (x, y) in polygon]) + def _drawturtle(self): """Manages the correct rendering of the turtle with respect to its shape, resizemode, stretch and tilt etc.""" @@ -2802,35 +3000,20 @@ class RawTurtle(TPen, TNavigator): self._hidden_from_screen = False tshape = shape._data if ttype == "polygon": - if self._resizemode == "noresize": - w = 1 - shape = tshape - else: - if self._resizemode == "auto": - lx = ly = max(1, self._pensize/5.0) - w = self._pensize - tiltangle = 0 - elif self._resizemode == "user": - lx, ly = self._stretchfactor - w = self._outlinewidth - tiltangle = self._tilt - shape = [(lx*x, ly*y) for (x, y) in tshape] - t0, t1 = math.sin(tiltangle), math.cos(tiltangle) - shape = [(t1*x+t0*y, -t0*x+t1*y) for (x, y) in shape] - shape = self._polytrafo(shape) + if self._resizemode == "noresize": w = 1 + elif self._resizemode == "auto": w = self._pensize + else: w =self._outlinewidth + shape = self._polytrafo(self._getshapepoly(tshape)) fc, oc = self._fillcolor, self._pencolor screen._drawpoly(titem, shape, fill=fc, outline=oc, width=w, top=True) elif ttype == "image": screen._drawimage(titem, self._position, tshape) elif ttype == "compound": - lx, ly = self._stretchfactor - w = self._outlinewidth for item, (poly, fc, oc) in zip(titem, tshape): - poly = [(lx*x, ly*y) for (x, y) in poly] - poly = self._polytrafo(poly) + poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), - outline=self._cc(oc), width=w, top=True) + outline=self._cc(oc), width=self._outlinewidth, top=True) else: if self._hidden_from_screen: return @@ -2867,22 +3050,10 @@ class RawTurtle(TPen, TNavigator): tshape = shape._data if ttype == "polygon": stitem = screen._createpoly() - if self._resizemode == "noresize": - w = 1 - shape = tshape - else: - if self._resizemode == "auto": - lx = ly = max(1, self._pensize/5.0) - w = self._pensize - tiltangle = 0 - elif self._resizemode == "user": - lx, ly = self._stretchfactor - w = self._outlinewidth - tiltangle = self._tilt - shape = [(lx*x, ly*y) for (x, y) in tshape] - t0, t1 = math.sin(tiltangle), math.cos(tiltangle) - shape = [(t1*x+t0*y, -t0*x+t1*y) for (x, y) in shape] - shape = self._polytrafo(shape) + if self._resizemode == "noresize": w = 1 + elif self._resizemode == "auto": w = self._pensize + else: w =self._outlinewidth + shape = self._polytrafo(self._getshapepoly(tshape)) fc, oc = self._fillcolor, self._pencolor screen._drawpoly(stitem, shape, fill=fc, outline=oc, width=w, top=True) @@ -2895,13 +3066,10 @@ class RawTurtle(TPen, TNavigator): item = screen._createpoly() stitem.append(item) stitem = tuple(stitem) - lx, ly = self._stretchfactor - w = self._outlinewidth for item, (poly, fc, oc) in zip(stitem, tshape): - poly = [(lx*x, ly*y) for (x, y) in poly] - poly = self._polytrafo(poly) + poly = self._polytrafo(self._getshapepoly(poly, True)) screen._drawpoly(item, poly, fill=self._cc(fc), - outline=self._cc(oc), width=w, top=True) + outline=self._cc(oc), width=self._outlinewidth, top=True) self.stampItems.append(stitem) self.undobuffer.push(("stamp", stitem)) return stitem @@ -3137,57 +3305,6 @@ class RawTurtle(TPen, TNavigator): """ return isinstance(self._fillpath, list) -## def fill(self, flag=None): -## """Call fill(True) before drawing a shape to fill, fill(False) when done. -## -## Optional argument: -## flag -- True/False (or 1/0 respectively) -## -## Call fill(True) before drawing the shape you want to fill, -## and fill(False) when done. -## When used without argument: return fillstate (True if filling, -## False else) -## -## Example (for a Turtle instance named turtle): -## >>> turtle.fill(True) -## >>> turtle.forward(100) -## >>> turtle.left(90) -## >>> turtle.forward(100) -## >>> turtle.left(90) -## >>> turtle.forward(100) -## >>> turtle.left(90) -## >>> turtle.forward(100) -## >>> turtle.fill(False) -## """ -## filling = isinstance(self._fillpath, list) -## if flag is None: -## return filling -## screen = self.screen -## entry1 = entry2 = () -## if filling: -## if len(self._fillpath) > 2: -## self.screen._drawpoly(self._fillitem, self._fillpath, -## fill=self._fillcolor) -## entry1 = ("dofill", self._fillitem) -## if flag: -## self._fillitem = self.screen._createpoly() -## self.items.append(self._fillitem) -## self._fillpath = [self._position] -## entry2 = ("beginfill", self._fillitem) # , self._fillpath) -## self._newLine() -## else: -## self._fillitem = self._fillpath = None -## if self.undobuffer: -## if entry1 == (): -## if entry2 != (): -## self.undobuffer.push(entry2) -## else: -## if entry2 == (): -## self.undobuffer.push(entry1) -## else: -## self.undobuffer.push(["seq", entry1, entry2]) -## self._update() - def begin_fill(self): """Called just before drawing a shape to be filled. @@ -3243,7 +3360,6 @@ class RawTurtle(TPen, TNavigator): >>> turtle.dot() >>> turtle.fd(50); turtle.dot(20, "blue"); turtle.fd(50) """ - #print "dot-1:", size, color if not color: if isinstance(size, (str, tuple)): color = self._colorstr(size) @@ -3256,10 +3372,8 @@ class RawTurtle(TPen, TNavigator): if size is None: size = self._pensize + max(self._pensize, 4) color = self._colorstr(color) - #print "dot-2:", size, color if hasattr(self.screen, "_dot"): item = self.screen._dot(self._position, size, color) - #print "dot:", size, color, "item:", item self.items.append(item) if self.undobuffer: self.undobuffer.push(("dot", item)) @@ -3355,7 +3469,7 @@ class RawTurtle(TPen, TNavigator): >>> p = turtle.get_poly() >>> turtle.register_shape("myFavouriteShape", p) """ - ## check if there is any poly? -- 1st solution: + ## check if there is any poly? if self._poly is not None: return tuple(self._poly) @@ -3399,35 +3513,11 @@ class RawTurtle(TPen, TNavigator): ### screen oriented methods recurring to methods of TurtleScreen ################################################################ -## def window_width(self): -## """ Returns the width of the turtle window. -## -## No argument. -## -## Example (for a TurtleScreen instance named screen): -## >>> screen.window_width() -## 640 -## """ -## return self.screen._window_size()[0] -## -## def window_height(self): -## """ Return the height of the turtle window. -## -## No argument. -## -## Example (for a TurtleScreen instance named screen): -## >>> screen.window_height() -## 480 -## """ -## return self.screen._window_size()[1] - def _delay(self, delay=None): """Set delay value which determines speed of turtle animation. """ return self.screen.delay(delay) - ##### event binding methods ##### - def onclick(self, fun, btn=1, add=None): """Bind fun to mouse-click event on this turtle on canvas. @@ -3593,8 +3683,8 @@ class _Screen(TurtleScreen): topbottom = _CFG["topbottom"] self._root.setupcanvas(width, height, canvwidth, canvheight) _Screen._canvas = self._root._getcanvas() - self.setup(width, height, leftright, topbottom) TurtleScreen.__init__(self, _Screen._canvas) + self.setup(width, height, leftright, topbottom) def setup(self, width=_CFG["width"], height=_CFG["height"], startx=_CFG["leftright"], starty=_CFG["topbottom"]): @@ -3634,6 +3724,7 @@ class _Screen(TurtleScreen): if starty is None: starty = (sh - height) / 2 self._root.set_geometry(width, height, startx, starty) + self.update() def title(self, titlestring): """Set title of turtle-window @@ -3803,16 +3894,8 @@ def getmethparlist(ob): argText1 = argText2 = "" # bit of a hack for methods - turn it into a function # but we drop the "self" param. -## if type(ob)==types.MethodType: -## fob = ob.im_func -## argOffset = 1 -## else: -## fob = ob -## argOffset = 0 # Try and build one for Python defined functions argOffset = 1 -## if type(fob) in [types.FunctionType, types.LambdaType]: -## try: counter = ob.__code__.co_argcount items2 = list(ob.__code__.co_varnames[argOffset:counter]) realArgs = ob.__code__.co_varnames[argOffset:counter] @@ -3831,8 +3914,6 @@ def getmethparlist(ob): argText1 = "(%s)" % argText1 argText2 = ", ".join(items2) argText2 = "(%s)" % argText2 -## except: -## pass return argText1, argText2 def _turtle_docrevise(docstr): @@ -3871,7 +3952,6 @@ for methodname in _tg_screen_functions: continue defstr = ("def %(key)s%(pl1)s: return _getscreen().%(key)s%(pl2)s" % {'key':methodname, 'pl1':pl1, 'pl2':pl2}) -## print("Screen:", defstr) exec(defstr) eval(methodname).__doc__ = _screen_docrevise(eval('_Screen.'+methodname).__doc__) @@ -3882,13 +3962,11 @@ for methodname in _tg_turtle_functions: continue defstr = ("def %(key)s%(pl1)s: return _getpen().%(key)s%(pl2)s" % {'key':methodname, 'pl1':pl1, 'pl2':pl2}) -## print("Turtle:", defstr) exec(defstr) eval(methodname).__doc__ = _turtle_docrevise(eval('Turtle.'+methodname).__doc__) -done = mainloop = TK.mainloop -#del pl1, pl2, defstr +done = mainloop if __name__ == "__main__": def switchpen(): diff --git a/Misc/NEWS b/Misc/NEWS index 2e4c6bd84c7..5b4f0ab4828 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -118,6 +118,9 @@ Installation Library ------- +- Issue #5923: Update the ``turtle`` module to version 1.1, add two new + turtle demos in Demo/turtle. + - Issue #5692: In :class:`zipfile.Zipfile`, fix wrong path calculation when extracting a file to the root directory.