Extension - Player Class

In this extensions tutorial, we will refactor our code to create a Player class. This will give the game a player object which we can use to add features associated with the player (health, inventory, gear, weapons etc.). The first feature we will add is the player inventory, known in our game as the backpack.

Planning

Currently the code dealing with the backpack is held in main.py. Before we plan our new class, we need to look at this code an identify all relevant feature we need to incorporate. So let’s look at the main.py from the end of the standard tutorials.

  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# create characters
 24ugine = Enemy("Ugine")
 25ugine.description = "a huge troll with rotting teeth."
 26ugine.weakness = "cheese"
 27
 28nigel = Friend("Nigel")
 29nigel.description = "a burly dwarf with golden bead in woven through his beard."
 30nigel.conversation = "Well youngan, what are you doing here?"
 31
 32# add characters to rooms
 33armoury.character = ugine
 34lab.character = nigel
 35
 36# create items
 37cheese = Item("Cheese")
 38cheese.description = "super smelly"
 39
 40chair = Item("Chair")
 41chair.description = "designed to be sat on"
 42
 43elmo = Item("Elmo")
 44elmo.description = "wanting to be tickled"
 45
 46# add items to rooms
 47cavern.item = chair
 48armoury.item = elmo
 49lab.item = cheese
 50
 51# initialise variables
 52running = True
 53current_room = cavern
 54backpack = []
 55
 56# ----- MAIN LOOP -----
 57while running:
 58    current_room.describe()
 59    
 60    command = input("> ").lower()
 61    
 62    # move
 63    if command in ["north", "south", "east", "west"]:
 64        current_room = current_room.move(command)
 65        print(f"You travel {command}")
 66    # talk
 67    elif command == "talk":
 68        if current_room.character is not None:
 69            current_room.character.talk()
 70        else:
 71            print("There is no one here to talk to")
 72    # hug
 73    elif command == "hug":
 74        if current_room.character is not None:
 75            current_room.character.hug()
 76        else:
 77            print("There is no one here to hug")
 78    # fight
 79    elif command== "fight":
 80        if current_room.character is not None:
 81            weapon = input("What will you fight with? > ").lower()
 82            available_weapons = []
 83            for item in backpack:
 84                available_weapons.append(item.name)
 85            if weapon in available_weapons:
 86                if current_room.character.fight(weapon):
 87                    current_room.character = None
 88                    if Enemy.num_of_enemy == 0:
 89                        print("You have slain the enemy. You are victorious!")
 90                        running = False
 91                else:
 92                    running = False
 93            else:
 94                print(f"You don't have {weapon}")
 95                print(f"{current_room.character.name} strikes you down.")
 96                running = False
 97        else:
 98            print("There is no one here to fight")
 99    # take
100    elif command == "take":
101        if current_room.item is not None:
102            backpack.append(current_room.item)
103            print(f"You put {current_room.item.name} into your backpack")
104            current_room.item = None
105        else:
106            print("There is nothing here to take")
107    # backpack
108    elif command == "backpack":
109        if backpack == []:
110            print("It is empty")
111        else:
112            print("You have:")
113            for item in backpack:
114                print(f"- {item.name.capitalize()}")
115    # help
116    elif command == "help":
117        print("Type which direction you wish to move,")
118        print("or use one of these commands:")
119        print("- Talk")
120        print("- Fight")
121        print("- Hug")
122        print("- Take")
123        print("- Backpack")
124    # quit
125    elif command == "quit":
126        running = False
127    # incorrect command
128    else:
129        print("Enter 'help' for list of commands")
130    input("\nPress <Enter> to continue")
131    
132print("Thank you for playing Darkest Dungeon")

You will notice that there are four places that main.py interacts with the player’s backpack.

  • line 54 → defines the backpack variable as an empty list

  • lines 82 - 85 → checks if chosen weapon is in the backpack

  • lines 101 - 106 → adds item to backpack

  • lines 109-114 → displays the contents of the backpack

If we were to move these features to a Player class, we need to consider the nature of the four features:

  • the backpack describes part of the player → attribute

  • checking for weapon in backpack is an action → method

  • adding an item to backpack is an action → method

  • displaying the contents of the backpack is an actions → method

Therefore the class diagram would look like this:

player class diagram

Now that we have a plan. Lets implement it in our code.

Coding

We are going to refactor this code is little chunks. That way we can regularly test to ensure we haven’t introduced new bugs

Create Player class

First we need to create the Player Class.

  1. Create a new file called player.py. Make sure it is in the same folder as the other program files.

  2. Next add the Player class and it’s __init__ with the code below.

1# player.py
2
3class Player():
4    
5    def __init__(self):
6        self.backpack = []

Replace references to backpack

Now in main.py we will create a instance of the Player class by making a player object.

  1. Import the Player class (code below)

3from room import Room
4from character import Enemy, Friend
5from item import Item
6from player import Player
  1. Create a player object before the initialization of variables

47# add items to rooms
48cavern.item = chair
49armoury.item = elmo
50lab.item = cheese
51
52# create player
53player = Player()
  1. Delete the backback variable in line 58

55# initialise variables
56running = True
57current_room = cavern
58
59# ----- MAIN LOOP -----
  1. Change the backpack reference in the fight command to refer to player.backpack

 81    # fight
 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 player.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.num_of_enemy == 0:
 92                        print("You have slain the enemy. 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")
  1. Change the backpack reference in the take command to refer to player.backpack

102    # take
103    elif command == "take":
104        if current_room.item is not None:
105            player.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")
  1. Change the backpack references in the backpack command to refer to player.backpack

110    # backpack
111    elif command == "backpack":
112        if player.backpack == []:
113            print("It is empty")
114        else:
115            print("You have:")
116            for item in player.backpack:
117                print(f"- {item.name.capitalize()}")

Test backpack replacement

That is our first block of refactoring. Time to test to ensure everything still works.

add_item method

To create effective code we should shift all backpack related code into the Player class. We will start with the add_method that add items to the backpack.

  1. In player.py create the add_item method using the code below.

 1# player.py
 2
 3class Player():
 4    
 5    def __init__(self):
 6        self.backpack = []
 7        
 8    def add_item(self, item):
 9        self.backpack.append(item)
10        print(f"You put {item.name} into your backpack")

Now we need to remove that code from main.py, and replace it with a call to the add_item method.

  1. replace the highlighted main.py code below:

102    # take
103    elif command == "take":
104        if current_room.item is not None:
105            player.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")
  1. with this highlighted code:

102    # take
103    elif command == "take":
104        if current_room.item is not None:
105            player.add_item(current_room.item)
106            current_room.item = None
107        else:
108            print("There is nothing here to take")

Test add_item method

Now go and test that you can still add items to your backpack.

display_contents method

Next we will refactor the code that displays the contents of the backpack.

  1. Return to the player.py file

  2. Add the code below to the Player class

 1# player.py
 2
 3class Player():
 4    
 5    def __init__(self):
 6        self.backpack = []
 7        
 8    def add_item(self, item):
 9        self.backpack.append(item)
10        print(f"You put {item.name} into your backpack")
11        
12    def display_contents(self):
13        if self.backpack == []:
14            print("It is empty")
15        else:
16            print("You have:")
17            for item in self.backpack:
18                print(f"- {item.name.capitalize()}")
  1. Go back to main.py

  2. Replace the highlighted code below:

109    # backpack
110    elif command == "backpack":
111        if player.backpack == []:
112            print("It is empty")
113        else:
114            print("You have:")
115            for item in player.backpack:
116                print(f"- {item.name.capitalize()}")
  1. with this code:

109    # backpack
110    elif command == "backpack":
111        player.display_contents()

Test display_contents method

Run your code and ensure that you can still see your backpack content.

check_item_in method

Finally we need to change the backpack interaction in the fight command, but we’re going to do more than just a simple replace. We’re going to change the fight section so that the backpack returns the item rather than just item.name. This will allow future extensions including weapons with damage and players and characters with hit points.

In player.py add the code below:

 1# player.py
 2
 3class Player():
 4    
 5    def __init__(self):
 6        self.backpack = []
 7        
 8    def add_item(self, item):
 9        self.backpack.append(item)
10        print(f"You put {item.name} into your backpack")
11        
12    def display_contents(self):
13        if self.backpack == []:
14            print("It is empty")
15        else:
16            print("You have:")
17            for item in self.backpack:
18                print(f"- {item.name.capitalize()}")
19        
20    def check_item_in(self, item_name):
21        for item in self.backpack:
22            if item.name == item_name:
23                return item
24        return None

This code is different, so lets investigate it:

  • def check_item_in(self, item_name): → defines the function and expects the item name (a string) to be provided.

  • for item in self.backpack: → iterate through each item object stored in self.backpack

  • if item.name == item_name: → checks if the current item’s name is the same as the item_name provided

    • return item → returns the current item ending the method. Note this only happens if the names match

  • return None → returns a None value to indicate that none of the items in self.backpack have the same name as the item_name provided

In main.py change the fight command code to the same as below. Take note of the highlighted lines.

81    # fight
82    elif command== "fight":
83        if current_room.character is not None:
84            choice = input("What will you fight with? > ").lower()
85            weapon = player.check_item_in(choice)
86            if weapon:
87                if current_room.character.fight(weapon.name):
88                    current_room.character = None
89                    if Enemy.num_of_enemy == 0:
90                        print("You have slain the enemy. You are victorious!")
91                        running = False
92                else:
93                    running = False
94            else:
95                print(f"You don't have {choice}")
96                print(f"{current_room.character.name} strikes you down.")
97                running = False
98        else:
99            print("There is no one here to fight")

Lets investigate those lines of code.

  • choice = input("What will you fight with? > ").lower() → we had to change the variable name, since weapon will be used to hold the item object return from check_item_in

  • weapon = player.check_item_in(choice) → if the requested item is in the back, the item object is stored in weapon otherwise, None will be stored there.

  • if weapon: → uses the Truthiness of objects. If there is an item_object in weapon then this will equate to True, if None is stored in weapon then this will equate to False

  • if current_room.character.fight(weapon.name):weapon now stores a item object rather than a name, but the fight method requires a string of the item name. So we have to pass the weapon.name attribute.

  • print(f"You don't have {choice}") → if the choice is not in the backpack, then weapon will be None, but None doesn’t have a name attribute, so we can use the same trick we use in line 87. Rather we will just feedback what the user entered.

check_item_in test

Final test. Run your code and make sure that all the fight options work.