# Stage 8 - Useability ```{topic} In this lesson you will: - Improve the code useabilty ``` ## Introduction Our dungeon is complete. We have a fully functioning game, but before we sign-off we need to improve the useability of our program and tidy up our code. To do this we will: - Create a **help** command - Improve the user experience and user interface - Add a farewell message - Add some in-code comments - Remove unused code - Standardise our white-space ## Help command We know all the commands we can use because we wrote the code. Someone else might not know what they can say. In addition, if they don't enter one of the commands, the program only says `I don't understand.`, which is far from helpful. To make life easier for new players, we should create a **help** command which lists all the commands. But how will the player know about the **help** command, without using the **help** command? Simple, we change the catch-all event handler (under the `else:`) to inform the user about the help command. To implement this, add the highlighted code below to **main.py**: ```{code-block} python :linenos: :emphasize-lines: 116-123, 127 # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): 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 copmmands.") ``` By now this code should be familiar and makes sense to you. ## Improving the UI and UX Although our program is visually very simple, the user stills interacts with it, which means we need to consider the UI and the UX. ```{admonition} UI and UX UI stands for User Interface, which is basically what you see on your screen when you use an app or a website. This includes things like buttons, menus, icons, and colors. UI design focuses on making things look good and easy to use. UX stands for User Experience, which is how you feel when you use an app or a website. It's about how easy it is to use, how it makes you feel, and whether it helps you achieve what you're trying to do. UX design focuses on making things easy and enjoyable to use. ``` We have already addressed some UI and UX problems by adding the **help** command. Play the game and see if you can identify anything else. Did you notice that after entering a command, the game responds and then quickly describes the room again. It's really easy to loose the command response in this quick action. Let's change that by writing the response and then asking the user to press a key to proceed. I implement this, add the highlighted code below. ```{code-block} python :linenos: :emphasize-lines: 128 # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): 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 copmmands.") input("\nPress key to continue") ``` Save the file, **predict** and then **run** the code. How does that work? Let's **investigate**: - `input("\nPress key to continue")` → this is a hack. That is we are using `input` in an unconventional way to achieve our desired outcome. - the normal operation of `input` is to wait for the user response → addresses the pausing of the game - normally the user enters their response by pressing the Enter key → stops the pausing - the value the user enters is **not** assigned to a variable, so it just disappears ## Farewell message When the game ends, it just stops. Whether the player won or lost it just exits the the Shell prompt. To make things a little more polite, we should add an farewell message. To include a farewell message add the highlighted code below ```{code-block} python :linenos: :emphasize-lines: 129 # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): 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 copmmands.") input("\nPress key to continue") print("Thank you for playing Deepest Dungeon") ``` ## In-code comments We already have some in-code comment that helps structure our code, but we have missed much of the main loop. We should add some comment to the main loop to make our code more readable and therefore maintainable. Let's first start with **main.py** ```{code-block} python :linenos: :emphasize-lines: 70, 73, 79, 85, 106, 114, 122, 131, 134 # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): 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 copmmands.") input("\nPress key to continue") print("Thank you for playing Deepest Dungeon") ``` When you create classes your should really have a comment that explains what each method does. If you look at the classes in our **room.py**, **item.py** and **character.py** files you will see that we have already done this. ## Remove unused code Remember the code in **main.py** that we commented out. Well we no longer need it. Go ahead and **delete** the code highlighted below. ```{code-block} python :linenos: :emphasize-lines: 52-57 # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): 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 copmmands.") input("\nPress key to continue") print("Thank you for playing Deepest Dungeon") ``` ## Final code White space is the blank lines between our code. You can use this to help structure your code. Finalise your code by adjusting it so to look the same as the following code: ### main.py ```{code-block} python :linenos: # 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 unknownable 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 bead in woven through his beard." nigel.conversation = "Well youngan, 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): current_room.character = None if Enemy.num_of_enemy == 0: print("You have slain the enemy. 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' for list of commands") input("\nPress to continue") print("Thank you for playing Darkest Dungeon") ``` ### room.py ```{code-block} python :linenos: # 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, direction): # links the provided room, in the provided direction self.linked_rooms[direction.lower()] = room 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 ```{code-block} python :linenos: # 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 converstation 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 ```{code-block} python :linenos: # 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 tutorials are now over. It is time to make this dungeon yours by adding new features. The Extensions Ideas page has some suggestions. The current code does have a couple of logical errors, but you will have to test it to find out what they are and then try and solve them. Don't forget to use your debugger to help you with this. Good luck. May your adventure be long and fruitful.