Stage 2 - Movement¶
Introduction¶
In Stage 1 you made three rooms, connected them, and got the program to describe each one. That was a good start, but it’s not much of a game yet. In Stage 2 you’ll write code that lets the player move between rooms, which means changing the game’s state (for example, which room you are in), and you’ll build the main loop.
State machines
A state machine is a way of thinking about how a program changes as things happen. At any moment, the program is in one state (like being in a certain room in your game). When an event happens, such as the player typing a command, the program follows a rule that decides what the next state should be.
For example, typing “east” might move you from the Armoury to the Lab. Each state has certain things you can do, and each action can move you to a new state. It’s like following a map where every choice leads to a different place, and the program always knows exactly where it is and what it should do next.
The main loop is a key part of event-driven programming. Your main.py file will set up the game and create all the objects it needs. Then it will enter the main loop, where the program waits for the player to type something and then reacts to that input.
Event-driven programming
Event-driven programming is when a program doesn’t just run straight from top to bottom, but instead waits for things to happen and reacts to them. These things are called events, like the user typing a command, clicking a button, or a sensor sending data.
The program sits in a loop, listening for these events, and when one occurs, it runs the code that matches that event. This makes programs more flexible because they only do something when there’s a reason to, just like you don’t answer someone until they speak to you first.
To achieve this we will need to complete the following steps:
Pseudocode
Create the
movemethodInitialize 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
Roomobject
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.
Code Explaination
def move(self, direction):→ defines themovefunction and takes one input: the direction the player wants to go.# returns the room linked in the given direction→ a comment explaining what the function does.if direction in self.linked_rooms.keys():→ checks whether the direction the player typed is actually one of the directions this room allows.self.linked_rooms.keys()→ gets all the possible directions you can go from this room.if direction in→ checks if the player’s direction is one of those options.
return self.linked_rooms[direction]→ returns the room in that direction if it’s valid.self.linked_rooms[direction]→ retrieves the room object linked to that direction from the dictionary.
else:→ runs if the direction isn’t allowed.print("You can't go that way")→ tells the player they tried an invalid direction.return self→ keeps the player in the same room because the move didn’t work.
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
Code Explaination
The
'''on lines 21 and 26 → turns the room descriptions into a big comment, so Python ignores that code.You could delete it, but leaving it commented out means you can bring it back later if you need it for debugging.
# initialise variables→ a comment to explain what the next lines of code are doing.current_room = cavernmakes a variable that tracks which room the player is currently in.
starts the player in the
cavernroom.
Create main loop¶
Still working in the main.py file, we will now make the main loop.
Add the highlighted code below so you can see the main loop for the first time.
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
If your Python program gets stuck in an infinite loop, you can stop it by pressing Ctrl + C on Windows or Control + C on a Mac.
If you’re using Thonny, you can also click the stop button to end the program.
Let’s investigate the new code line-by-line.
Code Explaination
running = True→ used to keep the main loop going until the player decides to quit.This is called a flag variable—it starts as
True, and when the player wants to exit, it gets changed toFalse.
# ----- MAIN LOOP -----→ a comment showing where the main loop begins.while running:→ starts the main loop.The loop keeps repeating as long as
runningisTrue.
current_room.describe()→ runs thedescribefunction for whatever room is stored in thecurrent_roomvariable.At the start, this is the
cavern.
command = input("> ").lower()→ reads what the player types.input("> ")→ shows"> "on the screen and waits for the player to type something..lower()→ turns the input into lowercase so the program can read it more easily.command =→ stores the final text in thecommandvariable.
Notice that no matter what the player types, the same thing keeps happening. That’s because we’ve built the main loop, which is waiting for events (the player’s input), but we haven’t written any code to react to those events yet.
In a state machine, the game should change state when something happens—like moving to a new room—but right now there are no rules telling the program how to change state when an event occurs. So the loop just repeats without doing anything new.
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.
Code Explaination
if command in ["north", "south", "east", "west"]:→ runs this block only if the player typed a direction.["north", "south", "east", "west"]→ the list of directions the game will accept.if command in→ checks whether what the player typed is in that list.
current_room = current_room.move(command)→ works out which room to go to next.current_room.move(command)→ calls themovefunction, using the player’s direction to find the next room.current_room =→ updatescurrent_roomso the game’s state now matches the new 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 |
|
“You can’t go that way” |
“You can’t go that way” |
cavern |
|
moved to armoury |
moved to armoury |
cavern |
|
“You can’t go that way” |
“You can’t go that way” |
cavern |
|
“You can’t go that way” |
“You can’t go that way” |
armoury |
|
moved to cavern |
moved to cavern |
armoury |
|
“You can’t go that way” |
“You can’t go that way” |
armoury |
|
moved to lab |
moved to lab |
armoury |
|
“You can’t go that way” |
“You can’t go that way” |
lab |
|
“You can’t go that way” |
“You can’t go that way” |
lab |
|
“You can’t go that way” |
“You can’t go that way” |
lab |
|
“You can’t go that way” |
“You can’t go that way” |
lab |
|
moved to armoury |
moved to 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
Code Explaination
elif command == "quit":→ if the command is not an acceptable direction, then check if it isquitrunning = Falsechange our flag variable toFalsethis means that when the loops returns to the top,
where runningwill beFalseand the loop will exit.
Capture incorrect commands¶
The code now understands the movement commands and the quit command, but what if the player types something completely different? The loop just keeps going and shows the same room again. That’s not very helpful.
We should tell the player when their command doesn’t make sense, so they know they need to try something else. Let’s fix that.
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:
Information
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.
Testing¶
Now we need to test those two additional features. Draw up a table to test each option. Below is an example of my table.
Command |
Expected Result |
Actual Result |
|---|---|---|
|
moved to armoury |
moved to armoury |
|
“I don’t understand.” |
“I don’t understand.” |
|
program exits |
program exits |
Stage 2 task¶
There is not much to do for our Make phase of this stage, but you do need to test that you can navigate to and from your stage 1 task additional room.
Take the table you used to test navigating the rooms and expand it to also test navigating to your stage 1 task room.