# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# An alternate Python Minecraft library for the Rasperry-Pi
# Copyright (c) 2013-2016 Dave Jones <dave@waveform.org.uk>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
from __future__ import (
unicode_literals,
absolute_import,
print_function,
division,
)
str = type('')
import re
import os
import math
import inspect
import weakref
from threading import Lock, local
from collections import namedtuple
from .world import World
from .vector import Vector, O, X, Y, Z, vector_range, line, filled
from .block import Block
class TurtleCache(object):
"""
A representation of the world's blocks with implicit, thread-safe caching.
Using the cache as a context manager starts a batch operation which builds
up state changes until the termination of the ``with`` block. If the block
is terminated without an exception being raised, all changes are applied
to the world.
Requesting the state of blocks will always read from the cache and, if
a batch is active, from the (as yet uncommitted) changes made by the batch.
Note that batches are stored in thread-local state.
"""
def __init__(self, world):
self._world = world
self._lock = Lock()
self._cache = {}
self._batch = local()
def __enter__(self):
try:
self._batch.level += 1
except AttributeError:
self._batch.level = 1
self._batch.state = {}
return self
def __exit__(self, exc_type, exc_value, exc_tb):
self._batch.level -= 1
if self._batch.level == 0:
state = self._batch.state
del self._batch.level, self._batch.state
if exc_type is None:
self.__setitem__(state.keys(), state.values())
def __getitem__(self, positions):
try:
# no need for thread lock, as we're getting a thread local
batch = self._batch.state
except AttributeError:
batch = {} # no active batch
with self._lock:
unknown = set(positions) - set(self._cache.keys()) - set(batch.keys())
if unknown:
self._cache.update({
v: b
for v, b in zip(unknown, self._world.blocks[unknown])
})
return {
v: batch.get(v, self._cache[v])
for v in positions
}
def __setitem__(self, positions, blocks):
try:
# no need for thread lock, as we're updating a thread local
self._batch.state.update({v: b for (v, b) in zip(positions, blocks)})
except AttributeError:
with self._lock:
diff = {
v: b
for v, b in zip(positions, blocks)
if b != self._cache[v]
}
with self._world.connection.batch_start():
self._world.blocks[diff.keys()] = diff.values()
self._cache.update(diff)
class TurtleScreen(object):
def __init__(self, world=None):
if world is None:
world = _default_world()
self._world = world
self._blocks = TurtleCache(world)
@property
def world(self):
return self._world
@property
def blocks(self):
return self._blocks
def draw(self, state):
self.blocks[state.keys()] = state.values()
TurtleState = namedtuple('TurtleState', (
'position', # Vector
'heading', # Vector
'elevation', # angle (-90..90)
'visible', # bool
'pendown', # bool
'penblock', # Block
'fillblock', # Block
'changed', # Vector->Block map
'action', # home/move/draw/begin_fill/end_fill/turtle
))
clamp = lambda value, min_value, max_value: min(max_value, max(min_value, value))
class TurtleSprite(object):
def __init__(self, screen, pos):
self.screen = screen
self.state = TurtleState(
position=pos,
heading=Z,
elevation=0.0,
visible=True,
pendown=True,
penblock=Block('stone'),
fillblock=Block('stone'),
changed={},
action='home',
)
self.last_position = self.state.position
self.history = [self.state] # undo buffer
self.draw()
def draw_vectors(self):
"""
Calculates and returns the arm and head unit vectors based on the
current heading and elevation.
"""
arm_v = self.state.heading.cross(Y).unit
if arm_v == O:
arm_v = X
head_v = self.state.heading.rotate(self.state.elevation, about=arm_v)
return arm_v, head_v
def draw(self):
"""
Draw the turtle's head and arms, and the pen block, committing the
resulting changes as an undo history entry with the action "turtle".
"""
arm_v, head_v = self.draw_vectors()
head = (self.state.position + head_v).round()
left_arm = (self.state.position + arm_v).round()
right_arm = (self.state.position - arm_v).round()
state = {
v: Block('wool', 15)
for v in (head, left_arm, right_arm)
}
if self.state.pendown:
state[self.state.position] = self.state.penblock
self.commit(state, 'turtle')
def undraw(self):
"""
If the last action in the undo history is "turtle" (indicating that the
last action taken was to draw the turtle), remove it and revert the
affected blocks to their prior state.
"""
with self.screen.blocks:
while self.history and self.history[-1].action == 'turtle':
self.screen.draw(self.history.pop().changed)
def commit(self, changes, action):
"""
Given *changes*, a mapping of vectors to blocks, and *action*, a string
describing the change, append a new state to the undo history with the
original state of the affected blocks, and draw the changes to the
screen.
"""
self.history.append(self.state._replace(
changed=self.screen.blocks[changes.keys()], # reverse diff
action=action
))
if changes:
self.screen.draw(changes)
def update(self):
"""
Commit the difference between the ephemeral :attr:`state` and the last
recorded position to the undo history as a line (if the pen is down) or
a move (if it's not).
"""
with self.screen.blocks, self:
if self.state.pendown and self.state.position != self.last_position:
self.commit({
v: self.state.penblock
for v in line(self.last_position, self.state.position)
}, 'draw')
else:
self.commit({}, 'move')
self.last_position = self.state.position
def __enter__(self):
self.undraw()
return self
def __exit__(self, exc_type, exc_value, exc_tb):
if self.state.visible:
self.draw()
class Turtle(object):
def __init__(self, screen=None, pos=None):
if screen is None:
screen = _default_screen()
if pos is None:
pos = screen.world.player.tile_pos - Y
self._screen = screen
self._sprite = TurtleSprite(screen, pos)
def getturtle(self):
"""
Return the :class:`Turtle` object itself. Only reasonable use: as a function to
return the "anonymous" turtle::
>>> pet = getturtle()
>>> pet.fd(50)
>>> pet
<picraft.turtle.Turtle object at 0x...>
"""
return self
def getscreen(self):
"""
Return the :class:`TurtleScreen` object the turtle is drawing on::
>>> ts = turtle.getscreen()
>>> ts
<picraft.turtle.TurtleScreen object at 0x...>
>>> ts.world.say("Hello world!")
"""
return self._screen
def undobufferentries(self):
"""
Return number of entries in the undobuffer::
>>> while turtle.undobufferentries():
... turtle.undo()
"""
return len(self._sprite.history) - 1 # ignore "home" action
def undo(self):
"""
Undo (repeatedly) the last turtle action(s)::
>>> for i in range(4):
... turtle.fd(5)
... turtle.lt(90)
...
>>> for i in range(8):
... turtle.undo()
"""
with self._screen.blocks, self._sprite:
if self._sprite.history[-1].action != 'home':
self._screen.draw(self._sprite.history.pop().changed)
self._sprite.state = self._sprite.history[-1]
self._sprite.last_position = self._sprite.state.position
def home(self):
"""
Move the turtle to its starting position (this is usually beneath where
the player was standing when the turtle was spawned), and set its
heading to its start orientation (0 degrees heading, 0 degrees
elevation)::
>>> turtle.heading()
90.0
>>> turtle.elevation
45.0
>>> turtle.position()
Vector(x=2, y=-1, z=16)
>>> turtle.home()
>>> turtle.position()
Vector(x=0, y=-1, z=0)
>>> turtle.heading()
0.0
>>> turtle.elevation()
0.0
"""
self._sprite.state = self._sprite.state._replace(
position=self._sprite.history[0].position,
heading=Z,
elevation=0.0,
)
self._sprite.update()
def clear(self):
with self._screen.blocks:
while self._sprite.history[-1].action != 'home':
self._screen.draw(self._sprite.history.pop().changed)
self._sprite.update()
def reset(self):
with self._screen.blocks:
self.clear()
self._sprite.last_position = self._sprite.history[0].position
self.home()
def pos(self):
"""
Return the turtle's current location (x, y, z) as a
:class:`~picraft.vector.Vector`::
>>> turtle.pos()
Vector(x=2, y=-1, z=18)
"""
return self._sprite.state.position
def xcor(self):
"""
Return the turtle's x coordinate::
>>> turtle.home()
>>> turtle.xcor()
0
>>> turtle.left(90)
>>> turtle.forward(2)
>>> turtle.xcor()
2
"""
return self._sprite.state.position.x
def ycor(self):
"""
Return the turtle's y coordinate::
>>> turtle.home()
>>> turtle.ycor()
-1
>>> turtle.up(90)
>>> turtle.forward(2)
>>> turtle.ycor()
1
"""
return self._sprite.state.position.y
def zcor(self):
"""
Return the turtle's z coordinate::
>>> turtle.home()
>>> turtle.zcor()
0
>>> turtle.forward(2)
>>> turtle.zcor()
2
"""
return self._sprite.state.position.z
def towards(self, x, y=None, z=None):
"""
:param float x: the target x coordinate or a turtle / triple /
:class:`~picraft.vector.Vector` of numbers
:param float y: the target y coordinate or ``None``
:param float z: the target z coordinate or ``None``
Return the angle between the line from the turtle's position to the
position specified within the ground plane (X-Z)::
>>> turtle.home()
>>> turtle.forward(5)
>>> turtle.towards(0, 0, 0)
-180.0
>>> turtle.left(90)
>>> turtle.forward(5)
>>> turtle.towards(0, 0, 0)
135.0
If *y* and *z* are ``None``, *x* must be a triple of coordinates, a
:class:`~picraft.vector.Vector`, or another Turtle.
"""
if isinstance(x, Turtle):
other = x.pos()
else:
try:
x, y, z = x
except (TypeError, ValueError) as exc:
pass
other = Vector(x, y, z)
v = (other - self._sprite.state.position).replace(y=0).unit
return math.degrees(math.atan2(-v.x, v.z))
def goto(self, x, y=None, z=None):
"""
:param float x: the new x coordinate or a turtle / triple /
:class:`~picraft.vector.Vector` of numbers
:param float y: the new y coordinate or ``None``
:param float z: the new z coordinate or ``None``
Moves the turtle to an absolute position. If the pen is down, draws
a line between the current position and the newly specified position.
Does not change the turtle's orientation::
>>> tp = turtle.pos()
>>> tp
Vector(x=2, y=-1, z=16)
>>> turtle.setpos(4, -1, 16)
>>> turtle.pos()
Vector(x=4, y=-1, z=16)
>>> turtle.setpos((0, -1, 16))
>>> turtle.pos()
Vector(x=0, y=-1, z=16)
>>> turtle.setpos(tp)
>>> turtle.pos()
Vector(x=2, y=-1, z=16)
If *y* and *z* are ``None``, *x* must be a triple of coordinates, a
:class:`~picraft.vector.Vector`, or another Turtle.
"""
if isinstance(x, Turtle):
other = x.pos()
else:
try:
x, y, z = x
except (TypeError, ValueError) as exc:
pass
other = Vector(x, y, z)
self._sprite.state = self._sprite.state._replace(position=other)
self._sprite.update()
def setx(self, x):
"""
:param float x: the new x coordinate
Set the turtle's first coordinate to *x*; leave the second and third
coordinates unchanged::
>>> turtle.position()
Vector(x=2, y=-1, z=16)
>>> turtle.setx(5)
>>> turtle.position()
Vector(x=5, y=-1, z=16)
"""
self.goto(pos().replace(x=x))
def sety(self, y):
"""
:param float y: the new y coordinate
Set the turtle's second coordinate to *y*; leave the first and third
coordinates unchanged::
>>> turtle.position()
Vector(x=2, y=-1, z=16)
>>> turtle.sety(5)
>>> turtle.position()
Vector(x=2, y=5, z=16)
"""
self.goto(pos().replace(y=y))
def setz(self, z):
"""
:param float z: the new z coordinate
Set the turtle's third coordinate to *z*; leave the first and second
coordinates unchanged::
>>> turtle.position()
Vector(x=2, y=-1, z=16)
>>> turtle.setz(5)
>>> turtle.position()
Vector(x=2, y=-1, z=5)
"""
self.goto(pos().replace(z=z))
def distance(self, x, y=None, z=None):
"""
:param float x: the target x coordinate or a turtle / triple /
:class:`~picraft.vector.Vector` of numbers
:param float y: the target y coordinate or ``None``
:param float z: the target z coordinate or ``None``
Return the distance from the turtle to (x, y, z), the given vector, or
the given other turtle, in blocks::
>>> turtle.home()
>>> turtle.distance((0, -1, 5))
5.0
>>> turtle.forward(2)
>>> turtle.distance(0, -1, 5)
3.0
"""
if isinstance(x, Turtle):
other = x.pos()
else:
try:
x, y, z = x
except (TypeError, ValueError) as exc:
pass
other = Vector(x, y, z)
return self._sprite.state.position.distance_to(other)
def elevation(self):
"""
Return the turtle's current elevation (its orientation away from the
ground plane, X-Z)::
>>> turtle.home()
>>> turtle.up(90)
>>> turtle.elevation()
90.0
"""
return self._sprite.state.elevation
def heading(self):
"""
Return the turtle's current heading (its orientation along the ground
plane, X-Z)::
>>> turtle.home()
>>> turtle.right(90)
>>> turtle.heading()
90.0
"""
result = self._sprite.state.heading.angle_between(Z)
if self._sprite.state.heading.cross(Z).y < 0:
result += 180
return result
def setelevation(self, to_angle):
"""
:param float to_angle: the new elevation
Set the elevation of the turtle away from the ground plane (X-Z) to
*to_angle*. At 0 degrees elevation, the turtle moves along the ground
plane (X-Z). At 90 degrees elevation, the turtle moves vertically
upward, and at -90 degrees, the turtle moves vertically downward::
>>> turtle.setelevation(90)
>>> turtle.elevation()
90.0
"""
self._sprite.state = self._sprite.state._replace(elevation=clamp(to_angle, -90, 90))
self._sprite.update()
def setheading(self, to_angle):
"""
:param float to_angle: the new heading
Set the orientation of the turtle on the ground plane (X-Z) to
*to_angle*. The common directions in degrees correspond to the
following axis directions:
======= ====
heading axis
======= ====
0 +Z
90 +X
180 -Z
270 -X
======= ====
::
>>> turtle.setheading(90)
>>> turtle.heading()
90.0
"""
self._sprite.state = self._sprite.state._replace(heading=Z.rotate(to_angle, about=Y))
self._sprite.update()
def forward(self, distance):
"""
:param float distance: the number of blocks to move forward.
Move the turtle forward by the specified *distance*, in the direction
the turtle is headed::
>>> turtle.position()
Vector(x=2, y=-1, z=13)
>>> turtle.forward(5)
>>> turtle.position()
Vector(x=2, y=-1, z=18)
>>> turtle.forward(-2)
>>> turtle.position()
Vector(x=2, y=-1, z=16)
"""
arm_v, head_v = self._sprite.draw_vectors()
self._sprite.state = self._sprite.state._replace(
position=(self._sprite.state.position + distance * head_v).round()
)
self._sprite.update()
def backward(self, distance):
"""
:param float distance: the number of blocks to move back.
Move the turtle backward by the specified *distance*, opposite to the
direction the turtle is headed. Does not change the turtle's heading::
>>> turtle.heading()
0.0
>>> turtle.position()
Vector(x=2, y=-1, z=18)
>>> turtle.backward(2)
>>> turtle.position()
Vector(x=2, y=-1, z=16)
>>> turtle.heading()
0.0
"""
arm_v, head_v = self._sprite.draw_vectors()
self._sprite.state = self._sprite.state._replace(
position=(self._sprite.state.position - distance * head_v).round()
)
self._sprite.update()
def right(self, angle):
"""
:param float angle: the number of degrees to turn clockwise.
Turns the turtle right (clockwise) by *angle* degrees::
>>> turtle.heading()
0.0
>>> turtle.right(90)
>>> turtle.heading()
90.0
"""
self._sprite.state = self._sprite.state._replace(
heading=self._sprite.state.heading.rotate(-angle, about=Y)
)
self._sprite.update()
def left(self, angle):
"""
:param float angle: the number of degrees to turn counter clockwise.
Turns the turtle left (counter-clockwise) by *angle* degrees::
>>> turtle.heading()
90.0
>>> turtle.left(90)
>>> turtle.heading()
0.0
"""
self._sprite.state = self._sprite.state._replace(
heading=self._sprite.state.heading.rotate(angle, about=Y)
)
self._sprite.update()
def down(self, angle):
"""
:param float angle: the number of degrees to reduce elevation by.
Turns the turtle's nose (its elevation) down by *angle* degrees::
>>> turtle.elevation()
0.0
>>> turtle.down(45)
>>> turtle.elevation()
-45.0
"""
self._sprite.state = self._sprite.state._replace(
elevation=clamp(self._sprite.state.elevation - angle, -90, 90)
)
self._sprite.update()
def up(self, angle):
"""
:param float angle: the number of degrees to increase elevation by.
Turns the turtle's nose (its elevation) up by *angle* degrees::
>>> turtle.elevation()
-45.0
>>> turtle.up(45)
>>> turtle.elevation()
0.0
"""
self._sprite.state = self._sprite.state._replace(
elevation=clamp(self._sprite.state.elevation + angle, -90, 90)
)
self._sprite.update()
def isdown(self):
"""
Returns ``True`` if the pen is down, ``False`` if it's up.
"""
return self._sprite.state.pendown
def pendown(self):
"""
Put the "pen" down; the turtle draws new blocks when it moves.
"""
self._sprite.state = self._sprite.state._replace(pendown=True)
self._sprite.update()
def penup(self):
"""
Put the "pen" up; movement doesn't draw new blocks.
"""
self._sprite.state = self._sprite.state._replace(pendown=False)
self._sprite.update()
def isvisible(self):
"""
Return ``True`` if the turtle is shown, ``False`` if it's hidden::
>>> turtle.hideturtle()
>>> turtle.isvisible()
False
>>> turtle.showturtle()
>>> turtle.isvisible()
True
"""
return self._sprite.state.visible
def showturtle(self):
"""
Make the turtle visible::
>>> turtle.showturtle()
"""
self._sprite.state = self._sprite.state._replace(visible=True)
self._sprite.update()
def hideturtle(self):
"""
Make the turtle invisible::
>>> turtle.hideturtle()
"""
self._sprite.state = self._sprite.state._replace(visible=False)
self._sprite.update()
def penblock(self, *args):
"""
Return or set the block that the turtle draws when it moves. Several
input formats are allowed:
``penblock()``
Return the current pen block. May be used as input to another
penblock or fillblock call.
``penblock(Block('grass'))``
Set the pen block to the specified :class:`~picraft.block.Block`
instance.
``penblock('grass')``
Implicitly make a :class:`~picraft.block.Block` from the given
arguments and set that as the pen block.
::
>>> turtle.penblock()
<Block "stone" id=1 data=0>
>>> turtle.penblock('diamond_block')
>>> turtle.penblock()
<Block "diamond_block" id=57 data=0>
>>> turtle.penblock(1, 0)
>>> turtle.penblock()
<Block "stone" id=1 data=0>
"""
if not args:
return self._sprite.state.penblock
else:
if isinstance(args[0], Block):
self._sprite.state = self._sprite.state._replace(penblock=args[0])
else:
self._sprite.state = self._sprite.state._replace(penblock=Block(*args))
self._sprite.update()
def fillblock(self, *args):
"""
Return or set the block that the turtle fills shapes with. Several
input formats are allowed:
``fillblock()``
Return the current fill block. May be used as input to another
penblock or fillblock call.
``fillblock(Block('grass'))``
Set the fill block to the specified :class:`~picraft.block.Block`
instance.
``fillblock('grass')``
Implicitly make a :class:`~picraft.block.Block` from the given
arguments and set that as the fill block.
::
>>> turtle.fillblock()
<Block "stone" id=1 data=0>
>>> turtle.fillblock('diamond_block')
>>> turtle.fillblock()
<Block "diamond_block" id=57 data=0>
>>> turtle.fillblock(1, 0)
>>> turtle.fillblock()
<Block "stone" id=1 data=0>
"""
if not args:
return self._sprite.state.fillblock
else:
if isinstance(args[0], Block):
self._sprite.state = self._sprite.state._replace(fillblock=args[0])
else:
self._sprite.state = self._sprite.state._replace(fillblock=Block(*args))
self._sprite.update()
def fill(self, flag=None):
"""
:param bool flag: True if beginning a fill, False if ending a fill.
Call ``fill(True)`` before drawing the shape you want to fill, and
``fill(False)`` when done. When used without argument: return the
fill state (``True`` if filling, ``False`` otherwise).
"""
if flag is None:
for state in reversed(self._sprite.history):
if state.action == 'end-fill':
return False
elif state.action == 'begin-fill':
return True
return False
elif flag:
self.begin_fill()
else:
self.end_fill()
def begin_fill(self):
"""
Call just before drawing a shape to be filled. Equivalent to
``fill(True)``.
"""
with self._screen.blocks, self._sprite:
self._sprite.commit({}, 'begin-fill')
def end_fill(self):
"""
Fill the shape drawn after the last call to :meth:`begin_fill`.
Equivalent to ``fill(False)``.
"""
with self._screen.blocks, self._sprite:
fill_nodes = set()
for state in reversed(self._sprite.history):
if state.action == 'begin-fill':
break
elif state.action == 'end-fill':
# ending fill before starting one
return
elif state.action == 'draw':
fill_nodes |= state.changed.keys()
# fill in the last edge if the begin and end positions differ
if state.position != self._sprite.state.position:
fill_nodes |= set(line(self._sprite.state.position, state.position))
self._sprite.commit({
v: self._sprite.state.fillblock
for v in set(filled(fill_nodes)) - fill_nodes
}, 'end_fill')
position = pos
setpos = goto
setposition = goto
sete = setelevation
seth = setheading
fd = forward
bk = backward
back = backward
rt = right
lt = left
dn = down
st = showturtle
ht = hideturtle
pd = pendown
pu = penup
getpen = getturtle
class TurtlePlayer(object):
def __init__(self, screen=None, player_id=None):
if screen is None:
screen = _default_screen()
self._screen = screen
if player_id is None:
self._player = self._screen.world.player
else:
self._player = self._screen.world.players[player_id]
def where(self):
return self._player.pos
def teleport(self, x, y=None, z=None):
if isinstance(x, Turtle):
other = x.pos() + Y
else:
try:
x, y, z = x
except (TypeError, ValueError) as exc:
pass
other = Vector(x, y, z)
self._player.pos = other
def jump(self, height=2):
self.teleport(self._player.pos + height * Y)
# default objects constructed when the straight function interface is used
_WORLD = None # The global World() used by default
_SCREEN = None # The global TurtleScreen() used by default
_TURTLE = None # The global Turtle() used by default
_PLAYER = None # The global TurtlePlayer() used by default
def _default_world():
global _WORLD
if _WORLD is None:
_WORLD = World()
return _WORLD
def _default_screen():
global _SCREEN
if _SCREEN is None:
_SCREEN = TurtleScreen()
return _SCREEN
def _default_turtle():
global _TURTLE
if _TURTLE is None:
_TURTLE = Turtle()
return _TURTLE
def _default_player():
global _PLAYER
if _PLAYER is None:
_PLAYER = TurtlePlayer()
return _PLAYER
def _method_to_func(name, method, factory):
"""
Creates a procedural variant of *method* on the instance returned by
calling *factory*.
"""
template = """\
def {name}{defargs}:
return {factory}().{name}{callargs}"""
spec = inspect.getargspec(method)
defargs = inspect.formatargspec(spec.args[1:], spec.varargs, spec.keywords, spec.defaults)
callargs = inspect.formatargspec(spec.args[1:], spec.varargs, spec.keywords, ())
exec(template.format(
name=name,
defargs=defargs,
callargs=callargs,
factory=factory.__name__,
), globals())
# If the method has a doc-string, copy it to the new function ... but only
# when the method isn't an alias (name==method.__name__) or we're not
# building the picraft docs (in which we don't want to repeat all the docs
# for the aliases)
if method.__doc__ is not None:
if not 'PICRAFTDOCS' in os.environ or name == method.__name__:
# Replace "turtle." in all the examples with a blank string
globals()[name].__doc__ = re.sub(
r'^( *(?:>>>|\.\.\.).*)turtle\.', r'\1',
method.__doc__, flags=re.MULTILINE)
def _classes_to_funcs():
"""
Uses :func:`_method_to_func` to construct procedural variants of the
:class:`Turtle`, :class:`TurtleScreen`, and :class:`TurtlePlayer` class'
methods.
"""
for method in dir(Turtle):
if not method.startswith('_'):
_method_to_func(method, getattr(Turtle, method), _default_turtle)
for method in dir(TurtlePlayer):
if not method.startswith('_'):
_method_to_func(method, getattr(TurtlePlayer, method), _default_player)
_classes_to_funcs()