# Koch snowflake in Python with Turtle

After finding a piece of code on StackOverflow that drew the Koch snowflake fractal, I made a ton of modifications to it and used it to produce an animation divided in two parts:

• Constant size, recursion depth increasing.
• Constant recursion depth, size increasing.

You can see the animation here.

I am interested mainly in:

• Have I got too many constants? I like them because they make the program easy to customize, but 12 constants looks like a lot.

• Any idea to avoid repetition in these blocks of code?

write_as_title(\
"Constant size, recursion depth increasing.", font=FONT)
time.sleep(WRITING_SLEEPING_INTERVAL)

for deepness in range(MAX_DEPTH):
costum_reset()
snowflake(deepness, SIZE_WHEN_CONSTANT_DEPTH)
time.sleep(DRAWING_SLEEPING_INTERVAL)


costum_reset()
write_as_title(\
"Constant recursion depth, size increasing.", font=FONT)
time.sleep(WRITING_SLEEPING_INTERVAL)
for size in RANGE_OF_SIZES:
costum_reset()
snowflake(DEPTH_WHEN_CONSTANT_SIZE, size)


They are similar but I cannot quite abstract the similarity out.

from __future__ import division

import turtle
import time

WIDTH, HEIGHT = 800, 600
FONT = ("Arial", 25, "normal")
DRAWING_SLEEPING_INTERVAL = 0.5
WRITING_SLEEPING_INTERVAL = 2.5
MAX_DEPTH = 5
SIZE_WHEN_CONSTANT_DEPTH = 300
DEPTH_WHEN_CONSTANT_SIZE = 4
RANGE_OF_SIZES = range(0, 5000, 5)
SIDES_OF_AN_EQUILATERAL_TRIANGLE = 3
EQUILATERAL_TRIANGLE_INTERNAL_ANGLE = 120
DRAWING_START_X = - 200
DRAWING_START_Y = 150

def snowflake(n, size=300):

for _ in range(SIDES_OF_AN_EQUILATERAL_TRIANGLE):
snowflake_edge(n, size)
turtle.right(EQUILATERAL_TRIANGLE_INTERNAL_ANGLE)

turtle.update()

def snowflake_edge(n, size=300):
if n == 0:
turtle.forward(size)
return

for movement in (lambda: turtle.left(EQUILATERAL_TRIANGLE_INTERNAL_ANGLE // 2),
lambda: turtle.right(EQUILATERAL_TRIANGLE_INTERNAL_ANGLE),
lambda: turtle.left(EQUILATERAL_TRIANGLE_INTERNAL_ANGLE // 2)):
snowflake_edge(n - 1, size / SIDES_OF_AN_EQUILATERAL_TRIANGLE)
movement()

snowflake_edge(n - 1, size / SIDES_OF_AN_EQUILATERAL_TRIANGLE)

def costum_reset():
turtle.reset()
turtle.penup()
turtle.setx( DRAWING_START_X )
turtle.sety( DRAWING_START_Y )
turtle.pendown()

def write_as_title(title, font=FONT):
turtle.setx(- (WIDTH // 2.5) )
turtle.sety(HEIGHT // 3)
turtle.write(title, font=font)

if __name__ == "__main__":
turtle.setup(width=WIDTH, height=HEIGHT)
turtle.hideturtle()
turtle.tracer(0, 0)

write_as_title(\
"Constant size, recursion depth increasing.", font=FONT)
time.sleep(WRITING_SLEEPING_INTERVAL)

for deepness in range(MAX_DEPTH):
costum_reset()
snowflake(deepness, SIZE_WHEN_CONSTANT_DEPTH)
time.sleep(DRAWING_SLEEPING_INTERVAL)

costum_reset()
write_as_title(\
"Constant recursion depth, size increasing.", font=FONT)
time.sleep(WRITING_SLEEPING_INTERVAL)
for size in RANGE_OF_SIZES:
costum_reset()
snowflake(DEPTH_WHEN_CONSTANT_SIZE, size)


Your code reads well and is easily understandable. On top of also finding that you might have too much constant (more on that in a little bit), I just have a few nitpicks:

• whitespace in expressions is weird sometimes;
• a docstring would help understanding the recursion involved in snowflake_edge;
• you don't need the newline continuation (\) inside parenthesis, they already act as an implicit one;
• you allow to parametrize write_as_title with a custom font but nothing similar is done with other kind of parameters.

# On reducing the similarities between the two drawings

I would have put a call to costum_reset (costum? really?) at the beginning of both write_as_title and snowflake since both drawings need a clear state to begin with.

I would also put time.sleep(WRITING_SLEEPING_INTERVAL) at the end of write_as_title. Considering its value, it won't hurt anyone in an interactive session and is still needed in a program for people to be able to read the text.

This way, the main part would look like:

if __name__ == "__main__":
turtle.setup(width=WIDTH, height=HEIGHT)
turtle.hideturtle()
turtle.tracer(0, 0)

write_as_title(
"Constant size, recursion depth increasing.", font=FONT)
for deepness in range(MAX_DEPTH):
snowflake(deepness, SIZE_WHEN_CONSTANT_DEPTH)
time.sleep(DRAWING_SLEEPING_INTERVAL)

write_as_title(
"Constant recursion depth, size increasing.", font=FONT)
for size in RANGE_OF_SIZES:
snowflake(DEPTH_WHEN_CONSTANT_SIZE, size)


You can even make the two sensible parts two functions if you want to separate the concerns better.

# On reducing the amount of constants

Of your 12 constants, a few are really constant, some are arbitrary fixed for the purpose of your application, and some could be tweaked to achieve a better (prettier?) result.

Maybe that allowing to change the latter group using the command line could be more intuitive to tweak the parameters of the display. I personally would keep the following constants:

• WIDTH, HEIGHT = 800, 600
• FONT = ("Arial", 25, "normal")
• WRITING_SLEEPING_INTERVAL = 2.5
• SIDES_OF_AN_EQUILATERAL_TRIANGLE = 3
• EQUILATERAL_TRIANGLE_INTERNAL_ANGLE = 120
• DRAWING_START_X = - 200
• DRAWING_START_Y = 150

And use argparse to provide default values or custom ones from the command line for:

• DRAWING_SLEEPING_INTERVAL
• MAX_DEPTH
• SIZE_WHEN_CONSTANT_DEPTH
• DEPTH_WHEN_CONSTANT_SIZE
• SIZES_LIMIT

Note the use of SIZES_LIMIT which will then create the range object RANGE_OF_SIZES.

All of these last "constants" are only used within the if __name__ == '__main__' so it's pretty easy to adapt.

Just looking at this function:

def snowflake(n, size=300):
for _ in range(SIDES_OF_AN_EQUILATERAL_TRIANGLE):
snowflake_edge(n, size)
turtle.right(EQUILATERAL_TRIANGLE_INTERNAL_ANGLE)
turtle.update()

1. There's no docstring. What does it do? What is the meaning of the arguments?

2. The name n does not give much of a clue. I would call this argument depth.

3. The function does two things: it draws the snowflake, and it updates the screen. It is usually best if a function does just one thing: then you have more flexibility about how you use it. For example, what if you wanted to draw several snowflakes before updating the screen?

4. The constant EQUILATERAL_TRIANGLE_INTERNAL_ANGLE is wrongly named. It has the value 120, but the interior angle of an equilateral triangle is actually 60°. The term you want here is exterior angle.

5. The function uses two constants, but these are not independent. The exterior angle $θ$ of a regular polygon can be computed from the number of sides $k$, like this: $θ = {360°\over k}$.

6. One way to reduce the number of constants is to turn them into function parameters. I would write:

# Number of degrees in a full circle.
FULLCIRCLE = 360

def snowflake(depth=4, size=300, sides=3):
"""Draw a Koch snowflake fractal.

Arguments:
depth -- depth of recursion.
size -- length of each side, in pixels.
sides -- number of sides of the snowflake.

"""
theta = FULLCIRCLE / sides
for _ in range(sides):
snowflake_edge(depth, size)
turtle.right(theta)


(A global constant for the number of degrees in a full circle is necessary, unfortunately, because Turtle allows you to change this using the degrees function, but doesn't have a documented way of getting the current value.)