]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-88773: Added teleport method to Turtle library (#103974)
authorLiam Gersten <gerstenliam@gmail.com>
Sun, 30 Apr 2023 20:17:36 +0000 (16:17 -0400)
committerGitHub <noreply@github.com>
Sun, 30 Apr 2023 20:17:36 +0000 (13:17 -0700)
Add a `teleport` method to `turtle` module turtle instances that acts a lot like `goto`, _but_ ensures the pen is up while warping to the new position to and can control shape filling behavior as part of the jump.

Based on an educator user feature request.

---------

Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
Co-authored-by: Hugo van Kemenade <hugovk@users.noreply.github.com>
Co-authored-by: Gregory P. Smith <greg@krypto.org>
Doc/library/turtle.rst
Lib/test/test_turtle.py
Lib/turtle.py
Misc/NEWS.d/next/Library/2023-04-28-18-04-23.gh-issue-88773.xXCNJw.rst [new file with mode: 0644]

index 05392d04e5226358f871fdba6c9c5fde4dc16593..10138f4f406f85ab735c22288794527ac38b7921 100644 (file)
@@ -107,6 +107,7 @@ Turtle motion
       | :func:`right` | :func:`rt`
       | :func:`left` | :func:`lt`
       | :func:`goto` | :func:`setpos` | :func:`setposition`
+      | :func:`teleport`
       | :func:`setx`
       | :func:`sety`
       | :func:`setheading` | :func:`seth`
@@ -372,6 +373,44 @@ Turtle motion
        (0.00,0.00)
 
 
+.. function:: teleport(x, y=None, *, fill_gap=False)
+
+   :param x: a number or ``None``
+   :param y: a number or ``None``
+   :param fill_gap: a boolean
+
+   Move turtle to an absolute position. Unlike goto(x, y), a line will not
+   be drawn. The turtle's orientation does not change. If currently
+   filling, the polygon(s) teleported from will be filled after leaving,
+   and filling will begin again after teleporting. This can be disabled
+   with fill_gap=True, which makes the imaginary line traveled during
+   teleporting act as a fill barrier like in goto(x, y).
+
+   .. doctest::
+      :skipif: _tkinter is None
+      :hide:
+
+      >>> turtle.goto(0, 0)
+
+   .. doctest::
+      :skipif: _tkinter is None
+
+       >>> tp = turtle.pos()
+       >>> tp
+       (0.00,0.00)
+       >>> turtle.teleport(60)
+       >>> turtle.pos()
+       (60.00,0.00)
+       >>> turtle.teleport(y=10)
+       >>> turtle.pos()
+       (60.00,10.00)
+       >>> turtle.teleport(20, 30)
+       >>> turtle.pos()
+       (20.00,30.00)
+
+   .. versionadded: 3.12
+
+
 .. function:: setx(x)
 
    :param x: a number (integer or float)
@@ -537,8 +576,7 @@ Turtle motion
       :skipif: _tkinter is None
 
       >>> turtle.color("blue")
-      >>> turtle.stamp()
-      11
+      >>> stamp_id = turtle.stamp()
       >>> turtle.fd(50)
 
 
@@ -575,15 +613,8 @@ Turtle motion
    .. doctest::
 
       >>> for i in range(8):
-      ...     turtle.stamp(); turtle.fd(30)
-      13
-      14
-      15
-      16
-      17
-      18
-      19
-      20
+      ...     unused_stamp_id = turtle.stamp()
+      ...     turtle.fd(30)
       >>> turtle.clearstamps(2)
       >>> turtle.clearstamps(-2)
       >>> turtle.clearstamps()
index 95af84e37798246a96cea35457d4b3afc8e345a1..3f9f129a3dd2001ba070fa5d968b45eddb3606b2 100644 (file)
@@ -267,6 +267,14 @@ class TestTNavigator(VectorComparisonMixin, unittest.TestCase):
         self.assertAlmostEqual(self.nav.xcor(), 100)
         self.assertAlmostEqual(self.nav.ycor(), -100)
 
+    def test_teleport(self):
+        self.nav.teleport(20, -30, fill_gap=True)
+        self.assertAlmostEqual(self.nav.xcor(), 20)
+        self.assertAlmostEqual(self.nav.ycor(), -30)
+        self.nav.teleport(-20, 30, fill_gap=False)
+        self.assertAlmostEqual(self.nav.xcor(), -20)
+        self.assertAlmostEqual(self.nav.ycor(), 30)
+
     def test_pos(self):
         self.assertEqual(self.nav.pos(), self.nav._position)
         self.nav.goto(100, -100)
@@ -440,6 +448,18 @@ class TestTPen(unittest.TestCase):
         tpen.showturtle()
         self.assertTrue(tpen.isvisible())
 
+    def test_teleport(self):
+
+        tpen = turtle.TPen()
+
+        for fill_gap_value in [True, False]:
+            tpen.penup()
+            tpen.teleport(100, 100, fill_gap=fill_gap_value)
+            self.assertFalse(tpen.isdown())
+            tpen.pendown()
+            tpen.teleport(-100, -100, fill_gap=fill_gap_value)
+            self.assertTrue(tpen.isdown())
+
 
 if __name__ == '__main__':
     unittest.main()
index 1b369327bc8eff067ea8ec01a8bcbceb18fb6e57..2de406e0f517af30774b6d21224bd7b7058b821f 100644 (file)
@@ -135,7 +135,7 @@ _tg_turtle_functions = ['back', 'backward', 'begin_fill', 'begin_poly', 'bk',
         'pu', 'radians', 'right', 'reset', 'resizemode', 'rt',
         'seth', 'setheading', 'setpos', 'setposition', 'settiltangle',
         'setundobuffer', 'setx', 'sety', 'shape', 'shapesize', 'shapetransform', 'shearfactor', 'showturtle',
-        'speed', 'st', 'stamp', 'tilt', 'tiltangle', 'towards',
+        'speed', 'st', 'stamp', 'teleport', 'tilt', 'tiltangle', 'towards',
         'turtlesize', 'undo', 'undobufferentries', 'up', 'width',
         'write', 'xcor', 'ycor']
 _tg_utilities = ['write_docstringdict', 'done']
@@ -1614,6 +1614,13 @@ class TNavigator(object):
         """move turtle to position end."""
         self._position = end
 
+    def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
+        """To be overwritten by child class RawTurtle.
+        Includes no TPen references."""
+        new_x = x if x is not None else self._position[0]
+        new_y = y if y is not None else self._position[1]
+        self._position = Vec2D(new_x, new_y)
+
     def forward(self, distance):
         """Move the turtle forward by the specified distance.
 
@@ -2293,6 +2300,15 @@ class TPen(object):
         else:
             return self._color(self._fillcolor)
 
+    def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
+        """To be overwritten by child class RawTurtle.
+        Includes no TNavigator references.
+        """
+        pendown = self.isdown()
+        if pendown:
+            self.pen(pendown=False)
+        self.pen(pendown=pendown)
+
     def showturtle(self):
         """Makes the turtle visible.
 
@@ -2710,6 +2726,54 @@ class RawTurtle(TPen, TNavigator):
         if not ((0 <= r <= 255) and (0 <= g <= 255) and (0 <= b <= 255)):
             raise TurtleGraphicsError("bad color sequence: %s" % str(args))
         return "#%02x%02x%02x" % (r, g, b)
+    
+    def teleport(self, x=None, y=None, *, fill_gap: bool = False) -> None:
+        """Instantly move turtle to an absolute position.
+
+        Arguments:
+        x -- a number      or     None
+        y -- a number             None
+        fill_gap -- a boolean     This argument must be specified by name.
+
+        call: teleport(x, y)         # two coordinates
+        --or: teleport(x)            # teleport to x position, keeping y as is
+        --or: teleport(y=y)          # teleport to y position, keeping x as is
+        --or: teleport(x, y, fill_gap=True)          
+                                     # teleport but fill the gap in between
+
+        Move turtle to an absolute position. Unlike goto(x, y), a line will not
+        be drawn. The turtle's orientation does not change. If currently
+        filling, the polygon(s) teleported from will be filled after leaving,
+        and filling will begin again after teleporting. This can be disabled
+        with fill_gap=True, which makes the imaginary line traveled during 
+        teleporting act as a fill barrier like in goto(x, y).
+
+        Example (for a Turtle instance named turtle):
+        >>> tp = turtle.pos()
+        >>> tp
+        (0.00,0.00)
+        >>> turtle.teleport(60)
+        >>> turtle.pos()
+        (60.00,0.00)
+        >>> turtle.teleport(y=10)
+        >>> turtle.pos()
+        (60.00,10.00)
+        >>> turtle.teleport(20, 30)
+        >>> turtle.pos()
+        (20.00,30.00)
+        """
+        pendown = self.isdown()
+        was_filling = self.filling()
+        if pendown:
+            self.pen(pendown=False)
+        if was_filling and not fill_gap:
+            self.end_fill()
+        new_x = x if x is not None else self._position[0]
+        new_y = y if y is not None else self._position[1]
+        self._position = Vec2D(new_x, new_y)
+        self.pen(pendown=pendown)
+        if was_filling and not fill_gap:
+            self.begin_fill()   
 
     def clone(self):
         """Create and return a clone of the turtle.
diff --git a/Misc/NEWS.d/next/Library/2023-04-28-18-04-23.gh-issue-88773.xXCNJw.rst b/Misc/NEWS.d/next/Library/2023-04-28-18-04-23.gh-issue-88773.xXCNJw.rst
new file mode 100644 (file)
index 0000000..f14c953
--- /dev/null
@@ -0,0 +1 @@
+Added :func:`turtle.teleport` to the :mod:`turtle` module to move a turtle to a new point without tracing a line, visible or invisible.  Patch by Liam Gersten.