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:

Pseudocode

  • Define a character class

  • Create characters

  • Add characters to the rooms

  • Include characters in the room descriptions

  • Create character interactions

    • talk method

    • hug method

    • fight method

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

Code Explaination

  • # character.py → a note to remind us which file this code belongs to

  • class Character(): → creates a new type of object called Character

  • def __init__(self, name): → this special method runs every time you make a new Character

  • # initialises the character object → a note explaining what the method is for

  • self.name = name → saves the character’s name inside the object

  • self.description = None → sets up a description for the character, but leaves it empty for now

  • self.conversation = None → sets up something the character might say, but also leaves it empty for now

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:

Code Explaination

  • from character import Character → brings the Character class into this file so we can use it

  • # create characters → a note showing this section is where we make characters

  • ugine = Character("Ugine") → makes a new Character named Ugine and stores it in the variable ugine

  • ugine.description = "a huge troll with rotting teeth." → gives Ugine a description

  • nigel = Character("Nigel") → makes a new Character named Nigel and stores it in the variable nigel

  • nigel.description = "a burly dwarf with golden bead in woven through his beard." → gives Nigel a description

  • nigel.conversation = "Well youngan, what are you doing here?" → gives Nigel something he can say

  • Ugine has no conversation set, so his conversation stays empty (None)

Add Characters to the Rooms

Now we have two classes that work together: Room and Character. We need a way for our code to show which character is in which room. In the Room class diagram, you can see we added a new character attribute. This lets each room store the character that’s inside it.

lesson 3 class diagram

This is an arbitrary decision. We could easily had added the new attribute to the Character 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:

Code Explaination

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

Code Explaination

  • armoury.character = ugine → puts the character Ugine into the armoury room

  • lab.character = nigel → puts the character Nigel into the lab room

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:

Code Explaination

  • def describe(self): → creates a describe method for characters

    • even though we already have a describe method, it’s fine because that one belongs to the Room class

    • they’re in different “namespaces,” which just means they belong to different objects

    • so character.describe() and room.describe() are completely separate

  • # sends a description of the character to the terminal → a note explaining what the method does

  • print(f"{self.name} is here, {self.description}") → prints the character’s name and what they look like

Name spaces

Imagine your wardrobe at home. You have different spots for different things — shelves for shirts, drawers for socks, hangers for jackets. When you need something, you go to the right spot and grab it.

Namespaces in programming work the same way. They’re like labelled sections that keep code organised. Each namespace stores its own variables and functions, just like each part of your wardrobe stores its own type of clothes.

For example, a “math” namespace might hold maths-related functions, while a “game” namespace might hold game-related functions. They are kept separate so nothing gets mixed up.

Using namespaces keeps your code tidy and makes it easy to find exactly what you need.

Modify the Room class describe method

Before we change the describe method, we need to fix a small issue. We have three rooms but only two characters, which means one room (the cavern) has no character in it. We don’t want the game to talk about a character unless one is actually there.

Because we set character to None when a room is empty, the cavern still has cavern.character = None. So, when we describe a room, we should only show the character’s description if the character value is not None.

Return to room.py and modify the describe method as highlighted 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        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:

Code Explaination

  • if self.character is not None: → checks if this room actually has a character in it

    • the is keyword is used to check if something is equal to None

  • if there is a character, the code runs the character’s describe method to show their details

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 in it.

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:

Code Explaination

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

  • # send conversation to the terminal → a note explaining what the method does

  • if self.conversation is not None: → checks if the character actually has something to say

    • in main.py, Nigel has a conversation, but Ugine doesn’t

    • the method needs to handle both situations

  • print(f"{self.name}: {self.conversation}") → if the character has a conversation, print their name and what they say

  • else: → runs when the character has no conversation set

  • print(f"{self.name} doesn't want to talk to you") → shows a message for characters who won’t talk

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:

Code Explaination

  • 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