Stage 8: Usability
In this lesson we will learn
- what UI and UX mean and why they matter to the people who use our program
- how to add features that help the user, like a help command and clearer messages
- how comments and neat code make a program easier to read and fix
- how to tidy a program by removing code we don't need
Terminology
- UI – short for user interface, what we see on the screen when we use a program, such as text, buttons, menus and colours.
- UX – short for user experience, what it feels like to use a program, such as easy, confusing, fun or annoying.
- code maintainability – how easy our code is to understand, fix and update later, helped by neat organisation and clear comments.
- whitespace – the blank lines and spaces in our code, which we use to break it into clear sections.
Introduction
Our dungeon works, but before we finish we need to make it easier to use and clean up the code.
Pseudocode
Stage 8 pseudocode- Add a help command
- Make the program easier to read and use
- Add a goodbye message at the end
- Write more comments in the code
- Delete code we don't need
- Make our spacing neat and consistent
Help command
We know the commands because we wrote the code, but a new player won't know what they're allowed to type. Right now, if they type something wrong, the program just says "I don't understand.", which isn't very helpful.
To fix this, let's add a help command that lists all the commands. Players won't know help exists unless we tell them, so we'll also change the final else to remind them to type help when they get something wrong.
Add the highlighted code to main.py.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
elif command == "quit":
running = False
else:
print("Enter 'help' to list the commands.")
|
PRIMM
- Predict what you think will happen. Be specific.
- Run the program.
- Time to investigate the code. What does each line do?
Code explanation
- line 117 → checks whether the player's command was
help…
- lines 118–124 → …and prints how to move and the list of commands.
- line 128 → tells the player to type
help when their command isn't recognised.
Improve the UI and UX
Even though our program looks simple, people still have to use it, so we need to think about its UI and UX: how it looks and how easy it is to use.
UI and UX
UI means user interface. It's what we actually see on the screen when we use an app or website, such as buttons, menus, icons and colours. UI is about making everything look clear and easy to use.
UX means user experience. It's about what it feels like to use the app: easy, confusing, fun or annoying. UX is about making sure the user has a smooth and enjoyable time using the program.
We've already fixed some UI and UX issues by adding the help command. Now play the game and see if you can spot anything else that feels confusing.
You might notice that after we type a command, the game shows the response and then instantly prints the room description again. This makes it easy to miss what the game just told us. To fix that, we'll show the response and then make the player press Enter before the game continues.
Add the highlighted code below.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
elif command == "quit":
running = False
else:
print("Enter 'help' to list the commands.")
input("\nPress <Enter> key to continue")
|
PRIMM
- Predict what you think will happen. Be specific.
- Run the program.
- Time to investigate the code. What does each line do?
Code explanation
- line 129 → uses
input as a little trick to pause the game. input waits until the player presses Enter, and because we don't store what they type, it's ignored. It's indented one level, so it runs at the end of every loop, whatever the command was.
Farewell message
When the game ends, it just stops straight away, with no message whether we win or lose. To make the ending feel nicer, let's add a goodbye message.
Add the highlighted code below.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
elif command == "quit":
running = False
else:
print("Enter 'help' to list the commands.")
input("\nPress <Enter> key to continue")
print("Thank you for playing Deepest Dungeon")
|
Code explanation
- line 130 → prints a goodbye message. It isn't indented, so it's outside the main loop and only runs once the loop has stopped.
We already have some comments that explain what parts of the code do, but most of the main loop doesn't have any. Adding comments there makes the code easier to read and easier to fix later.
Code maintainability
Code maintainability means making our code easy to understand, fix and update later. If our code is neat, well organised and has clear comments, we or someone else can quickly work out how it works. This makes it easier to find bugs, add new features and change things without breaking the program.
Let's start with main.py. Add the highlighted comments.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
# move
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
# talk
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
# hug
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
# fight
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
# take
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
# backpack
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
# help
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
# quit
elif command == "quit":
running = False
# incorrect command
else:
print("Enter 'help' to list the commands.")
input("\nPress <Enter> key to continue")
print("Thank you for playing Deepest Dungeon")
|
When we make classes, it's a good idea to add a comment explaining what each method does. If we check room.py, item.py and character.py, we'll see we've already done this.
Remove unused code
Remember the code in main.py that we turned into a string? We don't need it any more. Delete the highlighted lines below.
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
'''
# describe the rooms
cavern.describe()
armoury.describe()
lab.describe()
'''
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
# move
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
# talk
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
# hug
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
# fight
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
# take
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
# backpack
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
# help
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
# quit
elif command == "quit":
running = False
# incorrect command
else:
print("Enter 'help' to list the commands.")
input("\nPress <Enter> key to continue")
print("Thank you for playing Deepest Dungeon")
|
Final code
Whitespace is the blank lines and spaces in our code. We can use blank lines to break our code into clear sections, which makes it easier to read.
Tidy your code so it looks the same as the code below.
main.py
| # main.py
from room import Room
from character import Enemy, Friend
from item import Item
# create rooms
cavern = Room("Cavern")
cavern.description = ("A room so big that the light of your torch doesn’t reach the walls.")
armoury = Room("Armoury")
armoury.description = ("The walls are lined with racks that once held weapons and armour.")
lab = Room("Laboratory")
lab.description = ("A strange odour hangs in a room filled with unknowable contraptions.")
# link rooms
cavern.link_rooms(armoury,"south")
armoury.link_rooms(cavern,"north")
armoury.link_rooms(lab,"east")
lab.link_rooms(armoury,"west")
# create characters
ugine = Enemy("Ugine")
ugine.description = "a huge troll with rotting teeth."
ugine.weakness = "cheese"
nigel = Friend("Nigel")
nigel.description = "a burly dwarf with golden beads woven through his beard."
nigel.conversation = "Well youngun, what are you doing here?"
# add characters to rooms
armoury.character = ugine
lab.character = nigel
# create items
cheese = Item("Cheese")
cheese.description = "super smelly"
chair = Item("Chair")
chair.description = "designed to be sat on"
elmo = Item("Elmo")
elmo.description = "wanting to be tickled"
# add items to rooms
cavern.item = chair
armoury.item = elmo
lab.item = cheese
# initialise variables
running = True
current_room = cavern
backpack = []
# ----- MAIN LOOP -----
while running:
current_room.describe()
command = input("> ").lower()
# move
if command in ["north", "south", "east", "west"]:
current_room = current_room.move(command)
print(f"You travel {command}")
# talk
elif command == "talk":
if current_room.character is not None:
current_room.character.talk()
else:
print("There is no one here to talk to")
# hug
elif command == "hug":
if current_room.character is not None:
current_room.character.hug()
else:
print("There is no one here to hug")
# fight
elif command== "fight":
if current_room.character is not None:
weapon = input("What will you fight with? > ").lower()
available_weapons = []
for item in backpack:
available_weapons.append(item.name)
if weapon in available_weapons:
if current_room.character.fight(weapon):
if isinstance(current_room.character, Enemy):
current_room.character = None
if Enemy.get_num_of_enemy() == 0:
print("You have slain all the enemies. You are victorious!")
running = False
else:
running = False
else:
print(f"You don't have {weapon}")
print(f"{current_room.character.name} strikes you down.")
running = False
else:
print("There is no one here to fight")
# take
elif command == "take":
if current_room.item is not None:
backpack.append(current_room.item)
print(f"You put {current_room.item.name} into your backpack")
current_room.item = None
else:
print("There is nothing here to take")
# backpack
elif command == "backpack":
if backpack == []:
print("It is empty")
else:
print("You have:")
for item in backpack:
print(f"- {item.name.capitalize()}")
# help
elif command == "help":
print("Type which direction you wish to move,")
print("or use one of these commands:")
print("- Talk")
print("- Fight")
print("- Hug")
print("- Take")
print("- Backpack")
# quit
elif command == "quit":
running = False
# incorrect command
else:
print("Enter 'help' to list the commands.")
input("\nPress <Enter> key to continue")
print("Thank you for playing Deepest Dungeon")
|
room.py
| # room.py
class Room():
def __init__(self,room_name):
# initialises the room object
self.name = room_name.lower()
self.description = None
self.linked_rooms = {}
self.character = None
self.item = None
def describe(self):
# sends a description of the room to the terminal
print(f"\nYou are in the {self.name}")
print(self.description)
if self.character is not None:
self.character.describe()
if self.item is not None:
self.item.describe()
for direction in self.linked_rooms.keys():
print(f"To the {direction} is the {self.linked_rooms[direction].name}")
def link_rooms(self, room_to_link, direction):
# links the provided room, in the provided direction
self.linked_rooms[direction.lower()] = room_to_link
def move(self, direction):
# returns the room linked in the given direction
if direction in self.linked_rooms.keys():
return self.linked_rooms[direction]
else:
print("You can't go that way")
return self
|
character.py
| # character.py
class Character():
def __init__(self, name):
# initialises the character object
self.name = name
self.description = None
self.conversation = None
def describe(self):
# sends a description of the character to the terminal
print(f"{self.name} is here, {self.description}")
def talk(self):
# send conversation to the terminal
if self.conversation is not None:
print(f"{self.name}: {self.conversation}")
else:
print(f"{self.name} doesn't want to talk to you")
def hug(self):
# the character responds to a hug
print(f"{self.name} doesn't want to hug you")
def fight(self,item):
# the character response to a threat
print(f"{self.name} doesn't want to fight you")
return True
class Friend(Character):
def __init__(self, name):
# initialise the Friend object by calling the character initialise
super().__init__(name)
def hug(self):
# the friend responds to a hug
print(f"{self.name} hugs you back.")
class Enemy(Character):
num_of_enemy = 0
def __init__(self,name):
# initialise the Enemy object by calling the character initialise
super().__init__(name)
self.weakness = None
Enemy.num_of_enemy += 1
def fight(self, item):
# fights enemy with provided item and returns if player survives
if item == self.weakness:
print(f"You strike {self.name} down with {item}.")
Enemy.num_of_enemy -= 1
return True
else:
print(f"{self.name} crushes you. Puny adventurer")
return False
def get_num_of_enemy():
return Enemy.num_of_enemy
|
item.py
| # item.py
class Item():
def __init__(self,name):
# initialise the Item object
self.name = name.lower()
self.description = None
def describe(self):
# prints description of item to the terminal
print(f"You see {self.name} in the room. It is {self.description}.")
|
Final make
The stages are finished. Now it's our turn to make the dungeon our own by adding new features. The Extension Ideas page has some inspiration, the Extensions walk through two bigger changes, and Developing a New Program shows how to plan and build an object-oriented program of our own.
There's at least one logic mistake hidden in the final code. We'll need to test the game to find it, and work out how to fix it. Use the debugger to help.
Good luck on your adventure.