Graphic by Keith Ohlfs

CS111, Wellesley College, Spring 2000

Lab 5 Solutions

[CS111 Home Page] [Syllabus] [Lecture Notes] [Assignments] [Programs] [Documentation] [Software Installation] [FAQ] [CS Dept.] [CWIS]


The working code for the following solutions can be downloaded from:

http://cs111.wellesley.edu/~cs111/download/lab_solutions/lab5_solutions.

HurdleWorld Solution

Exercise 1: Running the Hurdles

In HurdleWorld, a hurdle is a 1 square-high wall; the finish line is the right-hand grid wall. A Hurdler object, which starts in position (1,1) runs a race by moving EAST, jumping over all the hurdles, and stopping at the finish line. The race may be decomposed into the possible situations which may occur as the Hurdler runs, and the action to take for that particular situation:

  1. If the Hurdler is at the finish line, stop.
  2. If the Hurdler is at a hurdle, jump over it.
  3. Otherwise, move forward one space.

The HurdleWorld.java file provides the following skeleton:

class Hurdler extends TickBuggle {

public void tick() {

// Flesh out this skeleton

}

public boolean atFinishLine() {

// Flesh out this skeleton

return /* true or false */;

}

public void jumpHurdle() {

// Flesh out this skeleton

}

}

Basically, the tick() method consists of an if-else construct which addresses each of the possible situations listed above:

public void tick() {

if (atFinishLine()) {

// Do nothing

} else if (isFacingWall()) {

jumpHurdle();

} else {

forward();

}

}

The atFinishLine() method is used by tick() to test if the Hurdler is at the finish line. The isFacingWall() method may be used to determine is the buggle is facing a wall; if so, the Hurdler must distinguish between whether the wall is a hurdle or the final wall. To do this, the Hurdler must move up one square and look to see whether it is still facing a wall. If so, it is the finish line, and the method should return true; otherwise, it should return false. In either case, an invariant is applied such that the Hurdler must be restored to its original position and heading before exitting the method.

In this solution, a local boolean variable result is declared at the start of the method. It is not immediately assigned a value, but is used later on to temporarily hold the value of true or false indicating whether or not the finish line has been reached. This local variable is used specifically because of the invariant which requires the Hurdler to be returned to its original position and heading after the result is determined.

public boolean atFinishLine() {

/*

Return true if buggle is facing wall (as opposed to hurdle) and false otherwise.

Should leave buggle in same position and heading as when it starts.

Should not leave any marks behind.

*/

boolean result;

if (isFacingWall()) {

brushUp();

left();

forward();

right();

result = isFacingWall();

left();

backward();

right();

brushDown();

} else {

result = false;

}

return result;

}

The jumpHurdle() method is simply the steps taken by the Hurdler is go up the hurdle, over it, down the opposite side, and finish facing EAST:

public void jumpHurdle() {

left();

forward();

right();

forward();

right();

forward();

left();

}

Exercise 2: TiredHurdler

The TiredHurdler is a Hurdler which can jump no more than a specified number of hurdles. The constructor method for TiredHurdler takes an integer that is the maximum number of hurdles the buggle will jump.

For instance, new TiredHurdler(3) creates a buggle that will jump no more than three hurdles. If it encounters a fourth hurdle, it will stop moving.

Here is the skeleton for a TiredHurdler class in HurdleWorld.java:

class TiredHurdler extends Hurdler {

private int hurdlesLeft;

public TiredHurdler(int hurdlesLeft) {

this.hurdlesLeft = hurdlesLeft;

}

}

public void tick() {

//Flesh this skeleton out

}

}

The class has an integer instance variable hurdlesLeft that keeps track of how many more hurdles the buggle is willing to jump. The constructor method for TiredHurdler initialize this instance variable to the number supplied when the instance is constructed. When a TiredHurdler runs the course, it should decrement this instance variable every time it jumps a hurdle, and refuse to jump a hurdle when this instance variable contains 0.

The solution is very similar to exercise one, but with an additional condition to test for and action to take:

  1. If the Hurdler is at the finish line, stop.
  2. If the Hurdler is at a hurdle, and hurdles are left (i.e. hurdlesLeft > 0), jump over the hurdle and decrement the number of hurdles left (i.e. hurdlesLeft = hurdlesLeft - 1)
  3. If the Hurdler is at a hurdle and no hurdles are left, stop.
  4. Otherwise, move forward one space.

This can be expressed with the following tick() method:

public void tick() {

if (atFinishLine()) {

// Do nothing

} else if ((hurdlesLeft > 0) && isFacingWall()) {

jumpHurdle();

hurdlesLeft = hurdlesLeft - 1;

}else if (isFacingWall()){

//do nothing

} else {

forward();

}

}

 

FollowWorld Solution

The FollowWorld problem can be broken down into three subproblems:

  1. How does the Follower figure out in which direction the next bagel is, if any?
  2. How does the Follower behave once she knows where the next bagel is?
  3. How does the Follower know when to stop searching for bagels?

Solving subproblem 1: Where's the next bagel?

For the Follower buggle to figure out if a bagel is in a surrounding cell, she must go to that cell and get the answer for isOverBagel(). The best way to do this is to write a method that will return the answer to the question to us. This method should also meet the invariant that the state of the buggle does not change before and after invoking the method and the environment does not change, either (no cells are painted, no bagels eaten or dropped). We will also work under the assumption that the buggle's brush is always down (unless we change it). The simplest case involves finding out if there is a bagel directly in front of the buggle. If there is a wall in front of the buggle, then there can not be a bagel in front of the buggle. Otherwise, the buggle will have to go into the cell in front and check to see if there is a bagel there. Since we don't want to paint our cells, the buggle should pick up her brush before moving and place it back down when she returns to her initial position. We can call this method isBagelToFront and specify that it will return a boolean value of true if there is a bagel in the cell in front and false otherwise. The code follows:

// This method returns true if there is a bagel in the cell in front of the buggle,
// else it returns false.
// This method maintains the invariant that the state of the buggle
// and the environment is not changed after the invocation of the method.
public boolean isBagelInFront() {
  if (isFacingWall()) {
    return false;   // can not have bagel in front if facing wall
  } else {
    brushUp();      // don't modify the environment
    forward();      // move to cell in front
    boolean result = isOverBagel(); // check for bagel, store result
    backward();     // move back to initial state
    brushDown();    // return brush state to initial state
    return result;  // return our answer
  }
}
 

If our buggle knows how to figure out if there is a bagel in front of her, she can figure out if there is a bagel to her left by turning left and then figuring out if there is a bagel in front of her. Similarly, to figure out if there is a bagel to her right, she can turn right and then figure out if there is a bagel in front of her. There is no need to check if their is a bagel behind her because to reach the current cell, there must have been a bagel behind her and she would have already picked it up before leaving the cell.Instead of copying the code for isBagelInFront into two new methods which tell us if there are bagels to our left and right, we should use the method in our new methods. This is the reason why we write methods, i.e. so that we can name a particular sequence of actions and use that name to invoke the sequence of actions instead of listing the sequence of actions over again. The essence of abstraction is giving names to actions and only worrying about how that sequence of actions works in only one place. Lets call our two new methods isBagelToLeft and isBagelToRight which we will define as follows:

// This method returns true if there is a bagel in the cell to the left of the buggle,
// else it returns false.
// This method maintains the invariant that the state of the buggle
// and the environment is not changed after the invocation of the method.
public boolean isBagelToLeft() {
  left();             // turn left
  boolean result = isBagelInFront(); // figure out if bagel in front
  right();            // undo the left turn
  return result;      // return our answer
}
 
// This method returns true if there is a bagel in the cell to the right of the buggle,
// else it returns false.
// This method maintains the invariant that the state of the buggle
// and the environment is not changed after the invocation of the method. 
public boolean isBagelToRight() {
  right();            // turn right
  boolean result = isBagelInFront(); // figure out if bagel in front
  left();             // undo the right turn
  return result;      // return our answer
}
 

Solving subproblem 2: How does a Follower behave?

A Follower is an extension of the TickBuggle which means that we define its behavior as a sequence of ticks. We can view each tick as the answer to the question "What do we do now?" where now is the buggle at each instance of time (ie in a particular cell). Looking at our initial condition, we see that the buggle is on top of a bagel which it must pick up. After that, we need to figure out where to go. If there is a bagel in front, the buggle should move forward. If there is a bagel to the left, the buggle should move left. If there is a bagel to the right, the buggle should move right. We can express the stategy above in the following code which we would put in the tick method:

pickUpBagel();
if (isBagelInFront()) {
  forward();
} else if (isBagelToLeft()) {
  left();
  forward();
} else if (isBagelToRight()) {
  right();
  forward();
} else { // no bagels around buggle
  // do nothing
}
 

There are two things to observe about the code above:
1. Since the buggle's brush is always down, it colors the cell that the buggle was just in which had a bagel.
2. We can leave off the else clause since it does nothing.

Solving subproblem 3: When does a Follower stop?

Now we need to move ourself into the last bagel on the trail and think like a buggle. Once we've arrived at that last cell with a bagel, we follow the strategy (code) from solving the second subproblem. We pick up the last bagel, and then we check for bagels all around us. Since we don't find any bagels around us, we don't move anywhere. We are still in that last cell with the bagel. For the next tick, we will again pick up a bagel. However, this won't work because there is no longer a bagel in the last cell (we picked it up in the last tick). So what do we do? We stop. We can tell if we're at the last cell because it won't have a bagel. If there was another bagel in the path, we would have moved on to it in the previous tick. So, our stop condition can be expressed in code as follows:

if (!isOverBagel()) { // not over a bagel
  // stop, do nothing
} else {
  // do the code from subproblem 2
}
 

From the notes on the first page of this problem set's solutions, we know that the above code can be written more compactly as follows:

if (isOverBagel()) {
  // do the code from subproblem 2
}
 

This code behaves exactly the same as the previous code block. If the buggle is not over a bagel, it will skip all the statements in the if clause and effectively do nothing.

Putting it all together: the tick() method

Our final solution includes the three auxiliary methods (isBagelInFront, isBagelToLeft, and isBagelToRight) we defined when we solved the first subproblem and a completed tick method defined below which includes the solutions we came up with when we solved the second and third subproblems.

public void tick () {
  // Override default tick method of TickBuggle class. 
  // Follow the trail of bagels until they have all been picked up. 
  if (isOverBagel()) {
    pickUpBagel();
    // decide on what's the next move
    if (isBagelInFront()) {
      forward();
    } else if (isBagelToLeft()) {
      left();
      forward();
    } else if (isBagelToRight()) {
      right();
      forward();
    } // else don't move if no bagels around
  } // else stop when not over bagel
}