turtleshell 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- turtleshell-1.0.0/PKG-INFO +18 -0
- turtleshell-1.0.0/README.md +3 -0
- turtleshell-1.0.0/pyproject.toml +39 -0
- turtleshell-1.0.0/setup.cfg +4 -0
- turtleshell-1.0.0/src/turtleshell/__init__.py +3 -0
- turtleshell-1.0.0/src/turtleshell/core.py +168 -0
- turtleshell-1.0.0/src/turtleshell.egg-info/PKG-INFO +18 -0
- turtleshell-1.0.0/src/turtleshell.egg-info/SOURCES.txt +10 -0
- turtleshell-1.0.0/src/turtleshell.egg-info/dependency_links.txt +1 -0
- turtleshell-1.0.0/src/turtleshell.egg-info/requires.txt +4 -0
- turtleshell-1.0.0/src/turtleshell.egg-info/top_level.txt +1 -0
- turtleshell-1.0.0/tests/test_turtle.py +106 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: turtleshell
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Convenience wrapper around turtle.Turtle and turtle.Screen
|
5
|
+
Author-email: Sam Mangan <sam.mangan2@gmail.com>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/SamMangan/turtleshell
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Requires-Python: >=3.9
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
Provides-Extra: dev
|
13
|
+
Requires-Dist: bumpver; extra == "dev"
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
15
|
+
|
16
|
+
# TurtleShell
|
17
|
+
|
18
|
+
Convenience wrapper around Python turtle standard library.
|
@@ -0,0 +1,39 @@
|
|
1
|
+
[project]
|
2
|
+
name = "turtleshell"
|
3
|
+
version = "1.0.0"
|
4
|
+
authors = [
|
5
|
+
{ name="Sam Mangan", email="sam.mangan2@gmail.com" },
|
6
|
+
]
|
7
|
+
description = "Convenience wrapper around turtle.Turtle and turtle.Screen"
|
8
|
+
readme = "README.md"
|
9
|
+
requires-python = ">=3.9"
|
10
|
+
classifiers = [
|
11
|
+
"Programming Language :: Python :: 3",
|
12
|
+
"Operating System :: OS Independent",
|
13
|
+
]
|
14
|
+
license = "MIT"
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
16
|
+
|
17
|
+
[project.optional-dependencies]
|
18
|
+
dev = ["bumpver", "pytest"]
|
19
|
+
|
20
|
+
[project.urls]
|
21
|
+
Homepage = "https://github.com/SamMangan/turtleshell"
|
22
|
+
|
23
|
+
[tool.pytest.ini_options]
|
24
|
+
pythonpath = "src"
|
25
|
+
addopts = [
|
26
|
+
"--import-mode=importlib",
|
27
|
+
]
|
28
|
+
|
29
|
+
[tool.bumpver]
|
30
|
+
current_version = "1.0.0"
|
31
|
+
version_pattern = "MAJOR.MINOR.PATCH"
|
32
|
+
commit_message = "Bump version {old_version} -> {new_version}"
|
33
|
+
commit = true
|
34
|
+
tag = true
|
35
|
+
push = false
|
36
|
+
|
37
|
+
[tool.bumpver.file_patterns]
|
38
|
+
"pyproject.toml" = ['current_version = "{version}"', 'version = "{version}"']
|
39
|
+
"src/turtleshell/__init__.py" = ["{version}"]
|
@@ -0,0 +1,168 @@
|
|
1
|
+
"""Convenience wrappers around turtle.Turtle and turtle.Screen
|
2
|
+
See https://github.com/python/cpython/blob/main/Lib/turtle.py
|
3
|
+
"""
|
4
|
+
#TODO handle floats, randcoords, default screensize, default shape
|
5
|
+
|
6
|
+
import colorsys
|
7
|
+
from dataclasses import dataclass
|
8
|
+
import turtle
|
9
|
+
|
10
|
+
@dataclass
|
11
|
+
class HSV:
|
12
|
+
hue: float
|
13
|
+
sat: float
|
14
|
+
val: float
|
15
|
+
def __iter__(self):
|
16
|
+
return iter(self.__dict__.values())
|
17
|
+
|
18
|
+
def Screen():
|
19
|
+
"""Return the singleton screen object."""
|
20
|
+
if Turtle._screen is None:
|
21
|
+
Turtle._screen = _Screen()
|
22
|
+
return Turtle._screen
|
23
|
+
|
24
|
+
class _Screen(turtle._Screen):
|
25
|
+
def __init__(self):
|
26
|
+
super().__init__()
|
27
|
+
turtle.TurtleScreen.__init__(self, _Screen._canvas)
|
28
|
+
if turtle.Turtle._screen is None:
|
29
|
+
turtle.Turtle._screen = self
|
30
|
+
self.colormode(255)
|
31
|
+
|
32
|
+
def _colorstr(self, color):
|
33
|
+
isnumber = lambda x: isinstance(x, (int, float))
|
34
|
+
if len(color) == 3 and all([isnumber(c) for c in color]):
|
35
|
+
lower, upper = 0, Turtle._screen.colormode()
|
36
|
+
color = [max(min(upper, round(c)), lower) for c in color]
|
37
|
+
return super()._colorstr(color)
|
38
|
+
|
39
|
+
def _hsv_to_rgb(hsv):
|
40
|
+
rgb = colorsys.hsv_to_rgb(*hsv)
|
41
|
+
return [round(c*Turtle._screen.colormode()) for c in rgb]
|
42
|
+
|
43
|
+
class Turtle(turtle.RawTurtle):
|
44
|
+
|
45
|
+
_pen = None
|
46
|
+
_screen = None
|
47
|
+
|
48
|
+
def __init__(self,
|
49
|
+
shape=turtle._CFG["shape"],
|
50
|
+
undobuffersize=turtle._CFG["undobuffersize"],
|
51
|
+
visible=turtle._CFG["visible"]):
|
52
|
+
if Turtle._screen is None:
|
53
|
+
Turtle._screen = Screen()
|
54
|
+
turtle.RawTurtle.__init__(self, Turtle._screen,
|
55
|
+
shape=shape,
|
56
|
+
undobuffersize=undobuffersize,
|
57
|
+
visible=visible)
|
58
|
+
self.shapesize(20)
|
59
|
+
self._pen_hsv = HSV(0, 1, 1)
|
60
|
+
self._fill_hsv = HSV(0, 1, 1)
|
61
|
+
|
62
|
+
@property
|
63
|
+
def x(self):
|
64
|
+
return self.xcor()
|
65
|
+
|
66
|
+
@x.setter
|
67
|
+
def x(self, value):
|
68
|
+
self.setx(value)
|
69
|
+
|
70
|
+
@property
|
71
|
+
def y(self):
|
72
|
+
return self.ycor()
|
73
|
+
|
74
|
+
@y.setter
|
75
|
+
def y(self, value):
|
76
|
+
self.sety(value)
|
77
|
+
|
78
|
+
@property
|
79
|
+
def shapewidth(self):
|
80
|
+
xcoords = [vertex[0] for vertex in self.get_shapepoly()]
|
81
|
+
return max(xcoords) - min(xcoords)
|
82
|
+
|
83
|
+
@property
|
84
|
+
def shapeheight(self):
|
85
|
+
ycoords = [vertex[1] for vertex in self.get_shapepoly()]
|
86
|
+
return max(ycoords) - min(ycoords)
|
87
|
+
|
88
|
+
def hue(self, degrees):
|
89
|
+
self.penhue(degrees)
|
90
|
+
self.fillhue(degrees)
|
91
|
+
|
92
|
+
def penhue(self, degrees):
|
93
|
+
self._pen_hsv.hue = degrees/360
|
94
|
+
self.pencolor(_hsv_to_rgb(self._pen_hsv))
|
95
|
+
|
96
|
+
def fillhue(self, degrees):
|
97
|
+
self._fill_hsv.hue = degrees/360
|
98
|
+
self.fillcolor(_hsv_to_rgb(self._fill_hsv))
|
99
|
+
|
100
|
+
def sat(self, value):
|
101
|
+
self.pensat(value)
|
102
|
+
self.fillsat(value)
|
103
|
+
|
104
|
+
def pensat(self, value):
|
105
|
+
self._pen_hsv.sat = value/100
|
106
|
+
self.pencolor(_hsv_to_rgb(self._pen_hsv))
|
107
|
+
|
108
|
+
def fillsat(self, value):
|
109
|
+
self._fill_hsv.sat = value/100
|
110
|
+
self.fillcolor(_hsv_to_rgb(self._fill_hsv))
|
111
|
+
|
112
|
+
def val(self, value):
|
113
|
+
self.penval(value)
|
114
|
+
self.fillval(value)
|
115
|
+
|
116
|
+
def penval(self, value):
|
117
|
+
self._pen_hsv.val = value/100
|
118
|
+
self.pencolor(_hsv_to_rgb(self._pen_hsv))
|
119
|
+
|
120
|
+
def fillval(self, value):
|
121
|
+
self._fill_hsv.val = value/100
|
122
|
+
self.fillcolor(_hsv_to_rgb(self._fill_hsv))
|
123
|
+
|
124
|
+
def shapesize(self, stretch_wid=None, stretch_len=None, outline=None):
|
125
|
+
stretch_wid = stretch_wid/20 if stretch_wid else None
|
126
|
+
stretch_len = stretch_len/20 if stretch_len else None
|
127
|
+
super().shapesize(stretch_wid, stretch_len, outline)
|
128
|
+
|
129
|
+
def teleport(self, x, y):
|
130
|
+
pendown = self.isdown()
|
131
|
+
if pendown:
|
132
|
+
self.pen(pendown=False)
|
133
|
+
self.penup()
|
134
|
+
self._position = turtle.Vec2D(x, y)
|
135
|
+
self.pen(pendown=pendown)
|
136
|
+
|
137
|
+
def write(self, arg, move=False, align="center", font=("Arial", 18, "bold")):
|
138
|
+
super().write(arg, move, align, font)
|
139
|
+
|
140
|
+
Pen = Turtle
|
141
|
+
|
142
|
+
if __name__ == "__main__":
|
143
|
+
canvas = Screen()
|
144
|
+
canvas.bgcolor("gold")
|
145
|
+
pen = Turtle()
|
146
|
+
print(f"\n\n***\nTURTLE TYPE: {type(pen)}\nSCREEN TYPE: {type(canvas)}\n***\n")
|
147
|
+
|
148
|
+
pen.shape("square")
|
149
|
+
print(f"{pen.shapewidth}, {pen.shapeheight}")
|
150
|
+
pen.shapesize(30, 25)
|
151
|
+
print(f"{pen.shapewidth}, {pen.shapeheight}")
|
152
|
+
|
153
|
+
pen.hue(0)
|
154
|
+
pen.stamp()
|
155
|
+
pen.forward(50)
|
156
|
+
pen.hue(60)
|
157
|
+
pen.stamp()
|
158
|
+
pen.forward(50)
|
159
|
+
pen.hue(120)
|
160
|
+
pen.stamp()
|
161
|
+
pen.forward(50)
|
162
|
+
pen.hue(180)
|
163
|
+
pen.stamp()
|
164
|
+
pen.forward(50)
|
165
|
+
pen.hue(240)
|
166
|
+
pen.stamp()
|
167
|
+
|
168
|
+
canvas.exitonclick()
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: turtleshell
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Convenience wrapper around turtle.Turtle and turtle.Screen
|
5
|
+
Author-email: Sam Mangan <sam.mangan2@gmail.com>
|
6
|
+
License-Expression: MIT
|
7
|
+
Project-URL: Homepage, https://github.com/SamMangan/turtleshell
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Requires-Python: >=3.9
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
Provides-Extra: dev
|
13
|
+
Requires-Dist: bumpver; extra == "dev"
|
14
|
+
Requires-Dist: pytest; extra == "dev"
|
15
|
+
|
16
|
+
# TurtleShell
|
17
|
+
|
18
|
+
Convenience wrapper around Python turtle standard library.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
README.md
|
2
|
+
pyproject.toml
|
3
|
+
src/turtleshell/__init__.py
|
4
|
+
src/turtleshell/core.py
|
5
|
+
src/turtleshell.egg-info/PKG-INFO
|
6
|
+
src/turtleshell.egg-info/SOURCES.txt
|
7
|
+
src/turtleshell.egg-info/dependency_links.txt
|
8
|
+
src/turtleshell.egg-info/requires.txt
|
9
|
+
src/turtleshell.egg-info/top_level.txt
|
10
|
+
tests/test_turtle.py
|
@@ -0,0 +1 @@
|
|
1
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
turtleshell
|
@@ -0,0 +1,106 @@
|
|
1
|
+
import pytest
|
2
|
+
from turtleshell import Screen, Turtle
|
3
|
+
|
4
|
+
BLACK = (0, 0, 0)
|
5
|
+
WHITE = (255, 255, 255)
|
6
|
+
RED = (255, 0, 0)
|
7
|
+
GREEN = (0, 255, 0)
|
8
|
+
BLUE = (0, 0, 255)
|
9
|
+
|
10
|
+
@pytest.fixture
|
11
|
+
def pen():
|
12
|
+
Screen().tracer(0)
|
13
|
+
_pen = Turtle(visible=False)
|
14
|
+
return _pen
|
15
|
+
|
16
|
+
@pytest.fixture
|
17
|
+
def screen():
|
18
|
+
return Screen()
|
19
|
+
|
20
|
+
def test_x(pen):
|
21
|
+
pen.setx(100)
|
22
|
+
assert pen.x == 100
|
23
|
+
|
24
|
+
def test_y(pen):
|
25
|
+
pen.sety(200)
|
26
|
+
assert pen.y == 200
|
27
|
+
|
28
|
+
def test_shapesize(pen):
|
29
|
+
pen.shape("square")
|
30
|
+
pen.shapesize(30,40)
|
31
|
+
assert pen.shapewidth == 30
|
32
|
+
assert pen.shapeheight == 40
|
33
|
+
|
34
|
+
def test_teleport(pen):
|
35
|
+
assert pen.position() == (0, 0)
|
36
|
+
pen.pendown()
|
37
|
+
pen.teleport(100, 200)
|
38
|
+
assert pen.position() == (100, 200)
|
39
|
+
assert len(pen.currentLine) == 1 # single point => no line drawn
|
40
|
+
assert pen.isdown() # pen still down
|
41
|
+
|
42
|
+
def test_hsv_hue(pen):
|
43
|
+
# pencolor
|
44
|
+
pen.penhue(0)
|
45
|
+
assert pen.pencolor() == RED
|
46
|
+
pen.penhue(120)
|
47
|
+
assert pen.pencolor() == GREEN
|
48
|
+
pen.penhue(240)
|
49
|
+
assert pen.pencolor() == BLUE
|
50
|
+
|
51
|
+
# fillcolor
|
52
|
+
pen.fillhue(0)
|
53
|
+
assert pen.fillcolor() == RED
|
54
|
+
pen.fillhue(120)
|
55
|
+
assert pen.fillcolor() == GREEN
|
56
|
+
pen.fillhue(240)
|
57
|
+
assert pen.fillcolor() == BLUE
|
58
|
+
|
59
|
+
def test_hsv_saturation(pen):
|
60
|
+
# pencolor
|
61
|
+
pen.pensat(0)
|
62
|
+
assert pen.pencolor() == WHITE
|
63
|
+
pen.pensat(50)
|
64
|
+
assert pen.pencolor() == (255, 128, 128) # light red
|
65
|
+
pen.pensat(100)
|
66
|
+
assert pen.pencolor() == RED
|
67
|
+
|
68
|
+
# fillcolor
|
69
|
+
pen.fillsat(0)
|
70
|
+
assert pen.fillcolor() == WHITE
|
71
|
+
pen.fillsat(50)
|
72
|
+
assert pen.fillcolor() == (255, 128, 128) # light red
|
73
|
+
pen.fillsat(100)
|
74
|
+
assert pen.fillcolor() == RED
|
75
|
+
|
76
|
+
def test_hsv_value(pen):
|
77
|
+
# pencolor
|
78
|
+
pen.penval(0)
|
79
|
+
assert pen.pencolor() == BLACK
|
80
|
+
pen.penval(50)
|
81
|
+
assert pen.pencolor() == (128, 0, 0) # dark red
|
82
|
+
pen.penval(100)
|
83
|
+
assert pen.pencolor() == RED
|
84
|
+
|
85
|
+
# fillcolor
|
86
|
+
pen.fillval(0)
|
87
|
+
assert pen.fillcolor() == BLACK
|
88
|
+
pen.fillval(50)
|
89
|
+
assert pen.fillcolor() == (128, 0, 0) # dark red
|
90
|
+
pen.fillval(100)
|
91
|
+
assert pen.fillcolor() == RED
|
92
|
+
|
93
|
+
def test_color(pen, screen):
|
94
|
+
# Colors are clamped to [0, 255]
|
95
|
+
pen.pencolor(-20, -5, 0)
|
96
|
+
assert pen.pencolor() == BLACK
|
97
|
+
|
98
|
+
pen.pencolor(300, 300, 300)
|
99
|
+
assert pen.pencolor() == WHITE
|
100
|
+
|
101
|
+
# Floats are rounded
|
102
|
+
screen.bgcolor(255, 0.3, 0)
|
103
|
+
assert screen.bgcolor() == (255, 0, 0)
|
104
|
+
|
105
|
+
screen.bgcolor(255, 0.7, 0)
|
106
|
+
assert screen.bgcolor() == (255, 1, 0)
|