Graphic by Keith Ohlfs |
|
The working code for the following solutions can be downloaded from:
http://cs111.wellesley.edu/~cs111/download/lab_solutions/lab5_solutions.
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:
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();
}
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:
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();
}
}
The FollowWorld problem can be broken down into three subproblems:
Follower
figure out in which
direction the next bagel is, if any?
Follower
behave once she knows where
the next bagel is?
Follower
know when to stop searching
for bagels?
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 }
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.
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.
tick()
methodOur 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 }