Choose Your Fighter • Inheritance vs. Composition (#2 in Inheritance vs Composition Pair)
The second article in the inheritance vs composition pair • Compare and contrast
Terminology can be off-putting. Every subject likes to use long, complex-sounding terms. But often, these terms make the topic sound more complicated than it is.
And my oh my! OOP has plenty of these. Abstraction, polymorphism, inheritance, encapsulation, composition. Let's deal with two of these.
I'm splitting today's article into two. But they're not Part 1 and Part 2. Both articles can stand alone. This is the second article, which focuses on a comparison between inheritance and composition. I'll only provide the briefest overview of the code in this article. If you prefer, you can first follow the detailed step-by-step tutorial in the first article before reading this one.
Classes always interact with other classes. In Python, (almost) everything is an object. Therefore, it's impossible to use a class that doesn't somehow interact with another one except in the simplest of examples.
And classes are often related to other classes.
One type of relationship is when a class is a special case of another class. In this situation, you can use inheritance and define a class that's derived from another class. The classic and rather boring examples you see overused when explaining inheritance are a vehicle class that serves as a parent class for a car or a truck, say. Or I'm sure you've seen the one where a dog class inherits from an animal class.
In my series on OOP here on The Python Coding Stack, inspired by the Harry Potter universe, I defined a Wizard
class. Then I defined a Professor
class and a Student
class, which both inherit from Wizard
.
Inheritance works in these examples since a car is a vehicle, a dog is an animal, and a Hogwarts professor and student are wizards.
But sometimes classes are related differently. A class can be part of another class. For example, you could have an engine class, and every vehicle will have an engine. In the Harry Potter series, I defined a Wand
class, and every Wizard
object had a Wand
object.
A car is not an engine. It has an engine. And a wizard is not a wand. A wizard has a wand. In these situations, you can use composition to create an instance attribute within a class that contains an object from another class.
There are cases where the relationship is clear. The examples I listed above are among these. However, there are often cases when both inheritance and composition are reasonable options.
Let's look at an example. This article is not an in-depth discussion of inheritance, composition, or a thorough blow-by-blow comparison between the two. Instead, I'll keep this post focused on a direct comparison between the two in one specific example: a simple shooting game.
The Turtle Alien Invasion Game
Here's the game you'll explore:
In this article, I won't provide a step-by-step tutorial for the code. However, you can find a detailed tutorial in the companion article.
Here's the main script for this game:

In this code, you:
Set various parameters needed for the game.
Create the screen and the player.
Define functions to control the rotation of the player and bind these functions to keys on your keyboard.
Define a function to shoot lasers and bind this function to the spacebar.
Define a helper function for when you need to clear entities from the screen.
Create the game loop. In this
while
loop you:Turn the player (if a key is depressed).
Spawn a new alien every few seconds.
Move all the enemies and check if they left the screen.
Move all the lasers and check if they left the screen.
Check if any laser has hit any alien. If there's a hit, you remove both laser and alien and increment the score.
But this script only shows part of what's going on. As with all OOP code, there's a lot that happens within the classes. This code relies on two classes: Enemy
and Laser
.
Here's the code where these classes are defined. This version uses inheritance:
Both classes, Enemy
and Laser
, inherit primarily from turtle.Turtle
. They also inherit one method from the mixin class, but let's put the mixin class to one side for now.
See the companion article to this post for more details on this code.
Did you see my Breaking the Rules project?
From Inheritance to Composition
An Enemy
object is a Turtle
object. It also has further data attributes and methods that give it more functionality than a Turtle
object. And the same conclusion applies to a Laser
object.
Note how there's no distinction between the data attributes and methods that these classes inherit from Turtle
and their own unique data attributes and methods. They've all been "mixed in the same pot".
Let's convert these classes that use inheritance to relate Enemy
and Laser
with Turtle
to ones that use composition instead.
Let's start with the .__init__()
method in the Enemy
class. Here's the starting point, which is the inheritance version:
The first two changes are to remove the base classes from the parentheses in the first line and the super().__init__()
line at the start of the .__init__()
method. An Enemy
object is no longer a Turtle
object. Therefore, you can't use the Turtle
methods, such as .shape()
and .color()
directly since these methods don't belong to an Enemy
object anymore.
Instead, you create a new data attribute and assign a Turtle
object to it. Let's call this new attribute ._turtle
. The leading underscore is a convention in Python to indicate that this attribute is non-public–it's not meant to be accessed directly by anyone using the class but only from within the class.
Therefore, the class definition starts as follows. You can write this new version in a module called game_entities_composition.py
to separate it from the inheritance-based version:
Look at the rest of the lines in the original .__init__()
method. Some attributes are Turtle
attributes. Others are specific for the Enemy
class. Now, you need to separate these and call the Turtle
methods on the ._turtle
attribute you just created. The highlighted lines indicate changes from the inheritance-based version:
Note how the last few lines are unchanged. They don't rely on Turtle
methods. An Enemy
object is no longer a Turtle
object. But it has a Turtle
object. This is the key distinction between inheritance and composition.
Yes, the code is more verbose. You need to include the ._turtle
attribute each time you want to use a Turtle
method. However, it's also clear which methods come from Turtle
and which are native to Enemy
. I'll return to this point later.
Here's the rest of the Enemy
class using composition:
And let's also speed through the changes required for the Laser
class:
However, each of these classes has a missing method. In the inheritance version, both classes inherited the .is_out_of_bounds()
method from the mixin class they also inherited from.
You can replace the mixin class with a standalone function in the script and then define methods in each class:
This option still allows you to reuse the code in the is_out_of_bounds()
function in several places–in this case, in two separate classes. If you need to make changes to the logic for this function, you only need to change the function once.
Let's see whether this change is sufficient. Back in turtle_invasion.py
, you can replace the import
statement to import the classes from this new module instead of the old one:
But you'll get an error when you try to shoot the first time:
Traceback (most recent call last):
File ".../turtle_invasion.py", line 108, in <module>
if laser.has_collided_with(enemy):
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
File ".../game_entities_composition.py", line 104,
in has_collided_with
self._turtle.distance(entity)
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
File ".../turtle.py", line 1856, in distance
return abs(pos - self._position)
^^^
UnboundLocalError: cannot access local variable 'pos'
where it is not associated with a value
You perform some detective work to figure out this bug. The issue is with the .has_collided_with()
method in the Laser
class, specifically with the line:
self._turtle.distance(entity)
Let's look at the entire method:
You pass an Enemy
object to this method. In the inheritance-based version, Enemy
is a Turtle
, and therefore, it's a valid data type to use in .distance()
, which is a Turtle
method.
However, Enemy
is no longer a Turtle
. But it has a Turtle
:
Therefore, you pass entity._turtle
instead of entity
. And this resolves the problem…
But you now face another one. The code now fails either when an enemy or a laser leaves the screen or when a laser hits an enemy.
Here's the error:
Traceback (most recent call last):
File ".../turtle_invasion.py", line 100, in <module>
cleanup_entity(enemy, enemies)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File ".../turtle_invasion.py", line 66, in cleanup_entity
entity.hideturtle()
^^^^^^^^^^^^^^^^^
AttributeError: 'Enemy' object has no attribute 'hideturtle'
If the game crashes because a laser leaves the screen, the final line will highlight the Laser
object rather than Enemy
. However, you'll see this is the same issue. The problem is now with cleanup_entity()
in turtle_invasion.py
. Here's the function you currently have in your code:
In the previous version, the entity, which is either an Enemy
object or a Laser
object, was a Turtle
object. Therefore, it had access to the Turtle
methods .hideturtle()
and .clear()
. The new classes don't have these methods.
You could replace these lines with entity._turtle.hideturtle()
and entity._turtle.clear()
in this code. However, it's best to implement the change directly within the classes rather than using the ._turtle
data attribute directly in turtle_invasion.py
. How can you do this?
Define these methods in the classes:
Sure, it's a bit of work, but these methods are relatively straightforward. They provide a way of accessing the Turtle
methods directly using Enemy
and Laser
methods with the same name.
However, there's one more error left. Run the code and shoot a laser to find out.
The problem is still in cleanup_entity()
. Look at the last line in this function:
turtle.turtles().remove(entity)
This line tries to remove the Turtle
object from the list of all turtles maintained by the turtle
module. However, entity
is not a Turtle
anymore.
In this case, you can't redefine .remove()
since this is a list method and not a method of one of your classes. Therefore, you have to access the ._turtle
attribute directly. You can do so in the call to .remove
, or you can define a method in your two classes allowing you to access this attribute. Let's use the latter option:
And finally, you can use this method in the cleanup_entity()
function in turtle_invasion.py
:
And this works. Notice that this last change is the only change needed in turtle_invasion.py
, and it's due to a peculiarity in the turtle
module. Otherwise, the classes using inheritance and composition appear identical to an end user.
I avoided inheritance entirely in this version. However, notice how you defined several helper methods that are identical in Enemy
and Laser
. You could use inheritance to avoid this repetition using techniques similar to the ones you used in the first version.
Final Words
So, which is better? There's no easy answer that applies to all situations. Inheritance and composition are both useful tools. Composition creates less dependency between the classes. The features and characteristics of the component class–such as Turtle
in this example–are accessed by creating an object of that class. However, when you use inheritance, the features and characteristics of the parent class are absorbed directly into the child class.
Many programmers will prefer the option that creates less dependency whenever possible–composition.
Also, try to look at the classes from the perspective of an outsider who didn't write the code. Composition makes it clear which methods come from the component class. Arguably, this makes the code more readable, less prone to bugs, and easier to maintain.
This doesn't mean inheritance shouldn't be used. There are cases where inheritance is the most intuitive approach to creating a relationship between classes.
Make sure you have both tools in your toolbox so you can use whichever works best in your application. And you may not always get it right. But that's OK!
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:
Final Version of the Code
turtle_invasion.py
# turtle_invasion.py
import random
import time
import turtle
from game_entities_composition import Enemy, Laser
WIDTH = 600
HEIGHT = 600
player_rotation_step = 3
spawn_interval_range = 1, 3
laser_speed = 15
total_ammunition = 100
# Initialise variables
enemies = []
lasers = []
score = 0
# Create Screen
screen = turtle.Screen()
screen.tracer(0)
screen.setup(WIDTH, HEIGHT)
screen.bgcolor(0.2, 0.2, 0.2)
screen.listen()
# Create player
player = turtle.Turtle()
player.shape("triangle")
player.shapesize(stretch_len=1.5)
player.color("purple")
player.rotation = 0
player.ammunition = total_ammunition
# Control player
def turn_left():
player.rotation = player_rotation_step
def turn_right():
player.rotation = -player_rotation_step
def stop_turning():
player.rotation = 0
screen.onkeypress(turn_left, "Left")
screen.onkeyrelease(stop_turning, "Left")
screen.onkeypress(turn_right, "Right")
screen.onkeyrelease(stop_turning, "Right")
# Shoot lasers
def shoot_laser():
laser = Laser(player)
laser.set_speed(laser_speed)
lasers.append(laser)
player.ammunition -= 1
if not player.ammunition:
screen.onkeypress(None, "space")
screen.onkeypress(shoot_laser, "space")
# Deal with out-of-bounds objects
def cleanup_entity(entity, entities_list):
entity.hideturtle()
entity.clear()
screen.update()
entities_list.remove(entity)
turtle.turtles().remove(entity.get_turtle())
# Main game loop
start_enemy_spawn_timer = time.time()
next_enemy_spawn_delay = 0
while player.ammunition > 0 or lasers:
screen.title(
f"Ammunition: {player.ammunition} "
f"| Score: {int(score):3}"
)
player.left(player.rotation)
# Spawn new enemy if time interval elapsed
if (
time.time() - start_enemy_spawn_timer
> next_enemy_spawn_delay
):
new_enemy = Enemy()
enemies.append(new_enemy)
new_enemy.set_start_position(WIDTH, HEIGHT)
next_enemy_spawn_delay = random.uniform(
*spawn_interval_range
)
start_enemy_spawn_timer = time.time()
# Move enemies
for enemy in enemies.copy():
enemy.move()
enemy.change_direction()
if enemy.is_out_of_bounds(WIDTH, HEIGHT):
cleanup_entity(enemy, enemies)
# Move lasers
for laser in lasers.copy():
laser.move()
if laser.is_out_of_bounds(WIDTH, HEIGHT):
cleanup_entity(laser, lasers)
# check for collisions
for enemy in enemies:
if laser.has_collided_with(enemy):
score += enemy.enemy_speed
cleanup_entity(enemy, enemies)
cleanup_entity(laser, lasers)
screen.update()
print(len(enemies), len(lasers), len(turtle.turtles()))
turtle.done()
game_entities.py
# game_entities_composition.py
import math
import random
import time
import turtle
def _is_out_of_bounds(self, screen_width, screen_height):
return (
abs(self.xcor()) > screen_width / 2
or abs(self.ycor()) > screen_height / 2
)
class Enemy:
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
self._turtle = turtle.Turtle()
self._turtle.shape("turtle")
self._turtle.penup()
self._turtle.color(
random.random(),
random.random(),
random.random(),
)
self._turtle.left(random.randint(1, 359))
self.enemy_speed = random.uniform(*self.SPEED_RANGE)
self.turning_period = random.uniform(
*self.TURNING_PERIOD_RANGE
)
self.start_timer = time.time()
def set_start_position(self, screen_width, screen_height):
self._turtle.backward(
math.sqrt(
(screen_width / 2) ** 2
+ (screen_height / 2) ** 2
)
)
if self._turtle.ycor() > screen_height / 2:
self._turtle.sety(screen_height / 2)
if self._turtle.ycor() < -screen_height / 2:
self._turtle.sety(-screen_height / 2)
if self._turtle.xcor() > screen_width / 2:
self._turtle.setx(screen_width / 2)
if self._turtle.xcor() < -screen_width / 2:
self._turtle.setx(-screen_width / 2)
def move(self):
self._turtle.forward(self.enemy_speed)
def change_direction(self):
if (
time.time() - self.start_timer
> self.turning_period
):
self._turtle.left(
random.uniform(
-self.MAX_TURN_ANGLE,
self.MAX_TURN_ANGLE,
)
)
self.start_timer = time.time()
def is_out_of_bounds(self, screen_width, screen_height):
return _is_out_of_bounds(
self._turtle, screen_width, screen_height
)
def hideturtle(self):
self._turtle.hideturtle()
def clear(self):
self._turtle.clear()
def get_turtle(self):
return self._turtle
class Laser:
LENGTH = 10
THICKNESS = 5
DEFAULT_SPEED = 1
COLLISION_TOLERANCE = 20 # Default diameter for turtles
def __init__(self, player):
self._turtle = turtle.Turtle()
self._turtle.hideturtle()
self._turtle.penup()
self._turtle.color("red")
self._turtle.pensize(self.THICKNESS)
self._turtle.setposition(player.position())
self._turtle.setheading(player.heading())
self.laser_speed = self.DEFAULT_SPEED
def set_speed(self, speed):
self.laser_speed = speed
def _draw(self):
self._turtle.clear()
self._turtle.pendown()
self._turtle.forward(self.LENGTH)
self._turtle.forward(-self.LENGTH)
self._turtle.penup()
def move(self):
self._turtle.forward(self.laser_speed)
self._draw()
def has_collided_with(self, entity):
return (
self._turtle.distance(entity._turtle)
<= self.COLLISION_TOLERANCE
)
def is_out_of_bounds(self, screen_width, screen_height):
return _is_out_of_bounds(
self._turtle, screen_width, screen_height
)
def hideturtle(self):
self._turtle.hideturtle()
def clear(self):
self._turtle.clear()
def get_turtle(self):
return self._turtle
Appendix: Code Blocks
Code Block #1
# turtle_invasion.py
import random
import time
import turtle
from game_entities import Enemy, Laser
WIDTH = 600
HEIGHT = 600
player_rotation_step = 3
spawn_interval_range = 1, 3
laser_speed = 15
total_ammunition = 100
# Initialise variables
enemies = []
lasers = []
score = 0
# Create Screen
screen = turtle.Screen()
screen.tracer(0)
screen.setup(WIDTH, HEIGHT)
screen.bgcolor(0.2, 0.2, 0.2)
screen.listen()
# Create player
player = turtle.Turtle()
player.shape("triangle")
player.shapesize(stretch_len=1.5)
player.color("purple")
player.rotation = 0
player.ammunition = total_ammunition
# Control player
def turn_left():
player.rotation = player_rotation_step
def turn_right():
player.rotation = -player_rotation_step
def stop_turning():
player.rotation = 0
screen.onkeypress(turn_left, "Left")
screen.onkeyrelease(stop_turning, "Left")
screen.onkeypress(turn_right, "Right")
screen.onkeyrelease(stop_turning, "Right")
# Shoot lasers
def shoot_laser():
laser = Laser(player)
laser.set_speed(laser_speed)
lasers.append(laser)
player.ammunition -= 1
if not player.ammunition:
screen.onkeypress(None, "space")
screen.onkeypress(shoot_laser, "space")
# Deal with out-of-bounds objects
def cleanup_entity(entity, entities_list):
entity.hideturtle()
entity.clear()
screen.update()
entities_list.remove(entity)
turtle.turtles().remove(entity)
# Main game loop
start_enemy_spawn_timer = time.time()
next_enemy_spawn_delay = 0
while player.ammunition > 0 or lasers:
screen.title(
f"Ammunition: {player.ammunition} "
f"| Score: {int(score):3}"
)
player.left(player.rotation)
# Spawn new enemy if time interval elapsed
if (
time.time() - start_enemy_spawn_timer
> next_enemy_spawn_delay
):
new_enemy = Enemy()
enemies.append(new_enemy)
new_enemy.set_start_position(WIDTH, HEIGHT)
next_enemy_spawn_delay = random.uniform(
*spawn_interval_range
)
start_enemy_spawn_timer = time.time()
# Move enemies
for enemy in enemies.copy():
enemy.move()
enemy.change_direction()
if enemy.is_out_of_bounds(WIDTH, HEIGHT):
cleanup_entity(enemy, enemies)
# Move lasers
for laser in lasers.copy():
laser.move()
if laser.is_out_of_bounds(WIDTH, HEIGHT):
cleanup_entity(laser, lasers)
# check for collisions
for enemy in enemies:
if laser.has_collided_with(enemy):
score += enemy.enemy_speed
cleanup_entity(enemy, enemies)
cleanup_entity(laser, lasers)
screen.update()
turtle.done()
Code Block #2
# game_entities.py
import math
import random
import time
import turtle
class OutOfBoundsMixin:
def is_out_of_bounds(self, screen_width, screen_height):
return (
abs(self.xcor()) > screen_width / 2
or abs(self.ycor()) > screen_height / 2
)
class Enemy(turtle.Turtle, OutOfBoundsMixin):
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
super().__init__()
self.shape("turtle")
self.penup()
self.color(
random.random(),
random.random(),
random.random(),
)
self.left(random.randint(1, 359))
self.enemy_speed = random.uniform(*self.SPEED_RANGE)
self.turning_period = random.uniform(
*self.TURNING_PERIOD_RANGE
)
self.start_timer = time.time()
def move(self):
self.forward(self.enemy_speed)
def set_start_position(self, screen_width, screen_height):
self.backward(
math.sqrt(
(screen_width / 2) ** 2
+ (screen_height / 2) ** 2
)
)
if self.ycor() > screen_height / 2:
self.sety(screen_height / 2)
if self.ycor() < -screen_height / 2:
self.sety(-screen_height / 2)
if self.xcor() > screen_width / 2:
self.setx(screen_width / 2)
if self.xcor() < -screen_width / 2:
self.setx(-screen_width / 2)
def change_direction(self):
if (
time.time() - self.start_timer
> self.turning_period
):
self.left(
random.uniform(
-self.MAX_TURN_ANGLE,
self.MAX_TURN_ANGLE,
)
)
self.start_timer = time.time()
class Laser(turtle.Turtle, OutOfBoundsMixin):
LENGTH = 10
THICKNESS = 5
DEFAULT_SPEED = 1
COLLISION_TOLERANCE = 20 # Default diameter for turtles
def __init__(self, player):
super().__init__()
self.hideturtle()
self.penup()
self.color("red")
self.pensize(self.THICKNESS)
self.setposition(player.position())
self.setheading(player.heading())
self.laser_speed = self.DEFAULT_SPEED
def set_speed(self, speed):
self.laser_speed = speed
def _draw(self):
self.clear()
self.pendown()
self.forward(self.LENGTH)
self.forward(-self.LENGTH)
self.penup()
def move(self):
self.forward(self.laser_speed)
self._draw()
def has_collided_with(self, entity):
return (
self.distance(entity) <= self.COLLISION_TOLERANCE
)
Code Block #3
class Enemy(turtle.Turtle, OutOfBoundsMixin):
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
super().__init__()
self.shape("turtle")
self.penup()
self.color(
random.random(),
random.random(),
random.random(),
)
self.left(random.randint(1, 359))
self.enemy_speed = random.uniform(*self.SPEED_RANGE)
self.turning_period = random.uniform(
*self.TURNING_PERIOD_RANGE
)
self.start_timer = time.time()
Code Block #4
# game_entities_composition.py
import math
import random
import time
import turtle
class Enemy:
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
self._turtle = turtle.Turtle()
Code Block #5
# game_entities_composition.py
import math
import random
import time
import turtle
class Enemy:
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
self._turtle = turtle.Turtle()
self._turtle.shape("turtle")
self._turtle.penup()
self._turtle.color(
random.random(),
random.random(),
random.random(),
)
self._turtle.left(random.randint(1, 359))
self.enemy_speed = random.uniform(*self.SPEED_RANGE)
self.turning_period = random.uniform(
*self.TURNING_PERIOD_RANGE
)
self.start_timer = time.time()
Code Block #6
# game_entities_composition.py
import math
import random
import time
import turtle
class Enemy:
SPEED_RANGE = 0.1, 3
MAX_TURN_ANGLE = 90
TURNING_PERIOD_RANGE = 0.5, 2
def __init__(self):
# ...
def set_start_position(self, screen_width, screen_height):
self._turtle.backward(
math.sqrt(
(screen_width / 2) ** 2
+ (screen_height / 2) ** 2
)
)
if self._turtle.ycor() > screen_height / 2:
self._turtle.sety(screen_height / 2)
if self._turtle.ycor() < -screen_height / 2:
self._turtle.sety(-screen_height / 2)
if self._turtle.xcor() > screen_width / 2:
self._turtle.setx(screen_width / 2)
if self._turtle.xcor() < -screen_width / 2:
self._turtle.setx(-screen_width / 2)
def move(self):
self._turtle.forward(self.enemy_speed)
def change_direction(self):
if (
time.time() - self.start_timer
> self.turning_period
):
self._turtle.left(
random.uniform(
-self.MAX_TURN_ANGLE,
self.MAX_TURN_ANGLE,
)
)
self.start_timer = time.time()
Code Block #7
# game_entities_composition.py
# ...
class Laser:
LENGTH = 10
THICKNESS = 5
DEFAULT_SPEED = 1
COLLISION_TOLERANCE = 20 # Default diameter for turtles
def __init__(self, player):
self._turtle = turtle.Turtle()
self._turtle.hideturtle()
self._turtle.penup()
self._turtle.color("red")
self._turtle.pensize(self.THICKNESS)
self._turtle.setposition(player.position())
self._turtle.setheading(player.heading())
self.laser_speed = self.DEFAULT_SPEED
def set_speed(self, speed):
self.laser_speed = speed
def _draw(self):
self._turtle.clear()
self._turtle.pendown()
self._turtle.forward(self.LENGTH)
self._turtle.forward(-self.LENGTH)
self._turtle.penup()
def move(self):
self._turtle.forward(self.laser_speed)
self._draw()
def has_collided_with(self, entity):
return (
self._turtle.distance(entity)
<= self.COLLISION_TOLERANCE
)
Code Block #8
# game_entities_composition.py
import math
import random
import time
import turtle
def _is_out_of_bounds(self, screen_width, screen_height):
return (
abs(self.xcor()) > screen_width / 2
or abs(self.ycor()) > screen_height / 2
)
class Enemy:
# ...
def is_out_of_bounds(self, screen_width, screen_height):
return _is_out_of_bounds(
self._turtle, screen_width, screen_height
)
class Laser:
# ...
def is_out_of_bounds(self, screen_width, screen_height):
return _is_out_of_bounds(
self._turtle, screen_width, screen_height
)
Code Block #9
# turtle_invasion.py
import random
import time
import turtle
from game_entities_composition import Enemy, Laser
# ...
Code Block #10
def has_collided_with(self, entity):
return (
self._turtle.distance(entity)
<= self.COLLISION_TOLERANCE
)
Code Block #11
def has_collided_with(self, entity):
return (
self._turtle.distance(entity._turtle)
<= self.COLLISION_TOLERANCE
)
Code Block #12
# Deal with out-of-bounds objects
def cleanup_entity(entity, entities_list):
entity.hideturtle()
entity.clear()
screen.update()
entities_list.remove(entity)
turtle.turtles().remove(entity)
Code Block #13
# game_entities_composition.py
# ...
class Enemy:
# ...
def hideturtle(self):
self._turtle.hideturtle()
def clear(self):
self._turtle.clear()
class Laser:
# ...
def hideturtle(self):
self._turtle.hideturtle()
def clear(self):
self._turtle.clear()
Code Block #14
# game_entities_composition.py
# ...
class Enemy:
# ...
def get_turtle(self):
return self._turtle
class Laser:
# ...
def get_turtle(self):
return self._turtle
Code Block #15
# turtle_invasion.py
# ...
# Deal with out-of-bounds objects
def cleanup_entity(entity, entities_list):
entity.hideturtle()
entity.clear()
screen.update()
entities_list.remove(entity)
turtle.turtles().remove(entity.get_turtle())
# ...
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