CSE 231 Spring 2011 Programming Project 10 This assignment is worth 80 points (8.0% of the course grade) and must be completed and turned in before 11:59 on Monday April 25, 2011. Assignment Overview This assignment will give you more experience with writing your own classes, as well as exception handling. You will write four classes that implement a simple text adventure game. Background Text adventure games have a long, storied history in computer science and were some of the first computer games. They interface with the player using only text, describing locations and objects and allowing the user to type a set of commands to interact with the game. The earliest text adventure games were often written by students or programmers in their spare time, and generally featured fantasy adventures full of puzzles and clever responses from the computer. Project Description / Specification You, on the other hand, will write a very simple text adventure game for credit. As designed, it contains no puzzles, and your program’s responses are not required to be clever. Your program must consist of four classes: TextAdventure, Room, Character, and an Exception subclass. You must also have a main() function designed to run the game. Character is a fairly simple class. A character knows the id of the room it is currently in, and has a list of its inventory. It contains these methods: __init__(self, inventory_list, room_id): This method initializes the character with a room id and an inventory list. By default the inventory list is empty, and the room id is -1. You also need a method to return the character’s inventory as a string. You may choose to implement this either as the special __str__() method or as a normal method with some other name, but some such method is required. Don’t just print the inventory as a Python list. Room is the class that represents a single room. Associated with each room is a unique id and an adjective which describes it. It may also have one or more exits – links to other rooms – and one or more items. It contains these methods: __init__(self, text_desc): This method initializes the room from a text string which describes it. The string contains, in order, the integer id of the room, the adjective that describes the room, and a mix of strings which may describe exits or items in the room. Exit strings are always of the form <capital letter><number>, where the capital letter gives the direction of the exit and the number gives the id of the adjoining room. For example, the string ‘S13’ indicates that there is an exit to the south and that it leads to a room with id 13. (Any string that is not an exit is an item; exits always start with N, S, E, or W, and items are never capitalized.) See the provided .txt files for example strings, and be sure to check the Notes and Hints section below for some advice on how to store all this information. The default value of text_desc is “0 bare”. You also need a method to return a description of the room as a string. You may choose to implement this either as the special __str__() method or as a normal method with some other name, but some such method is required. The description should include the room’s adjective, tell the user about the direction of any exits, and list any items in the room. You are not required to exactly match the grammatically-correct output in the examples; output like “You see 1 doors to the west.” is okay. However, you must print sentences rather than just printing Python lists. TextAdventure is the class that governs the game itself. It is responsible for interactions between the user, the character, and the rooms. It has these methods: __init__(self, filename): This method creates a default Character and reads a file to create a set of Rooms. The character starts the game with nothing in the inventory and in the room with the lowest id number. prompt(self): This method prompts the user for a command, processes the command (usually using one of the other TextAdventure methods below), and either carries it out (if it’s valid) or prints an error message. No re-prompting for invalid commands is necessary. Here are the valid commands: L or look - print a description of the room you are in I or inventory - print your inventory P blah or pickup blah - pick up the item called blah D blah or drop blah - drop the item called blah N or north - go north S or south - go south E or east - go east W or west - go west Q or quit - quit H or help - print this list of commands Whenever the user moves (or tries to move), you should print the description of the current room after the move is complete. User input is notoriously evil. However, your program must be immune to such affronts. Moreover, you must make use of Python’s Exception feature to make it so. When the user attempts an invalid move (picking up something that doesn't exist, ...), your helper functions move, pickup, and drop must raise an exception, and pass a message to be displayed that describes the error. The prompt function will need to catch these exceptions and display the error message. You may also find it useful to guard your command parsing code. Sometimes the user is forgetful and may not include all the required arguments. look(self): This method prints the description of the current room, using the method you wrote for Room. inventory(self): This method prints the character’s inventory, using the method you wrote for Character. pickup(self, item): This method coordinates the character with its current room to remove the item from the room and add it to the character’s inventory. If the item is not present in the room, it must not be added to the character’s inventory, and an exception must be raised. Either way, a message must print. drop(self, item): This method coordinates the character with its current room to remove the item from the character’s inventory and add it to the room. If the item is not present in the character’s inventory, it must not be added to the room, and an exception must be raised. Either way, a message must print. move(self, direction): This method moves the character in the specified direction if the current room has an exit in that direction. If not, the move fails, and an exception must be raised. Either way, a message must print. help(self): This method prints a list of the valid commands. Exception Subclass The fourth class must be a subclass of the built-in Exception class. This new exception class indicates an invalid game action. Creating your own exception class allows you to differentiate it from other exceptions that could occur so you can handle it appropriately. (It also lets you specify information that needs to be tracked with a particular error, e.g., a string that describes what illegal action the user attempted to perform or a response to it. These Exception classes look and feel like your typical class. They can contain a constructor, a string method, etc. They should be derived from the Exception class, instead of the Object class. Additionally, to create one you issue a raise command. For example, to create a new exception class, you would define it as follows. class CustomError(Exception): <class code> When you wish to raise the error, you issue either of the following: raise CustomError raise CustomError, arg The latter form is used to pass a single argument to the except block that handles the exception. Remember, program execution will immediately jump to the closest enclosing try-except block that defined an except for that error. The program will not return to the line that raised the exception after handling has finished. NOTE: You may include more methods than those described above in any class, but the methods described must be present. main(): This simple function runs the game. It prompts the user for a file name, and then uses that file to create a TextAdventure. It then prints a short introductory message (including a list of commands) and uses the TextAdventure’s prompt() method until the user quits. You do not need to do error checking on the format of the input file, but if the user enters a bad file name, you should recover and prompt the user for a good one (a file that can be opened for reading). There should be nothing in your code outside of your classes and functions except a single call to main(). Specifically, do not use global variables. Each class, method, and function (including main()) must have a docstring. See the course coding standards for more about docstrings. Deliverables Turn in proj10.py using the handin program. Save a copy to your H drive. Downloadables: Sample .txt files: oneroom.txt, oneroomstuff.txt, tworooms.txt, textadventure.txt Output examples: errors.txt, moves.txt, items.txt Notes and Hints: 1. For big projects like this, frequent testing is an excellent idea. For example, you can write and test the Character class without any of the other classes. You can also write and test the Room class alone. Even the TextAdventure class can be broken up into pieces; write and test the various command methods separately, then incorporate them into the prompt() method. 2. We have provided several different .txt documents describing various adventures. You should test your code with each, from the most simple to the most complex. Don’t hardcode a particular file name – your TA may also test with multiple different adventures. 3. You may find it helpful to write a few extra methods for Room and Character dealing with the pickup and drop commands. For each class, add_item(self, item) and remove_item(self, item) methods can be written very simply. For example, add_item(self, item) for the Character class can simply add the item, not checking to see if it actually exists. The relevant TextAdventure method (e.g., pickup()) handles the coordination, only calling add_item() for the Character if it can also call remove_item() for the current Room. 4. Room ids are designed to allow the TextAdventure class to keep track of rooms – so use a dictionary there with room ids as the keys and Rooms as the values. Room ids never overlap, so there’s no need to worry about that. 5. Rooms don’t know what other rooms they link to, only the ids of those rooms. So it is the responsibility of the TextAdventure class to do all id-to-Room translations, and hence to update the Character’s current room id. 6. The TextAdventure class doesn’t really need to do much parsing of the file it reads – just create a new Room from each non-blank line in the file by passing that line in to Room(). 7. When creating a Room from a text string, the first word is the id and the second is the adjective. After that, exits and items may come in any order. Design a loop to go through these remaining strings, and remember exits start with N, S, E, or W, and items are never capitalized. When you see an exit, you’ll need to associate the correct direction with the id number of the linked room. A dictionary with directions as keys and ids as values is a good idea here. Items are simple strings, and it’s probably best to store them in a list. 8. For the purpose of TextAdventure’s move() method, you may find it helpful to write a function in Room which, given a direction, returns either the id of the attached room in that direction or False, if there is no exit in that direction. 9. General advice: Start early. Save often, and submit to handin whenever you have a working project, even if it’s not finished. Read over the Project Description carefully and double-check you have met all the requirements. Don’t forget your docstrings. Following is a sample interaction with our solution: >>> ============================ RESTART ====================== >>> Enter a text adventure filename: Cannot open file: Enter a text adventure filename: textadventure.txt Welcome to this Generic Text Adventure! You may explore and collect any items you find. There's no winning, just exploration. Valid commands are: L or look - print a description of the room you are in I or inventory - print your inventory P blah or pickup blah - pick up the item called blah D blah or drop blah - drop the item called blah N or north - go north S or south - go south E or east - go east W or west - go west Q or quit - quit H or help - print this list of commands Capitalization doesn't matter. You see a simple room. It has one exit, to the north. Enter a command (H for help): l You see a simple room. It has one exit, to the north. Enter a command (H for help): i Alas, your inventory is empty. Enter a command (H for help): n You move into the room to the north. You see a leafy room. It contains a flower. the west, east, and south. Enter a command (H for help): p flower It has exits to You pick up the flower. Enter a command (H for help): i Your inventory contains these items: - flower Enter a command (H for help): h Valid commands are: L or look - print a description of the room you are in I or inventory - print your inventory P blah or pickup blah - pick up the item called blah D N S E W Q H blah or drop blah - drop the item called blah or north - go north or south - go south or east - go east or west - go west or quit - quit or help - print this list of commands Capitalization doesn't matter. Enter a command (H for help): l You see a leafy room. It has exits to the west, east, and south. Enter a command (H for help): p flower You attempt to pick up a flower, but cannot. no such item to pick up. Enter a command (H for help): l Perhaps there is You see a leafy room. It has exits to the west, east, and south. Enter a command (H for help): w You move into the room to the west. You see a dark room. Scattered about are a diamond and a lantern. It has exits to the east and north. Enter a command (H for help): d lamp You attempt to drop the lamp, but discover you don't have one to drop. Oh well. Enter a command (H for help): d flower You drop the flower. Enter a command (H for help): p diamond You pick up the diamond. Enter a command (H for help): i Your inventory contains these items: - diamond Enter a command (H for help): p flower You pick up the flower. Enter a command (H for help): i Your inventory contains these items: - diamond - flower Enter a command (H for help): l You see a dark room. It contains a lantern. the east and north. Enter a command (H for help): e It has exits to You move into the room to the east. You see a leafy room. It has exits to the west, east, and south. Enter a command (H for help): s You move into the room to the south. You see a simple room. It has one exit, to the north. Enter a command (H for help): i Your inventory contains these items: - diamond - flower Enter a command (H for help): l You see a simple room. It has one exit, to the north. Enter a command (H for help): s There is no room to the south. Enter a command (H for help): w There is no room to the west. Enter a command (H for help): h Valid commands are: L or look - print a description of the room you are in I or inventory - print your inventory P blah or pickup blah - pick up the item called blah D blah or drop blah - drop the item called blah N or north - go north S or south - go south E or east - go east W or west - go west Q or quit - quit H or help - print this list of commands Capitalization doesn't matter. Enter a command (H for help): d hammer You attempt to drop the hammer, but discover you don't have one to drop. Oh well. Enter a command (H for help): q Goodbye >>>