CMSC 132 -- Project 2
Heroes and Vampires
Project Due: Monday, 2/21/05 at 11:00 PM
UML Diagram Due: in your Lab Session Monday, 2/21/05
This will be the only "open" project this semester. An "open" project is one where you are free to discuss with your classmates details about how you are implementing specific parts of the program. However, you must adhere to the following guidelines:
You are required to write your own code without copying someone else's work
You must document the names of students with whom you have collaborated
The class wiki is still "closed" for this project!
The primary goal of this project is for you to practice designing a fairly complex project using the principles of Object Oriented programming that you've been learning. You will decide what classes and interfaces to write and how they will relate to one another. As you read this project description, begin thinking about ways to organize your program that will eliminate code duplication and would make it easier for someone to understand or possibly modify your code later. Also, you should plan that the classes you are writing in this project may be used in a completely different project one day.
You will write a program that will simulate an adventure. A hero (either a Wizard or a Warrior) will enter a dungeon. As the hero travels from one room to another he will do battle with numerous vampires. The program terminates when the hero is killed.
To give you an idea of what the finished product will look like, here are the outputs from a few runs of the program that you will write. (Actually, these are the outputs from the five public tests that you will be given.)
Your output will be sent to the standard output stream using System.out. In order for us to automatically grade your project, your output must match ours exactly. It is okay if you capitalize where we haven't (and vice versa), and it is also okay if you have extra or fewer spaces between words (or extra or fewer blank lines). However: you must spell words exactly as we have, you must punctuate exactly as we have, and you may not insert spaces where we have none. Check your output carefully! The public test cases that we have provided should help you to verify that your output is formatted correctly.
Please do not correct grammatical errors generated by the output specifications we have provided. For example, the first room will always contain "1 vampires" and that first vampire always has "1 hit points". Please do not report us to the English Department.
Any dungeon historian will tell you that it makes perfect sense to have an "enchanted enchanted sword" or a "cursed enchanted cursed cursed potion", so please don't be concerned if such descriptions occur in your output sometimes!
Here we give a brief description of the project components. Details about how to implement behaviors will come later.
Items are things that can be carried. Every item has a description (a String) and a monetary value (an int).
This project involves various life forms. Every life form has a certain number of hit points (an int). As life forms are damaged, their hit point count decreases -- if it goes to 0 (or negative) then the life form has died. Every life form carries exactly one item with him at all times.
All life forms are able to:
attack a life form -- how the attack takes place depends on the kind of life form who is attacking. (For example, a wizard will attack differently from a vampire.)
taunt anyone who is capable of physical fighting (see below for a description of "physical fighting") -- all life forms taunt in exactly the same way, so you should be implementing this method just once.
confuse anyone who is capable of spell casting (see below for a description of "spell casting") -- all life forms confuse in exactly the same way, so you should be implementing this method just once.
Some life forms are capable of spell casting. How these capabilities are implemented depends on the kind of life form who is casting the spells. Life forms who cast spells are able to:
enchant an item
frighten a life form
Some life forms are capable of physical fighting. How these capabilities are implemented depends on the kind of life form who is physically fighting. Life forms who physically fight are able to:
destroy an item
smite a life form
There are two kinds of life forms in this project: heroes (the good guys) and vampires (the bad guys).
Vampires are life forms that are capable of both physical fighting and spell casting. Scary!
Every Hero has a name (a String). There are two kinds of heroes: warriors and wizards. Warriors are capable of physical fighting but not spell casting. Wizards are capable of spell casting but not physical fighting.
At this point you should start giving serious thought to how the classes and/or interfaces in your project will be organized. Try to sketch out a preliminary UML class diagram.
For this project, you are expected to write many different classes and/or interfaces of your choosing, but we are going to insist that you write one particular class: Dungeon. The Dungeon class must have a main method. This is the method that the user will call to "run" the program.
The user will pass exactly 5 arguments to main. These arguments describe the hero who will be entering the dungeon. You will see that the outcome of the adventure is completely determined by the arguments that are provided. (Nothing is random.) A typical set of arguments might look like this:
Wizard Gandalf 80 Staff 17
The first argument will always be either "Wizard" or "Warrior". This describes which type of hero will be entering the dungeon.
The second argument is the name of the hero.
The third argument is the number of hit points that the hero starts with.
The fourth argument is a description of the item that the hero is carrying
The last argument is the monetary value of the item that the hero is carrying
When the program starts, a message is displayed that says "<heroName> enters the dungeon".
The hero will go from one room to the next fighting vampires until he has been killed. (See the example run called output5.txt for the precise formatting of output as the hero moves from one room to another.) Each room contains more vampires than the last, and each vampire is harder to kill than the last. The first room contains 1 vampire, the second room contains 2 vampires, then 3, etc. The first vampire in the adventure will have 1 hit point, the second vampire in the adventure will have 2 hit points, then 3, etc. Since all life forms carry an item with them at all times, we must specify what items the vampires will carry when they are created. The first vampire will carry a "sword", the second will carry a "ruby", the third will carry a "potion", the fourth will carry a "helmet", the fifth will carry a "key". After that they will continue to cycle through these same five items. (So the sixth vampire encountered will carry a "sword".) The monetary value of the vampires' items will continue to increase. The value of the first vampire's item will be 5, the value of the second vampire's item will be 10, then 15, etc.
The hero will fight one vampire at a time! [Hint: Do not bother to create all of the vampires that are in a room at once. Just create one vampire and then when he is killed create the next one, etc.]
When a battle takes place between a vampire and the hero, the combatants will take turns attacking each other (as described below). The vampire always goes first. The battle continues until one of the combatants is dead. If the hero dies, the message "<heroName> has died" is displayed and the program terminates immediately. [Note: Do not use System.exit to terminate your program! Just use a return statement to terminate the Dungeon.main() method.] If the vampire dies, the message "the vampire has died" is displayed. At this point the hero will consider replacing the item he is carrying with the one that the dead vampire is carrying. If the value of the dead vampire's item is larger than the value of the hero's current item, then the hero will replace his current item with the dead vampire's item. In that case the message "<heroName> takes the <itemDescription> (value <itemValue>)" is displayed.
If all of the vampires in the room are slain, then the hero moves on to the next room. (See the example run called output5.txt for details on the output and correct formatting when moving on to the next room.)
According to legend, when two life forms engage in combat, their actions are dictated by what the sages refer to as the "Magical Aura Grappling Index Coefficient", or MAGIC. MAGIC is an integer from 0 to 16 (inclusive) whose value depends on the precise state of the two combatants. If you can compute the value of this mystical number just before an attack takes place, you will be able to predict exactly what will transpire during the attack. (Of course the value must be re-computed for the next attack.) According to the ancient tomes:
MAGIC = [ (attacker hit points) + (defender hit points) + (value of item attacker is holding) + (value of item defender is holding) ] % 17
Note: the percent sign (%) in the formula above is the modulus operator.
taunting an enemy who is capable of physical fighting -- when a life form taunts a physical fighter, the physical fighter will smite the taunting life form
confusing an enemy who is capable of spell casting -- when a life form confuses a spell caster, the spell caster will enchant the item that the life form is holding.
attack -- How a warrior attacks his enemy depends on the situation. If the item the enemy is carrying has a value of 0, the warrior will smite him. Otherwise, the warrior's action depends on the value of MAGIC. If MAGIC = 0, the warrior will destroy his enemy's item. If MAGIC = 1 check if the enemy is capable of spell casting*. [Hint: You may find it useful to use the java keyword instanceof.] In the case where MAGIC = 1 and the enemy is capable of spell casting, the message "<heroName> confuses his foe, and in return " will be displayed and the warrior will confuse the enemy. [Hint: Use System.out.print to display the message instead of System.out.println. That way the enemy's reaction will be printed on the same line.] If MAGIC = 1 and the enemy is not capable of spell casting, the Warrior will take no action. If MAGIC > 1 then the warrior will smite his enemy.
* You may wonder why you are being instructed to check if the enemy is capable of spell casting when in this project warriors will only be fighting vampires, whom we know are capable of spell casting. The answer is that we may want to use your warriors in another project one day where they fight all sorts of other creatures. (See the "Important Note", below.)
destroy -- The message "<heroName> smashes the <itemDescription> into rubble" is displayed. The description of the item is changed to "rubble", and the value of the item is set to 0.
smite -- When a warrior smites a life form, the life form's hit point count is decreased by 4. The message "<heroName> smites his foe" is displayed.
attack -- How a wizard attacks his enemy depends on the situation. If the item the enemy is carrying has a value of 0, the wizard will frighten him. Otherwise, the wizard's action depends on the value of MAGIC. If MAGIC = 0, the wizard will enchant his enemy's item. If MAGIC = 1 check if the enemy is capable of physical fighting*. [Hint: You may find it useful to uses the java keyword instanceof.] In the case where MAGIC = 1 and the enemy is capable of physical fighting, the message "<heroName> taunts his foe, and in return " will be displayed and the wizard will taunt his enemy. [Hint: Use System.out.print to display the message instead of System.out.println. That way the enemy's reaction will be printed on the same line.] If MAGIC = 1 and the enemy is not capable of physical fighting, then the Wizard will take no action. If MAGIC > 1 then the wizard will frighten his enemy.
* You may wonder why you are being instructed to check if the enemy is capable of physical fighting when in this project wizards will only be fighting vampires, whom we know are physical fighters. The answer is that we may want to use your wizards in another project one day where they fight all sorts of other creatures. (See the "Important Note", below.)
enchant -- When a wizard enchants an item, the message "<heroName> enchants the <itemDescription>" is displayed. The value of the item doubles and the description of the item is prepended by "enchanted ". For example, if an item's description is "sword" and the item is enchanted by a wizard, then the description of the item becomes "enchanted sword".
frighten -- When a wizard frightens an enemy, the enemy's hit points are decreased by 3. The message "<heroName> frightens his foe" is displayed.
attack -- How a vampire attacks his enemy depends on the situation. If the item the enemy is carrying has a value of 0, the vampire will smite him. Otherwise, the vampire's action depends on the value of MAGIC. If MAGIC < 7 the vampire will smite his enemy. If MAGIC is 7, 8, or 9 then the vampire will enchant his enemy's item. If MAGIC is 10, 11, 12, 13, or 14, then the vampire will frighten his enemy. If MAGIC = 15, the vampire will destroy his enemy's item. If MAGIC = 16, check if the enemy is capable of physical fighting. If so, the message "the vampire taunts his foe, and in return " will be displayed and the vampire will taunt the enemy. If MAGIC = 16 but the enemy is not capable of physical fighting, then you should check if the enemy is capable of spell casting. If so, the message "the vampire confuses his foe, and in return " will be displayed and the vampire will confuse the enemy. If MAGIC = 16 and the enemy is neither capable of physical fighting, nor of spell casting, then the vampire will take no action.
enchant -- When a vampire enchants an item, the message "the vampire places a curse on the <itemDescription>" is displayed, and the value of the item is halved. You should round down -- for example, if an item of value 27 is enchanted by a vampire, it's new value will be 13. The description of the item is prepended by "cursed ". For example, if an item's description is "ruby" and the item is enchanted by a vampire, the description of the item becomes "cursed ruby".
frighten -- When a vampire frightens an enemy, the enemy's hit points are decreased by 7. The message "the vampire frightens his foe" is displayed.
smite -- When a vampire smites his enemy, the enemy's hit points are decreased by 2. The message "the vampire smites his foe" is displayed.
destroy -- When a vampire destroys an item, the message "the vampire pounds the <itemDescription> into junk" is displayed. The item's value is set to 0 and the description of the item is set to "junk".
Try to design classes/interfaces that can "stand alone" without the Dungeon class. In other words, design your classes so that they would work in a completely different scenario. For example, someone might want to write an application where vampires fight each other, or where a completely new kind of life form is introduced that will interact with the ones you have written. We want someone to be able to re-use our code in a new application with minimal (if any) changes.
If you use static variables for this project you will probably find that you will not pass the test cases. The reason is that after the first JUnit test runs, the second test begins and the values of the static variables are not re-initialized, even though your program runs again from the beginning.
System.exit does not terminate your program "gracefully", and it will throw exceptions during our testing, preventing you from passing any of our tests.
You may want to review the methods of the String class.
You may find the static method Integer.parseInt useful when reading the arguments into Dungeon.main.
You may want to use one or more abstract classes and/or abstract methods.
You may want to review the instanceof operator.
In addition to submitting your working program, you will also be required to submit a UML class diagram for your project. You are not required to include too many details in the diagram -- just show all of the classes and/or interfaces that you are using and the relationships among them. (You do not need to show state and behaviors -- just the names.) The UML diagram will account for 10% of your grade on the project. You must hand in the UML diagram during the lab session on Monday, 2/21.
If you look in the project folder provided, you'll find files named input1.txt, output1.txt, input2.txt, output2.txt, etc. These files correspond to the five public test cases. For example, the first public test case will run your program using the contents of the file input1.txt as arguments for main(). The test will verify that your corresponding output looks exactly like the contents of output1.txt. If your output doesn't match, you'll fail the test.
Three of the release tests have funny names. For example, one of them is called testWizard_X_1_Item_8. The name is giving you an idea of what the test will do. In this example, your program will be run by passing the following arguments to main(): Wizard X 1 Item 8
There are also two release tests with names that are much less helpful!
Your grade on this project will be calculated as follows: