1969: Can You Land on The Moon? • A Python `turtle` Lunar Lander
Revisiting one of my favourite projects: a lunar lander using, yes, the one and only `turtle` module
I have a reputation in some corners of the interweb. I've even been called The Turtle Guy from time to time. Not because I look like a turtle—I hope. But because I often use Python's turtle
module in somewhat unconventional ways, stretching this basic graphics module to its limit.
Many articles and tutorials you may find on the web that use the turtle
module are often basic, simple, maybe childish, a bit underwhelming. I don't like that. So, I write different types of turtle
articles. And if I had a penny for each time someone said they didn't know you could do that with the turtle
module, I'd be…erm…able to buy one or two boiled sweets.
The truth is that I don't publish that many articles using the turtle
module. They're a tiny fraction of the tutorials I write.
Anyway, it's about time I had another turtle
tutorial there on The Python Coding Stack. So, I'm revisiting a tutorial I wrote a long time ago in a publication far, far away. If you've been reading my stuff for several years, you may have seen this, and you already enjoyed playing it. So here it is, with some edits from the original one.
How difficult can it be to land on the moon? I have no idea. All I know is that it's not that easy landing the lunar module in this Python turtle
game:
But how about writing the game? I took a 'first-principles' approach to write this lunar landing game and used Python's turtle
module instead of other game-writing libraries. It's more fun this way. Trust me!
Let me take you all the way from launch to landing.
The Python Lunar Landing Game
Look at the video of the game again. The lunar module starts in the top-left corner of the game screen. It's also spinning with a random angular velocity at the beginning.
The aim of the game is to land the lunar module safely on the landing pad by controlling its descent.
Controlling the Lunar Module
You can turn on either of the two thrusters or both at once to control the lunar module. If only one thruster is turned on, the module's rotational velocity increases. This affects how fast the lunar module is spinning and in which direction. The longer the thruster is on, the faster it will spin.
If the module is spinning clockwise, let's say, and the anticlockwise (counterclockwise) thruster is turned on and kept on, the lunar module's spinning will slow down until it stops rotating completely for a brief period. Then, it will start spinning anticlockwise.
If both thrusters are turned on at the same time, the lunar module will accelerate in the direction opposite to the thrusters' direction. If the module is spinning and both thrusters are turned on, the direction of acceleration will keep changing as the module spins. This makes the module hard to control when it's spinning rapidly!
Landing the Lunar Module
The lunar module must land on the landing pad while facing upwards. There's some tolerance for both the location of the landing and the orientation of the lunar module when it reaches the landing pad.
However, if the lunar module hits the landing pad outside of these tolerances, the landing is unsuccessful. The landing is also considered a failed landing if the lunar module goes below the bottom edge of the game screen.
Setting Up the Scene
You'll use Python's turtle
module to create and move the graphics in this game. If you've used this module before, you're already familiar with the classes you'll use and the methods to draw and move things around.
However, it's not a problem if you've never used the turtle
module. I'll introduce everything that's required as and when required.
If you want to explore more turtle
animations and games:
Have a look at the Turtle Archives here at The Python Coding Stack.
And there's a Space Invaders Clone using
turtle
over at Real Python.
Creating the Game Window
You can start by creating the window you'll need for the game and setting its size and background colour:
You create an object representing the screen on which the game will run. You name it window
. When you call window.setup(0.6, 0.6)
, you set the size of the window to 60% of your display's width and height. You can also use integers as arguments in setup()
to set the width and height directly in pixels rather than as a fraction of your display's width and height.
You'll need to use the actual width and height of the window often throughout the code, so you assign the values returned by window_width()
and window_height()
to the variable names width
and height
.
The remaining methods set the window's title bar and the background colour. Finally, turtle.done()
keeps the program from exiting and keeps the window open. This is the mainloop of the game when using turtle
.
Creating the Stars and the Moon's Surface
The Turtle
object is the other main object you can create when using the turtle
module. This is the drawing pen, which you can move around the screen to draw things. You can create two "turtles" to create the stars and the moon:
You use several Turtle
methods:
hideturtle()
hides the arrow representing theTurtle
object on the screen.penup()
ensures that the turtle doesn't draw any lines as it moves around the screen.color()
sets the turtle's colour. The turtle will draw everything in this colour.dot()
draws a dot.setposition()
places the turtle at the coordinates you pass as arguments.setx()
andsety()
set only one of the coordinates, either x or y.
You've now set up the background for this Python lunar landing game:
However, you'll have noticed that it takes a long time for the turtles to move around, drawing all the stars and the moon. The turtle
module draws each small step the turtles make. This takes time. This issue will also cause lag during the gameplay. Every movement is slowed down due to the program having to draw every step.
You can turn this default behaviour off using the expression window.tracer(0)
, which does not draw any of the intermediate steps. The screen is refreshed each time you call window.update()
:
That completes the game's background. Now, for the fun part!
Creating the Lunar Module
Next, you need to draw the lunar module. However, the spaceship is not stationary in this Python lunar landing game. It spins, and it also moves around as the player tries to land it:
You add parameters to define the size and colours of the lunar module and create a new Turtle
object called lunar_module
. The position of this turtle is in the top-left region of the window.
Then, you define the function draw_lunar_module()
. Function names should clearly tell you what the function does, so this one draws the lunar module! You can read through the steps in the function to follow the lunar_module
turtle as it draws the landing gear, the outer pods, and the central part of the lunar module.
You add a temporary call to draw_lunar_module()
so you can look at what the lunar module looks like. You won't need this call later:
There is one problem that you can't see yet but will soon become evident. Try and add a second call to draw_lunar_module()
immediately after the one already in the code:
As the lunar_module
turtle moves around to draw the spaceship, it ends up in the same place it starts, which is at the centre of the spaceship but facing in a different orientation. Therefore, when you draw the lunar module a second time, it's facing in the wrong direction.
You can work out the maths you need to make sure the turtle ends its lunar module drawing facing the same way as when it started. However, there's a better solution:
You start the definition of draw_lunar_module()
by storing the position and heading of the turtle before it starts drawing. You finish the function definition by resetting the turtle's position and orientation. You don't really need to reset the position, as the turtle is already in the correct location. However, you may need to do this if you go for a different spaceship design!
You can remove the lines in the # Will remove this later
section now.
Adding Thrusters to Turn the Lunar Module
There are two steps you'll need to add thrusters to the game:
The "artistic" step visually shows the burning fuel coming out of the thrusters.
The "functional" step actually turns the lunar module.
Let's start with the second of these steps.
Create an instance variable bound to lunar_module
called rotation
, which determines the rotational speed of the lunar module. For the time being, you can set this to 0
:
You create two more instance variables, which are also bound to lunar_module
. These instance variables determine whether the clockwise and anticlockwise thrusters are on or off. Initially, you set these to False
, which means the thrusters are turned off:
Next, you define two functions to turn these thrusters on:
This is a good time to create the main game loop. This is a while
loop. Every step you need in every frame of the animation should be included in the while
loop.
Although you can set a required number of frames per second to make sure your game runs at a specific frame rate, I'm choosing a simpler version in this project in which we just let the while
loop run at its natural speed, which depends on your set up, without controlling its exact timing. However, you can add a short delay to each iteration of the while
loop to slow it down if it's running too fast. The sleep()
function from the time
module is useful for this:
You add a call to lunar_module.clear()
at the beginning of draw_lunar_module()
so that the program clears the previous drawing each time you redraw the spaceship.
You also bind the functions turn_on_clockwise_thruster()
and turn_on_anticlockwise_thruster()
to the right and left arrow keys using window.onkeypress()
and window.listen()
. The latter method ensures the program is "listening" out for keypresses. On some systems, you'll need to click on the game window to make it active before you can use the arrow keys.
This works. However, there's a problem:
The rotational speed of the lunar module keeps increasing once you hit the arrow key just once. That's because you can toggle the thruster on, but you can't turn it off.
You can amend this by adding a couple more functions to turn off the thrusters:
Releasing the right and left arrow keys now calls the functions turn_off_clockwise_thruster()
and turn_off_anticlockwise_thruster()
since these keys are bound to the key release through window.onkeyrelease()
. Therefore, you can keep the arrow key pressed to keep the thrusters on and release the keys when you need to turn the thrusters off.
Now you have more control over how the lunar module spins:
You can control the animation's speed by changing the argument in time.sleep()
if you need to.
Drawing the Burning Fuel
You can now change the rotational speed of the lunar module by using the arrow keys. Next, you can focus on the "artistic" element to visually show when you turn the thrusters on and off. You can create another Turtle
object and a function to draw the burning fuel:
In the draw_burning_fuel()
function definition, you move the turtle to the centre of one of the outer discs. The lunar_module
turtle's resting position is at the centre of the spaceship, facing downwards towards the landing gear. You use the direction
variable, which is either 1
or -1
, to send the turtle to the correct disc.
The drawing of the burning fuel is simply three thick lines: a yellow one in the middle and two red ones on either side of the middle yellow line!
You can now set the initial rotation to a random value. The initial parameters should be random to make the game more challenging:
Each time you run the program, the lunar module will start off spinning with a random rotational velocity. Before you move on to the next stage, you can practise controlling the rotation of the lunar module using the thrusters!
Moving the Lunar Module
You can rotate the lunar module by turning either thruster on and off. However, rotational movement is not the only type of movement for the lunar module. Now, you also need to translate the lunar module—change its overall position.
There are two factors that will make the lunar module move from its starting location: gravity and thrust. When both thrusters are turned on at the same time, the lunar module will be pushed forward in the direction it's facing. Gravity, on the other hand, will act on the lunar module at all times.
You can add two instance variables bound to lunar_module
called travel_speed
and travel_direction
. These instance variables determine the lunar module's speed and direction of travel at any time in the animation. Note that the orientation of the turtle used to draw the spaceship is not the same as the direction of travel of the spaceship:
You set both new instance variables to random values so that the lunar module's starting state is different each time you run the game. Next, you need to move the lunar module using these values. You can add a section in the while
loop that works out how much the spaceship should move in the x- and y-directions, and then move it:
When you run this code, the lunar module will travel at the speed and direction determined by the random values the program picks at the beginning. You can change the rotational speed of the lunar module using the thrusters:
However, you still cannot change the lunar module's speed and direction of travel. Let's work on this.
Some Maths
Let's review the maths you'll need to work out the change in the lunar module's speed and direction when a force acts on it. Consider the lunar module that's travelling in the direction shown by the green arrow below:
If both thrusters are turned on, they will create a force pointing in the direction shown by the red arrow in the diagram above. This direction represents the top of the lunar module.
You can break this force vector into two components, which are shown as blue arrows in the diagram:
the tangential component of the force created by the thrusters is the component that acts in the same direction as the spaceship's current direction of travel. This is the blue arrow that's pointing in the same direction as the green arrow.
the normal component of the force is the component that acts perpendicularly to the spaceship's current direction of travel. This is shown as the blue arrow that's 90º to the green arrow.
The tangential and normal components of the force created by the thrusters can be calculated by multiplying the magnitude of the force by the cosine and sine of the angle between the direction of the force and the direction of travel. That's a mouthful of a sentence. You'll see it represented in the code in the next section.
Turning On Both Thrusters
You can start by creating speed_step
, which determines the step size by which you increase the speed each time you apply a "unit" of force. You also define the function apply_force()
, which works out the change in direction and speed needed for each "unit" of thruster force applied. The function is called once in each iteration of the while
loop when both thrusters are turned on:
The tangential component of the force is the one that acts along the spaceship's direction of travel. Therefore, in apply_force()
, you start by setting the tangential component of the velocity to the current speed of the lunar module. The normal component is 0
at this point.
Since the turtle drawing the lunar module points towards the bottom of the lunar module in its "resting" state, you can set the direction of the force to the opposite direction of this by adding 180º
. The turtle
module measures angles in degrees. However, you'll need to convert these to radians.
Next, you can break down the change in speed from one iteration into its tangential and normal components and add them to the starting tangential and normal components of the lunar module's velocity.
Now that you have the new components, you can work out the new speed and direction of the spaceship.
You also add an if
statement in the while loop to call apply_force()
whenever both thrusters are turned on.
You can now fully steer the lunar module by:
turning thrusters on one at a time to change the lunar module's rotation, or
turning both thrusters on at the same time to change the lunar module's velocity.
The last thing you need to make the spaceship's movement more realistic is to add the effect of gravity on the lunar module.
Adding the Effects of Gravity
In this game, we can assume a constant value for the moon's gravitational pull on the spaceship. You create a variable called gravity
to define this value. You can fine-tune this and other initial values to change the game's difficulty level if you wish.
The force due to gravity is similar to the force applied when both thrusters are turned on. However, the magnitude of the force and the direction are different. Gravity always pulls the lunar module vertically downwards and with the same force.
This means that you don't need to write a new function to take gravity into account. You can re-use apply_force()
. But you'll need to make some modifications:
You refactor apply_force()
by adding the parameter mode
. The argument you pass will be either "gravity"
or "thrusters"
, depending on which function mode you need to use. Note that you'll need to update the existing apply_force()
call to include the "thrusters"
argument.
You also refactor the function to use the new local variable step
as the change in speed you need to apply to the lunar module. When using gravity mode, this value is equal to the gravity value, and the direction is -90º
, which is vertically downwards. When using thrusters-mode for this function, the step and direction values are the same as they were before this last set of changes.
You also need to call apply_force("gravity")
in each iteration of the while
loop since gravity will always act on the lunar module.
The spaceship will start falling and accelerating towards the lunar surface when you run the program now. You'll need to control its spinning and use the thrusters to push the lunar module back up:
You've now completed the part of this Python lunar landing program that controls the movement of the lunar module. Once you've practised your piloting skills, you're ready to work on landing the spaceship!
Landing the Lunar Module
It's time to land your lunar module. You'll first need to create the landing pad on the moon's surface. You also need to define acceptable tolerances for successfully landing the lunar module. Next, you'll need a function that checks whether a landing is successful. Here are the changes you need:
The module's landing position is shifted vertically upwards from the landing pad by a distance equal to branch_size
since this position refers to the centre of the lunar module.
The check_landing()
function first checks whether the lunar module's (x, y) position is within the tolerance range. If it is, then there are two possible outcomes:
The lunar module's orientation is within the tolerance range. The position and orientation of the lunar module are set to the correct landing values so that the spaceship "snaps" in place. The function returns
True
.The lunar module's orientation is outside the tolerance range. This means the spaceship has crashed on the landing pad. The function returns
False
.
The function also returns False
if the lunar module falls below the lower edge of the window. This case corresponds to the lunar module crashing on the moon's surface.
If neither of these conditions is met, the function returns None
, which means that the lunar module is still in flight.
Your final step is to check for each of these scenarios in the while
loop and end the game with a success or failure warning.
Here's the output of the final version of this game:
The full version of the code is available after the concluding remarks below.
Final Words
You've completed the Python lunar landing game. By using the turtle
module, you were able to build the game from first principles, controlling how the Turtle
objects move and how they interact with each other.
However, there's more you can add if you want to upgrade the game. For example, you can ensure that the lunar module doesn't land with a speed that's too high, or you can set a limited amount of fuel.
Have a go at adding more features to this game! And share them if you want.
Happy moon landings!
This tutorial was first published here: A Python Lunar Landing game using the turtle module in 2022.
Code in this article uses Python 3.13
The code images used in this article are created using Snappify. [Affiliate link]
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
Have a look at the Turtle Archives here at The Python Coding Stack.
And there's a Space Invaders Clone using
turtle
over at Real Python.
Complete Code
Here's the complete code:
import math
import random
import time
import turtle
# Set up the game window
window = turtle.Screen()
window.tracer(0)
window.setup(0.6, 0.6)
window.title("The Python Lunar Landing Game")
window.bgcolor("black")
width = window.window_width()
height = window.window_height()
# Game parameters
n_of_stars = 100
# Lunar module design parameters
branch_size = width / 16
n_of_discs = 5
disc_colour = "light gray"
centre_colour = "gold"
landing_gear_colour = "red"
# Lunar module movement parameters
rotation_step = 0.2
speed_step = 0.1
# Landing parameters
landing_pad_position = 0, -height / 2.1
module_landing_position = (
landing_pad_position[0],
landing_pad_position[1] + branch_size,
)
landing_pos_tolerance_x = 20
landing_pos_tolerance_y = 5
landing_orientation = 270 # vertically downwards
landing_orientation_tolerance = 15
gravity = 0.03
# Create stars and moon
stars = turtle.Turtle()
stars.hideturtle()
stars.penup()
stars.color("white")
for _ in range(n_of_stars):
x_pos = random.randint(-width // 2, width // 2)
y_pos = random.randint(-height // 2, height // 2)
stars.setposition(x_pos, y_pos)
stars.dot(random.randint(2, 6))
moon = turtle.Turtle()
moon.penup()
moon.color("slate gray")
moon.sety(-height * 2.8)
moon.dot(height * 5)
# Create landing pad
landing_pad = turtle.Turtle()
landing_pad.hideturtle()
landing_pad.penup()
landing_pad.setposition(landing_pad_position)
landing_pad.pendown()
landing_pad.pensize(10)
landing_pad.forward(branch_size / 2)
landing_pad.forward(-branch_size)
landing_pad.forward(branch_size / 2)
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
lunar_module.rotation = random.randint(-9, 9)
lunar_module.clockwise_thruster = False
lunar_module.anticlockwise_thruster = False
lunar_module.travel_speed = random.randint(1, 3)
lunar_module.travel_direction = random.randint(-45, 0)
def draw_lunar_module():
lunar_module.clear()
# "save" the starting position and orientation
position = lunar_module.position()
heading = lunar_module.heading()
lunar_module.pendown()
lunar_module.pensize(5)
# Landing gear
lunar_module.color(landing_gear_colour)
lunar_module.forward(branch_size)
lunar_module.left(90)
lunar_module.forward(branch_size / 2)
lunar_module.forward(-branch_size)
lunar_module.forward(branch_size / 2)
lunar_module.right(90)
lunar_module.forward(-branch_size)
lunar_module.pensize(1)
# Pods around the edge of the module
lunar_module.color(disc_colour)
for _ in range(n_of_discs - 1):
lunar_module.right(360 / n_of_discs)
lunar_module.forward(branch_size)
lunar_module.dot(branch_size / 2)
lunar_module.forward(-branch_size)
# Centre part of the lunar module
lunar_module.color(centre_colour)
lunar_module.dot(branch_size)
lunar_module.penup()
# reset the turtle to initial position and orientation
lunar_module.setposition(position)
lunar_module.setheading(heading)
# Create burning fuel
burning_fuel = turtle.Turtle()
burning_fuel.penup()
burning_fuel.hideturtle()
def draw_burning_fuel(thruster):
# Place turtle in the correct location
# depending on which thruster is on
if thruster == "clockwise":
direction = 1
elif thruster == "anticlockwise":
direction = -1
burning_fuel.penup()
burning_fuel.setposition(lunar_module.position())
burning_fuel.setheading(lunar_module.heading())
burning_fuel.right(direction * 360 / n_of_discs)
burning_fuel.forward(branch_size)
burning_fuel.left(direction * 360 / n_of_discs)
# Draw burning fuel
burning_fuel.pendown()
burning_fuel.pensize(8)
burning_fuel.color("yellow")
burning_fuel.forward(branch_size)
burning_fuel.backward(branch_size)
burning_fuel.left(5)
burning_fuel.color("red")
burning_fuel.pensize(5)
for _ in range(2):
burning_fuel.forward(branch_size)
burning_fuel.backward(branch_size)
burning_fuel.right(10)
def turn_on_clockwise_thruster():
lunar_module.clockwise_thruster = True
def turn_on_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = True
def turn_off_clockwise_thruster():
lunar_module.clockwise_thruster = False
def turn_off_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = False
window.onkeypress(turn_on_clockwise_thruster, "Right")
window.onkeypress(turn_on_anticlockwise_thruster, "Left")
window.onkeyrelease(turn_off_clockwise_thruster, "Right")
window.onkeyrelease(turn_off_anticlockwise_thruster, "Left")
window.listen()
# Applying forces to translate the lunar module
def apply_force(mode):
# Initial components of lunar module velocity
tangential = lunar_module.travel_speed
normal = 0
if mode == "gravity":
force_direction = -90
step = gravity
elif mode == "thrusters":
force_direction = lunar_module.heading() + 180
step = speed_step
angle = math.radians(
force_direction - lunar_module.travel_direction
)
# New components of lunar module velocity
tangential += step * math.cos(angle)
normal += step * math.sin(angle)
direction_change = math.degrees(
math.atan2(normal, tangential)
)
lunar_module.travel_direction += direction_change
lunar_module.travel_speed = math.sqrt(
normal ** 2 + tangential ** 2
)
# Check for successful landing
def check_landing():
if (
abs(lunar_module.xcor() - module_landing_position[0])
< landing_pos_tolerance_x
and abs(lunar_module.ycor() - module_landing_position[1])
< landing_pos_tolerance_y
):
if (
abs(lunar_module.heading() - landing_orientation)
< landing_orientation_tolerance
):
lunar_module.setposition(module_landing_position)
lunar_module.setheading(landing_orientation)
draw_lunar_module()
burning_fuel.clear()
return True
else:
burning_fuel.clear()
return False # Crash on landing pad - wrong angle
if lunar_module.ycor() < -height / 2:
burning_fuel.clear()
return False # Crash below moon surface
return None # No successful or unsuccessful landing yet
while True:
burning_fuel.clear()
# Apply thrust if both thrusters are on
if (
lunar_module.clockwise_thruster
and lunar_module.anticlockwise_thruster
):
apply_force("thrusters")
# Change rotational speed of lunar module
if lunar_module.clockwise_thruster:
draw_burning_fuel("clockwise")
lunar_module.rotation -= rotation_step
if lunar_module.anticlockwise_thruster:
draw_burning_fuel("anticlockwise")
lunar_module.rotation += rotation_step
# Apply effect of gravity
apply_force("gravity")
# Rotate lunar module
lunar_module.left(lunar_module.rotation)
# Translate lunar module
x = lunar_module.travel_speed * math.cos(
math.radians(lunar_module.travel_direction)
)
y = lunar_module.travel_speed * math.sin(
math.radians(lunar_module.travel_direction)
)
lunar_module.setx(lunar_module.xcor() + x)
lunar_module.sety(lunar_module.ycor() + y)
# Refresh image of lunar module
draw_lunar_module()
# Check for successful or unsuccessful landing
successful_landing = check_landing()
if successful_landing is not None:
if successful_landing:
window.title("Well Done! You've landed successfully")
else:
window.bgcolor("red")
window.title("The lunar module crashed")
break
time.sleep(0.05)
window.update()
turtle.done()
Appendix: Code Blocks
Code Block #1
import turtle
# Set up the game window
window = turtle.Screen()
window.setup(0.6, 0.6)
window.title("The Python Lunar Landing Game")
window.bgcolor("black")
width = window.window_width()
height = window.window_height()
turtle.done()
Code Block #2
import random
import turtle
# Set up the game window
window = turtle.Screen()
window.setup(0.6, 0.6)
window.title("The Python Lunar Landing Game")
window.bgcolor("black")
width = window.window_width()
height = window.window_height()
# Game parameters
n_of_stars = 100
# Create stars and moon
stars = turtle.Turtle()
stars.hideturtle()
stars.penup()
stars.color("white")
for _ in range(n_of_stars):
x_pos = random.randint(-width // 2, width // 2)
y_pos = random.randint(-height // 2, height // 2)
stars.setposition(x_pos, y_pos)
stars.dot(random.randint(2, 6))
moon = turtle.Turtle()
moon.penup()
moon.color("slate gray")
moon.sety(-height * 2.8)
moon.dot(height * 5)
turtle.done()
Code Block #3
import random
import turtle
# Set up the game window
window = turtle.Screen()
window.tracer(0)
window.setup(0.6, 0.6)
window.title("The Python Lunar Landing Game")
window.bgcolor("black")
# ...
window.update()
turtle.done()
Code Block #4
import random
import turtle
# Set up the game window
window = turtle.Screen()
window.tracer(0)
window.setup(0.6, 0.6)
window.title("The Python Lunar Landing Game")
window.bgcolor("black")
width = window.window_width()
height = window.window_height()
# Game parameters
n_of_stars = 100
# Lunar module design parameters
branch_size = width / 16
n_of_discs = 5
disc_colour = "light gray"
centre_colour = "gold"
landing_gear_colour = "red"
# Create stars and moon
# ...
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
def draw_lunar_module():
lunar_module.pendown()
lunar_module.pensize(5)
# Landing gear
lunar_module.color(landing_gear_colour)
lunar_module.forward(branch_size)
lunar_module.left(90)
lunar_module.forward(branch_size / 2)
lunar_module.forward(-branch_size)
lunar_module.forward(branch_size / 2)
lunar_module.right(90)
lunar_module.forward(-branch_size)
lunar_module.pensize(1)
# Pods around the edge of the module
lunar_module.color(disc_colour)
for _ in range(n_of_discs - 1):
lunar_module.right(360 / n_of_discs)
lunar_module.forward(branch_size)
lunar_module.dot(branch_size / 2)
lunar_module.forward(-branch_size)
# Centre part of the lunar module
lunar_module.color(centre_colour)
lunar_module.dot(branch_size)
lunar_module.penup()
# Will remove this later
draw_lunar_module()
window.update()
turtle.done()
Code Block #5
import random
import turtle
# ...
def draw_lunar_module():
# "save" the starting position and orientation
position = lunar_module.position()
heading = lunar_module.heading()
# rest of function here
# reset the turtle to initial position and orientation
lunar_module.setposition(position)
lunar_module.setheading(heading)
# Will remove this later
print(lunar_module.heading())
print(lunar_module.position())
draw_lunar_module()
draw_lunar_module()
print(lunar_module.heading())
print(lunar_module.position())
window.update()
turtle.done()
Code Block #6
# ...
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
lunar_module.rotation = 0
# ...
Code Block #7
# ...
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
lunar_module.rotation = 0
lunar_module.clockwise_thruster = False
lunar_module.anticlockwise_thruster = False
# ...
Code Block #8
# ...
def draw_lunar_module():
# ...
def turn_on_clockwise_thruster():
lunar_module.clockwise_thruster = True
def turn_on_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = True
# ...
Code Block #9
import random
import time
import turtle
# ...
# Game parameters
# ...
# Lunar module movement parameters
rotation_step = 0.2
# ...
def draw_lunar_module():
lunar_module.clear()
# ...
def turn_on_clockwise_thruster():
lunar_module.clockwise_thruster = True
def turn_on_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = True
window.onkeypress(turn_on_clockwise_thruster, "Right")
window.onkeypress(turn_on_anticlockwise_thruster, "Left")
window.listen()
while True:
# Change rotational speed of lunar module
if lunar_module.clockwise_thruster:
lunar_module.rotation -= rotation_step
if lunar_module.anticlockwise_thruster:
lunar_module.rotation += rotation_step
# Rotate lunar module
lunar_module.left(lunar_module.rotation)
# Refresh image of lunar module
draw_lunar_module()
time.sleep(0.05)
window.update()
turtle.done()
Code Block #10
# ...
def turn_on_clockwise_thruster():
lunar_module.clockwise_thruster = True
def turn_on_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = True
def turn_off_clockwise_thruster():
lunar_module.clockwise_thruster = False
def turn_off_anticlockwise_thruster():
lunar_module.anticlockwise_thruster = False
window.onkeypress(turn_on_clockwise_thruster, "Right")
window.onkeypress(turn_on_anticlockwise_thruster, "Left")
window.onkeyrelease(turn_off_clockwise_thruster, "Right")
window.onkeyrelease(turn_off_anticlockwise_thruster, "Left")
window.listen()
# ...
Code Block #11
# ...
def draw_lunar_module():
# ...
# Create burning fuel
burning_fuel = turtle.Turtle()
burning_fuel.penup()
burning_fuel.hideturtle()
def draw_burning_fuel(thruster):
# Place turtle in the correct location
# depending on which thruster is on
if thruster == "clockwise":
direction = 1
elif thruster == "anticlockwise":
direction = -1
burning_fuel.penup()
burning_fuel.setposition(lunar_module.position())
burning_fuel.setheading(lunar_module.heading())
burning_fuel.right(direction * 360 / n_of_discs)
burning_fuel.forward(branch_size)
burning_fuel.left(direction * 360 / n_of_discs)
# Draw burning fuel
burning_fuel.pendown()
burning_fuel.pensize(8)
burning_fuel.color("yellow")
burning_fuel.forward(branch_size)
burning_fuel.backward(branch_size)
burning_fuel.left(5)
burning_fuel.color("red")
burning_fuel.pensize(5)
for _ in range(2):
burning_fuel.forward(branch_size)
burning_fuel.backward(branch_size)
burning_fuel.right(10)
# ...
while True:
burning_fuel.clear()
# Change rotational speed of lunar module
if lunar_module.clockwise_thruster:
draw_burning_fuel("clockwise")
lunar_module.rotation -= rotation_step
if lunar_module.anticlockwise_thruster:
draw_burning_fuel("anticlockwise")
lunar_module.rotation += rotation_step
# Rotate lunar module
lunar_module.left(lunar_module.rotation)
# Refresh image of lunar module
draw_lunar_module()
time.sleep(0.05)
window.update()
turtle.done()
Code Block #12
# ...
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
lunar_module.rotation = random.randint(-9, 9)
lunar_module.clockwise_thruster = False
lunar_module.anticlockwise_thruster = False
# ...
Code Block #13
# ...
# Create the lunar module
lunar_module = turtle.Turtle()
lunar_module.penup()
lunar_module.hideturtle()
lunar_module.setposition(-width / 3, height / 3)
lunar_module.rotation = random.randint(-9, 9)
lunar_module.clockwise_thruster = False
lunar_module.anticlockwise_thruster = False
lunar_module.travel_speed = random.randint(1, 3)
lunar_module.travel_direction = random.randint(-45, 0)
# ...
Code Block #14
import math
import random
import time
import turtle
# ...
while True:
burning_fuel.clear()
# Change rotational speed of lunar module
if lunar_module.clockwise_thruster:
draw_burning_fuel("clockwise")
lunar_module.rotation -= rotation_step
if lunar_module.anticlockwise_thruster:
draw_burning_fuel("anticlockwise")
lunar_module.rotation += rotation_step
# Rotate lunar module
lunar_module.left(lunar_module.rotation)
# Translate lunar module
x = lunar_module.travel_speed * math.cos(
math.radians(lunar_module.travel_direction)
)
y = lunar_module.travel_speed * math.sin(
math.radians(lunar_module.travel_direction)
)
lunar_module.setx(lunar_module.xcor() + x)
lunar_module.sety(lunar_module.ycor() + y)
# Refresh image of lunar module
draw_lunar_module()
time.sleep(0.05)
window.update()
turtle.done()
Code Block #15
# ...
# Game parameters
n_of_stars = 100
# Lunar module design parameters
branch_size = width / 16
n_of_discs = 5
disc_colour = "light gray"
centre_colour = "gold"
landing_gear_colour = "red"
# Lunar module movement parameters
rotation_step = 0.2
speed_step = 0.1
# ...
# Applying forces to translate the lunar module
def apply_force():
# Initial components of lunar module velocity
tangential = lunar_module.travel_speed
normal = 0
force_direction = lunar_module.heading() + 180
angle = math.radians(
force_direction - lunar_module.travel_direction
)
# New components of lunar module velocity
tangential += speed_step * math.cos(angle)
normal += speed_step * math.sin(angle)
direction_change = math.degrees(
math.atan2(normal, tangential)
)
lunar_module.travel_direction += direction_change
lunar_module.travel_speed = math.sqrt(
normal ** 2 + tangential ** 2
)
while True:
burning_fuel.clear()
# Apply thrust if both thrusters are on
if (
lunar_module.clockwise_thruster
and lunar_module.anticlockwise_thruster
):
apply_force()
# Change rotational speed of lunar module
# ...
turtle.done()
Code Block #16
# ...
# Game parameters
# ...
gravity = 0.03
# ...
# Applying forces to translate the lunar module
def apply_force(mode):
# Initial components of lunar module velocity
tangential = lunar_module.travel_speed
normal = 0
if mode == "gravity":
force_direction = -90
step = gravity
elif mode == "thrusters":
force_direction = lunar_module.heading() + 180
step = speed_step
angle = math.radians(
force_direction - lunar_module.travel_direction
)
# New components of lunar module velocity
tangential += step * math.cos(angle)
normal += step * math.sin(angle)
direction_change = math.degrees(
math.atan2(normal, tangential)
)
lunar_module.travel_direction += direction_change
lunar_module.travel_speed = math.sqrt(
normal ** 2 + tangential ** 2
)
while True:
burning_fuel.clear()
# Apply thrust if both thrusters are on
if (
lunar_module.clockwise_thruster
and lunar_module.anticlockwise_thruster
):
apply_force("thrusters")
# Change rotational speed of lunar module
# ...
# Apply effect of gravity
apply_force("gravity")
# ...
Code Block #17
# ...
# Game parameters
# ...
# Landing parameters
landing_pad_position = 0, -height / 2.1
module_landing_position = (
landing_pad_position[0],
landing_pad_position[1] + branch_size,
)
landing_pos_tolerance_x = 20
landing_pos_tolerance_y = 5
landing_orientation = 270 # vertically downwards
landing_orientation_tolerance = 15
gravity = 0.03
# Create stars and moon
# ...
# Create landing pad
landing_pad = turtle.Turtle()
landing_pad.hideturtle()
landing_pad.penup()
landing_pad.setposition(landing_pad_position)
landing_pad.pendown()
landing_pad.pensize(10)
landing_pad.forward(branch_size / 2)
landing_pad.forward(-branch_size)
landing_pad.forward(branch_size / 2)
# ...
# Check for successful landing
def check_landing():
if (
abs(lunar_module.xcor() - module_landing_position[0])
< landing_pos_tolerance_x
and abs(lunar_module.ycor() - module_landing_position[1])
< landing_pos_tolerance_y
):
if (
abs(lunar_module.heading() - landing_orientation)
< landing_orientation_tolerance
):
lunar_module.setposition(module_landing_position)
lunar_module.setheading(landing_orientation)
draw_lunar_module()
burning_fuel.clear()
return True
else:
burning_fuel.clear()
return False # Crash on landing pad - wrong angle
if lunar_module.ycor() < -height / 2:
burning_fuel.clear()
return False # Crash below moon surface
return None # No successful or unsuccessful landing yet
while True:
# ...
# Check for successful or unsuccessful landing
successful_landing = check_landing()
if successful_landing is not None:
if successful_landing:
window.title("Well Done! You've landed successfully")
else:
window.bgcolor("red")
window.title("The lunar module crashed")
break
time.sleep(0.05)
window.update()
turtle.done()
For more Python resources, you can also visit Real Python—you may even stumble on one of my own articles or courses there!
And you can find out more about me at stephengruppetta.com
Further reading related to this article’s topic:
Have a look at the Turtle Archives here at The Python Coding Stack.
And there's a Space Invaders Clone using
turtle
over at Real Python.
Thanks for this it helps me figure out how to code two points touching. Something I was working on in 🐢.
Heh, cute! Nice demonstration of some of the richer features of the Python turtle.