Decorating for Christmas with recursive snowflakes#
Note
This tutorial is a little advanced and assumes that you are already somewhat familiar with functions in Python.
The image below shows an example of the decorations can create by using fractals and turtle programming.
If we look closer at the “arms” of a snowflake, we may notice that it appears to contain more miniature versions of it self, smaller “arms”. When a pattern appears to contain smaller copies of it self we call it a “fractal” or recursive pattern.
Fraktale mønstre dukker opp mange steder i naturen og kunst. Fractal patterns appear many places in both art and nature.
We can draw fractal patterns with code by using recursive functions.
A recursive function is a function that calls itself.
So what we need to do is to define a function that draws an arm that consists of smaller arms which are drawn by the same function.
This may be a bit confusing, so let’s start by creating a simple draw_arm
function that draws a simple snowflake arm.
1from turtlethread import Turtle
2
3def draw_arm(needle, length):
4 # Move forward a little bit
5 needle.forward(0.4*length)
6
7 # Draw the first "branch" pointing a little downwards
8 needle.right(45)
9 needle.forward(length*0.3)
10 needle.backward(length*0.3)
11 needle.left(45)
12 # Draw the second "branch" pointing a little upwards
13 needle.left(45)
14 needle.forward(length*0.3)
15 needle.backward(length*0.3)
16 needle.right(45)
17
18 # Move forward a little bit
19 needle.forward(length*0.6)
20
21needle = Turtle()
22with needle.running_stitch(30):
23 draw_arm(needle, 400)
24
25needle.visualise()
If we call this function, we get a drawing of an arm, and we observe that this arm is built up of four “pieces”, where each piece is a line segment.
Instead of a simple line segment, we want each piece to contain a small arm with two new branches. For this, we can replace the call to the forward
function with a call to the draw_arm
function.
Then we get a function which calls itself, also known as a recursive function.
1from turtlethread import Turtle
2
3def draw_arm(needle, length):
4 # Move forward a little bit
5 draw_arm(needle, 0.4*length)
6
7 # Draw the first "branch" pointing a little downwards
8 needle.right(45)
9 draw_arm(needle, length*0.3)
10 needle.backward(length*0.3)
11 needle.left(45)
12 # Draw the second "branch" pointing a little upwards
13 needle.left(45)
14 draw_arm(needle, length*0.3)
15 needle.backward(length*0.3)
16 needle.right(45)
17
18 # Move forward a little bit
19 draw_arm(needle, length*0.6)
20
21needle = Turtle()
22with needle.running_stitch(30):
23 draw_arm(needle, 400)
24
25needle.visualise()
If we run this code, the turtle will never be able to finish the drawing as each branch will consist of an arm with branches where each branch consists of an arm with brances where each branch…. etc.
This will result in an infinite amount of details that the computer cannot handle, so the program will terminate with a RecursionError
.
To ensure that the drawing is eventually completed, we need a variable that keeps track of which “recursion level” we are at. Then we can ensure that we stop after a certain amount of levels:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 2)
27
28needle.visualise()
- Line 3:
Here we have added an input argument, recursion_level which keeps track of which level of recursion we are currently at.
- Lines 4-5:
Here we check if we have reached level 0. In that case we should only draw a simple line segment without any branches. This ensures that the drawing is eventually completed.
- Lines 8, 12, 17 og 22:
When we call the
draw_arm
function inside thedraw_arm
function, we inputrecursion_level - 1
to indicate that we have “used up” one level of recursion. This ensures that we can keep track of how many levels we have.- Line 26:
When we call the
draw_arm
function outside the function to draw an arm, we can specify how many levels of recursion we want.
Below are some examples of how the arm looks for different levels of recursion:
Recursion level 0
Kode:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 0)
27
28needle.visualise()
Recursion level 1
Kode:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 1)
27
28needle.visualise()
Recursion level 2
Kode:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 2)
27
28needle.visualise()
Try it yourself:
Update the code to draw snowflake arms with recursion levels 3 and 4.
Click here to see the snowflake arm and the code for recursion level 3:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 3)
27
28needle.visualise()
Klikk her for å se snøfnugg-armen og koden for rekursjonsnivå 4:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 draw_arm(needle, 400, 4)
27
28needle.visualise()
Now that we have code to draw a snowflake arm, we can use repetition to draw a complete snowflake:
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 for arm in range(6):
27 draw_arm(needle, 400, 2)
28 needle.backward(400)
29 needle.right(60)
30
31needle.visualise()
Try it yourself:
Run the code and see what you get.
Update the code, so the branches point at a different angle (e.g. 60 degrees).
Create a modified version of the code where each arm has two sets of branches. Below is a visualisation of how this can look.
Click here to see an example of how the finished code can look
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23
24needle = Turtle()
25with needle.running_stitch(30):
26 for arm in range(6):
27 draw_arm(needle, 400, 2)
28 needle.backward(400)
29 needle.right(60)
30
31needle.visualise()
Example fractals#
Try it yourself:
Create your own fractal! Below is a gallery of examples:
Example fractal 1
Code for recursion level 1
1from turtlethread import Turtle
2
3
4def tegn_side(needle, length, rekursion_level):
5 if rekursion_level == 0:
6 needle.forward(length)
7 else:
8 tegn_side(needle, length/3, rekursion_level-1)
9 needle.left(60)
10 tegn_side(needle, length/3, rekursion_level-1)
11 needle.right(120)
12 tegn_side(needle, length/3, rekursion_level-1)
13 needle.left(60)
14 tegn_side(needle, length/3, rekursion_level-1)
15
16needle = Turtle()
17with needle.running_stitch(30):
18 for side in range(3):
19 tegn_side(needle, 200, 1)
20 needle.right(120)
21
22needle.visualise()
Code for recursion level 2
1from turtlethread import Turtle
2
3def tegn_side(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 tegn_side(needle, length/3, rekursion_level-1)
8 needle.left(60)
9 tegn_side(needle, length/3, rekursion_level-1)
10 needle.right(120)
11 tegn_side(needle, length/3, rekursion_level-1)
12 needle.left(60)
13 tegn_side(needle, length/3, rekursion_level-1)
14
15needle = Turtle()
16with needle.running_stitch(30):
17 for side in range(3):
18 tegn_side(needle, 200, 2)
19 needle.right(120)
20
21needle.visualise()
Example fractal 2
Code for recursion level 1
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 needle.forward(length)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(30)
12 draw_arm(needle, 0.75*length, rekursion_level-1)
13 needle.backward(0.75*length)
14 needle.left(30)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(30)
17 draw_arm(needle, 0.75*length, rekursion_level-1)
18 needle.backward(0.75*length)
19 needle.right(30)
20
21needle = Turtle()
22with needle.running_stitch(30):
23 needle.left(90)
24 draw_arm(needle, 50, 1)
25needle.visualise()
Code for recursion level 3
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 else:
7 # Move forward a little bit
8 needle.forward(length)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(30)
12 draw_arm(needle, 0.75*length, rekursion_level-1)
13 needle.backward(0.75*length)
14 needle.left(30)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(30)
17 draw_arm(needle, 0.75*length, rekursion_level-1)
18 needle.backward(0.75*length)
19 needle.right(30)
20
21needle = Turtle()
22with needle.running_stitch(30):
23 needle.left(90)
24 draw_arm(needle, 50, 3)
25needle.visualise()
Example fractal 3
Code for recursion level 2
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 elif rekursion_level % 2 == 0:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23 else:
24 # Move forward a little bit
25 draw_arm(needle, 0.4*length, rekursion_level-1)
26
27 # Draw the first "branch" pointing a little downwards
28 needle.right(60)
29 draw_arm(needle, length*0.2, rekursion_level-1)
30 needle.backward(length*0.2)
31 needle.left(60)
32 # Draw the second "branch" pointing a little upwards
33 needle.left(60)
34 draw_arm(needle, length*0.2, rekursion_level-1)
35 needle.backward(length*0.2)
36 needle.right(60)
37
38 draw_arm(needle, 0.2*length, rekursion_level-1)
39
40 # Draw the third "branch" pointing a little downwards
41 needle.right(60)
42 draw_arm(needle, length*0.2, rekursion_level-1)
43 needle.backward(length*0.2)
44 needle.left(60)
45 # Draw the fourth "branch" pointing a little upwards
46 needle.left(60)
47 draw_arm(needle, length*0.2, rekursion_level-1)
48 needle.backward(length*0.2)
49 needle.right(60)
50
51 # Move forward a little bit
52 draw_arm(needle, length*0.4, rekursion_level-1)
53
54needle = Turtle()
55with needle.running_stitch(50):
56 for arm in range(6):
57 draw_arm(needle, 400, 2)
58 needle.backward(400)
59 needle.right(60)
60
61needle.visualise()
Code for recursion level 3
1from turtlethread import Turtle
2
3def draw_arm(needle, length, rekursion_level):
4 if rekursion_level == 0:
5 needle.forward(length)
6 elif rekursion_level % 2 == 0:
7 # Move forward a little bit
8 draw_arm(needle, 0.4*length, rekursion_level-1)
9
10 # Draw the first "branch" pointing a little downwards
11 needle.right(45)
12 draw_arm(needle, length*0.3, rekursion_level-1)
13 needle.backward(length*0.3)
14 needle.left(45)
15 # Draw the second "branch" pointing a little upwards
16 needle.left(45)
17 draw_arm(needle, length*0.3, rekursion_level-1)
18 needle.backward(length*0.3)
19 needle.right(45)
20
21 # Move forward a little bit
22 draw_arm(needle, length*0.6, rekursion_level-1)
23 else:
24 # Move forward a little bit
25 draw_arm(needle, 0.4*length, rekursion_level-1)
26
27 # Draw the first "branch" pointing a little downwards
28 needle.right(60)
29 draw_arm(needle, length*0.2, rekursion_level-1)
30 needle.backward(length*0.2)
31 needle.left(60)
32 # Draw the second "branch" pointing a little upwards
33 needle.left(60)
34 draw_arm(needle, length*0.2, rekursion_level-1)
35 needle.backward(length*0.2)
36 needle.right(60)
37
38 draw_arm(needle, 0.2*length, rekursion_level-1)
39
40 # Draw the third "branch" pointing a little downwards
41 needle.right(60)
42 draw_arm(needle, length*0.2, rekursion_level-1)
43 needle.backward(length*0.2)
44 needle.left(60)
45 # Draw the fourth "branch" pointing a little upwards
46 needle.left(60)
47 draw_arm(needle, length*0.2, rekursion_level-1)
48 needle.backward(length*0.2)
49 needle.right(60)
50
51 # Move forward a little bit
52 draw_arm(needle, length*0.4, rekursion_level-1)
53
54needle = Turtle()
55with needle.running_stitch(50):
56 for arm in range(6):
57 draw_arm(needle, 400, 3)
58 needle.backward(400)
59 needle.right(60)
60
61needle.visualise()