C++代写:ICS214 Iceman Part4

从头开始实现一款大型的RPG游戏Iceman,第四部分是实现游戏所需的各个Actor类。

You Have to Create Classes for All Actors

The Iceman game has a number of different Game Objects, including:

  • The Iceman
  • Regular Protesters
  • Hardcore Protesters
  • Squirts of water (that can be shot by the Iceman)
  • Barrels of oil
  • Boulders
  • Gold Nuggets
  • Sonar kits
  • Water refills (that can be picked up to refill the Iceman’s squirt gun)
  • Ice

Each of these game objects can occupy a slot in the oil field and interact with other game objects within the oil field.

Now of course, many of your game objects will share things in common - for instance, every one of the objects in the game (Regular Protesters, the Iceman, Ice, Boulders, etc.) has an x,y coordinate. Many game objects have the ability to perform an action - to do something (e.g., move or shoot) - during each tick of the game. Many of them can be potentially annoyed (e.g., the Iceman and Protesters can be by annoyed by Squirts of water or bonked by Boulders, etc.) and could be irritated to the point of quitting during a tick. All of them need some attribute that indicates whether or not they are still active/alive (or they became inactive/died during the current tick), etc.

It is therefore your job to determine the commonalities between your different game objects and make sure to factor out common behaviors and traits and move these into appropriate base classes, rather than duplicate these items across your derived classes - this is in fact one of the tenets of object oriented programming.

Your grade on this project will largely depend upon your ability to create an intelligent set of classes that follow good object-oriented design principles. Your classes MUST NEVER duplicate code or member variables - if you find yourself writing the same (or largely similar) code across multiple classes, then this is an indication that you should define a common base class and migrate this common functionality/data to the base class. Duplication of code is the root of all evil in programming - it leads to bugs, inconsistencies, code bloat, etc. and should be avoided whenever possible.

Hint: When you notice this specification repeating the same text nearly exactly in the following sections (e.g., in the Regular Protester section and the Hardcore Protester section, or in the Gold Nugget or Sonar Kit sections) you MUST make sure to identify common behaviors and move these into proper base classes. NEVER duplicate behaviors across classes that can be moved into a base class!

You MUST derive all of your game objects directly or indirectly from a base class that we provide called GraphObject, e.g.:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Actor: public GraphObject {
public:

};

class Protester: public Actor {
public:

};

class HardcoreProtester: public Protester {
public:

};

GraphObject is a class that we have defined that helps hide a lot of the ugly logic required to graphically display your actors on the screen. If you don’t derive your classes from our GraphObject base class, then you won’t see anything displayed on the screen!

The GraphObject class provides the following methods that you may use in your classes:

1
2
3
4
5
6
7
8
9
GraphObject(int imageID, int startX, int startY,
DIRECTION startDirection, float size = 1.0,
unsigned int depth = 0);
void setVisible(bool shouldIDisplay);
void getX() const;
void getY() const;
void moveTo(int x, int y);
DIRECTION getDirection() const; // Directions: up, down, left, right
void setDirection(DIRECTION d); // Directions: up, down, left, right

You may use any of these methods in your derived classes, but you must not use any other methods found inside of GraphObject in your other classes (even if they are public in our class). You must not redefine any of these methods in your derived classes since they are not defined as virtual in our base class.

1
2
3
4
5
6
7
8
GraphObject(
int imageID,
int startX,
int startY,
DIRECTION startDirection,
float size = 1.0,
unsigned int depth = 0
)

When you construct a new GraphObject, you must specify the following parameters:

  1. An imageID that indicates what graphical image (aka sprite) our graphics engine should display on the screen. One of the following IDs, found in GameConstants.h.

  2. You must specify the initial x,y location of the object. The x value may range from 0 to VIEW_WIDTH-SPRITE_WIDTH inclusive, and the y value may range from 0 to VIEW_HEIGHT-SPRITE_HEIGHT inclusive. Notice that you pass the coordinates as x,y (i.e., column, row and not row, column.).

  3. You must specify the initial direction that each game object will be facing. Acceptable values include: up, down, left, and right. The definitions for these values may be found in the GraphObject class within GraphObject.h.

  4. You must specify the size of the image. Most images (e.g., Protesters, Gold Nuggets, the Iceman, etc.) will have a size of 1.0, which is the equivalent of a 4 squares by 4 squares on the screen. In contrast, Ice will always have a size of .25 making it 1 square by 1 square.

  5. You must specify the depth of the image. This value indicates whether the image is in the foreground (0), in the background (10) or somewhere in between. If two images are displayed at the same location, the graph object with the smaller depth value will be drawn on top of the graph object with the larger depth value, either partially or completely obscuring it. We will specify what the depth should be for each of your GraphObject-derived classes in the sections below.

setVisible(bool shouldIDisplay)

New GraphObjects start out invisible and are NOT displayed on the screen until the programmer calls the setVisible() method with a value of true for the parameter. setVisible(bool shouldIDisplay) is used to tell our graphical system whether or not to display a particular GraphObject on the screen.

If you call setVisible(true) on a GraphObject, then your object will be displayed on screen automatically by our framework (e.g., a Regular Protester image will be drawn to the screen at the GraphObject’s specified x,y coordinates if the object’s imageID is IMID_PROTESTER). If you call setVisible(false) then your GraphObject will not be displayed on the screen.

NOTE: When you create a new game object, always remember that if you want it to be displayed on the screen, you must call its setVisible() method with a value of true!

int getX() const

int getY() const

These methods are used to determine a GraphObject’s current location on the screen. Since each GraphObject maintains an x,y location, this means that your derived classes must not also have x,y member variables, but instead use those in the GraphObject base class.

void moveTo(int x, int y)

The moveTo() method must be used to update the location of a GraphObject within the oil field. For example, if a Regular Protester’s movement logic dictates that it should move to the right, you must do something like the following:

1
moveTo(getX() + 1, y); // move one square to the right

You must use the moveTo() method to adjust the location of a GameObject in the game if you want that object to be properly animated. As with the GraphObject constructor, note that the order of the parameters to moveTo is x,y (col,row) and NOT y,x (row,col).

DIRECTION getDirection() const

void setDirection(DIRECTION d)

These two methods are used to get and set the current direction the GraphObject is facing. Valid values include up, down, left, and right. Changing the direction causes our graphics engine to automatically rotate the image appropriately before displaying it on the screen.

Now that you know about our GraphObject base class, you’ll need to derive all of your game object classes based on this class (either directly, or indirectly).

The Iceman Class

Here are the requirements you must meet when implementing the Iceman class:

What a Iceman Object Must Do When It Is Created

When it is first created:

  1. The Iceman must have an image ID of IMID_PLAYER.
  2. The Iceman must always start at location x=30, y=60.
  3. The Iceman, in its default state:
    • a. Is given 10 hit points
    • b. Is given 5 units of water to squirt with his squirt gun (he may pick up additional Water in the oil field)
    • c. Is given 1 sonar charge
    • d. Starts out with zero gold nuggets
    • e. Should start facing rightward
  4. The Iceman has the following graphic parameters:
    • a. It has an image depth of 0 - meaning its graphic image should always be in the foreground (above other images)
    • b. It has a size of 1.0

In addition to any other initialization that you decide to do in your Iceman class, a Iceman (or one of its base classes) must set itself to be visible using the GraphObject class’s setVisible() method, e.g.:

1
setVisible(true);

What the Iceman Must Do During a Tick

The Iceman must be given an opportunity to do something during every tick (in its doSomething() method). When given an opportunity to do something, the Iceman must do the following:

  1. The Iceman must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. If the Iceman’s 4x4 image currently overlaps any Ice objects within the oil field, then it will call upon the StudentWorld object to:
    • a. Remove/destroy the Ice objects from the 4x4 area occupied by the Iceman (from x, y to x+3,y+3 inclusive)
    • b. Make a digging sound by playing the SOUND_DIG sound (see the StudentWorld section of this document for details on how to play a sound).
  3. Otherwise, the doSomething() method must check to see if the human player pressed a key (the section below shows how to check if the user pressed a key, and if so, how to determine what key the user pressed).
    • a. If the user presses the Escape key, this allows the user to abort the current level. In this case, the Iceman object should set itself to completely annoyed (aka dead). The code in your StudentWorld class should detect that the Iceman has died and address this appropriately (e.g., replay the level from scratch, or end the game if the player has run out of lives).
    • b. If the user presses the spacebar key, then the Iceman will fire a Squirt into the oil field, assuming they have sufficient water in their squirt gun to do so. The Iceman will then reduce their water count by 1. To fire a Squirt, the player must create and add a new Squirt object into the oil field at a location that is four squares immediately in front of the Iceman, facing the same direction as the Iceman. So if the Iceman is at x=10,y=7, and he is facing upward, then the new Squirt object would be placed at location x=10, y=11, and the Squirt would be facing upward. Every time the Iceman fires a Squirt, it must play the sound SOUND_PLAYER_SQUIRT. If the initial location where the Squirt starts (i.e., 4 squares in front of the Iceman) is occupied by one or more Ice objects or is within a radius of 3.0 of a Boulder object, then the Iceman must still make a squirting noise and will waste 1 unit of water, but the Iceman must not add any Squirt object into the oil field and thus no damage can be done to any Protesters (it’s as if the Iceman shot water against a wall and it immediately dissipated).
      Hint: One way to implement this is to have your Iceman create a new Squirt object in the proper location and facing the proper direction, and then give it to your StudentWorld to manage (i.e., to animate) along with your other game objects.
    • c. If the player wants their Iceman to turn to face a different direction then it is currently facing (by pressing one of the arrow keys), then the Iceman’s direction should be adjusted to the indicated direction but the Iceman must not move in that direction. For example, if the Iceman is currently facing up, and the player hits the right arrow key, then the Iceman should turn to face right, but not move right.
    • d. Else, if the Iceman is already facing in a particular direction and the user hits the arrow key to move in that same direction, AND the Iceman is allowed to move in that direction (e.g., there is no Boulder in the way, and the position doesn’t take the Iceman outside of the oil field), then your code must use the GraphObject’s moveTo() method to adjust the Iceman’s location by one square in the indicated direction. Valid coordinates for the Iceman are from x=0,y=0 to x=60,y=60, inclusive. Moreover, the Iceman cannot occupy a square that is less than or equal to a radius of 3 away from the center of any Boulder.
    • e. If the player presses the ‘Z’ or ‘z’ keys and the Iceman has one or more sonar charges remaining in his inventory, then this will cause the Iceman to use his sonar charge to illuminate the contents of the oil field within a radius of 12 of his location:
      • i. The count of sonar charges held by the Iceman must be decremented by 1.
      • ii. All hidden game objects (e.g., Gold Nuggets or Barrels of oil) that are within a radius of 12 (e.g., this includes 11.99 squares away) must be made visible via setVisible() and revealed to the player.
    • f. If the player presses the TAB key and they have one or more units of gold in their inventory, then they must add a Gold Nugget object into the oil field at their current x,y location and reduce their gold count by 1 unit. The dropped Gold Nugget must have a temporary state and a lifetime of 100 game ticks, and is only pickup-able by Protesters (either Regular or Hardcore). Such a dropped Nugget will start out in a visible state.

What the Iceman Must Do When It Is Annoyed

When the Iceman is annoyed (i.e., shouted at by a Protester), its hit-points should be decremented by the appropriate amount (e.g., 2 points). Hint: All objects that can be annoyed should have some type of method that can be called to annoy them.

If the Iceman’s hit-points reach zero or below due to being annoyed, the Iceman must set its object’s state to dead, and then play an “I give up” sound effect: SOUND_PLAYER_GIVE_UP. Note: The StudentWorld class should check the Iceman’s status during each tick and if he transitions to a dead state, it should return the appropriate value indicating that the player lost a life.

Getting Input From the User

Since Iceman is a real-time game, you can’t use the typical getline or cin approach to get a user’s key press within the player’s doSomething() method. This would stop your program and wait until the user types in the proper data and hits the Enter key. This would make for a really boring game (requiring the user to hit a directional key then hit Enter, then hit a direction key, then hit Enter, etc.). Instead, you will need to use a special function that we provide in our GameWorld class (which your StudentWorld class is derived from) called getKey()to get input from the user. This function rapidly checks to see if the user hit a key. If the user hit a key, the function returns true and the int variable passed to it is set to the code for the key. Otherwise, the function immediately returns false, meaning that no key was hit. This function could be used as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void Iceman::doSomething()
{
...
int ch;
if (getWorld()->getKey(ch) == true)
{
// user hit a key this tick!
switch (ch)
{
case KEY_PRESS_LEFT:
... move player to the left ...;
break;
case KEY_PRESS_RIGHT:
... move player to the right ...;
break;
case KEY_PRESS_SPACE:
... add a Squirt in front of the player...;
break;
// etc
}
}
...
}

Ice

Here are the requirements you must meet when implementing the Ice class.

Ice doesn’t really do much. It just sits still in place.

What Ice Must Do When It Is Created

When it is first created:

  1. A Ice object must have an image ID of IMID_ICE.
  2. Each Ice object must have its x,y location specified for it - the StudentWorld class can pass in this x,y location when constructing a new Ice object (e.g., when constructing the entire oil field).
  3. Each Ice object must start out facing rightward.
  4. Ice has the following graphic parameters:
    • a. It has an image depth of 3 - meaning its graphic image should always be in the background (all other game objects have smaller depth values)
    • b. It has a size of .25, meaning it occupies only a 1x1 square in the oil field. In addition to any other initialization that you decide to do in your Ice class, a Ice object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What a Ice Object Must Do During a Tick

It’s ice - what do you expect it to do? It does nothing! As such, it doesn’t need to have a doSomething() method Or if it does have one, it doesn’t need to do anything.

What a Ice Object Must Do When It Is Annoyed

Ice objects cannot be annoyed (i.e., when a Squirt collides with them). After all, if you were Ice, would you be annoyed if you were squirted with water?

Boulders

You must create a class to represent a Boulder. Boulders basically sit around until the Ice underneath them has been dug away, and then after a short delay, they fall down until they hit the bottom of the shaft and disintegrate. Here are the requirements you must meet when implementing the Boulder class.

What a Boulder object Must Do When It Is Created

When it is first created:

  1. The Boulder object must have an image ID of IMID_BOULDER.
  2. A Boulder object must have its x,y location specified for it - the StudentWorld class can pass in this x,y location when constructing a new Boulder object.
  3. Boulders start out in a stable state (more on this below).
  4. Boulders start out facing downward.
  5. Boulders have the following graphic parameters:
    • a. They have an image depth of 1 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

In addition to any other initialization that you decide to do in your Boulder class, a Boulder object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What the Boulder Object Must Do During a Tick

Each time the Boulder object is asked to do something (during a tick), it should:

  1. Check to see if it’s still alive. If not, its doSomething() method should immediately return.
  2. If the Boulder is currently in the stable state, then it must check to see if there is any Ice in the 4 squares immediately below it. For example, if the Boulder is at location x=20,y=30, it would check squares (20,29), (21,29), (22,29) and (23,29) for Ice. If there is any Ice below the Boulder, it does nothing - after all it’s a dumb rock. However, if none of the 4 squares beneath the Boulder have any Ice, then the Boulder must transition into a waiting state:
    • A. It must enter a waiting state for the next 30 ticks
  3. If the Boulder is in a waiting state and 30 ticks have elapsed, then it must transition into a falling state and play the sound SOUND_FALLING_ROCK.
  4. If the Boulder is in a falling state, then:
    • A. It must continue to move downward one square during each tick until it either (a) hits the bottom of the oil field (i.e., it tries to move to y=-1), (b) runs into the top of another Boulder, or (c) it runs into Ice (i.e., by moving down a square, the Boulder would overlap over one or more Ice objects). When any of the above conditions are met the Boulder must set its state to dead so it can be removed from the game at the end of the current tick.
    • B. If the Boulder comes within a radius of 3 (i.e. less than or equal to 3 squares, so 2.99 would count) of any Protester(s) or the Iceman while falling, it must cause 100 points of annoyance to those actors (effectively completely annoying them in one fell swoop). The Boulder will continue falling down as described above, even if it annoys one or more Protesters on its way down (i.e., it won’t stop falling if it runs into a Protester). However, if the Boulder annoys the Iceman, the player will instantly lose a life.

What a Boulder Must Do When It Is Annoyed

Boulders cannot be annoyed!

Squirt

You must create a class to represent a Squirt from a squirt gun. A Squirt object will be introduced into the oil field by the Iceman when he shoots his squirt gun, and will then travel 4 squares in its initially-facing direction or until it hits any Ice, a Protester of any type, or a Boulder. Here are the requirements you must meet when implementing the Squirt class.

What a Squirt object Must Do When It Is Created

When it is first created:

  1. The Squirt object must have an image ID of IMID_WATER_SPURT.
  2. All Squirt objects must have their x,y location specified for them by the Iceman.
  3. All Squirt objects must have their direction specified for them by the Iceman.
  4. All Squirt objects start off with an initial travel distance of 4 squares
  5. All Squirt objects have the following graphic parameters:
    • a. They have an image depth of 1 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

In addition to any other initialization that you decide to do in your Squirt class, a Squirt object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What the Squirt Object Must Do During a Tick

Each time the Squirt object is asked to do something (during a tick):

  1. If a Squirt is within a radius of 3.0 of one or more Protesters (up to and including a distance of 3.0 squares away), it will cause 2 points of annoyance to these Protester(s), and then immediately set its state to dead, so it can be removed from the oil field at the end of the tick.
  2. If a Squirt has traveled through its full travel distance, then it immediately sets its state to dead, so it can be removed from the oil field at the end of the current tick.
  3. Otherwise, the Squirt must check to see if it can move one square in its currentlyfacing direction. If this target location is occupied by either Ice or a Boulder, then the Squirt immediately sets its state to dead so it can be removed from the oil field at the end of the current tick.
  4. Otherwise, the Squirt moves one square forward in its currently-facing direction, and then returns.

What a Squirt Must Do When It Is Annoyed

Squirts can’t be annoyed, silly.

Barrel of Oil

You must create a class to represent a Barrel of oil that needs to be picked up by the Iceman. Here are the requirements you must meet when implementing the Barrel class.

What a Barrel object Must Do When It Is Created

When it is first created:

  1. All Barrels must have an image ID of IMID_BARREL.
  2. All Barrels must have their x,y location specified for them when they are created.
  3. All Barrels must start off facing rightward.
  4. All Barrels start out invisible - they must be discovered (by the Iceman walking in close proximity to them) before they become visible.
  5. Barrels have the following graphic parameters:
    • a. They have an image depth of 2 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

What the Barrel Object Must Do During a Tick

Each time the Barrel object is asked to do something (during a tick):

  1. The object must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. Otherwise, if the Barrel is not currently visible AND the Iceman is within a radius of 4.0 of it, then:
    • a. The Barrel must make itself visible with the setVisible() method.
    • b. The Barrel’s doSomething() method must immediately return.
  3. Otherwise, if the Barrel is within a radius of 3.0 from the Iceman, then the Barrel will activate, and:
    • a. The Barrel must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).
    • b. The Barrel must play a sound effect to indicate that the player picked up the Goodie: SOUND_FOUND_OIL.
    • c. The Barrel must increase the player’s score by 1000 points.
    • d. If necessary, the Barrel may inform the StudentWorld object that it was picked up. Once all Barrels on the level have been picked up, the player finishes the level and may advance to the next level.

What an Barrel Must Do When It Is Annoyed

Barrels can’t be attacked and will not block Squirts from the Iceman’s squirt gun (they pass right over the Barrels).

Gold Nugget

You must create a class to represent a Gold Nugget. If the Iceman picks up a Gold Nugget, he can then drop it into the oil field at a later time to bribe a Protester (of either type). Here are the requirements you must meet when implementing the Gold Nugget class.

What a Gold Nugget object Must Do When It Is Created

When it is first created:

  1. All Gold Nuggets must have an image ID of IMID_GOLD.
  2. All Gold Nuggets must have their x,y location specified for them when they are created.
  3. All Gold Nuggets must start off facing rightward.
  4. A Gold Nugget may either start out invisible or visible - this must be specified by the code that creates the Nugget, depending on the context of its creation. Nuggets buried within the Ice of the oil field always start out invisible, whereas Nuggets dropped by the Iceman start out visible.
  5. A Gold Nugget will either be pickup-able by the Iceman or pickup-able by Protesters, but not both. This state must be specified by the code that creates the Gold Nugget object.
  6. A Gold Nugget will either start out in a permanent state (where they will remain in the oil field until they are picked up by the Iceman or the level ends) or a temporary state (where they will only remain in the oil field for a limited number of ticks before disappearing or being picked up by a Protester). This state must be specified by the code that creates the Gold Nugget object.
  7. Gold Nuggets have the following graphic parameters:
    • a. They have an image depth of 2 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

What the Gold Nugget Object Must Do During a Tick

Each time the Gold Nugget object is asked to do something (during a tick):

  1. The object must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. Otherwise, if the Gold Nugget is not currently visible AND the Iceman is within a radius of 4.0 of it ([= 4.00 units away), then:
    • e. The Gold Nugget must make itself visible with the setVisible() method.
    • f. The Gold Nugget doSomething() method must immediately return.
  3. Otherwise, if the Gold Nugget is pickup-able by the Iceman and it is within a radius of 3.0 from the Iceman, then the Gold Nugget will activate, and:
    • a. The Gold Nugget must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).
    • b. The Gold Nugget must play a sound effect to indicate that the Iceman picked up the Goodie: SOUND_GOT_GOODIE.
    • c. The Gold Nugget increases the player’s score by 10 points (This increase can be performed by the Iceman class or the Gold Nugget class).
    • d. The Gold Nugget must tell the Iceman object that it just received a new Nugget so it can update its inventory.
  4. Otherwise, if the Gold Nugget is pickup-able by Protesters and it is within a radius of 3.0 from a Protester, then the Gold Nugget will activate, and:
    • a. The Gold Nugget must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).
    • b. The Gold Nugget must play a sound effect to indicate that the Iceman picked it up: SOUND_PROTESTER_FOUND_GOLD.
    • c. The Gold Nugget must tell the Protester object that it just received a new Nugget so it can react appropriately (e.g., be bribed).
    • d. The Gold Nugget increases the player’s score by 25 points (This increase can be performed by the Protester class or the Gold Nugget class).
  5. If the Gold Nugget has a temporary state, then it will check to see if its tick lifetime has elapsed, and if so it must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).

What a Gold Nugget Must Do When It Is Annoyed

Gold Nuggets can’t be attacked and will not block Squirts from the Iceman’s squirt gun (they pass right over the Nuggets).

Sonar Kit

You must create a class to represent a Sonar Kit. If the Iceman picks up a Sonar Kit, he can use it to scan the oil field at a later time to locate buried Gold Nuggets and Barrels of oil. Here are the requirements you must meet when implementing the Sonar Kit class.

What a Sonar Kit object Must Do When It Is Created

When it is first created:

  1. All Sonar Kits must have an image ID of IMID_SONAR.
  2. All Sonar Kits must have their x,y location specified for them when they are created.
  3. All Sonar Kits must start off facing rightward.
  4. All Sonar Kits starts out visible.
  5. A Sonar Kit is only pickup-able by the Iceman.
  6. A Sonar Kit will always start out in a temporary state (where they will only remain in the oil field for a limited number of ticks before disappearing) - the number of ticks T a Sonar Kit will exist can be determined from the following formula: T = max(100, 300 - 10*current_level_number)
  7. Sonar Kits have the following graphic parameters:
    • a. They have an image depth of 2 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

In addition to any other initialization that you decide to do in your Sonar Kit class, a Sonar Kit object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What the Sonar Kit Object Must Do During a Tick

Each time the Sonar Kit object is asked to do something (during a tick):

  1. The object must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. Otherwise, if the Sonar Kit is within a radius of 3.0 ([= 3.00 units away) from the Iceman, then the Sonar Kit will activate, and:
    • a. The Sonar Kit must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).
    • b. The Sonar Kit must play a sound effect to indicate that the Iceman picked up the Goodie: SOUND_GOT_GOODIE.
    • c. The Sonar Kit must tell the Iceman object that it just received a new Sonar Kit so it can update its inventory.
    • d. The Sonar Kit increases the player’s score by 75 points (This increase can be performed by the Iceman class or the Sonar Kit class).
  3. Since the Sonar Kit is always in a temporary state, it will check to see if its tick lifetime has elapsed, and if so it must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).

What an Sonar Kit Must Do When It Is Annoyed

Sonar Kits can’t be annoyed and will not block Squirts from the Iceman’s squirt gun.

Water Pool

You must create a class to represent a Water Pool. If the Iceman picks up Water, he can use it to increase the contents of his squirt gun. Here are the requirements you must meet when implementing the Water Pool class.

What a Water Pool object Must Do When It Is Created

When it is first created:

  1. All Water Pools must have an image ID of IMID_WATER_POOL.
  2. All Water Pools must have their x,y location specified for them when they are created.
  3. All Water Pools must start off facing rightward.
  4. All Water Pools start out visible.
  5. A Water Pool is only pickup-able by the Iceman.
  6. A Water Pool will always start out in a temporary state (where they will only remain in the oil field for a limited number of ticks before disappearing) - the number of ticks T that a Water Pool will exist can be determined from the following formula: T = max(100, 300 - 10*current_level_number)
  7. Water Pools have the following graphic parameters:
    • a. They have an image depth of 2 - behind actors like Protesters, but above Ice
    • b. They have a size of 1.0

In addition to any other initialization that you decide to do in your Water Pool class, a

Water Pool object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What a Water Pool Object Must Do During a Tick

Each time a Water pool object is asked to do something (during a tick):

  1. The object must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. Otherwise, if the Water pool is within a radius of 3.0 ([= 3.00 units away) from the Iceman, then the Water pool will activate, and:
    • e. The Water pool must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).
    • f. The Water pool must play a sound effect to indicate that the Iceman picked up the Goodie: SOUND_GOT_GOODIE.
    • g. The Water pool must tell the Iceman object that it just received 5 new squirts of water so it can update its inventory.
    • h. The Water pool increases the player’s score by 100 points (This increase can be performed by the Iceman class or the Sonar Kit class).
  3. Since the Pool of Water is always in a temporary state, it will check to see if its tick lifetime has elapsed, and if so it must set its state to dead (so that it will be removed by your StudentWorld class from the game at the end of the current tick).

What a Water Pool Must Do When It Is Annoyed

Water Pools can’t be annoyed and will not block Squirts from the Iceman’s squirt gun.

Regular Protesters

You must create a class to represent a Regular Protester. Here are the requirements you must meet when implementing a Regular Protester class.

What a Regular Protester object Must Do When It Is Created

When it is first created:

  1. Each Regular Protester must have an image ID of IMID_PROTESTER.
  2. Each Regular Protester must start out facing left.
  3. Each Regular Protester must decide how many squares, numSquaresToMoveInCurrentDirection, it will decide to move left before possibly switching its direction. This value must be: 8 <= numSquaresToMoveInCurrentDirection <= 60
  4. Each Regular Protester starts out with 5 hit-points.
  5. Each Regular Protester starts out NOT in a leave-the-oil-field state.
  6. Each Regular Protester have the following graphic parameters:
    • a. It has an image depth of 0 - they’re always in the foreground
    • b. It has a size of 1.0

In addition to any other initialization that you decide to do in your Regular Protester class, a Regular Protester object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What a Regular Protester Must Do During a Tick

Regular Protesters, unlike the player, don’t necessarily get to take an action during every tick of the game (this is to make the game easier to play, since if Regular Protesters moved once every tick, they’d move much faster than the typical player can think and hit the keys on the keyboard). The Regular Protester must therefore compute a value indicating how often they’re allowed to take an action (e.g., once every N ticks). This number of ticks (also known as “resting ticks”) may be computed as follows:

1
int ticksToWaitBetweenMoves = max(0, 3 - current_level_number/4)

So if the value of ticksToWaitBetweenMoves was 3, then the Regular Protester must “rest” for 3 ticks and may perform its normal behavior every 4 tick. If the value is zero, then the Regular Protester does something during every tick of the game.

Here is what a Regular Protester must do during *every* tick:

  1. The Regular Protester must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. If the Regular Protester is in a “rest state” during the current tick, it must do nothing other than update its resting tick count, and immediately return.
  3. Otherwise, if the Regular Protester is in a leave-the-oil-field state (because their hit points reached zero due to being repeatedly squirted by the Iceman’s squirt gun or bonked by a falling Boulder), then:
    • a. If the Regular Protester is at its exit point (at location x=60, y=60) then it will immediately set its status to dead so that it will be removed from the game at the end of the current tick (by your StudentWorld class).
    • b. Otherwise, the Regular Protester must move one square closer to its exit point (at x=60, y=60). It must determine what direction to move based on a queue-based maze-searching algorithm like the one we learned in class. The Regular Protester may not walk through Ice or Boulders to reach its exit point (meaning that none of the 16 squares that comprise the 4x4 Regular Protester icon may overlap with a Ice object and that the Regular Protester must not come within a distance of 3.0 units of any Boulders). Hint: It’s inefficient for every single Regular Protester to try to figure out its own optimal path to the exit, so see if you can figure out a way to compute a single data structure that enables all Protesters to quickly determine their optimal path to their common exit point of 60,60, no matter where they are located in the oil field.
    • c. After moving one step toward their exit point, the Regular Protester will return immediately. It will not perform any of the following steps.
  4. Otherwise, the Regular Protester will check to see if they are within a distance of 4 units of the Iceman, AND they are currently facing in the Iceman’s direction. If both are true and the Regular Protester hasn’t shouted within its last non-resting 15 ticks, then the Regular Protester will:
    • a. Play the shouting sound: SOUND_PROTESTER_YELL
    • b. Inform the Iceman that he’s been annoyed for a total of 2 annoyance points (deducting 2 points from the Iceman’s hit points, and possibly causing the Iceman to become sufficiently annoyed to quit the level (aka die)).
    • c. Update some state variable in a manner that prevents this Regular Protester from shouting again for at least 15 non-resting ticks.
    • d. Return immediately.
  5. Otherwise, if the Regular Protester:
    • a. Is in a straight horizontal or vertical line of sight to the Iceman (even if the Regular Protester isn’t currently facing the Iceman), and
    • b. Is more than 4 units away from the Iceman - that is, the radius from the Regular Protester and the Iceman is greater than 4.0 units away, and
    • c. Could actually move the entire way to the Iceman with no Ice or Boulders blocking its path (assuming it kept walking straight over the next N turns),
      Then the Regular Protester will:
    • a. Change its direction to face in the direction of the Iceman, AND then take one step toward him.
    • b. The Regular Protester will set its numSquaresToMoveInCurrentDirection value to zero, forcing it to pick a new direction/distance to move during its next non-resting tick (unless of course, the Regular Protester still sees the Iceman in its line of sight, in which case it will continue to move toward the Iceman).
    • c. Then the Regular Protester will immediately return.
  6. Otherwise, the Regular Protester can’t directly see the Iceman. As such, it will decrement its numSquaresToMoveInCurrentDirection variable by one. If the Regular Protester has finished walking numSquaresToMoveInCurrentDirection steps in its currently-selected direction, then:
    • a. The Regular Protester will pick a random new direction to move (up, down, left or right).
    • b. If the random direction is blocked either by Ice or a Boulder such that it can’t take even a single step in that chosen direction, then it will select a different direction and check it for blockage (it will continue checking directions until it has picked a direction that is not blocked).
    • c. The Regular Protester will then change its direction to this new chosen direction.
    • d. The Regular Protester will then pick a new value for numSquaresToMoveInCurrentDirection that will govern how far it should move in the selected direction over the next numSquaresToMoveInCurrentDirection non-resting ticks. The value must be such that: 8 <= numSquaresToMoveInCurrentDirection <= 60.
    • e. The Regular Protester will then continue with step 8.
  7. Otherwise, if the Regular Protester:
    • a. Is sitting at an intersection where it could turn and move at least one square in a perpendicular direction from its currently facing direction (e.g., it is currently facing left, and is at a junction where it could turn and move one step either upward/downward without being blocked by Ice or a Boulder), and
    • b. The Regular Protester hasn’t made a perpendicular turn in the last 200 non-resting ticks.
      Then the Regular Protester will:
    • a. Determine which of the two perpendicular directions are viable (a viable direction is one that allows movement of at least one square without the Regular Protester being blocked by Ice or a Boulder).
    • b. Pick a viable perpendicular direction. If both perpendicular directions are viable, then pick one of the two choices randomly.
    • c. Set its direction to the selected perpendicular direction.
    • d. Pick a new value for numSquaresToMoveInCurrentDirection that will govern how far it should move in the selected perpendicular direction over the next numSquaresToMoveInCurrentDirection non-resting ticks. The value must be such that: 8 <= numSquaresToMoveInCurrentDirection <= 60.
    • e. Remember that it made a perpendicular turn so that it doesn’t make another perpendicular turn for at least 200 more non-resting ticks.
    • f. Continue on with the next step.
  8. Finally, the Regular Protester will then attempt to take one step in its currently facing direction (which, by the way, might have just been changed by one of the last few steps above).
  9. If the Regular Protester is for some reason blocked from taking a step in its currently facing direction, it will set the numSquaresToMoveInCurrentDirection to zero, resulting in a new direction being chosen during the Regular Protester’s next non-resting tick (but not the current tick - the Regular Protester must do nothing during the current tick).

Note that only Ice and Boulders block a Regular Protester from moving. Regular Protesters can occupy the same square as all other game objects including the Iceman and other Protesters.

What the Regular Protester Must Do When It Is Annoyed

A Regular Protester can’t be further annoyed once it is in a leave-the-oil-field state - this means it can’t be squirted or bonked by Boulders while in this state.

When the Regular Protester is annoyed (e.g., by being hit by a Squirt object fired by the Iceman or being bonked by a Boulder), its hit-points should be decremented by the appropriate amount (the amount is specified elsewhere in this document).

If, after its hit-points have been decremented, the Regular Protester hasn’t been completely annoyed (to the point it wants to leave the oil field) then it must play a sound of: SOUND_PROTESTER_ANNOYED. It will then be “stunned” and placed in a resting state for N resting ticks, where:

1
N = max(50, 100 - current_level_number * 10)

Note: This essentially extends the Regular Protester’s normal resting state to N ticks. Remember, that when in a resting state, the Regular Protester will immediately return when its doSomething() method is called.

On the other hand, if the Regular Protester’s hit-points reach zero or below due to being annoyed for any reason, the Regular Protester:

  1. Transitions into a leave-the-oil-field state.
  2. Plays an “I give up” sound effect: SOUND_PROTESTER_GIVE_UP.
  3. Sets its resting tick count to zero, ensuring that it will do something on the very next game tick, even if it was previously stunned or otherwise in a resting state.
  4. If the Regular Protester was annoyed due to being bonked by a Boulder, then it will increase the player’s score by 500 points.
  5. On the other hand, if the Regular Protester was annoyed due to being squirted repeatedly, then it will increase the player’s score by 100 points.

What the Regular Protester Must Do When It Picks up a Gold Nugget

When a Gold Nugget has activated due to being stepped upon by a Regular Protester, it will notify the Regular Protester that it has picked up a Nugget. The Regular Protester must do the following in response to being notified that it has just stepped upon a Nugget:

  1. The Regular Protester plays an “I’m rich” sound effect: SOUND_PROTESTER_FOUND_GOLD.
  2. The Regular Protester increases the player’s score by 25 points for the bribery.
  3. The Regular Protester will immediately be bribed and transition into a leave-theoil-field state.

Hardcore Protesters

You must create a class to represent a Hardcore Protester. Here are the requirements you must meet when implementing a Hardcore Protester class.

What a Hardcore Protester object Must Do When It Is Created

When it is first created:

  1. Each Hardcore Protester must have an image ID of IMID_HARD_CORE_PROTESTER.
  2. Each Hardcore Protester must start out facing left.
  3. Each Hardcore Protester must decide how many squares, numSquaresToMoveInCurrentDirection, it will decide to move left before possibly switching its direction. This value must be: 8 <= numSquaresToMoveInCurrentDirection <= 60
  4. Each Hardcore Protester starts out with 20 hit-points.
  5. Each Hardcore Protester starts out NOT in a leave-the-oil-field state.
  6. Each Hardcore Protester have the following graphic parameters: a. It has an image depth of 0 - they’re always in the foreground b. It has a size of 1.0

In addition to any other initialization that you decide to do in your Hardcore Protesters class, a Hardcore Protester object must set itself to be visible using the GraphObject class’s setVisible() method, e.g.: setVisible(true);

What a Hardcore Protester Must Do During a Tick

Hardcore Protesters, unlike the player, don’t necessarily get to take an action during every tick of the game (this is to make the game easier to play, since if Hardcore Protesters moved once every tick, they’d move much faster than the typical user can think and hit the keys on the keyboard). The Hardcore Protester must therefore compute a value indicating how often they’re allowed to take an action (e.g., once every N ticks). This number of ticks (also known as “resting ticks”) may be computed as follows:

1
int ticksToWaitBetweenMoves = max(0, 3 - current_level_number/4)

So if the value of ticksToWaitBetweenMoves was 3, then the Hardcore Protester must “rest” for 3 ticks and may perform its normal behavior every 4 tick. If the value is zero, then the Hardcore Protester does something during every tick of the game.

Here is what a Hardcore Protester must do during *every* tick:

  1. The Hardcore Protester must check to see if it is currently alive. If not, then its doSomething() method must return immediately - none of the following steps should be performed.
  2. If the Hardcore Protester is in a “rest state” during the current tick, it must do nothing other than update its resting tick count, and immediately return.
  3. Otherwise, if the Hardcore Protester is in a leave-the-oil-field state (because their hit points reached zero due to being repeatedly squirted by Squirt objects fired from the Iceman’s squirt gun or bonked by a falling Boulder), then:
    • a. If the Hardcore Protester is at its exit point (at location x=60, y=60) then it will immediately set its status to dead so that it will be removed from the game at the end of the current tick (by your StudentWorld class).
    • b. Otherwise, the Hardcore Protester must move one square closer to its exit point (at x=60, y=60). It must determine what direction to move based on a queue-based maze-searching algorithm like the one we learned in class. The Hardcore Protester may not walk through Ice or Boulders to reach its exit point (meaning that none of the 16 squares that comprise the 4x4 Hardcore Protester icon may overlap with Ice and that the Hardcore Protester must not come within a distance of 3.0 units of any Boulders). Hint: It’s inefficient for every single Hardcore Protester to try to figure out its own optimal path to their common exit point of 60,60, so see if you can figure out a way to compute a single data structure that enables all Protesters to quickly determine their optimal path to their common exit, no matter where they are located in the oil field.
    • c. After moving one step toward their exit point, the Hardcore Protester will return immediately. It will not perform any of the following steps.
  4. Otherwise, the Hardcore Protester will check to see if they are within a distance of 4 units of the Iceman, AND they are currently facing in the Iceman’s direction . If both are true and the Hardcore Protester hasn’t shouted within the last non-resting 15 ticks, then the Hardcore Protester will:
    • a. Play the shouting sound: SOUND_PROTESTER_YELL
    • b. Inform the Iceman that he’s been annoyed for a total of 2 annoyance points (deducting 2 points from the Iceman’s hit points, and possibly causing the Iceman to become sufficiently annoyed to quit the level (aka die)).
    • c. Update some state variable in a manner that prevents this Hardcore Protester from shouting again for at least 15 non-resting ticks.
    • d. Return immediately.
  5. Otherwise, if the Hardcore Protester is more than 4.0 units away from the Iceman, the Hardcore Protester will:
    • a. Compute a value M, such that M = 16 + current_level_number * 2
    • b. If the Hardcore Protester is less than or equal to a total of M legal horizontal or vertical moves away from the current location of the Iceman (as can be determined using the same basic queue-based maze-searching algorithm described in Hardcore Protester item 3.b above), then the Hardcore Protester will sense the Iceman’s cell phone signal and know where he is. This is true even if the Hardcore Protester has no direct line of sight to the Iceman, so long as he can be reached in M moves without digging through Ice or going through a Boulder. In such a situation, the Hardcore Protester will:
      • i. Determine which horizontal/vertical direction to move in (as dictated by its maze-searching algorithm) such that if it were to make such a move it would be one square closer to the Iceman.
      • ii. Change its current direction to face this new direction.
      • iii. Move one square in this direction such that after the move it is one square closer to the Iceman’s current location.
      • iv. Return immediately.
  6. Otherwise, if the Hardcore Protester:
    • a. Is in a straight horizontal or vertical line of sight to the Iceman (even if the Hardcore Protester isn’t currently facing the Iceman), and
    • b. Is more than 4 units away from the Iceman - that is the radius from the Hardcore Protester and the Iceman is greater than 4.0 units away, and
    • c. Could actually move the entire way to the Iceman with no Ice or Boulders blocking its path (assuming it kept walking straight over the next N turns),
      Then the Hardcore Protester will:
    • d. Turn to face the Iceman and take one step toward him.
    • e. The Hardcore Protester will set its numSquaresToMoveInCurrentDirection value to zero, forcing it to pick a new direction/distance to move during its next non-resting tick (unless of course, the Hardcore Protester still sees the Iceman in its line of sight).
    • f. Then the Hardcore Protester will immediately return.
  7. Otherwise, the Hardcore Protester will decrement its numSquaresToMoveInCurrentDirection variable by one. If the Hardcore Protester has finished walking its numSquaresToMoveInCurrentDirection steps in its currently-selected direction, then:
    • a. The Hardcore Protester will pick a random new direction to move in (up, down, left or right).
    • b. If the random direction is blocked either by Ice or a Boulder such that it can’t take even a single step in that chosen direction, then it will select a different direction and check it for blockage (it will continue checking directions until it has picked a direction that is not blocked).
    • c. The Hardcore Protester will then change its direction to this new chosen direction.
    • d. The Hardcore Protester will then pick a new value for numSquaresToMoveInCurrentDirection that will govern how far it should move in the selected direction over the next numSquaresToMoveInCurrentDirection non-resting ticks. The value must be such that: 8 <= numSquaresToMoveInCurrentDirection <= 60.
    • e. The Hardcore Protester will then continue with step 8.
  8. Otherwise, if the Hardcore Protester:
    • a. Is sitting at an intersection where it could turn and move at least one square in a perpendicular direction from its currently facing direction (e.g., it is currently facing left, and is at a junction where it could turn and move one step either upward/downward without being blocked by Ice or a Boulder), and
    • b. The Hardcore Protester hasn’t made a perpendicular turn in the last 200 non-resting ticks.
      Then the Hardcore Protester will:
    • c. Determine which of the two perpendicular directions are viable (a viable direction is one that allows movement of at least one square without the Hardcore Protester being blocked by Ice or a Boulder).
    • d. Pick a viable perpendicular direction. If both perpendicular directions are viable, then pick one of the two choices randomly.
    • e. Set its direction to the selected perpendicular direction.
    • f. Pick a new value for numSquaresToMoveInCurrentDirection that will govern how far it should move in the selected perpendicular direction over the next numSquaresToMoveInCurrentDirection non-resting ticks. The value must be such that: 8 <= numSquaresToMoveInCurrentDirection <= 60.
    • g. Remember that it made a perpendicular turn so that it doesn’t make another perpendicular turn for at least 200 more non-resting ticks.
    • h. Continue on with the next step.
  9. Finally, the Hardcore Protester will then attempt to take one step in its currently facing direction (which, by the way, might have just been changed by one of the last few steps above).
  10. If the Hardcore Protester is for some reason blocked from taking a step in its currently facing direction, it will set the numSquaresToMoveInCurrentDirection to zero, resulting in a new direction being chosen during the Hardcore Protester’s next non-resting tick (but not the current tick - the Hardcore Protester must do nothing during the current tick).

Note that only Ice and Boulders block a Hardcore Protester from moving. Hardcore Protesters can occupy the same square as all other game objects including the Iceman and other Protesters.

What the Hardcore Protester Must Do When It Is Annoyed

A Hardcore Protester can’t be further annoyed once it is in a leave-the-oil-field state - this means it can’t be squirted or bonked by Boulders while in this state.

When the Hardcore Protester is annoyed (e.g., by being squirted by the Iceman or being bonked by a Boulder), its hit-points should be decremented by the appropriate amount (the amount is specified elsewhere in this document).

If, after its hit-points have been decremented, the Hardcore Protester hasn’t been completely annoyed (to the point it wants to leave the oil field) then it must play a sound of: SOUND_PROTESTER_ANNOYED. It will then be “stunned” and placed in a resting state for N resting ticks, where:

1
N = max(50, 100 - current_level_number * 10)

Note: This essentially extends the Hardcore Protester’s normal resting state to N ticks.
Remember, that when in a resting state, the Hardcore Protester will immediately return when its doSomething() method is called.

On the other hand, if the Hardcore Protester’s hit-points reach zero or below due to being annoyed for any reason, the Hardcore Protester:

  1. Transitions into a leave-the-oil-field state.
  2. Plays an “I give up” sound effect: SOUND_PROTESTER_GIVE_UP.
  3. Sets its resting tick count to zero, ensuring that it will do something on the very next game tick, even if it was previously stunned or otherwise in a resting state.
  4. If the Hardcore Protester was annoyed due to being bonked by a Boulder, then it will increase the player’s score by 500 points.
  5. On the other hand, if the Hardcore Protester was annoyed into submission due to being squirted repeatedly, then it will increase the player’s score by 250 points.

What the Hardcore Protester Must Do When It Picks up a Gold Nugget

When a Gold Nugget has activated due to being stepped upon by a Hardcore Protester, it will notify the Hardcore Protester that it has picked up a Nugget. The Hardcore Protester must do the following in response to being notified that it has just stepped upon a Nugget:

  1. The Hardcore Protester plays an “I’m rich!” sound effect: SOUND_PROTESTER_FOUND_GOLD.
  2. The Hardcore Protester increases the player’s score by 50 points for the bribery.
  3. The Hardcore Protester will become fixated on the Nugget and will pause to stare at it (just as if he/she were in a resting state - doing nothing else) for the following number of game ticks: ticks_to_stare = max(50, 100 - current_level_number * 10)
  4. After the specified number of game ticks, the Hardcore Protester will become bored with the Nugget and continue to pursue the Iceman using its normal algorithm, as described above.