Source code for lines

# lines.py
# by Chris Proctor
# Helper functions for playing with how the turtle draws

# =============================================================================
# ! Advanced !
# =============================================================================
# This module contains some fancy code that we don't expect you to understand 
# yet. That's ok--as long as we know how to use code, we don't have to 
# understand everything about it. (Do you understand everything about 
# MacOS?) Check out the README for documentation on how to use this code. 

# Of course, if you want to dig into this module, feel free. You can ask a 
# teacher about it if you're interested. 
# =============================================================================

from itertools import cycle
from turtle import Turtle, pendown, penup, pencolor

class Segmenter:
    """
    Breaks a distance (length) into segments, which are yielded one at a time.
    Whatever's left over at the end gets yielded too. If start_at is given, 
    the pattern is offset by this much. For example:
    
        >>> from drawing.lines import Segmenter
        >>> list(Segmenter([1, 5]).segment(20))
        [1, 5, 1, 5, 1, 5, 1, 1]
    """

    def __init__(self, pattern):
        "Should be initialized with a pattern like [(10, penup), (20, pendown)]"
        self.pattern = pattern
        self.remainder = 0
        self.remainder_state = None
        self.pattern_cycle = cycle(pattern)

    def segment(self, length):
        """
        Segments `length` into chunks according to the pattern, yielding each chunk
        along with a boolean indicating whether there is more coming
        """
        if self.remainder > 0:
            if length > self.remainder:
                yield self.remainder, self.remainder_state
                length -= self.remainder
                self.remainder = 0
            else:
                yield length, self.remainder_state
                self.remainder -= length
                length = 0
        if length > 0: 
            for (seg, state) in self.pattern_cycle:
                if length >= seg:
                    yield seg, state
                    length -= seg
                else:
                    if length > 0:
                        yield length, state
                    self.remainder = seg - length
                    self.remainder_state = state
                    return 

def go_segmented(turtle, distance):
    "This is the fake go function that we're going to inject into the turtle"
    for seg, state in turtle.segmenter.segment(distance):
        state()
        turtle.true_go(seg)

def color_setter_factory(color):
    "Returns a function that sets the pencolor"
    def set_color():
        pencolor(color)
    return set_color

[docs]class dashes: """ A context manager which causes a code block to draw with dashes. Arguments: spacing (int): (Optional) The length of each dash and space in pixels. Defaults to 20. :: from lines import dashes with dashes(): for side in range(4): forward(100) right(90) """ def __init__(self, spacing=20): self.spacing = spacing def __enter__(self): Turtle.segmenter = Segmenter([(self.spacing, pendown), (self.spacing, penup)]) Turtle.true_go = Turtle._go Turtle._go = go_segmented def __exit__(self, exc_type, exc_value, traceback): Turtle._go = Turtle.true_go del Turtle.true_go
[docs]class dots: """A context manager which causes a code block to draw with dots. Arguments: spacing (int): (Optional) The space between each dot in pixels. Defaults to 10. :: from lines import dots with dots(): for side in range(5): forward(100) right(360/5) """ def __init__(self, spacing=10): self.spacing = spacing def __enter__(self): Turtle.segmenter = Segmenter([(1, pendown), (self.spacing, penup)]) Turtle.true_go = Turtle._go Turtle._go = go_segmented def __exit__(self, exc_type, exc_value, traceback): Turtle._go = Turtle.true_go del Turtle.true_go
[docs]class rainbow: "A context manager which causes a code block to draw in rainbow colors" default_colors = ['red', 'orange', 'yellow', 'green', 'blue', 'purple'] def __init__(self, spacing=10, colors=None): self.spacing = spacing self.colors = colors or rainbow.default_colors def __enter__(self): Turtle.segmenter = Segmenter([(self.spacing, color_setter_factory(color)) for color in self.colors]) Turtle.true_go = Turtle._go Turtle._go = go_segmented def __exit__(self, exc_type, exc_value, traceback): Turtle._go = Turtle.true_go del Turtle.true_go
if __name__ == '__main__': from turtle import * pensize(6) with rainbow(): for i in range(100): forward(i) right(2 * 360/(i+1))