HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Python Turtle screen saver

Submitted by: @import:stackexchange-codereview··
0
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):

  • 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 color


Then, 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 color
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))

Context

StackExchange Code Review Q#106312, answer score: 4

Revisions (0)

No revisions yet.