Stage 2 - Movement¶
Introduction¶
In Stage 1 we created three rooms, link them together, and got the program to describe them. While that was necessary, it doesn’t make for a very good adventure game. So, in Stage 2 we will add code that allows the user to move between rooms, and we will create the main loop.
The main loop is an essential aspect of event-driven programming. Our main.py will initialize the game, creating all the necessary objects. It will then enter in main loop where it will wait for the user to provide input, and then respond to this input.
Event driven programming
Event-driven programming is a type of computer programming where the order of tasks is determined by events, like a user clicking a button or receiving information from a sensor.
Instead of following a set plan like in traditional programming, the program waits for events to happen and then reacts to them. This makes the program more flexible and able to change as needed.
To achieve this we will need to complete the following steps:
Create the
move
methodInitialize the starting room
Create the main loop which:
describes current room
accepts user input
responds to user input
Class Diagram¶
We have updated the Room
class diagram to reflect the Stage 2 work.
Notice we have a new method move(direction):room
accepts one argument (direction)
returns a
Room
object
Create the move method¶
Open the room.py file and add the code 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
11 def describe(self):
12 # sends a description of the room to the terminal
13 print(f"\nYou are in the {self.name}")
14 print(self.description)
15 for direction in self.linked_rooms.keys():
16 print(f"To the {direction} is the {self.linked_rooms[direction].name}")
17
18 def link_rooms(self, room_to_link, direction):
19 # links the provided room, in the provided direction
20 self.linked_rooms[direction.lower()] = room_to_link
21
22 def move(self, direction):
23 # returns the room linked in the given direction
24 if direction in self.linked_rooms.keys():
25 return self.linked_rooms[direction]
26 else:
27 print("You can't go that way")
28 return self
We need to create the main loop before we call this code, but let’s investigate our new code anyway.
def move(self, direction):
→ defines themove
method which accepts one argument:direction
# returns the room linked in the given direction
→ comment to explain the methodif direction in self.linked_rooms.keys():
→ checks if passeddirection
is a valid optionself.linked_rooms.keys()
gets all the
keys
from thelinked_rooms
attribute for thisRoom
objectsince the
keys
are the directions of the linked rooms, this list is a list of all the valid directions.
if direction in
→ checks if the provided direction is in the list of valid directions
return self.linked_rooms[direction]
→ if the direction is valid, theRoom
object in that direction will be returnedself.linked_rooms[direction]
→ get theRoom
object for the provideddirection
else:
→ if the provided direction is not valid, then this block will be executed.print("You can't go that way")
→ lets the user know the direction is not validreturn self
→ since the user cannot move into another room, the method returns the currentRoom
object (self
)
Initialize starting room¶
Now go to the main.py file and make the highlighted changes below
1# main.py
2
3from room import Room
4
5# create rooms
6cavern = Room("Cavern")
7cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
8
9armoury = Room("Armoury")
10armoury.description = ("The walls are lined with racks that once held weapons and armour.")
11
12lab = Room("Laboratory")
13lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
14
15# link rooms
16cavern.link_rooms(armoury,"south")
17armoury.link_rooms(cavern,"north")
18armoury.link_rooms(lab,"east")
19lab.link_rooms(armoury,"west")
20
21'''
22# describe the rooms
23cavern.describe()
24armoury.describe()
25lab.describe()
26'''
27
28# initialise variables
29current_room = cavern
Let’s investigate the new code
the
'''
at lines 21 and 26 turns the room descriptions in a comment block, so the program will ignore themyou could simply delete these lines, but by commenting them out they are still available if you need them for debugging purposes.
# initialise variables
→ a code structure commentcurrent_room = cavern
creates a variable to keep track of the current room the player is in.
sets the initial
current_room
as thecavern
Room
object
Create main loop¶
Still working in the main.py file, we will now make the main loop.
Add the code highlighted below to have our first look at the main loop code
1# main.py
2
3from room import Room
4
5# create rooms
6cavern = Room("Cavern")
7cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
8
9armoury = Room("Armoury")
10armoury.description = ("The walls are lined with racks that once held weapons and armour.")
11
12lab = Room("Laboratory")
13lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
14
15# link rooms
16cavern.link_rooms(armoury,"south")
17armoury.link_rooms(cavern,"north")
18armoury.link_rooms(lab,"east")
19lab.link_rooms(armoury,"west")
20
21'''
22# describe the rooms
23cavern.describe()
24armoury.describe()
25lab.describe()
26'''
27
28# initialise variables
29current_room = cavern
30running = True
31
32# ----- MAIN LOOP -----
33while running:
34 current_room.describe()
35
36 command = input("> ").lower()
Finally we can run our code, but don’t forget PRIMM. Predict you think the program will do, then run the program.
Escaping an infinite loop
In Python, when you find yourself in an infinte loop, you can exit the loop my pressing ctrl
+ c
on Windows or control
+ C
on macOS.
In addition, if you are using Thonny, you can click the stop icon.
Let’s investigate the new code line-by-line.
running = True
→ used to keep the main loop running until the user exits.this is called a flag variable which will be changed to
False
when the user chooses to exit
# ----- MAIN LOOP -----
→ code structure comment. This is a major component, hence the capitalization.while running:
→ the start of our main loopas long as
running
remainsTrue
the main loop will repeat
current_room.describe()
→ calls thedescribe
method for thecurrent_room
initially this is the
cavern
command = input("> ").lower()
→ gets user inputinput("> ")
→ places"> "
on the screen as a prompt then accepts the user input.lower()
→ converts the user input to all lowercasecommand =
→ assigns the converted user input to thecommand
variable
Notice that no matter what the user enters, the same thing repeats. That’s because we have create the main loop and accepted the user’s input, but we haven’t responded to that input.
Responding to commands¶
In Event Driven Programming the entering of user’s commands is called an event. Now we have to create code that responds to those events. This kind of code is called an event handler.
Back in our main.py we’re going to create an event handler to deal with the entry of a direction ("north"
, "south"
, "east"
or "west"
). Add the highlighted code.
1# main.py
2
3from room import Room
4
5# create rooms
6cavern = Room("Cavern")
7cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
8
9armoury = Room("Armoury")
10armoury.description = ("The walls are lined with racks that once held weapons and armour.")
11
12lab = Room("Laboratory")
13lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
14
15# link rooms
16cavern.link_rooms(armoury,"south")
17armoury.link_rooms(cavern,"north")
18armoury.link_rooms(lab,"east")
19lab.link_rooms(armoury,"west")
20
21'''
22# describe the rooms
23cavern.describe()
24armoury.describe()
25lab.describe()
26'''
27
28# initialise variables
29running = True
30current_room = cavern
31
32# ----- MAIN LOOP -----
33while running:
34 current_room.describe()
35
36 command = input("> ").lower()
37
38 if command in ["north", "south", "east", "west"]:
39 current_room = current_room.move(command)
Predict you think the program will do, then run the program.
Let’s investigate that code
if command in ["north", "south", "east", "west"]:
→ execute code if the command is a direction["north", "south", "east", "west"]
→ a list of all the acceptable directionsif command in
→ checks ifcommand
is one of the acceptable directions
current_room = current_room.move(command)
→ gets the new roomcurrent_room.move(command)
→ calls themove
method passing the value of command (which is a direction)current_room =
assigns the returnedRoom
object to thecurrent_room
Testing¶
Testing branching code
Whenever you test branching code, it is important to ensure you methodically test all possible branches.
To do this:
create a table which lists every possible branch
for each branch, list the expected results
record the actual results
idenfiy any discrepancies
Now that we can move between all our rooms, we can test that our code is working correctly. Draw up a table to test each option. Below is an example of my table.
Current Room |
Command |
Expected Result |
Actual Result |
---|---|---|---|
cavern |
|
|
|
cavern |
|
armoury |
armoury |
cavern |
|
|
|
cavern |
|
|
|
armoury |
|
cavern |
cavern |
armoury |
|
|
|
armoury |
|
lab |
lab |
armoury |
|
|
|
lab |
|
|
|
lab |
|
|
|
lab |
|
|
|
lab |
|
armoury |
armoury |
Notice that I tested each of the four directions in each of the three rooms in my dungeon.
Exiting¶
Although the user can now move around our dungeon, they cannot exit the game. Now we need to make an event handler to deal with the user wanting to quit the game.
1# main.py
2
3from room import Room
4
5# create rooms
6cavern = Room("Cavern")
7cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
8
9armoury = Room("Armoury")
10armoury.description = ("The walls are lined with racks that once held weapons and armour.")
11
12lab = Room("Laboratory")
13lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
14
15# link rooms
16cavern.link_rooms(armoury,"south")
17armoury.link_rooms(cavern,"north")
18armoury.link_rooms(lab,"east")
19lab.link_rooms(armoury,"west")
20
21'''
22# describe the rooms
23cavern.describe()
24armoury.describe()
25lab.describe()
26'''
27
28# initialise variables
29running = True
30current_room = cavern
31
32# ----- MAIN LOOP -----
33while running:
34 current_room.describe()
35
36 command = input("> ").lower()
37
38 if command in ["north", "south", "east", "west"]:
39 current_room = current_room.move(command)
40 elif command == "quit":
41 running = False
Predict you think the program will do, then run the program.
Make sure you test the quit option
Let’s investigate that code
elif command == "quit":
→ if the command is not an acceptable direction, then check if it isquit
running = False
change our flag variable toFalse
this means that when the loops returns to the top,
where running
will beFalse
and the loop will exit.
Capture incorrect commands¶
So the code now works with our directions and allows us to quit, but what happens if the user enters anything else? Well, the loop continues and just redescribes the current_room
.
Really, we should be given the user feedback that they command is invalid. Let’s do this.
Change main.py to include the highlighted code below.
1# main.py
2
3from room import Room
4
5# create rooms
6cavern = Room("Cavern")
7cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
8
9armoury = Room("Armoury")
10armoury.description = ("The walls are lined with racks that once held weapons and armour.")
11
12lab = Room("Laboratory")
13lab.description = ("A strange odour hangs in a room filled with unknownable contraptions.")
14
15# link rooms
16cavern.link_rooms(armoury,"south")
17armoury.link_rooms(cavern,"north")
18armoury.link_rooms(lab,"east")
19lab.link_rooms(armoury,"west")
20
21'''
22# describe the rooms
23cavern.describe()
24armoury.describe()
25lab.describe()
26'''
27
28# initialise variables
29running = True
30current_room = cavern
31
32# ----- MAIN LOOP -----
33while running:
34 current_room.describe()
35
36 command = input("> ").lower()
37
38 if command in ["north", "south", "east", "west"]:
39 current_room = current_room.move(command)
40 elif command == "quit":
41 running = False
42 else:
43 print("I don't understand.")
Predict you think the program will do, then run the program.
Make sure you test our error capturing by entering some incorrect commands.
Let’s investigate the new code:
else:
→ a catch-all option for any input which is not a recognised command.print("I don't understand.")
→ lets the user know their command doesn’t make sense.