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:

  1. Create the move method

  2. Initialize the starting room

  3. 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.

lesson 2 class diagram

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 the move method which accepts one argument: direction

  • # returns the room linked in the given direction → comment to explain the method

  • if direction in self.linked_rooms.keys(): → checks if passed direction is a valid option

    • self.linked_rooms.keys()

      • gets all the keys from the linked_rooms attribute for this Room object

      • since 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, the Room object in that direction will be returned

  • self.linked_rooms[direction] → get the Room object for the provided direction

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

  • return self → since the user cannot move into another room, the method returns the current Room 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 them

    • you 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 comment

  • current_room = cavern

    • creates a variable to keep track of the current room the player is in.

    • sets the initial current_room as the cavern 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 loop

    • as long as running remains True the main loop will repeat

  • current_room.describe() → calls the describe method for the current_room

    • initially this is the cavern

  • command = input("> ").lower() → gets user input

    • input("> ") → places "> " on the screen as a prompt then accepts the user input

    • .lower() → converts the user input to all lowercase

    • command = → assigns the converted user input to the command 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 directions

    • if command in → checks if command is one of the acceptable directions

  • current_room = current_room.move(command) → gets the new room

    • current_room.move(command) → calls the move method passing the value of command (which is a direction)

    • current_room = assigns the returned Room object to the current_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

north

You can't go that way

You can't go that way

cavern

south

armoury

armoury

cavern

east

You can't go that way

You can't go that way

cavern

west

You can't go that way

You can't go that way

armoury

north

cavern

cavern

armoury

south

You can't go that way

You can't go that way

armoury

east

lab

lab

armoury

west

You can't go that way

You can't go that way

lab

north

You can't go that way

You can't go that way

lab

south

You can't go that way

You can't go that way

lab

east

You can't go that way

You can't go that way

lab

west

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 is quit

  • running = False change our flag variable to False

    • this means that when the loops returns to the top, where running will be False 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.