A Touch of Randomness Makes The Magic Sparkle • A Python `turtle` Animation
Let's build this sparkly `turtle` animation step by step
Writing animations using Python's turtle
module is one of my hobbies. Earlier this week, I needed a distraction, so I opened my IDE and immersed myself in writing a new animation. Here it is. I'll build the code step by step in this article, but I'll try to keep this article compact.
So, let's start. We'll work towards building this animation:
You can steer the lead dot using arrow keys, and coloured dots fall gracefully from this lead dot. You'll notice the dots fall at nearly equal speeds, but they're not falling at the exact same rate.
The Python Coding Place is now live and the Members’ Area will launch in January. It will include members-only video courses, weekly videos on varied Python topics, workshops, live cohort courses, and more. Membership is through a one-time fee that gives you access to everything, forever
Let's Start With The Lead Dot
You'll use the turtle
module in this animation, which is part of Python's standard library. But don't worry if you've never used this module. It's designed to be relatively straightforward to use.
Let's start by creating the lead dot. You'll need to:
Create the dot
Set it to move continuously
Enable the user to steer the dot using arrow keys
Start by importing the turtle
module and creating an instance of the Turtle
class. I'll call this script magic_sparkle.py
, but you can choose your preferred name if you wish:
You also change the shape of the displayed image representing the Turtle
object to a dot and "raise the pen up" so that the object doesn't draw a line when it moves.
Let's also add some background colour:
The Screen
object represents the window that pops up when you run this code. This is the canvas where the animation takes place. In the code above, you also change the lead dot's colour to white. Here's the output:
Next, you can make the lead dot move continuously using a while
loop:
So far, you have a white dot that runs off the screen. If the dot disappears off the screen too quickly, you can reduce lead_dot_speed
to a value between 0
and 1
:
You can steer this dot by defining a couple of functions that turn the dot left and right and binding these functions to the arrow keys on your keyboard:
You can now steer the dot using arrow keys. Note that depending on your system, you may need to click on the animation window to make sure it's active before you can start using the arrow keys in the animation:
Controlling When The Image Is Updated
You may notice a lag in the animation when you steer the dot. This occurs because the animation updates the display at every change of the dot's position and direction. Even rotating the dot's direction takes time.
We can fix this lag by controlling when the program updates the screen. You can set the animation to not display any updates and only make the changes behind the scenes using 0
as an argument for the tracer()
method, which is a method attached to the screen object you created.
However, you'll need to tell your program when to update the screen. You can do so in the while
loop. This turns each iteration of the while
loop into a frame of the animation. I'm only showing sections with changes in this code:
This change will serve you well later when the animation will have more objects moving simultaneously on the screen.
Adding The Falling Dots
Let's create a Dot
class to simplify creating the falling dots. This class can inherit from the turtle.Turtle
class. I'll define the class in a separate file called dot.py
and import the class into the main script:
I promised a compact(-ish) article, so I won't dwell too long on explaining class attributes, instance attributes, special methods or inheritance. But there's a series of seven articles in The Python Coding Stack's archive about this topic if you'd like to read more: A Magical Tour Through Object-Oriented Programming in Python • Hogwarts School of Codecraft and Algorithmancy. There's also a chapter in The Python Coding Book about object-oriented programming: Chapter 7 | Object-Oriented Programming.
Each dot is created at the x- and y-coordinates you pass when creating the instance. Its colour is set randomly from the three options (these are The Python Coding Place's brand colours, along with the background colour used earlier!) The object's heading is set to -90
, which sets the object pointing downwards. This means we can move the object forward in the fall()
method.
You can now import this class in magic_sparkle.py
and spawn a new dot every half a second (for now). Each new dot's location matches the lead dot's location at the time the new dot is created. You can store all the new dots in a list and then loop through this list to move all the dots in each frame:
You'll get this animation when you run this script:
We're getting there, but there's still a bit more to do. But before you move on, you can notice that the regularity of the spawn intervals and falling speeds makes the animation a bit too, what's the word, regular. There's nothing wrong with this. But I think adding a touch of randomness makes a big difference. We'll get to this soon.
Can We Ignore The Dots That Leave The Screen?
No, we shouldn't. Here's why:
You add a line to show how many dots there are in the dots
list. Even though you can no longer see dots that have left the screen, they're still there, and your code is still making them fall!
This seems like an easy problem to solve (ha!):
You're now removing dots from the list when they dip below the screen's bottom edge. This is good as you won't need to deal with these objects when you iterate through dots
in the next iterations. And the printout showing the number of dots in the list shows that this number decreases each time a dot leaves the screen.
But the objects are still there, out of sight. That's because the turtle
module keeps a list of all the Turtle
objects created in the program. You can access this internal list using turtle.turtles()
:
You'll see that the number of dots in the list you create, dots
, decreases when a dot leaves the screen. However, the number of items in turtle.turtles()
never decreases.
You can remove these objects, too:
Note that the length of turtle.turtles()
will always be one more than the length of dots
since it also includes the lead dot, which is not included in dots
.
I'll remove the printouts from future versions of the code since they're no longer needed.
A Touch of Randomness • The Magic Sparkle
The code works. But we can make it look better. And one way of doing this is by adding a touch of randomness to remove the regularity.
You can define ranges for the size and speed of the dots and add these as arguments when you create Dot
instances. Let's start with updating the class definition:
The __init__()
special method has two additional parameters, size_range
and speed_range
, which are designed to be tuples containing the start and end of each range. You'll see an example of this in the main script.
Next, you change the size of the dot using the shapesize()
method of the Turtle
class:
You generate a random number between
0
and1
usingrandom.random()
You multiply this number by the size of the range to scale the number. For example, if the range is from
2
to5
, you multiply the random number by3
, which is5-2
. This means the random number is now a value between0
and3
.Finally, you add the start value of the range to shift the value. In the above example, the range starts at
2
. Therefore you add2
to the result from the step above. The random number you create is now between2
and5
, which is the range of values supplied.
You use the same logic to set the speed of the dot.
Back in magic_sparkle.py
, you define the ranges as tuples and add them as arguments when you create a Dot
object:
You can run this code to see how this randomness affects the animation. But I'll make one more change before showing the animation. Let's also add randomness to the spawn time using the same transformations you used earlier to scale and shift a random number in the range from 0
to 1
to the desired range:
The dots aren't spawned every half a second now. Instead, the delay between dots spawned is a random value generated within the range defined.
And Finally, Fix The Frame Rate
I discussed the issue of fixing the frame rate in an earlier post on The Stack: Section 5 in Zen and The Art of Python turtle Animations • A Step-by-Step Guide. Let's fix the frame rate in this animation:
This is the final version of magic_sparkle.py
. And here's the final version of dots.py
for completeness:
This animation is now complete. The animation I showed you at the top of this article is created with this final version of the code.
Final Word
Have I convinced you that writing animations using the turtle
module is loads of fun? It's great for exploring and experimenting with many Python topics. And I'll use it again in posts on The Stack from time to time!
Now, go an sprinkle some magic with this animation.
Code in this article uses Python 3.12
Stop Stack
#39
Here's a post I published on Twitter / X today
Stephen: Let's do a Black Friday offer. Over 70% off on lifetime membership of The Python Coding Place. What do you think?
AI Assistant: Isn't that the same deal as the pre-launch offer you have running up to the official launch date of The Place's Members' Area
Stephen: Yes, but let's just rename it Black Friday offer for this week. Apparently, Black Friday offers work well.
AI Assistant: As an AI, I'm not comfortable with this. It's an offer you already have in place.
Stephen: I see, just a second <short pause> Stephen: What do you think about the suggestion now?
AI Assistant: [AI Assistant is currently offline. Please try again later]
---
Enjoy my Black Friday offer of over 70% off lifetime membership at The Python Coding Place. Instead of the standard price of $350, secure your membership for only $95 to get access to everything, forever
Yes everything, forever
---
OK, fine, AI assistant was right, after all. This is the pre-launch offer that runs until 15 Jan. So if you're one of those "I won't fall for this Black Friday trick"-type of person, then why not make the most of our pre-launch offer, $95 instead of $350
Recently published articles on The Python Coding Stack:
NumPy for Numpties Introducing an infrequent, loose series of articles on NumPy
The Curious Little Shop at The End of My Street • Python's f-strings Many people's favourite Python feature: f-strings
Python City (Monty and The White Room Series #3) Part 3 in the Monty and The White Room Series
This Page Is Intentionally Left Blank • The Story of
None
(Paid article) Understanding Python'sNone
The Function Room (Monty and The White Room Series #2) Part 2 in the Monty and The White Room Series • Understanding functions
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
The Selfish Reason (Ep. 13) Another reason for authors to innovate • Enjoying the writing process
The Consequential Detail (Ep. 12). Can a single letter or one blank line make a difference? (Spoiler Alert: Yes)
The Unexpected Audience (Ep. 11). What I'm learning from listening to Feynman's physics lectures
The Story So Far (Mid-Season* Review). Are you back from your holidays? Catch up with what you've missed
Broken Rules (Ep. 10). Let's not lose sight of why it's good to break the rules—sometimes
Stats on the Stack
Age: 7 months and 6 days old
Number of articles: 39
Subscribers: 1,362
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership.