patternpythonMinor
Python Turtle screen saver
Viewed 0 times
screensaverpythonturtle
Problem
Leon asked a question which got closed both here and at Stack Overflow, but I kind of liked the idea, so I implemented a working version of his code, which I now want reviewed.
With regards to Leon's original code I've done the following improvement (in case he reads this new post):
Here is the working code:
```
import turtle
from random import random, randrange
import math
MIN_COLOR_STEP = 30
MAX_COLOR_STEP = 100
class MyTurtle(turtle.Turtle):
""" Helper turtle class to handle mouse clicks and keep state"""
def __init__(self, **args):
turtle.Turtle.__init__(self, **args)
self._alive = True
self._change_increment = False
def kill_turtle(self, x, y):
self._alive = False
def is_alive(self):
return self._alive
def wants_to_change_direction(self):
if self._change_increment:
self._change_increment = False
return True
return False
def change_increment(self, x, y):
# print "change increment"
self._change_increment = True
def draw_turtle(turtle, x1, y1, x2, y2, red, green, blue):
"""Change color of turtle, and draw a new line"""
turtle.color(red, green, blue)
turtle.up()
turtle.goto(x1, y1)
turtle.down()
turtle.goto(x2, y2)
d
With regards to Leon's original code I've done the following improvement (in case he reads this new post):
- Renamed variables so that they have a meaning
- Used tuple assignment, like
red, green, blue = random(), random(), random()to shorten the code, whilst still maintaining readability
- Removed some magic numbers. Including the one related to 100 vs 1000 which caused the previous version to crash from time to time as one of the color indexes got to be negative
- Added action to left and right mouseclick. Left click changes the direction, whilst right clicks kills the turtle
- Increased the area allowed for the turtle to move in to be the default screen size for the turtle
- Changed to using
randrange()with ranges from negative through positive values, to ease setting coordinate increments
Here is the working code:
```
import turtle
from random import random, randrange
import math
MIN_COLOR_STEP = 30
MAX_COLOR_STEP = 100
class MyTurtle(turtle.Turtle):
""" Helper turtle class to handle mouse clicks and keep state"""
def __init__(self, **args):
turtle.Turtle.__init__(self, **args)
self._alive = True
self._change_increment = False
def kill_turtle(self, x, y):
self._alive = False
def is_alive(self):
return self._alive
def wants_to_change_direction(self):
if self._change_increment:
self._change_increment = False
return True
return False
def change_increment(self, x, y):
# print "change increment"
self._change_increment = True
def draw_turtle(turtle, x1, y1, x2, y2, red, green, blue):
"""Change color of turtle, and draw a new line"""
turtle.color(red, green, blue)
turtle.up()
turtle.goto(x1, y1)
turtle.down()
turtle.goto(x2, y2)
d
Solution
draw_turtle_screensaver() is rather tedious, with many variables and some repetitive code. For example, you have red, green, blue, as well as their new_… counterparts. The code to manipulate those three color channels is written in triplicate. Furthermore, the color-changing code is somewhat copy-and-pasted from the color-initialization code.Therefore, it would pay off to have a better color abstraction. I would write a class like this:
from collections import namedtuple
MIN_COLOR_STEP = 30
MAX_COLOR_STEP = 100
class Color(namedtuple('Color', 'r g b')):
def __new__(cls, r=None, g=None, b=None):
return super(cls, Color).__new__(cls,
r or random(), g or random(), b or random()
)
def __add__(self, color):
return Color(self.r + color.r, self.g + color.g, self.b + color.b)
def __sub__(self, color):
return Color(self.r - color.r, self.g - color.g, self.b - color.b)
def __div__(self, factor):
return Color(self.r / factor, self.g / factor, self.b / factor)
@staticmethod
def progressions():
"""
A generator that yields colors from an endless somewhat-random
sequence of gradual color changes.
"""
def progression(steps, start_color=None, end_color=None):
color = start_color or Color()
delta = ((end_color or Color()) - color) / steps
for _ in xrange(steps):
yield color
color += delta
color = Color()
while True:
for color in progression(randrange(MIN_COLOR_STEP, MAX_COLOR_STEP), color):
yield colorThen, you could relieve
draw_turtle_screensaver() of the task of generating colors. The original nine color-related variables now become just a generator:def draw_turtle_screensaver():
…
colors = Color.progressions()
while bob_the_turtle.is_alive():
if bob_the_turtle.wants_to_change_direction():
x1_incr = …
…
x1 += x1_incr
…
if abs(x1) > MAX_WIDTH:
x1_incr *= -1
…
draw_turtle(bob_the_turtle, x1, y1, x2, y2, *next(colors))Having cleaned up the
red, green, and blue variables that way, I would then use the same technique to eliminate x1, y1, x2, y2, and the …_incr variables. That should leave you with a very simple and readable main loop.Code Snippets
from collections import namedtuple
MIN_COLOR_STEP = 30
MAX_COLOR_STEP = 100
class Color(namedtuple('Color', 'r g b')):
def __new__(cls, r=None, g=None, b=None):
return super(cls, Color).__new__(cls,
r or random(), g or random(), b or random()
)
def __add__(self, color):
return Color(self.r + color.r, self.g + color.g, self.b + color.b)
def __sub__(self, color):
return Color(self.r - color.r, self.g - color.g, self.b - color.b)
def __div__(self, factor):
return Color(self.r / factor, self.g / factor, self.b / factor)
@staticmethod
def progressions():
"""
A generator that yields colors from an endless somewhat-random
sequence of gradual color changes.
"""
def progression(steps, start_color=None, end_color=None):
color = start_color or Color()
delta = ((end_color or Color()) - color) / steps
for _ in xrange(steps):
yield color
color += delta
color = Color()
while True:
for color in progression(randrange(MIN_COLOR_STEP, MAX_COLOR_STEP), color):
yield colordef draw_turtle_screensaver():
…
colors = Color.progressions()
while bob_the_turtle.is_alive():
if bob_the_turtle.wants_to_change_direction():
x1_incr = …
…
x1 += x1_incr
…
if abs(x1) > MAX_WIDTH:
x1_incr *= -1
…
draw_turtle(bob_the_turtle, x1, y1, x2, y2, *next(colors))Context
StackExchange Code Review Q#106312, answer score: 4
Revisions (0)
No revisions yet.