computer science II
c m s c 214  
f a l l   2 0 0 1  

Project 4

Due Tuesday, Nov. 27, 11:59 PM

Late days will be 5 points off (1 day late), 10 points off (2 days), and 15 points off (3 days).

Project 4 FAQ

Latest Posting

Update: November 18, 2001. Added updates to commands.
Update: November 15, 2001. Added updates to commands.
Update: November 10, 2001. Added Task 5.

This was posted November 8, 2001.

Must Submit

You must submit a working version of project 4 by Friday, December 7 at 4 PM. It must pass the primary. If you do pass the primary by this date, you will receive 10 points. However, to get the most credit possible, turn in by the due date.

If you do not pass the primary by this date, you will be given a grade of F, regardless of any other grades you may have received in the class. This policy only applies to project 4.

Checklist

A link to a checklist will be provided. You should read the checklist several times, but especially before submitting your project. (To be posted soon)

Project 4 Checklist

Purpose

The purpose of the Project 4

  1. To learn about using public inheritance.
  2. To examine Purify and memory leak tools at your leisure.

Hardcoding is a Violation of Academic Integrity

If you "hardcode" the output, i.e., attempt to produce the primary output by using print statements or other similar techniques, or use simplified data structures (for example, using an array when you've been asked to use a binary search tree), you may be receive a 0 on the project, and be brought up on charges of academic dishonesty.

Academic Integrity Statement

Any evidence of unauthorized use of computer accounts or cooperation on projects will be submitted to the Student Honor Council, which could result in an XF for the course, suspension, or expulsion from the University. Projects are to be written INDIVIDUALLY. For academic honesty purposes, projects are to be considered comparable to a take-home exam. Any cooperation or exchange of ideas which would be prohibited on an exam is prohibited on a project assignment, and WILL BE REPORTED to the Honor Council.

VIOLATIONS OF ACADEMIC HONESTY INCLUDE:

  1. failing to do all or any of the work on a project by yourself, other than assistance from the instructional staff.

  2. using any ideas or any part of another student's project, or copying any other individual's work in any way.

  3. giving any parts or ideas from your project, including test data, to another student.

  4. having programs on an open account or on a PC that other students can access.

  5. transferring any part of a project to or from another student or individual by any means, electronic or otherwise.

  6. publishing any of your code publicly (e.g., via a webpage) which other students can access, even if the project is already due.

IT IS THE RESPONSIBILITY, UNDER THE UNIVERSITY HONOR POLICY, OF ANY STUDENT WHO LEARNS OF AN INCIDENT OF ACADEMIC DISHONESTY TO REPORT IT TO THEIR INSTRUCTOR.

Style Guide

Here it is

FAQ

We will provide corrections and announcements, as well as "frequently asked questions" on the Project 4 FAQ page.

Project Overview

There is no abstract list class to write.

You will also make Player into an abstract base class and derive four classes from this: Mage, Wizard, Fighter, and Knight.

You will also make Item into an base class (but not abstract) and derive two classes from this: Ring and Sword.

Task 1: Make Player an Abstract Base Class

To make Player into an abstract base class, you will create the following pure virtual methods:

You should put as many public methods into Player as possible, so that derived classes don't duplicate code.

Player should store a new private data member called experiencePts which is initially set to 0. As a player gains experience points, they will move from novice to expert.

Experience points is computed as follows: it is the number of gold pieces plus the worth of all treasure being carried plus the experience gained from disabling a robot. The experience gained from disabling a robot will be the total amount of treasure that the robot was holding onto (the player can gain even more experience points, by picking up the treasure that the robot has).

While a player is still a novice, dropping treasure will decrease the experience points of a player. However, once a player becomes an expert, there's no need to track the players experience points. It will stay fixed at the value when the player become an expert.

Task 2: Create Four Derived Classes From Player

You will create four derived classes from Player. Each should have its own .h file. Thus, you should create Fighter.h, Fighter.cpp, Knight.h, Knight.cpp, etc.

Here are some more details for each class.

Fighter

Fighter and Knight are related. Fighter is a novice, and Knight is an expert. It will take a fighter 1000 (now 1500!) experience points to become a knight.

getTitle() for a Fighter will return "Fighter" as a string.

getPower() for a Fighter will return "punches" if he carries no weapon. A Fighter may carry only one sword. If he carries a sword, then the sword will have a getPower() method, and the fighter will use that when attacking robots.

clone() will produce a dynamically allocated copy of type Fighter *. However, clone() must have a return type of Player * (it's inherited, remember?).

isExpert() const will return false, since a fighter is a novice.

A Fighter will have one private data member, a Sword * pointer. It will either be NULL, or it will be a pointer to a dynamically allocated sword. Even though it won't really be used in practice, the destructor for the Fighter must deallocate the sword object, if it exists.

A Fighter can only carry one sword at a time. He must drop the sword, then pick up a new sword, to get a new sword.

Knight

Once a Fighter becomes gains enough experience points, s/he becomes a Knight. This will occur by creating a new dynamically allocated Knight object, copying over any needed information (perhaps using a base class assignment operator), and deleting the Fighter object. Once a fighter becomes a knight, s/he never becomes a Fighter. There's also no need to keep track of experience points (it stays "frozen" at the value that the fighter became a knight).

getTitle() for a Knight will return "Knight" as a string.

getPower() for a Knight will return "kicks" if s/he carries no weapon. A Knight may carry up to two swords. If s/he carries a sword, then the sword will have a getPower() method, and the knight will use that when attacking robots. If s/he carries two swords, then s/he will attack a robot twice, once with each sword.

clone() will produce a dynamically allocated copy of type Knight *. However, clone() must have a return type of Player *.

isExpert() const will return true, since a knight is an expert.

A Knight will have one private data member, an array of two Sword * pointer. You may choose how to store the two swords (they will point to dynamically allocated swords). Even though it won't really be used in practice, the destructor for the Knight must deallocate the sword object, if it exists.

Mage

Mage and Wizard are related. Mage is a novice, and Wizard is an expert. It takes 1000 (now 1500!) experience points for a mage to become a wizard.

getTitle() for a Mage will return "Mage" as a string.

getPower() for a Mage will return "zaps" if s/he isn't wearing a ring. A Mage may carry only one ring. If s/he is wearing a ring, then the ring will have a getPower() method, and the mage will use that power when attacking robots.

clone() will produce a dynamically allocated copy of type Mage *. However, clone() must have a return type of Player *.

isExpert() const will return false, since a mage is a novice.

A Mage will have one private data member, a Ring * pointer. It will either be NULL, or it will be a pointer to a dynamically allocated ring. Even though it won't really be used in practice, the destructor for the Mage must deallocate the ring object, if it exists.

A Mage can only carry one ring at a time. S/he must drop the ring, then pick up a new ring, to get a new ring.

Wizard

Once a Mage becomes gains enough experience points, s/he becomes a Wizard. This will occur by creating a new dynamically allocated Wizard object, copying over any needed information (perhaps using a base class assignment operator), and deleting the Fighter object. Once a mage becomes a wizard, s/he never reverts back to a Mage. There's also no need to keep track of experience points (it stays "frozen" at the value that the mage became a wizard).

getTitle() for a Wizard will return "Wizard" as a string.

getPower() for a Wizard will return "hurls fireballs at" if s/he carries no rings. A Wizard may carry up to two rings. If s/he carries a ring, then the ring will have a getPower() method, and the mage will use the powers of the rings when attacking robots. If s/he wears two rings, then s/he will attack a robot twice, once with the power of each ring.

clone() will produce a dynamically allocated copy of type Wizard *. However, clone() must have a return type of Player *.

isExpert() const will return true, since a wizard is an expert.

A Wizard will have one private data member, an array of two Ring * pointer. You may choose how to store the two rings (they will point to dynamically allocated ring). Even though it won't really be used in practice, the destructor for the Wizard must deallocate the ring objects, if it exists.

Task 3: Derive Ring and Sword from Item

Item will still be the same class it's always been (thus, concrete), but now there will be two new classes called Ring and Sword. Both are quite similar. They will both have a data member called damagePoints, which will determine how much damage they do on Robots.

They will both have a data member called power (a string), which will be the phrase used when attacking robots.

Task 4: Create a RobotTeam class

There will only be one robot team. It will always be called "TeamRobot". The class will store robots in a SortedList template class. It will resemble Team except a SortedList is used instead of a BstSortedList.

Task 5: Redo Parser class

Perhaps you've heard of "just in time" on "on demand"? For example, many organizations keep a small inventory, but have quick delivery from their suppliers, so they can get their supplies "just in time" or "on demand".

Think of this as "just in time" project descriptions!

You will need to redo the part of the parser which reads in Items. In particular, rings and swords now have more information than other items.

Here are examples:

    <name> CloakRoom </name>
      <itemlist>
         <item>
           <name> ring </name>
           <type> lightning </type>
           <power> sends lightning bolts at </power>
           <damage> 30 </damage>
           <id> 2001 </id>
           <weight> 20 </weight>
           <cost> 300 </cost>
         </item>
        <item>
           <name> potion </name>
           <id> 2002 </id>
           <weight> 15 </weight>
           <cost> 350 </cost>
        </item>
        <item>
           <name> sword </name>
           <type> giant </type>
           <power> clubs </power>
           <damage> 15 </damage>
           <id> 2003 </id>
           <weight> 105 </weight>
           <cost> 1000 </cost>
        </item>
      </itemlist>
  </room>
Both rings and swords will have additional parts that other items do not. In particular, there is a UNIQUE type to the sword (thus, only one "giant" sword). power refers to a phrase that will be used when attacking a robot. damage refers to the number of points of damage on a robot, which you can assume is positive.

As a suggestion, you may want to write a static "parse()" function for Item, Ring, and Sword and have the parseDungeon() call those parse functions. The main drawback with static functions is that they can't be virtual since there's no object to refer to. Nevertheless, as long as they are public, you can still have a derived class static function refer to a base class static function (or vice versa, but that's unusual).

Part 2: Reparsing players

In addition, you will need to parse players differently. That's because players now have a type. The type will be the "novice" version of the player. Initially, a player will start off with the number of gold pieces being the number of experience points. If they start off with more than 1000 (now 1500!) pieces of gold, then they automatically move to expert.

For example, if Bill Cheng has 1000 (now 1500!) pieces of gold, and his type is "Fighter", he will actually start off as a Knight.

The second key issue is how to store the information about players. Currently, this information is stored in an ArrayList of Players. That will have to change to store an ArrayList of Player *, to correctly handle polymorphishm.

The question is how to use the clone() feature of ArrayList. Right now, I think I may have a clone() feature for a Team, which creates a copy of Team, including a copy of Team. This copy of Team will be a deep copy, meaning it will call clone() on all the Nodes.

For this particular project, clone() is far from useful because most people have used pointers in their Teams that point to Players in the ArrayList of Players. This means the ArrayList is maintaining the Players, not the Team.

Ideally, I would have Team contain pointers to Players which is dynamically allocates, and thereofre, Team would be in charge of making copies of Players.

So, for now, you can keep the Array List of Player * pointers (recall they have to be pointers so they can store Fighters, etc). I will work out a way to make Team create a copy just for the sake of making clone() useful, but the normal copy constructor merely copies pointers for Players (thus, does a shallow copy when it comes to Player * pointers). (Those details will come in a further task).

Here is an example of a player list feom the input file. If Samir Khuller had 1000 (now, 1500!!) gold pieces, his type would still be listed as a Fighter, but you would create a Knight instead.

<plist>
  <player>  
   <name> Samir Khuller </name>
   <type> Fighter </type>
   <strength> 400 </strength>
   <money> 100 </money>
  </player>

  <player>  
   <name> Leana Golubchik </name>
   <type> Mage </type>
   <strength> 350 </strength>
   <money> 200 </money>
  </player>

Task 6: Store Item * in ArrayList for Room, Player

Any classes that will require inheritance needs to store base class pointers. For example, Rooms store Items in an ArrayList. Those should now be Item * in the ArrayList.

What changes do you need to make ArrayList work with pointers? Fortunately, only one small change. The class can stay the same, but you should write sort214Ptr which will sort arrays of pointers (while using sort214Ptr with arrays of objects). The code should be very similar, but dereference the pointer so you compare objects. However, swap pointers (instead of copying objects).

Anything storing Player objects (such as the BST) will now need to store Player * pointers.

More Tasks!

OK, we're ready to deal with the tasks. For 15 points extra credit, do the following: In front of each player name, you must now print out the type, and their level. The level is defined to be the number of experience points divided by 500.

Thus, instead of saying:

[ATTACK] (Bill Gasarch) hurls icebolts at (Marty Cyborg)
you print:
[ATTACK] Mage Level 0 (Bill Gasarch) hurls icebolts at (Marty Cyborg)
Thus, the title is "Mage", followed by the "Level", followed by the level number. Robots, on the other hand, still print the same way.

Displaying Items

When printing out items, you will now display more information. This will show that you have used virtual functions and overridden them in Ring and Sword.
  Name: ring Id: 2001 Weight: 20 Cost: 300 Type: lightning Power: "sends lightning bolts at"
Both Rings and Swords will print Type and Power. The phrase for the power is surrounded by single quotes.

Other items (potions, scrolls, etc) will still print in the usual fashion, as they did before.

New Commands

Many of the commands all now altered.

JOIN_TEAM Extra Credit

Since a robot can join a team (TeamRobot), then similar message occur for Robot as Player.
  <join_team_out> ::== "[JOIN_TEAM] " 
                                   ( <join_team_first> | 
                                   <join_team_stay> ) <endl> <endl>
  <join_team_error> ::== "Robot (" <robotname> 
                                ") can only join (TeamRobot)"
  <join_team_first> ::== "Robot (" <robotname> ") joins team ("
                          <teamname> ")"
  <join_team_stay> ::== "Robot (" <robotname> ") is already on team ("
                         <teamname> ")"

(11/24) Once a robot is disabled, there will be no commands to cause a robot to join a team. Print the message <join_team_error> if a robot is made to join either a Player team or a non-existent team. If a robot is not on a team, then joins "TeamRobot", print <join_team_first>. If a robot is currently on "TeamRobot" and tries to join it again, print <join_team_stay>.

Initially, robots are already on a team (TeamRobot). This is UNLIKE players, which do not start off on any team. Thus, if you ask which team a robot is on, they start on "TeamRobot". They may not join any other team (NEW! 11/18).

This command won't be tested with robots except on the extra credit. The command is only modified slighty with players (using the title and level above)

WHICH_TEAM Extra Credit

 <which_team_out> ::== "[WHICH_TEAM] " 
                        ( <which_team_valid> | 
                          <which_team_invalid> ) <endl> <endl>
 <which_team_valid> ::== "(" <robotname> ") is on team ("
                             <teamname> ")"
 <which_team_invalid> ::== "(" <robotname> ") is not on any team"
Again, this won't be tested with robots, except on extra credit.

(11/24) This command won't be tested on disabled robots. A robot initially starts on "TeamRobot". If a robot is on this team, then print <which_team_valid>, otherwise print <which_team_invalid>.

LIST_TEAM_MEMBERS
LIST_TEAM_MEMBERS_REV Extra Credit

This now works for robots, if "TeamRobot" is specified. The difference is that if there are no robots on the team, then "NO ROBOTS ON THIS TEAM" is printed instead.

This command will not be tested on "TeamRobot" except on the extra credit.

(11/24) We won't test this command when there are disabled robots (technically, they should be removed from the team, but again, don't worry about it).

PICKUP

The usual messages apply to regular items. However, if a Mage or Wizard is picking up a ring, or a Fighter or Knight is picking up a sword, there are additional messages.
  <pickup_special> ::== "[PICKUP] " ( <player_holds_max> | <player_wields> )
  <player_holds_max> ::== <playername> " already wields " <number>
                          ( "ring(s)" | "sword(s)" )
  <player_wields> ::== <playername> " now wields: " 
                          "(" <word> ( "ring)" | "sword)" ) 
                           [ "and (" <word> ( "ring)" | "sword)" ) ]?
                           [ <upgrade> ]?
  <upgrade> ::== <playername> " is now upgraded to " <playername>
  <playername> ::== [ "Mage" | "Wizard" | "Fighter" | "Knight" ] 
                    " Level " <digits> " ("<firstname> 
                    " " <lastname> ")"
If a player already holds the maximum allowable rings/swords and it attempting to pick up another ring/sword, then it prints <player_holds_max>, where it prints out 1 if the player is a novice and 2 if the player is an expert. If the player successfully picks up an ring, then it prints <player_wields>.

Notice that <playername> has been redefined. It puts the title, the word "Level" and the level. The message is printed at the level prior to picking up the item.

After picking up the item, if a player then has 1500 points, then print the <upgrade> message.

If a player is picking up their weapon (Wizard picks up ring or Knight picks up a sword), they will pick it up in "queue" order. Thus, the first ring picked up by a Wizard becomes the first ring printed. The second ring picked up will be the second ring printed. If the first ring picked up is "ice" and the second is "fire", then a wizard wields a "(ice ring) and (fire ring)". If the wizard drop the ice ring, the fire ring becomes the primary ring, and if the wizard then picks up the ice ring, the wizard wields "(fire ring) and (ice ring)". At this point the first ring is the fire (it moved to the primary ring) and the ice ring is secondary.

(11/24) The <upgrade> line is only used in the extra credit when a player is upgraded from novice to expert. It is not printed if a player merely advances a level (for example, Fighter Level 0 to Fighter Level 1).

Notice the messages differ for "weapons" (for wizards/mages, the weapon is a ring; for fighters/knights, it's a sword) as opposed to normal items. When a "normal" item is picked it up, it uses the old BNF from Project 3 and earlier.

If a fighter/knight picks up a ring or a mage/qizard picks up a sword, then it will again, merely print the old message as it did in previous projects (thus, no adjectives).

DROP

As a player drops an item, you must adjust several things. First, a player must adjust experience points, if they are a novice. This doesn't affect what gets printed out. Also, you print the level of the player prior to dropping.

Finally, if either a robot or players drop a ring or sword, the message should say:

  <drop_success>  ::== "[DROP] " ( <robotname> | <playername> )
                         " dropped ("
                          <word> " " <itemname> ":id " <digits>{4,4} 
                          ") in room (" <roomname> ")"
  <robotname> ::== "Robot " ("<firstname> 
                    " " <lastname> ")"
where <word> refers to the "type" of the item dropped (thus, "ice", "fire", etc.). This type is always printed for all rings and swords.

There is a difference in the command to drop. If a player wants to drop their weapon (Mage/Wizard drops a ring or Fighter/Knight drops a sword), they must specify the type.

Thus the input command is:

DROP <firstname> <lastname> <itemname> <type>
The type would be left out if a Mage were dropping a sword or a Fighter were dropping a ring. If a Wizard has two rings and drops one, the remaining ring becomes the primary ring. If a Knight has two swords and drops one, the remaining sword becomes the primary sword.

If a wizard/mage drops a ring, it must print the type of the ring in front of the ring. If a fighter/knight drops a sword, it must print the type of the sword in front of the sword. Otherwise, dropping any other item does not print its type.

DISP_TREASURE

(11/24) When displaying a treasure for a player, the output will look like:
<disp_treasure> ::== "   Treasure for player (" <playername> ")" <endl>
                     "   ----*----*----*----*----*----*" <endl>
                     (<items>+ | "  NO TREASURE"<endl> )
                     "   ----*----*----*----*----*----*" <endl>
                     <playername> " wields : " <weapon> [ " and " <weapon> ]? <endl>
                    "and carries " <digits> " gold pieces " <endl>
                    "and has " <digits> " experience points" <endl><endl>
The gold pieces will be the actual gold pieces (not the total amount of treasure held) carried by the player.

Normal items are printed as before. Rings and swords must additionally print "Type: " followed by the type, then "Power: " followed by the power.

Note: Rings carried by fighters/knights are part of the treasure (between the two dashed lines) as are swords carried by mages/wizards. However, rings carried by mages/wizards are not part of the displayed treasure. You can see this information displayed underneathe where it says a player "wields" certain rings or swords.

For a team, you display all players, using the BNF from above. The total gold will refer to the total gold pieces (and gold pieces only---not the worth of the treasure) carried by the entire team.

As extra credit, display the robot teams treasure. Since robots carry no gold, the total gold will be displayed as 0.

ATTACK

The command to attack is:
ATTACK <firstname> <lastname> <firstname> <lastname> 
where you give the player name first and the robot name second. We won't test for other combinations (i.e., we won't test for one players attacking another player, or a robot attacking either a robot or a player).

There are several possibilities.

  <attack_cmmd> ::== "[ATTACK] " ( <robot_down> | <not_same_room> | <outside> |
                       <robot_is_hit> )
  <robot_down> ::== <robotname> " is already disabled" <endl> <endl>
  <not_same_room> ::== <playername> " and " <robotname>
                       " are not in the same room" <endl> <endl>
  <outside> ::== <playername> " and " <robotname>
                  " are outside the dungeon" <endl> <endl>
  <robot_is_hit> ::== <playername> <power> <robotname>
                            <endl>
                            ( <robot_hurt> | <robot_disabled> )
  <robot_hurt> ::== <robotname> " has " <digits>
                          " hit points " <endl> <endl>
  <robot_disabled> ::== <robotname> " is disabled!" <endl>
                              <playername> " gains " <digits>
                              " experience points"<endl>
                              <playername> " has " <digits>
                              " experience points"<endl>
                              [ <upgrade> ]?

CLONE

No, this isn't some promo for the new Star Wars film. Just coincidence. Since most people have implemented the Node class to store a pointer which points to memory that is not allocated by the BST. Instead, this memory is allocated, say, in the Parser, and maintained by the ArrayList.

However, there are occasions when you want to copy the entire BST, including the Player objects to create a complete copy. For example, sometimes the BST will completely maintain those objects.

Under such circumstances, it's important to not only copy the Node, but also the Player object pointed to by the Node. Since those players can be fighters, mages, etc.

The command will only be applied to player teams. Here's the command.

CLONE <teamname>
This must create a complete dynamic copy of the team, including cloning all player objects. It will then do a PRINT_TREE function (meaning it prints the tree). Then, you will destroy the entire tree (all nodes, and players).

You must be very careful with the destruction. Calling the destructor may not be enough (since most people do not delete the player pointers, allowing the ArrayList to manage that).

TAs will check to see if the destructor is called. Here is one way to code part of it at a higher level.

void CommandParser::cloneOp( Team *teamPtr )
{
  Team *newTeamPtr = teamPtr->clone();  // clones
  cout << "[CLONE]" << endl;
  cout << "   Tree for team: (" << newTeamPtr->getName() << ")" << endl;
  cout << "   ----*----*----*----*----*----*" << endl;
  newTeamPtr->printTree();  // prints it up
  cout << endl;
  newTeamPtr->destruct();   // destroys everything, including player obj
  delete newTeamPtr;
}
Notice that this will only destroy a copy of the player objects. Thus, the original ones that may appear in an ArrayList should still be there.

The output should be identical to a PRINT_TREE function.

Sample Output Posted soon!

Should be ready by Sunday evening.

Assumptions, etc.

Extra Credit

Again, the extra credit involves the following.

Background Reading

Background readings may be provided.

Commands

Soon!

Special Methods (NEW)

For each class you write, you must determine whether to write the following methods: Using the following is nearly impossible with inheritance. If defined in the base class, they are inaccessible in the derived class if they are private in the base class. You can make them protected, but then the implementer must be aware of it (however, it is an option!).

README

Submit a README with your files.

How to Submit Your Project

As with Project 1
submit p4.tar 4 SUBMIT
Your executable must be called exactly p4. Any other name will cause your program not to be submitted.

You can name the tar whatever you want. However, you should wait until a primary input and output have been posted. (If you submit before that, then if your project does not pass the primary input/output, you will receive a poor grade on the project, most likely a zero).

Please do not submit until a primary input and output have been posted. If you do, you may receive a 0 for failing to pass the primary.