Skip to content

Instantly share code, notes, and snippets.

@jsbain jsbain/turtle2.py
Created Aug 6, 2018

Embed
What would you like to do?
turtle2.py
#\input texinfo
# coding: utf-8
# ui-based iOS port of the turtle module (not 100% compatible with standard library
# turtle module, but most things beginners would use should work)
import ui
from math import *
import math
import time
import sys
import numbers
import threading
import objc_util
PY3 = sys.version_info[0] >= 3
if PY3:
basestring = str
_default_size = min(ui.get_screen_size()) - 100
_main_view = None
_default_pen = None
_default_color_mode = 1.0
_canvas_size = max(ui.get_screen_size())
_canvas_image = None
_all_pens = []
# When the pen is set to 'fastest', this is how many drawing commands are batched:
batch_size = 100
def Shape(shape,pts):
assert (shape=='polygon')
p=ui.Path()
p.move_to(*pts[0])
for pt in pts[1:]:
p.line_to(*pt)
p.close()
return p
class Vec2D(tuple):
"""A 2 dimensional vector class, used as a helper class
for implementing turtle graphics.
May be useful for turtle graphics programs also.
Derived from tuple, so a vector is a tuple!
Provides (for a, b vectors, k number):
a+b vector addition
a-b vector subtraction
a*b inner product
k*a and a*k multiplication with scalar
|a| absolute value of a
a.rotate(angle) rotation
"""
def __new__(cls, x, y):
return tuple.__new__(cls, (x, y))
def __add__(self, other):
return Vec2D(self[0]+other[0], self[1]+other[1])
def __mul__(self, other):
if isinstance(other, Vec2D):
return self[0]*other[0]+self[1]*other[1]
return Vec2D(self[0]*other, self[1]*other)
def __rmul__(self, other):
if isinstance(other, int) or isinstance(other, float):
return Vec2D(self[0]*other, self[1]*other)
def __sub__(self, other):
return Vec2D(self[0]-other[0], self[1]-other[1])
def __neg__(self):
return Vec2D(-self[0], -self[1])
def __abs__(self):
return (self[0]**2 + self[1]**2)**0.5
def rotate(self, angle):
"""rotate self counterclockwise by angle
"""
perp = Vec2D(-self[1], self[0])
angle = angle * math.pi / 180.0
c, s = math.cos(angle), math.sin(angle)
return Vec2D(self[0]*c+perp[0]*s, self[1]*c+perp[1]*s)
def __getnewargs__(self):
return (self[0], self[1])
def __repr__(self):
return "(%.2f,%.2f)" % self
class PenView (ui.View):
def __init__(self):
self.shape='turtle'
self._shapes = {
"arrow" : Shape("polygon", ((-10,0), (10,0), (0,10))),
"turtle" : Shape("polygon", ((0,16), (-2,14), (-1,10), (-4,7),
(-7,9), (-9,8), (-6,5), (-7,1), (-5,-3), (-8,-6),
(-6,-8), (-4,-5), (0,-7), (4,-5), (6,-8), (8,-6),
(5,-3), (7,1), (6,5), (9,8), (7,9), (4,7), (1,10),(2,14))),
"circle" : Shape("polygon", ((10,0), (9.51,3.09), (8.09,5.88),
(5.88,8.09), (3.09,9.51), (0,10), (-3.09,9.51),
(-5.88,8.09), (-8.09,5.88), (-9.51,3.09), (-10,0),
(-9.51,-3.09), (-8.09,-5.88), (-5.88,-8.09),
(-3.09,-9.51), (-0.00,-10.00), (3.09,-9.51),
(5.88,-8.09), (8.09,-5.88), (9.51,-3.09))),
"square" : Shape("polygon", ((10,-10), (10,10), (-10,10),
(-10,-10))),
"triangle" : Shape("polygon", ((10,-5.77), (0,11.55),
(-10,-5.77))),
"classic": Shape("polygon", ((0,0),(-5,-9),(0,-7),(5,-9))),
"blank": Shape("polygon", ((0,0),(0,0)))
}
def draw(self):
if _canvas_image:
_canvas_image.draw()
for pen in _all_pens:
if not pen._turtle_visible:
continue
with ui.GState():
ui.concat_ctm(ui.Transform.translation(self.superview.width/2, self.superview.height/2))
pen_tip = self._shapes[self.shape]
ui.concat_ctm(ui.Transform.translation(*pen._pos))
ui.concat_ctm(ui.Transform.rotation(pen._angle-math.radians(90)))
ui.set_color(pen._current_color)
pen_tip.fill()
def set_needs_display(self, make_visible=True):
ui.View.set_needs_display(self)
if make_visible:
_main_view.present()
_pen_view = PenView()
class TurtleView (ui.View):
def __init__(self, *args, **kwargs):
ui.View.__init__(self, *args, **kwargs)
self.name = 'Turtle Graphics'
self.canvas_view = ui.View(frame=(0, 0, _default_size, _default_size), bg_color='white')
self.bg_color = (0.9, 0.9, 0.9, 1.0)
self.tint_color = (0.2, 0.2, 0.2, 1.0)
self.add_subview(self.canvas_view)
clear_btn_item = ui.ButtonItem('Clear', action=self.clear_action)
save_btn_item = ui.ButtonItem('Save', action=self.save_action)
self.right_button_items = [save_btn_item, clear_btn_item]
_pen_view.frame = self.canvas_view.bounds
_pen_view.flex = 'WH'
self.canvas_view.add_subview(_pen_view)
self.title_label = ui.Label(frame=(0, 0, self.width, 40), flex='W')
self.title_label.alignment = ui.ALIGN_CENTER
self.title_label.text = 'Turtle Graphics'
self.title_label.text_color = (0.4, 0.4, 0.4, 1.0)
self.title_label.font = ('<System>', 14)
self.add_subview(self.title_label)
save_button = ui.Button(image=ui.Image('iow:ios7_download_outline_24'), flex='LB')
save_button.frame = (self.width - 40, 0, 40, 40)
save_button.action = self.save_action
self.add_subview(save_button)
def layout(self):
# NOTE: When this is called, the previous imports might already have gone out of scope.
import ui
center = ui.Point(self.width/2, self.height/2)
self.canvas_view.frame = (center.x - self.canvas_view.width/2, center.y - self.canvas_view.height/2, self.canvas_view.width, self.canvas_view.height)
def present(self):
objc_view = objc_util.ObjCInstance(self)
user_info = {'view': objc_view}
objc_util.ObjCClass('NSNotificationCenter').defaultCenter().postNotificationName_object_userInfo_('ConsoleShowGraphicsViewNotification', None, user_info)
def reset(self):
self.setup(_default_size, _default_size)
global _canvas_image
_canvas_image = None
_default_pen.reset()
_all_pens[:] = [_default_pen]
self.canvas_view.bg_color = 'white'
self.title('Turtle Graphics')
def clear_action(self, sender):
# NOTE: Used by clearscreen, could also be attached to a UI element (but isn't currently)
global _canvas_image
_canvas_image = None
_pen_view.set_needs_display()
def save_action(self, sender):
# NOTE: When this is called, the previous imports might already have gone out of scope.
import photos, dialogs, ui
with ui.ImageContext(self.canvas_view.width, self.canvas_view.height) as ctx:
self.canvas_view.draw_snapshot()
img = ctx.get_image()
photos.save_image(img)
if not photos.is_authorized():
dialogs.hud_alert('Cannot Save', 'error')
else:
dialogs.hud_alert('Saved to Photos')
def title(self, title_str):
self.name = str(title_str)
self.title_label.text = title_str
def tracer(self, n=None, delay=None):
if delay is not None:
for pen in self.turtles():
pen.delay_sec = delay / 100.0
def delay(self, delay=None):
if delay is not None:
for pen in self.turtles():
pen.delay_sec = delay / 100.0
def setup(self, width=None, height=None, **kwargs):
if width is None:
width = _default_size
if height is None:
height = _default_size
self.canvas_view.bounds = (0, 0, width, height)
def bgcolor(self, *args):
if len(args) == 1 and isinstance(args[0], basestring):
self.canvas_view.bg_color = ui.parse_color(args[0])
elif len(args) == 1:
r, g, b = args[0]
if _default_pen._colormode == 255:
r, g, b = (r/255.0, g/255.0, b/255.0)
self.canvas_view.bg_color = (r, g, b)
elif len(args) == 3:
r, g, b = args
if _default_pen._colormode == 255:
r, g, b = (r/255.0, g/255.0, b/255.0)
self.canvas_view.bg_color = (r, g, b)
def turtles(self):
return _all_pens
def clearscreen(self):
self.clear_action(None)
def update(self):
pass
class Pen (object):
def __init__(self, *args, **kwargs):
self._use_rad = False
self._current_color = (0, 0, 0, 1)
self._current_fill_color = (0, 0, 0, 1)
self._colormode = _default_color_mode
self._line_width = 1.0
self._angle = 0.0
self._pos = ui.Point(0, 0)
self._turtle_visible = True
self.is_down = True
self.fill_path = None
self.delay_sec = 0.01
self.anim_steps = 4
self.canvas_size = ui.Size(512, 512)
self.batched_drawings = []
_all_pens.append(self)
def _sleep(self):
time.sleep(self.delay_sec)
def degrees(self):
""" Set angle measurement units to degrees."""
self._use_rad = False
def radians(self):
""" Set angle measurement units to radians."""
self._use_rad = True
def _color_args_to_rgba(self, args):
"""Helper method to convert various color formats to r,g,b,a (with 0.0-1.0 ranges)"""
r, g, b, a = 0, 0, 0, 1
if len(args) >= 3:
r, g, b = args[:3]
if self._colormode == 255:
r, g, b = (r/255.0, g/255.0, b/255.0)
a = args[3] if len(args) > 3 else 1.0
elif len(args) == 1 and isinstance(args[0], basestring):
r, g, b, a = ui.parse_color(args[0])
elif len(args) == 1:
r, g, b = args[0][:3]
if self._colormode == 255:
r, g, b = (r/255.0, g/255.0, b/255.0)
a = args[0][3] if len(args[0]) > 3 else 1.0
return (r, g, b, a)
def _angle_to_rad(self, angle):
if self._use_rad:
return angle
return math.radians(angle)
def _add_drawing(self, draw_func, force_flush=False):
if self.delay_sec == 0:
self.batched_drawings.append(draw_func)
if len(self.batched_drawings) < batch_size and not force_flush:
return
with ui.ImageContext(_canvas_size, _canvas_size) as ctx:
with ui.autoreleasepool():
global _canvas_image
if _canvas_image:
_canvas_image.draw(0, 0)
ui.concat_ctm(ui.Transform.translation(_pen_view.superview.width/2, _pen_view.superview.height/2))
if self.batched_drawings:
for batched_draw_func in self.batched_drawings:
batched_draw_func()
self.batched_drawings = []
_canvas_image = ctx.get_image()
self.update_view()
else:
draw_func()
_canvas_image = ctx.get_image()
def _force_flush(self):
if self.batched_drawings:
self._add_drawing(lambda: None, force_flush=True)
def reset(self):
"""Delete the turtle's drawings and restore its default values.
No argument.
Delete the turtle's drawings from the screen, re-center the turtle
and set variables to the default values.
Example (for a Turtle instance named turtle):
>>> turtle.position()
(0.00,-22.00)
>>> turtle.heading()
100.0
>>> turtle.reset()
>>> turtle.position()
(0.00,0.00)
>>> turtle.heading()
0.0
"""
self._pos = ui.Point(0, 0)
self._current_color = (0, 0, 0, 1)
self._current_fill_color = (0, 0, 0, 1)
self._line_width = 1.0
self._angle = 0.0
self._turtle_visible = True
self.is_down = True
self.fill_path = None
self.batched_drawings = []
self.colormode(255)
self.update_view(make_visible=False)
def clone(self):
"""Create and return a clone of the turtle.
No argument.
Create and return a clone of the turtle with same position, heading
and turtle properties.
Example (for a Turtle instance named mick):
mick = Turtle()
joe = mick.clone()
"""
p = Pen()
p._pos = self._pos
p._angle = self._angle
p._current_color = self._current_color
p._current_fill_color = self._current_fill_color
p._turtle_visible = self._turtle_visible
p._line_width = self._line_width
return p
def speed(self, speed_value=None):
""" Return or set the turtle's speed.
Optional argument:
speed -- an integer in the range 0..10 or a speedstring (see below)
Set the turtle's speed to an integer value in the range 0 .. 10.
If no argument is given: return current speed.
If input is a number greater than 10 or smaller than 0.5,
speed is set to 0.
Speedstrings are mapped to speedvalues in the following way:
'fastest' : 0
'fast' : 10
'normal' : 6
'slow' : 3
'slowest' : 1
speeds from 1 to 10 enforce increasingly faster animation of
line drawing and turtle turning.
Attention:
speed = 0 : *no* animation takes place. forward/back makes turtle jump
and likewise left/right make the turtle turn instantly.
Example (for a Turtle instance named turtle):
>>> turtle.speed(3)
"""
# TODO: The mapping is incorrect, fastest is 10 here, but should be 0.
if speed_value is not None:
speed_delays = {'fastest': 0.0 , 'fast': 0.01, 'normal': 0.03, 'slow': 0.05, 'slowest': 0.1}
if isinstance(speed_value, basestring):
self.delay_sec = speed_delays.get(speed_value, 0.03)
elif isinstance(speed_value, int):
self.delay_sec = 0.0 if (speed_value >= 10 or speed_value <= 0) else (10 - speed_value) / 100.0
return 10 if self.delay_sec == 0 else min(10, max(0, (1.0/self.delay_sec)/10 - 1))
def delay(self, ms):
""" Return or set the drawing delay in milliseconds.
Optional argument:
delay -- positive integer
Example (for a TurtleScreen instance named screen):
>>> screen.delay(15)
>>> screen.delay()
15
"""
self.delay_sec = min(1.0, ms / 100.0)
return self.delay_sec * 100.0
def _draw_line(self, from_pos, to_pos, color, line_width):
line_path = ui.Path()
line_path.move_to(*from_pos)
line_path.line_to(*to_pos)
line_path.line_width = line_width
line_path.line_cap_style = ui.LINE_CAP_ROUND
line_path.line_join_style = ui.LINE_JOIN_ROUND
ui.set_color(color)
line_path.stroke()
def _forward(self, d):
self._sleep()
new_pos = self._pos + (cos(self._angle) * d, sin(self._angle) * d)
if self.fill_path:
self.fill_path.line_to(*new_pos)
if self.is_down:
from_pos = self._pos
line_width = self._line_width
color = self._current_color
def _draw():
self._draw_line(from_pos, new_pos, color, line_width)
self._add_drawing(_draw)
self._pos = new_pos
self.update_view()
def forward(self, d):
"""Move the turtle forward by the specified distance.
Aliases: forward | fd
Argument:
distance -- a number (integer or float)
Move the turtle forward by the specified distance, in the direction
the turtle is headed.
Example (for a Turtle instance named turtle):
>>> turtle.position()
(0.00, 0.00)
>>> turtle.forward(25)
>>> turtle.position()
(25.00,0.00)
>>> turtle.forward(-75)
>>> turtle.position()
(-50.00,0.00)
"""
for i in range(self.anim_steps):
self._forward(float(d)/self.anim_steps)
fd = forward
def backward(self, d):
"""Move the turtle backward by distance.
Aliases: back | backward | bk
Argument:
distance -- a number
Move the turtle backward by distance ,opposite to the direction the
turtle is headed. Do not change the turtle's heading.
Example (for a Turtle instance named turtle):
>>> turtle.position()
(0.00, 0.00)
>>> turtle.backward(30)
>>> turtle.position()
(-30.00, 0.00)
"""
self.forward(-d)
back = backward
bk = backward
def _left(self, angle):
self._sleep()
self._angle -= self._angle_to_rad(angle)
self.update_view()
def left(self, angle):
"""Turn turtle left by angle units.
Aliases: left | lt
Argument:
angle -- a number (integer or float)
Turn turtle left by angle units. (Units are by default degrees,
but can be set via the degrees() and radians() functions.)
Angle orientation depends on mode. (See this.)
Example (for a Turtle instance named turtle):
>>> turtle.heading()
22.0
>>> turtle.left(45)
>>> turtle.heading()
67.0
"""
for i in range(self.anim_steps):
self._left(float(angle)/self.anim_steps)
lt = left
def right(self, angle):
"""Turn turtle right by angle units.
Aliases: right | rt
Argument:
angle -- a number (integer or float)
Turn turtle right by angle units. (Units are by default degrees,
but can be set via the degrees() and radians() functions.)
Angle orientation depends on mode. (See this.)
Example (for a Turtle instance named turtle):
>>> turtle.heading()
22.0
>>> turtle.right(45)
>>> turtle.heading()
337.0
"""
self.left(-angle)
rt = right
def begin_fill(self):
"""Called just before drawing a shape to be filled.
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.color("black", "red")
>>> turtle.begin_fill()
>>> turtle.circle(60)
>>> turtle.end_fill()
"""
if self.fill_path:
self.end_fill()
self.fill_path = ui.Path()
self.fill_path.move_to(*self._pos)
def end_fill(self):
"""Fill the shape drawn after the call begin_fill().
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.color("black", "red")
>>> turtle.begin_fill()
>>> turtle.circle(60)
>>> turtle.end_fill()
"""
if not self.fill_path:
return
fill_path = self.fill_path
fill_color = self._current_fill_color
def _draw():
ui.set_color(fill_color)
fill_path.fill()
self._add_drawing(_draw)
self.fill_path = None
self.update_view()
def fill(self, flag):
if bool(flag):
self.begin_fill()
else:
self.end_fill()
def filling(self):
"""Return fillstate (True if filling, False else).
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.begin_fill()
>>> if turtle.filling():
... turtle.pensize(5)
... else:
... turtle.pensize(3)
"""
return self.fill_path is not None
def color(self, *args):
"""Return or set the pencolor and fillcolor.
Arguments:
Several input formats are allowed.
They use 0, 1, 2, or 3 arguments as follows:
color()
Return the current pencolor and the current fillcolor
as a pair of color specification strings as are returned
by pencolor and fillcolor.
color(colorstring), color((r,g,b)), color(r,g,b)
inputs as in pencolor, set both, fillcolor and pencolor,
to the given value.
color(colorstring1, colorstring2),
color((r1,g1,b1), (r2,g2,b2))
equivalent to pencolor(colorstring1) and fillcolor(colorstring2)
and analogously, if the other input format is used.
If turtleshape is a polygon, outline and interior of that polygon
is drawn with the newly set colors.
For mor info see: pencolor, fillcolor
Example (for a Turtle instance named turtle):
>>> turtle.color('red', 'green')
>>> turtle.color()
('red', 'green')
>>> colormode(255)
>>> color((40, 80, 120), (160, 200, 240))
>>> color()
('#285078', '#a0c8f0')
"""
if len(args) == 1 or len(args) >= 3:
self.fillcolor(*args)
self.pencolor(*args)
elif len(args) == 2:
self.pencolor(args[0])
self.fillcolor(args[1])
return self.pencolor(), self.fillcolor()
def pencolor(self, *args):
""" Return or set the pencolor.
Arguments:
Four input formats are allowed:
- pencolor()
Return the current pencolor as color specification string,
possibly in hex-number format (see example).
May be used as input to another color/pencolor/fillcolor call.
- pencolor(colorstring)
s is a Tk color specification string, such as "red" or "yellow"
- pencolor((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..colormode,
where colormode is either 1.0 or 255
- pencolor(r, g, b)
r, g, and b represent an RGB color, and each of r, g, and b
are in the range 0..colormode
If turtleshape is a polygon, the outline of that polygon is drawn
with the newly set pencolor.
Example (for a Turtle instance named turtle):
>>> turtle.pencolor('brown')
>>> tup = (0.2, 0.8, 0.55)
>>> turtle.pencolor(tup)
>>> turtle.pencolor()
'#33cc8c'
"""
if args:
r, g, b, a = self._color_args_to_rgba(args)
self._current_color = (r, g, b, a)
return self._current_color
def fillcolor(self, *args):
""" Return or set the fillcolor.
Arguments:
Four input formats are allowed:
- fillcolor()
Return the current fillcolor as color specification string,
possibly in hex-number format (see example).
May be used as input to another color/pencolor/fillcolor call.
- fillcolor(colorstring)
s is a Tk color specification string, such as "red" or "yellow"
- fillcolor((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..colormode,
where colormode is either 1.0 or 255
- fillcolor(r, g, b)
r, g, and b represent an RGB color, and each of r, g, and b
are in the range 0..colormode
If turtleshape is a polygon, the interior of that polygon is drawn
with the newly set fillcolor.
Example (for a Turtle instance named turtle):
>>> turtle.fillcolor('violet')
>>> col = turtle.pencolor()
>>> turtle.fillcolor(col)
>>> turtle.fillcolor(0, .5, 0)
"""
if args:
r, g, b, a = self._color_args_to_rgba(args)
self._current_fill_color = (r, g, b, a)
return self._current_fill_color
def width(self, w=None):
"""Set or return the line thickness.
Aliases: pensize | width
Argument:
width -- positive number
Set the line thickness to width or return it. If resizemode is set
to "auto" and turtleshape is a polygon, that polygon is drawn with
the same line thickness. If no argument is given, current pensize
is returned.
Example (for a Turtle instance named turtle):
>>> turtle.pensize()
1
>>> turtle.pensize(10) # from here on lines of width 10 are drawn
"""
if w is not None:
self._line_width = w
return self._line_width
pensize = width
def down(self):
"""Pull the pen down -- drawing when moving.
Aliases: pendown | pd | down
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.pendown()
"""
self.is_down = True
pendown = down
pd = down
def up(self):
"""Pull the pen up -- no drawing when moving.
Aliases: penup | pu | up
No argument
Example (for a Turtle instance named turtle):
>>> turtle.penup()
"""
self.is_down = False
penup = up
pu = up
def isdown(self):
"""Return True if pen is down, False if it's up.
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.penup()
>>> turtle.isdown()
False
>>> turtle.pendown()
>>> turtle.isdown()
True
"""
return self.is_down
def clear(self):
"""Delete the turtle's drawings from the screen. Do not move turtle.
No arguments.
Delete the turtle's drawings from the screen. Do not move turtle.
State and position of the turtle as well as drawings of other
turtles are not affected.
Examples (for a Turtle instance named turtle):
>>> turtle.clear()
"""
self.fill_path = None
global _canvas_image
_canvas_image = None
self.update_view()
def write(self, text, move=False, align=None, font=None):
"""Write text at the current turtle position.
Arguments:
arg -- info, which is to be written to the TurtleScreen
move (optional) -- True/False
align (optional) -- one of the strings "left", "center" or right"
font (optional) -- a triple (fontname, fontsize, fonttype)
Write text - the string representation of arg - at the current
turtle position according to align ("left", "center" or right")
and with the given font.
If move is True, the pen is moved to the bottom-right corner
of the text. By default, move is False.
Example (for a Turtle instance named turtle):
>>> turtle.write('Home = ', True, align="center")
>>> turtle.write((0,0), True)
"""
# TODO: Implement `align` and `font` parameters
text = str(text)
w, h = ui.measure_string(text)
color = self._current_color
pos = self._pos
def _draw():
ui.set_color(color)
ui.draw_string(text, (pos.x, pos.y - h, 0, 0))
self._add_drawing(_draw)
if move:
self._pos += (w, 0)
self.update_view()
def circle(self, radius, extent=None, steps=None):
""" Draw a circle with given radius.
Arguments:
radius -- a number
extent (optional) -- a number
steps (optional) -- an integer
Draw a circle with given radius. The center is radius units left
of the turtle; extent - an angle - determines which part of the
circle is drawn. If extent is not given, draw the entire circle.
If extent is not a full circle, one endpoint of the arc is the
current pen position. Draw the arc in counterclockwise direction
if radius is positive, otherwise in clockwise direction. Finally
the direction of the turtle is changed by the amount of extent.
As the circle is approximated by an inscribed regular polygon,
steps determines the number of steps to use. If not given,
it will be calculated automatically. Maybe used to draw regular
polygons.
call: circle(radius) # full circle
--or: circle(radius, extent) # arc
--or: circle(radius, extent, steps)
--or: circle(radius, steps=6) # 6-sided polygon
Example (for a Turtle instance named turtle):
>>> turtle.circle(50)
>>> turtle.circle(120, 180) # semicircle
"""
if extent is None:
extent = math.radians(360)
else:
extent = math.radians(extent) if not self._use_rad else extent
frac = abs(extent) / math.radians(360)
if steps is None or steps == 0:
steps = 1 + int(min(11 + abs(radius)/6.0, 59.0) * frac)
elif not isinstance(steps, int):
raise TypeError('steps must be an integer')
w = 1.0 * extent / steps
w2 = 0.5 * w
l = 2.0 * radius * sin(w2)
if radius < 0:
l, w, w2 = -l, -w, -w2
if not self._use_rad:
w, w2 = math.degrees(w), math.degrees(w2)
self._left(w2)
for i in range(steps):
self._forward(l)
self._left(w)
self._left(-w2)
def dot(self, size=None, *color):
"""Draw a dot with diameter size, using color.
Optional arguments:
size -- an integer >= 1 (if given)
color -- a colorstring or a numeric color tuple
Draw a circular dot with diameter size, using color.
If size is not given, the maximum of pensize+4 and 2*pensize is used.
Example (for a Turtle instance named turtle):
>>> turtle.dot()
>>> turtle.fd(50); turtle.dot(20, "blue"); turtle.fd(50)
"""
if size is None:
size = max(self._line_width + 4, self._line_width * 2)
if not isinstance(size, numbers.Number):
raise TypeError('size must be a number or None')
pos = self.pos()
current_color = self._current_color
def draw_dot():
dot_path = ui.Path.oval(pos[0] - size*0.5, pos[1] - size*0.5, size, size)
if color:
rgba = self._color_args_to_rgba(color)
ui.set_color(rgba)
else:
ui.set_color(current_color)
dot_path.fill()
self._add_drawing(draw_dot)
def shape(self, shapename):
if shapename in _pen_view._shapes:
_pen_view.shape=shapename
def stamp():
# Not implemented
return 0
def clearstamp(self, stampid):
# Not implemented
pass
def clearstamps(self, n=None):
# Not implemented
pass
def undo(self):
#---todo: keep buffer of n previous images for undoing?
pass
def goto(self, x, y=None):
"""Move turtle to an absolute position.
Aliases: setpos | setposition | goto:
Arguments:
x -- a number or a pair/vector of numbers
y -- a number None
call: goto(x, y) # two coordinates
--or: goto((x, y)) # a pair (tuple) of coordinates
--or: goto(vec) # e.g. as returned by pos()
Move turtle to an absolute position. If the pen is down,
a line will be drawn. The turtle's orientation does not change.
Example (for a Turtle instance named turtle):
>>> tp = turtle.pos()
>>> tp
(0.00, 0.00)
>>> turtle.setpos(60,30)
>>> turtle.pos()
(60.00,30.00)
>>> turtle.setpos((20,80))
>>> turtle.pos()
(20.00,80.00)
>>> turtle.setpos(tp)
>>> turtle.pos()
(0.00,0.00)
"""
if y is None:
return self.goto(*x)
self._sleep()
if self.fill_path:
self.fill_path.line_to(x, -y)
if self.is_down:
pos = self._pos
color = self._current_color
line_width = self._line_width
def _draw():
self._draw_line(pos, (x, -y), color, line_width)
self._add_drawing(_draw)
self._pos = ui.Point(x, -y)
self.update_view()
setpos = goto
setposition = goto
def home(self):
"""Move turtle to the origin - coordinates (0,0).
No arguments.
Move turtle to the origin - coordinates (0,0) and set its
heading to its start-orientation (which depends on mode).
Example (for a Turtle instance named turtle):
>>> turtle.home()
"""
self.goto(0, 0)
def towards(self, *args):
"""Return the angle of the line from the turtle's position to (x, y).
Arguments:
x -- a number or a pair/vector of numbers or a turtle instance
y -- a number None None
call: distance(x, y) # two coordinates
--or: distance((x, y)) # a pair (tuple) of coordinates
--or: distance(vec) # e.g. as returned by pos()
--or: distance(mypen) # where mypen is another turtle
Return the angle, between the line from turtle-position to position
specified by x, y and the turtle's start orientation. (Depends on
modes - "standard" or "logo")
Example (for a Turtle instance named turtle):
>>> turtle.pos()
(10.00, 10.00)
>>> turtle.towards(0,0)
225.0
"""
if len(args) == 1:
args = args[0]
if isinstance(args, Pen):
x, y = args._pos
elif len(args) == 2:
x, y = args
else:
raise TypeError('Expected x, y or Pen object')
dx, dy = x - self._pos.x, y - self._pos.y
a = math.atan2(dy, dx)
if not self._use_rad:
a = math.degrees(a)
return a
def heading(self):
""" Return the turtle's current heading.
No arguments.
Example (for a Turtle instance named turtle):
>>> turtle.left(67)
>>> turtle.heading()
67.0
"""
if self._use_rad:
return self._angle
return math.degrees(self._angle)
def setheading(self, angle):
"""Set the orientation of the turtle to to_angle.
Aliases: setheading | seth
Argument:
to_angle -- a number (integer or float)
Set the orientation of the turtle to to_angle.
Here are some common directions in degrees:
standard - mode: logo-mode:
-------------------|--------------------
0 - east 0 - north
90 - north 90 - east
180 - west 180 - south
270 - south 270 - west
Example (for a Turtle instance named turtle):
>>> turtle.setheading(90)
>>> turtle.heading()
90
"""
self._sleep()
self._angle = self._angle_to_rad(angle)
self.update_view()
seth = setheading
def position(self):
"""Return the turtle's current location (x,y), as a Vec2D-vector.
Aliases: pos | position
No arguments.
Example (for a Turtle instance named turtle):
>>> turtle.pos()
(0.00, 240.00)
"""
return Vec2D(self._pos[0], -self._pos[1])
pos = position
def xcor(self):
""" Return the turtle's x coordinate.
No arguments.
Example (for a Turtle instance named turtle):
>>> reset()
>>> turtle.left(60)
>>> turtle.forward(100)
>>> print turtle.xcor()
50.0
"""
return self._pos.x
def ycor(self):
""" Return the turtle's y coordinate
---
No arguments.
Example (for a Turtle instance named turtle):
>>> reset()
>>> turtle.left(60)
>>> turtle.forward(100)
>>> print turtle.ycor()
86.6025403784
"""
return -self._pos.y
def distance(self, x, y=None):
"""Return the distance from the turtle to (x,y) in turtle step units.
Arguments:
x -- a number or a pair/vector of numbers or a turtle instance
y -- a number None None
call: distance(x, y) # two coordinates
--or: distance((x, y)) # a pair (tuple) of coordinates
--or: distance(vec) # e.g. as returned by pos()
--or: distance(mypen) # where mypen is another turtle
Example (for a Turtle instance named turtle):
>>> turtle.pos()
(0.00, 0.00)
>>> turtle.distance(30,40)
50.0
>>> pen = Turtle()
>>> pen.forward(77)
>>> turtle.distance(pen)
77.0
"""
if y is None:
x, y = x
return abs(self._pos - (x, y))
def setx(self, x):
"""Set the turtle's first coordinate to x
Argument:
x -- a number (integer or float)
Set the turtle's first coordinate to x, leave second coordinate
unchanged.
Example (for a Turtle instance named turtle):
>>> turtle.position()
(0.00, 240.00)
>>> turtle.setx(10)
>>> turtle.position()
(10.00, 240.00)
"""
self._sleep()
self._pos = ui.Point(x, self._pos.y)
self.update_view()
def sety(self, y):
"""Set the turtle's second coordinate to y
Argument:
y -- a number (integer or float)
Set the turtle's first coordinate to x, second coordinate remains
unchanged.
Example (for a Turtle instance named turtle):
>>> turtle.position()
(0.00, 40.00)
>>> turtle.sety(-10)
>>> turtle.position()
(0.00, -10.00)
"""
self._sleep()
self._pos = ui.Point(self._pos.x, -y)
self.update_view()
def pen(self, pen=None, **pendict):
"""Return or set the pen's attributes.
NOTE: resizemode, stretchfactor, outline, tilt are not supported in Pythonista
Arguments:
pen -- a dictionary with some or all of the below listed keys.
**pendict -- one or more keyword-arguments with the below
listed keys as keywords.
Return or set the pen's attributes in a 'pen-dictionary'
with the following key/value pairs:
"shown" : True/False
"pendown" : True/False
"pencolor" : color-string or color-tuple
"fillcolor" : color-string or color-tuple
"pensize" : positive number
"speed" : number in range 0..10
"resizemode" : "auto" or "user" or "noresize"
"stretchfactor": (positive number, positive number)
"shearfactor": number
"outline" : positive number
"tilt" : number
This dictionary can be used as argument for a subsequent
pen()-call to restore the former pen-state. Moreover one
or more of these attributes can be provided as keyword-arguments.
This can be used to set several pen attributes in one statement.
Examples (for a Turtle instance named turtle):
>>> turtle.pen(fillcolor="black", pencolor="red", pensize=10)
>>> turtle.pen()
{'pensize': 10, 'shown': True, 'resizemode': 'auto', 'outline': 1,
'pencolor': 'red', 'pendown': True, 'fillcolor': 'black',
'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, '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, 'shearfactor': 0.0}
"""
current_pen_info = {'shown': self.isvisible(),
'pendown': self.isdown(),
'pencolor': self._current_color,
'fillcolor': self._current_fill_color,
'pensize': self._line_width,
'speed': 10 if self.delay_sec == 0 else min(10, max(0, (1.0/self.delay_sec)/10 - 1))
}
if pen is None:
pen = {}
if not pen_dict is None:
pen.update(pen_dict)
if 'shown' in pen:
self.showturtle() if pen['shown'] else self.hideturtle()
if 'pendown' in pen:
self.pendown() if pen['pendown'] else self.penup()
if 'pencolor' in pen:
self.pencolor(pen['pencolor'])
if fillcolor in pen:
self.fillcolor(pen['fillcolor'])
if 'pensize' in pen:
self.width(pen['pensize'])
if 'speed' in pen:
self.speed(pen['speed'])
return current_pen_info
def showturtle(self):
"""Makes the turtle visible.
Aliases: showturtle | st
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.hideturtle()
>>> turtle.showturtle()
"""
self._turtle_visible = True
self.update_view()
st = showturtle
def hideturtle(self):
"""Makes the turtle invisible.
Aliases: hideturtle | ht
No argument.
It's a good idea to do this while you're in the
middle of a complicated drawing, because hiding
the turtle speeds up the drawing observably.
Example (for a Turtle instance named turtle):
>>> turtle.hideturtle()
"""
self._turtle_visible = False
self.update_view()
ht = hideturtle
def isvisible(self):
"""Return True if the Turtle is shown, False if it's hidden.
No argument.
Example (for a Turtle instance named turtle):
>>> turtle.hideturtle()
>>> print turtle.isvisible():
False
"""
return self._turtle_visible
def getscreen(self):
return _main_view
def getturtle(self):
return self
def getpen(self):
return self
def setundobuffer(self, buffer_size):
# Not implemented
pass
def undobufferentries(self):
# Not implemented
return 0
def mode(self, mode=None):
#--- todo?
return 'standard'
def colormode(self, cmode=None):
if cmode == 1.0 or cmode == 255:
self._colormode = cmode
global _default_color_mode
_default_color_mode = cmode
return self._colormode
def update_view(self, make_visible=True):
if len(self.batched_drawings) > 0:
return
_pen_view.set_needs_display(make_visible=make_visible)
class Turtle (Pen):
pass
def done():
pass
mainloop = done
def Screen():
return _main_view
def textinput(title, prompt):
import dialogs
try:
return dialogs.input_alert(title, prompt, '', 'OK')
except KeyboardInterrupt:
return None
def numinput(title, prompt, default=None, minval=None, maxval=None):
import dialogs
default_text = str(default) if default is not None else ''
result = None
while result is None:
entered_text = dialogs.input_alert(title, prompt, default_text, 'OK')
try:
result = float(entered_text)
if minval is not None and result < minval:
prompt = 'The number you entered is too small.'
result = None
elif maxval is not None and result > maxval:
prompt = 'The number you entered is too big.'
result = None
except ValueError:
prompt = 'Please enter a number!'
return result
_main_view = TurtleView()
_default_pen = Pen()
# Move and draw
forward = _default_pen.forward
fd = forward
backward = _default_pen.backward
back = backward
bk = backward
left = _default_pen.left
lt = left
right = _default_pen.right
rt = right
goto = _default_pen.goto
setpos = goto
setposition = goto
setx = _default_pen.setx
sety = _default_pen.sety
setheading = _default_pen.setheading
seth = setheading
home = _default_pen.home
circle = _default_pen.circle
dot = _default_pen.dot
stamp = _default_pen.stamp
clearstamp = _default_pen.clearstamp
clearstamps = _default_pen.clearstamps
undo = _default_pen.undo
speed = _default_pen.speed
# Tell Turtle's state
position = _default_pen.position
pos = position
towards = _default_pen.towards
xcor = _default_pen.xcor
ycor = _default_pen.ycor
heading = _default_pen.heading
distance = _default_pen.distance
# Setting and measurement
degrees = _default_pen.degrees
radians = _default_pen.radians
# Pen control
# Drawing state
down = _default_pen.down
pendown = down
pd = down
up = _default_pen.up
penup = up
pu = up
width = _default_pen.width
pensize = width
isdown = _default_pen.isdown
pen = _default_pen.pen
# Color control
color = _default_pen.color
pencolor = _default_pen.pencolor
fillcolor = _default_pen.fillcolor
# Filling
fill = _default_pen.fill
begin_fill = _default_pen.begin_fill
end_fill = _default_pen.end_fill
# More drawing control
reset = _main_view.reset
clear = _default_pen.clear
write = _default_pen.write
# Turtle state
# Visibility
showturtle = _default_pen.showturtle
st = showturtle
hideturtle = _default_pen.hideturtle
ht = hideturtle
isvisible = _default_pen.isvisible
# Appearance
'''
todo:
shape()
resizemode()
shapesize() | turtlesize()
settiltangle()
tiltangle()
tilt()
'''
# Special Turtle methods
getturtle = _default_pen.getturtle
getpen = getturtle
getscreen = _default_pen.getscreen
setundobuffer = _default_pen.setundobuffer
undobufferentries = _default_pen.undobufferentries
'''
todo:
begin_poly()
end_poly()
get_poly()
window_width()
window_height()
'''
clone = _default_pen.clone
filling = _default_pen.filling
tracer = _main_view.tracer
delay = _default_pen.delay
title = _main_view.title
setup = _main_view.setup
mode = _default_pen.mode
colormode = _default_pen.colormode
bgcolor = _main_view.bgcolor
clearscreen = _main_view.clearscreen
def _flush_all_pens():
for p in _all_pens:
p._force_flush()
import _pykit_atexit
_pykit_atexit.register('turtle', _flush_all_pens)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.