Stage 7 - Victory Conditions

Introduction

We are close to finishing our text-based adventure game. We now have a dungeon in which the user can move around, collect different items, interact with different characters and have the result of those interactions determined by the character type.

In this stage we will look at establishing victory conditions for the game.

We will do this by:

  • keeping a count of the number of enemies

  • reducing the enemy count when each enemy is defeated

  • declare the player victorious when there are no enemies left

Class Diagram

Look to our class diagram for this stage and you will notice something new.

The Enemy class has a new:

  • num_of_enemy attribute

  • get_num_of_enemy method

lesson 7 class diagram

Notice the num_of_enemy attribute is underlined. This indicates that this is a class variable. This means that the variable is shared across to all instances of that class. Each instance of that class can access and modify the variable, and any changes will be shared with other classes.

In our example, the num_of_enemy will be shared with all the Enemy objects we create in our game.

Count Number of Enemies

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

So we need to open the character.py file, and 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

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

Investigating the code:

  • num_of_enemy = 0 → creates our class variable

    • important to note the location and the indent level of class variables:

      • need to be placed before the __init__ method

      • indented once → same level as method definitions

    • also note that, unlike the other attributes, num_of_enemy does start with self because it is a class variable

  • Enemy.num_of_enemy += 1 → increases num_of_enemy by one each time a new Enemy object is created.

    • since __init__ runs each time a new enemy is made, the value of num_of_enemy will increase

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:

  • def get_num_of_enemy(): → defines the method

    • there is no self argument → this method is not tied to a particular instance, but rather the whole class.

  • return Enemy.num_of_enemy → provides the current value of num_of_enemy

    • notice the Enemy. → tells Python this is a class variable of the Enemy class

Reduce Enemy Count

So we have a count of the number of enemies, and we can retrieve that value, now we need to reduce the enemy count when the player defeats an enemy.

The Enemy class fight method already has code that is executed when the player defeats the enemy, so we just need to add to that.

Still in character.py go to insert 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:

  • line 53 → already determines if the player beats the enemy

  • Enemy.num_of_enemy -= 1 → reduces the value of the class variable by one.

Check for Victory

The player will be victorious when they have defeated all the enemies in the dungeon. When all the enemies have been defeated the Enemy.num_of_emeny will be 0. So we need to find the best place to check this.

Let’s look back at main.py. Line 89 is where Python decides if they player wins, so it makes sense to put out num_of_enemy check around here.

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                    current_room.character = None
 91                    if Enemy.get_num_of_enemy() == 0:
 92                        print("You have slain all the enemies. You are victorious!")
 93                        running = False
 94                else:
 95                    running = False
 96            else:
 97                print(f"You don't have {weapon}")
 98                print(f"{current_room.character.name} strikes you down.")
 99                running = False
100        else:
101            print("There is no one here to fight")
102    elif command == "take":
103        if current_room.item is not None:
104            backpack.append(current_room.item)
105            print(f"You put {current_room.item.name} into your backpack")
106            current_room.item = None
107        else:
108            print("There is nothing here to take")
109    elif command == "backpack":
110        if backpack == []:
111            print("It is empty")
112        else:
113            print("You have:")
114            for item in backpack:
115                print(f"- {item.name.capitalize()}")
116    elif command == "quit":
117        running = False
118    else:
119        print("I don't understand.")

Investigating that code:

  • line 89 → already established that our new code will only run when the player defeats an enemy

  • if Enemy.get_num_of_enemy() == 0: → checks if the class variable is 0

  • print("You have slain all the enemies. You are victorious!") → displays a victory message

  • running = False → readies to finish the game by exiting the main loop.

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.