Stage 3 - Character Creation

Introduction

Now that the user has a dungeon that they can move around, we need to make it interesting. At this stage we will populate our dungeon with characters that the user can interact with.

To achieve this we will:

  1. Define a character class

  2. Create characters

  3. Add characters to the rooms

  4. Include characters in the room descriptions

  5. Create character interactions

    • talk method

    • hug method

    • fight method

  6. Add interactions to the main loop

Class Diagram

The Character class is a new class, so it will require a second class diagram.

We also need to add a character attribute to the Room class so we can record who is in each room.

lesson 3 class diagram

Define the Character class

In Thonny create a new file and enter the code below. Then save it as character.py in the same folder as main.py and room.py (remember capitalisation).

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

Let’s investigate this code:

  • # character.py → comment to identify the file we are working in

  • class Character(): → defining our new class called Character

  • def __init__(self, name): → the dunder init method that is run whenever a Character object is created.

  • # initialises the character object → explains the purpose of the method

  • self.name = name → assigned the value passed in the name argument to this Character object’s name attribute.

  • self.description = None → creates a description attribute for the Character object

  • self.conversation = None → creates a converstaion attribute for the Character object

Create characters

Now that we have a Character class, we can go to main.py and create Character objects.

Open main.py and add the highlighted code below.

 1# main
 2
 3from room import Room
 4from character import Character
 5
 6# create rooms
 7cavern = Room("Cavern")
 8cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
 9
10armoury = Room("Armoury")
11armoury.description = ("The walls are lined with racks that once held weapons and armour.")
12
13lab = Room("Laboratory")
14lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
15
16# link rooms
17cavern.link_rooms(armoury,"south")
18armoury.link_rooms(cavern,"north")
19armoury.link_rooms(lab,"east")
20lab.link_rooms(armoury,"west")
21
22# create characters
23ugine = Character("Ugine")
24ugine.description = "a huge troll with rotting teeth."
25
26nigel = Character("Nigel")
27nigel.description = "a burly dwarf with golden bead in woven through his beard."
28nigel.conversation = "Well youngan, what are you doing here?"
29
30'''
31# describe the rooms
32cavern.describe()
33armoury.describe()
34lab.describe()
35'''

Investigating that code:

  • from character import Character → get our Character class from the character.py file

  • # create characters → code structure comment

  • ugine = Character("Ugine") → creates a Character object with the name Ugine and assigns it to ugine

  • ugine.description = "a huge troll with rotting teeth." → changes the ugine description attribute to "a huge troll with rotting teeth."

  • nigel = Character("Nigel") → creates a Character object with the name Nigel and assigns it to nigel

  • nigel.description = "a burly dwarf with golden bead in woven through his beard." → changes the nigel description attribute to "a burly dwarf with golden bead in woven through his beard."

  • nigel.conversation = "Well youngan, what are you doing here?" → changes the nigel conversation attribute to "Well youngan, what are you doing here?"

  • Note that we didn’t change the conversation attribute for ugine. This means it will remain with the default value of None

Add Characters to the Rooms

So now we have two classes that interact with each other, Room and Character. Now we need to work out how we represent that interaction in our class structures. Checking our class diagram you will notice that we have added a new charcetr attribute to the Room class. This is how we show which Character is in the each Room.

lesson 3 class diagram

This is an arbitrary decision. We could easily had added the new attributer to the Charcter class showing this is the room the character is in. Both are valid. The important thing is to be consistent, and to document your decision for others to understand. That’s why the class diagram is so important.

Add character attribute to Room class in room.py

Return to room.py and add the highlighted line below.

 1# room.py
 2
 3class Room():
 4    
 5    def __init__(self,room_name):
 6        # initialises the room object
 7        self.name = room_name.lower()
 8        self.description = None
 9        self.linked_rooms = {}
10        self.character = None
11        
12    def describe(self):
13        # sends a description of the room to the terminal
14        print(f"\nYou are in the {self.name}")
15        print(self.description)
16        for direction in self.linked_rooms.keys():
17            print(f"To the {direction} is the {self.linked_rooms[direction].name}")
18    
19    def link_rooms(self, room_to_link, direction):
20        # links the provided room, in the provided direction
21        self.linked_rooms[direction.lower()] = room_to_link
22        
23    def move(self, direction):
24        # returns the room linked in the given direction
25        if direction in self.linked_rooms.keys():
26            return self.linked_rooms[direction]
27        else:
28            print("You can't go that way")
29            return self

Investigating that code:

  • self.character = None → creates a new attribute called character and assigns None to it.

Add characters to the rooms in main.py

The return to main.py and add characters to our rooms using the highlighted code below.

 1# main.py
 2
 3from room import Room
 4from character import Character
 5
 6# create rooms
 7cavern = Room("Cavern")
 8cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
 9
10armoury = Room("Armoury")
11armoury.description = ("The walls are lined with racks that once held weapons and armour.")
12
13lab = Room("Laboratory")
14lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
15
16# link rooms
17cavern.link_rooms(armoury,"south")
18armoury.link_rooms(cavern,"north")
19armoury.link_rooms(lab,"east")
20lab.link_rooms(armoury,"west")
21
22
23# create characters
24ugine = Character("Ugine")
25ugine.description = "a huge troll with rotting teeth."
26
27nigel = Character("Nigel")
28nigel.description = "a burly dwarf with golden bead in woven through his beard."
29nigel.conversation = "Well youngan, what are you doing here?"
30
31# add characters to rooms
32armoury.character = ugine
33lab.character = nigel
34
35'''
36# describe the rooms
37cavern.describe()
38armoury.describe()
39lab.describe()
40'''
41
42# initialise variables
43running = True
44current_room = cavern
45
46# ----- MAIN LOOP -----
47while running:
48    current_room.describe()
49    
50    command = input("> ").lower()
51    
52    if command in ["north", "south", "east", "west"]:
53        current_room = current_room.move(command)
54    elif command == "quit":
55        running = False
56    else:
57        print("I don't understand.")

Investigating the code:

  • armoury.character = ugine → assigns the ugine Character object to the character attribute of the armoury Room object.

  • lab.character = nigel → assigns the nigel Character object to the character attribute of the lab Room object.

Let’s do some testing. Predict what you think will happen and then Run the program. It should do nothing new, unless there is an error. That’s because we haven’t adjusted the room descriptions to include the characters. Let’s do that now.

Include characters in room description

To add the characters to the room description is a two step method:

  1. Create a describe method in the Character class

  2. modify the describe method in the Room class so it calls the character.describe method

Add describe method to Character class

Go to character.py and add the highlighted code below to create the describe method

 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}")

Investigating the new code:

  • def describe(self): → defines the describe method for the Character class

    • although our program already has a describe method, this will work because the other describe method belongs to the Room class.

    • in coding we say they have a different name space. The name space is all the parts of the name that are separated by ..

    • so character.describe() is not the same as room.describe()

  • # sends a description of the character to the terminal → the method description

  • print(f"{self.name} is here, {self.description}") → a f-string which prints details of this character.

Name Spaces

A closet has multiple shelves, drawers, and hangers, each designated for different types of clothes. When you want to get dressed for a specific occasion, you go to the corresponding section of the closet and pick out the clothes you need.

In the same way, in programming, we have namespaces which are like sections in a closet. Each namespace has a set of variables and functions that are related to a specific topic, just like the different sections in a closet designated for different types of clothes. When you want to use a specific variable or function, you go to the corresponding namespace and use what you need.

For example, we might have a namespace called “math” that contains all the variables and functions related to math problems, just like a section in a closet designated for work clothes. Another namespace might be called “game” that has variables and functions for playing games, like a section designated for casual clothes.

By using namespaces, we can keep our code organized, just like the clothes in a closet. This way, we can easily find the right variable or function for each task.

Modify the Room class describe method

Before we modify the describe method, we have to deal with a little problem. We have three rooms, but we only have two characters, so there is one room (the cavern) with no character. We only want to room description to mention the character, when there is one present.

Fortunately, we initially assigned None to the character attribute. We haven’t added a character to the cavern, so cavern.character is still None. Therefore we only want to describe the character, when the character attribute is not None.

To achieve this, add the highlighted code to room.py

 1# room.py
 2
 3class Room():
 4    
 5    def __init__(self,room_name):
 6        # initialises the room object
 7        self.name = room_name.lower()
 8        self.description = None
 9        self.linked_rooms = {}
10        self.character = None
11        
12    def describe(self):
13        # sends a description of the room to the terminal
14        print(f"\nYou are in the {self.name}")
15        print(self.description)
16        if self.character is not None:
17            self.character.describe()
18        for direction in self.linked_rooms.keys():
19            print(f"To the {direction} is the {self.linked_rooms[direction].name}")
20    
21    def link_rooms(self, room_to_link, direction):
22        # links the provided room, in the provided direction
23        self.linked_rooms[direction.lower()] = room_to_link
24        
25    def move(self, direction):
26        # returns the room linked in the given direction
27        if direction in self.linked_rooms.keys():
28            return self.linked_rooms[direction]
29        else:
30            print("You can't go that way")
31            return self

Let’s investigate that code:

  • if self.character is not None: → checks if the this room has a character

    • we use the is operator is to check if a variable’s value is None

  • calls the describe method for this room

Testing

Predict what you think will happen and the Run the code.

Test to make sure that you get character descriptions, but only when you enter a room that has a character.

Create character interactions

We want to add three interactions with out characters:

  • talk

  • hug

  • fight

If we look once again at our class diagram, we will see that in the character class, there is a method for each of these interactions.

lesson 3 class diagram

Add new methods to Character class

Return to the character.py file. First lets add the talk method by adding code highlighted 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")

Let’s investigate this code:

  • def talk(self): → is defining the talk method for this character

  • # send converstation to the terminal → our method description comment

  • if self.conversation is not None: → checks whether the character conversation attributer has a value

    • checking the main.py → Nigel has a conversation value but Ugine does not

    • our talk method needs to allow for characters that don’t have a conversation

  • print(f"{self.name}: {self.conversation}") → if there is a conversation value, then display the character name and what they say

  • else: → when the character doesn’t have a conversation value

  • print(f"{self.name} doesn't want to talk to you") → display a message that doesn’t require a conversation attribute

Now let’s add both the hug and fight methods with 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):
27        # the character response to a threat
28        print(f"{self.name} doesn't want to fight you")

By this stage the code for both methods should look familiar:

  • define the method with self as the first argument

  • provide a comment describing what the method does

  • display a message that uses one of the character’s attributes

Add the interactions to the main loop

Now that the player can interact with our characters, we need to add the three options (talk, hug, fight) to our event handler in the main loop.

Return to main.py, and add the highlighted code:

 1# main.py
 2
 3from room import Room
 4from character import Character
 5
 6# create rooms
 7cavern = Room("Cavern")
 8cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
 9
10armoury = Room("Armoury")
11armoury.description = ("The walls are lined with racks that once held weapons and armour.")
12
13lab = Room("Laboratory")
14lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
15
16# link rooms
17cavern.link_rooms(armoury,"south")
18armoury.link_rooms(cavern,"north")
19armoury.link_rooms(lab,"east")
20lab.link_rooms(armoury,"west")
21
22
23# create characters
24ugine = Character("Ugine")
25ugine.description = "a huge troll with rotting teeth."
26
27nigel = Character("Nigel")
28nigel.description = "a burly dwarf with golden bead in woven through his beard."
29nigel.conversation = "Well youngan, what are you doing here?"
30
31# add characters to rooms
32armoury.character = ugine
33lab.character = nigel
34
35'''
36# describe the rooms
37cavern.describe()
38armoury.describe()
39lab.describe()
40'''
41
42# initialise variables
43running = True
44current_room = cavern
45
46# ----- MAIN LOOP -----
47while running:
48    current_room.describe()
49    
50    command = input("> ").lower()
51    
52    if command in ["north", "south", "east", "west"]:
53        current_room = current_room.move(command)
54    elif command == "talk":
55        if current_room.character is not None:
56            current_room.character.talk()
57        else:
58            print("There is no one here to talk to")
59    elif command == "hug":
60        if current_room.character is not None:
61            current_room.character.hug()
62        else:
63            print("There is no one here to hug")
64    elif command== "fight":
65        if current_room.character is not None:
66            current_room.character.fight()
67        else:
68            print("There is no one here to fight")
69    elif command == "quit":
70        running = False
71    else:
72        print("I don't understand.")

Since the event handler for all three interactions is virtually the same, we’ll just investigate the code for the talk method:

  • elif command == "talk": → checks if the user’s command was talk

  • if current_room.character is not None: → checks if there is a character in the room

    • remember that rooms can not have a character (eg. Cavern) so we need to allow for this.

  • current_room.character.talk() → if there is a character, the call its talk() method

  • else: → deals with rooms with no character

  • print("There is no one here to talk to") → message for when there is no character

Stage 3 task

Once again we have only been focusing on the first four stages of the PRIMM model. Now it is time for your to implement the Make phase.

In Stage 1 you created an additional room. So now it is time to populate that room.

  • Create an additional character for each extra room you’ve added

  • Add those characters to your additional rooms