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:
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.
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 inclass Character():
→ defining our new class calledCharacter
def __init__(self, name):
→ the dunder init method that is run whenever aCharacter
object is created.# initialises the character object
→ explains the purpose of the methodself.name = name
→ assigned the value passed in thename
argument to thisCharacter
object’sname
attribute.self.description = None
→ creates adescription
attribute for theCharacter
objectself.conversation = None
→ creates aconverstaion
attribute for theCharacter
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 ourCharacter
class from the character.py file# create characters
→ code structure commentugine = Character("Ugine")
→ creates aCharacter
object with the nameUgine
and assigns it tougine
ugine.description = "a huge troll with rotting teeth."
→ changes theugine
description
attribute to"a huge troll with rotting teeth."
nigel = Character("Nigel")
→ creates aCharacter
object with the nameNigel
and assigns it tonigel
nigel.description = "a burly dwarf with golden bead in woven through his beard."
→ changes thenigel
description
attribute to"a burly dwarf with golden bead in woven through his beard."
nigel.conversation = "Well youngan, what are you doing here?"
→ changes thenigel
conversation
attribute to"Well youngan, what are you doing here?"
Note that we didn’t change the
conversation
attribute forugine
. This means it will remain with the default value ofNone
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
.
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 calledcharacter
and assignsNone
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 theugine
Character
object to thecharacter
attribute of thearmoury
Room
object.lab.character = nigel
→ assigns thenigel
Character
object to thecharacter
attribute of thelab
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:
Create a
describe
method in theCharacter
classmodify the
describe
method in theRoom
class so it calls thecharacter.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 thedescribe
method for theCharacter
classalthough our program already has a
describe
method, this will work because the otherdescribe
method belongs to theRoom
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 asroom.describe()
# sends a description of the character to the terminal
→ the method descriptionprint(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 characterwe use the
is
operator is to check if a variable’s value isNone
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.
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 commentif self.conversation is not None:
→ checks whether the characterconversation
attributer has a valuechecking the main.py → Nigel has a
conversation
value but Ugine does notour
talk
method needs to allow for characters that don’t have a conversation
print(f"{self.name}: {self.conversation}")
→ if there is aconversation
value, then display the character name and what they sayelse:
→ when the character doesn’t have aconversation
valueprint(f"{self.name} doesn't want to talk to you")
→ display a message that doesn’t require aconversation
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 argumentprovide 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 wastalk
if current_room.character is not None:
→ checks if there is a character in the roomremember 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 itstalk()
methodelse:
→ deals with rooms with no characterprint("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