Stage 7: Victory Conditions
In this lesson we will learn
- the difference between class variables and instance variables
- how objects share information using a class variable
- how to create and change a class variable
- where to put
if statements so the game can check for a win
Terminology
- class variable – a variable that belongs to the whole class and is shared by every object made from it, so a change by one object is seen by all.
- instance variable – an attribute, such as
name, where each object has its own separate copy.
Introduction
We're almost done with our text-based adventure game. We have a dungeon where the player can move around, pick up items, and talk to or fight different characters. What happens in these interactions depends on the type of character they meet.
In this stage, we'll set up how the player wins the game.
Pseudocode
Stage 7 pseudocode- Count how many enemies exist
- Lower the count each time an enemy is defeated
- Check when the count reaches zero, so the player wins the game
Class diagram
Have a look at the class diagram for this stage and you'll see something new. The Enemy class now has:
- a
num_of_enemy attribute
- a
get_num_of_enemy method

The num_of_enemy attribute is underlined in the diagram. The underline shows it's a class variable. A class variable is shared by every object made from that class. All Enemy objects can read it and change it, and when one object changes it, the others see the new value too.
The attributes we've used so far, like name and weakness, are instance variables: each object has its own copy.
In this game, num_of_enemy keeps track of how many Enemy objects exist, and every enemy shares that same number.
Count the enemies
To keep track of how many Enemy objects are in the game, let's add a class variable to the Enemy class.
Open character.py and add the highlighted code below.
| # character.py
class Character():
def __init__(self, name):
# initialises the character object
self.name = name
self.description = None
self.conversation = None
def describe(self):
# sends a description of the character to the terminal
print(f"{self.name} is here, {self.description}")
def talk(self):
# send conversation to the terminal
if self.conversation is not None:
print(f"{self.name}: {self.conversation}")
else:
print(f"{self.name} doesn't want to talk to you")
def hug(self):
# the character responds to a hug
print(f"{self.name} doesn't want to hug you")
def fight(self,item):
# the character response to a threat
print(f"{self.name} doesn't want to fight you")
return True
class Friend(Character):
def __init__(self, name):
# initialise the Friend object by calling the character initialise
super().__init__(name)
def hug(self):
# the friend responds to a hug
print(f"{self.name} hugs you back.")
class Enemy(Character):
num_of_enemy = 0
def __init__(self,name):
# initialise the Enemy object by calling the character initialise
super().__init__(name)
self.weakness = None
Enemy.num_of_enemy += 1
def fight(self, item):
# fights enemy with provided item and returns if player survives
if item == self.weakness:
print(f"You strike {self.name} down with {item}.")
return True
else:
print(f"{self.name} crushes you. Puny adventurer")
return False
|
PRIMM
- Predict what you think will happen. Be specific.
- Run the program to make sure there are no errors.
- Time to investigate the code. What does each line do?
Code explanation
- line 43 → creates the class variable
num_of_enemy and sets it to 0. It's written inside the class but outside any method, before __init__, and doesn't use self, because it belongs to the whole class rather than one object.
- line 49 → adds 1 to
Enemy.num_of_enemy. Because __init__ runs whenever we make a new enemy, the count goes up each time.
We're now counting the enemies, but we can't see the number. Let's create a method that tells us how many enemies are in the dungeon.
Still in character.py, add the highlighted code below.
| # character.py
class Character():
def __init__(self, name):
# initialises the character object
self.name = name
self.description = None
self.conversation = None
def describe(self):
# sends a description of the character to the terminal
print(f"{self.name} is here, {self.description}")
def talk(self):
# send conversation to the terminal
if self.conversation is not None:
print(f"{self.name}: {self.conversation}")
else:
print(f"{self.name} doesn't want to talk to you")
def hug(self):
# the character responds to a hug
print(f"{self.name} doesn't want to hug you")
def fight(self,item):
# the character response to a threat
print(f"{self.name} doesn't want to fight you")
return True
class Friend(Character):
def __init__(self, name):
# initialise the Friend object by calling the character initialise
super().__init__(name)
def hug(self):
# the friend responds to a hug
print(f"{self.name} hugs you back.")
class Enemy(Character):
num_of_enemy = 0
def __init__(self,name):
# initialise the Enemy object by calling the character initialise
super().__init__(name)
self.weakness = None
Enemy.num_of_enemy += 1
def fight(self, item):
# fights enemy with provided item and returns if player survives
if item == self.weakness:
print(f"You strike {self.name} down with {item}.")
return True
else:
print(f"{self.name} crushes you. Puny adventurer")
return False
def get_num_of_enemy():
return Enemy.num_of_enemy
|
Code explanation
- line 60 → defines the
get_num_of_enemy method. It doesn't have self, because it isn't about one object; it works for the whole class.
- line 61 → returns the current number of enemies.
Enemy. tells Python to use the class variable from the Enemy class.
Calling a method without self
Because get_num_of_enemy has no self, we call it on the class, like Enemy.get_num_of_enemy(), not on an object like ugine.
Reduce the enemy count
We can now count the enemies, but we also need to lower the count when the player beats one.
The fight method in the Enemy class already runs code when an enemy is defeated, so we just need to add one line there. Stay in character.py and add the highlighted line below.
| # character.py
class Character():
def __init__(self, name):
# initialises the character object
self.name = name
self.description = None
self.conversation = None
def describe(self):
# sends a description of the character to the terminal
print(f"{self.name} is here, {self.description}")
def talk(self):
# send conversation to the terminal
if self.conversation is not None:
print(f"{self.name}: {self.conversation}")
else:
print(f"{self.name} doesn't want to talk to you")
def hug(self):
# the character responds to a hug
print(f"{self.name} doesn't want to hug you")
def fight(self,item):
# the character response to a threat
print(f"{self.name} doesn't want to fight you")
return True
class Friend(Character):
def __init__(self, name):
# initialise the Friend object by calling the character initialise
super().__init__(name)
def hug(self):
# the friend responds to a hug
print(f"{self.name} hugs you back.")
class Enemy(Character):
num_of_enemy = 0
def __init__(self,name):
# initialise the Enemy object by calling the character initialise
super().__init__(name)
self.weakness = None
Enemy.num_of_enemy += 1
def fight(self, item):
# fights enemy with provided item and returns if player survives
if item == self.weakness:
print(f"You strike {self.name} down with {item}.")
Enemy.num_of_enemy -= 1
return True
else:
print(f"{self.name} crushes you. Puny adventurer")
return False
def get_num_of_enemy():
return Enemy.num_of_enemy
|
Code explanation
- line 55 → lowers the class variable by one. It's inside the
if on line 53, so it only runs when the player wins the fight.
Check for victory
The player wins when every enemy has been defeated. When that happens, the enemy count will be 0. We need to choose the right spot in the code to check for this.
In main.py, line 89 is where the game already checks whether the player won a fight, so that's the best place to add our check.
Add the highlighted code below.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
elif command == "quit":
running = False
else:
print("I don't understand.")
|
PRIMM
- Predict what you think will happen. Be specific.
- Run the program. Make sure the player wins when they have defeated all the enemies.
- Time to investigate the code. What does each line do?
Code explanation
- line 92 → checks whether the enemy count has reached
0. It's inside the if statements on lines 89 and 90, so it only runs after the player defeats an enemy.
- line 93 → shows the winning message.
- line 94 → stops the main loop, so the game ends.
Exercises
Our code now works, so it's time to reflect on what we've built.
Exercise 1
Can you explain how the game knows when the player has won? Read over your code and be ready to explain to your teacher, in your own words:
- what a class variable is, and why
num_of_enemy needs to be one
- which lines change the enemy count, and when they run
- where the game checks for a win, and why it's checked there