Stage 7 - Victory Conditions

Introduction

We’re almost done with our text-based adventure game. You now have a dungeon where the player can move around, pick up items, and talk or fight with different characters. What happens in these interactions depends on the type of character they meet.

In this stage, you’re going to set up how the player wins the game.

You will do this by:

Pseudocode

  • counting how many enemies exist

  • lowering that count each time an enemy is defeated

  • checking 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

lesson 7 class diagram

The num_of_enemy attribute is underlined in the diagram. That 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 whenever one object changes it, the others see the new value too.

In this game, num_of_enemy keeps track of how many Enemy objects exist, and every enemy you create will share that same number.

Count Number of Enemies

To keep track of how many Enemy objects are in the game, we need to add a class variable to the Enemy class.

Open character.py and add the highlighted code shown below.

 1# character.py
 2
 3class Character():
 4    
 5    def __init__(self, name):
 6        # initialises the character object
 7        self.name = name
 8        self.description = None
 9        self.conversation = None
10        
11    def describe(self):
12        # sends a description of the character to the terminal
13        print(f"{self.name} is here, {self.description}")
14        
15    def talk(self):
16        # send converstation to the terminal
17        if self.conversation is not None:
18            print(f"{self.name}: {self.conversation}")
19        else:
20            print(f"{self.name} doesn't want to talk to you")
21    
22    def hug(self):
23        # the character responds to a hug
24        print(f"{self.name} doesn't want to hug you")
25
26    def fight(self,item):
27        # the character response to a threat
28        print(f"{self.name} doesn't want to fight you")
29        return True
30        
31class Friend(Character):
32    
33    def __init__(self, name):
34        # initialise the Friend object by calling the character initialise
35        super().__init__(name)
36        
37    def hug(self):
38        # the friend responds to a hug
39        print(f"{self.name} hugs you back.")
40        
41class Enemy(Character):
42    
43    num_of_enemy = 0
44
45    def __init__(self,name):
46        # initialise the Enemy object by calling the character initialise
47        super().__init__(name)
48        self.weakness = None
49        Enemy.num_of_enemy += 1
50        
51    def fight(self, item):
52        # fights enemy with provided item and returns if player survives
53        if item == self.weakness:
54            print(f"You strike {self.name} down with {item}.")
55            return True
56        else:
57            print(f"{self.name} crushes you. Puny adventurer")
58            return False

Save the code, and run it to ensure there are no errors.

Investigating the code:

Code Explaination

  • num_of_enemy = 0 → this creates the class variable.

    • Class variables must be written before the __init__ method.

    • They are indented once, at the same level as the methods.

    • It does not use self because it belongs to the whole class, not to one object.

  • Enemy.num_of_enemy += 1 → this adds 1 to num_of_enemy every time a new Enemy is created.

    • Because __init__ runs whenever you make a new enemy, the total number of enemies goes up each time.

Now that we are keeping track of the number of enemies, but we can’t see what that number is. So let’s create a method to find out how many enemies are in the dungeon

Still working in character.py, add the highlighted code below:

 1# character.py
 2
 3class Character():
 4    
 5    def __init__(self, name):
 6        # initialises the character object
 7        self.name = name
 8        self.description = None
 9        self.conversation = None
10        
11    def describe(self):
12        # sends a description of the character to the terminal
13        print(f"{self.name} is here, {self.description}")
14        
15    def talk(self):
16        # send converstation to the terminal
17        if self.conversation is not None:
18            print(f"{self.name}: {self.conversation}")
19        else:
20            print(f"{self.name} doesn't want to talk to you")
21    
22    def hug(self):
23        # the character responds to a hug
24        print(f"{self.name} doesn't want to hug you")
25
26    def fight(self,item):
27        # the character response to a threat
28        print(f"{self.name} doesn't want to fight you")
29        return True
30        
31class Friend(Character):
32    
33    def __init__(self, name):
34        # initialise the Friend object by calling the character initialise
35        super().__init__(name)
36        
37    def hug(self):
38        # the friend responds to a hug
39        print(f"{self.name} hugs you back.")
40        
41class Enemy(Character):
42    
43    num_of_enemy = 0
44
45    def __init__(self,name):
46        # initialise the Enemy object by calling the character initialise
47        super().__init__(name)
48        self.weakness = None
49        Enemy.num_of_enemy += 1
50        
51    def fight(self, item):
52        # fights enemy with provided item and returns if player survives
53        if item == self.weakness:
54            print(f"You strike {self.name} down with {item}.")
55            return True
56        else:
57            print(f"{self.name} crushes you. Puny adventurer")
58            return False
59
60    def get_num_of_enemy():
61        return Enemy.num_of_enemy

Let’s investigate that method:

Code Explaination

  • def get_num_of_enemy(): → this creates the method.

    • It doesn’t have self because it’s not linked to one object; it works for the whole class.

  • return Enemy.num_of_enemy → this gives back the current number of enemies.

    • The Enemy. part tells Python to use the class variable from the Enemy class.

Reduce Enemy Count

We can now count how many enemies there are and check that number, but we also need to lower the count when the player beats an enemy.

The fight method in the Enemy class already runs code when an enemy is defeated, so we just need to add one extra line there.

Stay in character.py and add the highlighted line below.

 1# character.py
 2
 3class Character():
 4    
 5    def __init__(self, name):
 6        # initialises the character object
 7        self.name = name
 8        self.description = None
 9        self.conversation = None
10        
11    def describe(self):
12        # sends a description of the character to the terminal
13        print(f"{self.name} is here, {self.description}")
14        
15    def talk(self):
16        # send converstation to the terminal
17        if self.conversation is not None:
18            print(f"{self.name}: {self.conversation}")
19        else:
20            print(f"{self.name} doesn't want to talk to you")
21    
22    def hug(self):
23        # the character responds to a hug
24        print(f"{self.name} doesn't want to hug you")
25
26    def fight(self,item):
27        # the character response to a threat
28        print(f"{self.name} doesn't want to fight you")
29        return True
30        
31class Friend(Character):
32    
33    def __init__(self, name):
34        # initialise the Friend object by calling the character initialise
35        super().__init__(name)
36        
37    def hug(self):
38        # the friend responds to a hug
39        print(f"{self.name} hugs you back.")
40        
41class Enemy(Character):
42    
43    num_of_enemy = 0
44
45    def __init__(self,name):
46        # initialise the Enemy object by calling the character initialise
47        super().__init__(name)
48        self.weakness = None
49        Enemy.num_of_enemy += 1
50        
51    def fight(self, item):
52        # fights enemy with provided item and returns if player survives
53        if item == self.weakness:
54            print(f"You strike {self.name} down with {item}.")
55            Enemy.num_of_enemy -= 1
56            return True
57        else:
58            print(f"{self.name} crushes you. Puny adventurer")
59            return False
60
61    def get_num_of_enemy():
62        return Enemy.num_of_enemy

Save the code before we Investigate it:

Code Explaination

  • Line 53 already checks if the player wins the fight.

  • Enemy.num_of_enemy -= 1 makes the class variable go down by one when an enemy is defeated.

Check for Victory

The player wins the game when every enemy has been defeated. When that happens, Enemy.num_of_enemy will be 0. So we need to choose the right spot in the code to check for this.

In main.py, line 89 is already where the game checks if the player wins, so that’s the best place to add our num_of_enemy check.

Add the highlighted code below:

  1# main.py
  2
  3from room import Room
  4from character import Enemy, Friend
  5from item import Item
  6
  7# create rooms
  8cavern = Room("Cavern")
  9cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
 10
 11armoury = Room("Armoury")
 12armoury.description = ("The walls are lined with racks that once held weapons and armour.")
 13
 14lab = Room("Laboratory")
 15lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
 16
 17# link rooms
 18cavern.link_rooms(armoury,"south")
 19armoury.link_rooms(cavern,"north")
 20armoury.link_rooms(lab,"east")
 21lab.link_rooms(armoury,"west")
 22
 23
 24# create characters
 25ugine = Enemy("Ugine")
 26ugine.description = "a huge troll with rotting teeth."
 27ugine.weakness = "cheese"
 28
 29nigel = Friend("Nigel")
 30nigel.description = "a burly dwarf with golden bead in woven through his beard."
 31nigel.conversation = "Well youngan, what are you doing here?"
 32
 33# add characters to rooms
 34armoury.character = ugine
 35lab.character = nigel
 36
 37# create items
 38cheese = Item("Cheese")
 39cheese.description = "super smelly"
 40
 41chair = Item("Chair")
 42chair.description = "designed to be sat on"
 43
 44elmo = Item("Elmo")
 45elmo.description = "wanting to be tickled"
 46
 47# add items to rooms
 48cavern.item = chair
 49armoury.item = elmo
 50lab.item = cheese
 51
 52'''
 53# describe the rooms
 54cavern.describe()
 55armoury.describe()
 56lab.describe()
 57'''
 58
 59# initialise variables
 60running = True
 61current_room = cavern
 62backpack = []
 63
 64# ----- MAIN LOOP -----
 65while running:
 66    current_room.describe()
 67    
 68    command = input("> ").lower()
 69    
 70    if command in ["north", "south", "east", "west"]:
 71        current_room = current_room.move(command)
 72    elif command == "talk":
 73        if current_room.character is not None:
 74            current_room.character.talk()
 75        else:
 76            print("There is no one here to talk to")
 77    elif command == "hug":
 78        if current_room.character is not None:
 79            current_room.character.hug()
 80        else:
 81            print("There is no one here to hug")
 82    elif command== "fight":
 83        if current_room.character is not None:
 84            weapon = input("What will you fight with? > ").lower()
 85            available_weapons = []
 86            for item in backpack:
 87                available_weapons.append(item.name)
 88            if weapon in available_weapons:
 89                if current_room.character.fight(weapon):
 90                    if isinstance(current_room.character, Enemy):
 91                        current_room.character = None
 92                        if Enemy.get_num_of_enemy() == 0:
 93                            print("You have slain all the enemies. You are victorious!")
 94                            running = False
 95                else:
 96                    running = False
 97            else:
 98                print(f"You don't have {weapon}")
 99                print(f"{current_room.character.name} strikes you down.")
100                running = False
101        else:
102            print("There is no one here to fight")
103    elif command == "take":
104        if current_room.item is not None:
105            backpack.append(current_room.item)
106            print(f"You put {current_room.item.name} into your backpack")
107            current_room.item = None
108        else:
109            print("There is nothing here to take")
110    elif command == "backpack":
111        if backpack == []:
112            print("It is empty")
113        else:
114            print("You have:")
115            for item in backpack:
116                print(f"- {item.name.capitalize()}")
117    elif command == "quit":
118        running = False
119    else:
120        print("I don't understand.")

Investigating that code:

Code Explaination

  • Line 89 makes sure this code only runs after the player defeats an enemy.

  • if Enemy.get_num_of_enemy() == 0: checks if the class variable has reached 0.

  • print("You have slain all the enemies. You are victorious!") shows the win message.

  • running = False stops the main loop so the game can end.

Testing

Now time to test our code.

Predict what you think will happen, then run your code. Make sure that you test that the player wins when they have defeated all the enemies.