gh-88773: Added teleport method to Turtle library (#103974)

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>
This commit is contained in:
Liam Gersten 2023-04-30 16:17:36 -04:00 committed by GitHub
parent 654d44b3a4
commit 74a2b79c62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 12 deletions

View 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()

View 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()

View 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.

View File

@ -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.