Mini-Lab:
A Whirl of Color
Using Conditional Statements
This set of Mini-Lab Exercises is the second in a series in which students
build a small program with several fish moving around in an aquarium.
The set includes the following exercises:
Each section contains an Introduction to a problem
or task, descriptions or examples of one or more
Concepts to apply in solving the problem or
completing the task, and an Exercise.
In the exercises that precede this one, students created three fish, moved
them forward one step, and displayed them graphically. Therefore, students
should be familiar with constructing objects, using variables, and invoking
methods. Some familiarity with logical expressions is also required
for this set of exercises.
About Face!
Introduction
In our current program, fish move to the right and then get stuck at the right
wall. This does not make sense. We can use a Simple
Conditional statement to determine when a fish should turn
around and start swimming the other direction.
Concept: Simple Conditional
When writing a computer program, there are some actions that we
only want to perform some of the time, when a certain condition is
true.
For example, consider a program that simulates a student
getting lunch in a cafeteria. In this case we will simulate a student
who has a sandwich and coffee every day, but almost never eats
dessert. The one exception is when the cafeteria has lemon squares,
this student's favorite.
Example 1: Condition with Single Statement |
Example 2: Condition with Single Statement |
Example 3: Condition with Multiple Statements |
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
student.sitDownAndEat();
|
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
{
student.takeALemonSquare();
}
student.sitDownAndEat();
|
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
{
student.takeALemonSquare();
student.takeALemonSquare();
}
student.sitDownAndEat();
|
Note that an if statement takes just a single
statement as the one to do when the given condition is true.
If there are several statements to perform as part of the condition,
you must use curly braces ( { } ) to group them into a single
block of statements, as in Example 3 above. You can also use
curly braces around a
single statement, as in Example 2, for consistency or clarity.
Stop and Think
- Which statements in Example 1 will be executed when there are
no lemon squares? In what order will they be
executed? Which statements will be executed, and in what
order, when there are lemon squares?
Exercise
- In your previous testing of the program you may or may not have
noticed that fish swim only to the right and get stuck at the right
wall. To verify the problem, make a copy of the statement that sets
the dimensions of the aquarium.
(Stop and Think:
where is the
statement that sets the dimensions of the aquarium? In what
class, and what method?) "Comment out" the original, and change
the dimensions in the copy to be 100 x 200. Copy the code that moves
and redisplays the fish to let them move a second time. Now run the
program several times and make sure you see the problem.
- Research the AquaFish
specification to find out how to determine whether a fish is at a
wall and how to make it reverse direction.
Modify the
AquariumController class to
have each fish check whether to change direction
whenever it moves forward.
(Stop and Think:
You could check whether to reverse direction or not and then
move forward, or you could move forward first and then check
whether to reverse direction. Does the order
matter? Consider three cases: a) for a fish that
was constructed along the left wall, b) for a fish that was
constructed in the middle of the aquarium, and c) for a fish
that was constructed along the right wall.)
Test your program in the narrower aquarium you created
above. When you are satisfied that your program is
behaving correctly, restore the aquarium to its original
size.
|
Mixing It Up
Introduction
The runProgram method in the
AquariumController class is starting to get long and
tedious. Furthermore, as the program evolves, it is not clear that the
right classes are taking responsibility for the right actions. The
AquariumController object wants to tell all of the fish to
move, but why is it concerned with the details of how a fish
moves, such as when it should reverse direction?
Concept: Refactoring
As a program evolves, it is not uncommon to decide that a different
way of organizing the classes or methods in the program would make it
easier to read, to understand, or to enhance. It is not a good idea
to change the structure of existing code at the same time as adding
new functionality, because if the program stops working correctly it
will be difficult to know whether it is the restructured functionality
or the new functionality that is the cause of the problem. It is
better to rearrange or redesign the existing code first, test that
it still works correctly, and only then add any new functionality.
The restructuring or redesigning phase, without changing the program's
behavior, is called refactoring.
Exercise
- Look through the
AquaFish class. Just
before the moveForward and
changeDir methods, there is a commented-out
move method. Uncomment the move
method and then modify it so that the fish checks
whether to change direction whenever it moves forward.
This is similar to the check that you just added to the
AquariumController class, but not exactly the
same.
(Stop and Think:
In the AquariumController class, what
is the object associated with the calls to the
moveForward, atWall, and
changeDir methods? Will the same object be
responsible for those tasks when the calls move to the
newly uncommented move method? How would
that be represented in the AquaFish
class?)
- Modify the
AquariumController class to use
the new move method rather than separate calls to
moveForward, atWall, and
changeDir. Test your changes by running
the program several times and making sure that the
program behavior is consistent with what it was before
the refactoring.
- Now that
moveForward, atWall, and
changeDir are only used internally in the
AquaFish class, change the two modifying
methods (moveForward and
changeDir) to be protected
rather than public. (There are
commented-out lines that do this already there. You
could uncomment them and delete the other lines that were
being used instead, or you could change the
public keyword to protected
in the current lines, using the commented-out lines as a
template. (The exact meanings of the
public and protected keywords
will be the focus of a future lab.)
|
One Fish, Two Fish, Red Fish,
Blue Fish
Introduction
Our aquarium is a little boring, since the fish are all the same color. We could
specify the color of each fish as we construct it, giving each fish a different
color.
Concept: Two Alternatives
Sometimes there is a situation in which one of two actions is
appropriate, depending on whether a particular condition is true or
not.
When the condition holds you want to do one action; otherwise
you want to do the other.
Consider a variation on the cafeteria lunch example from above.
Rather than simulating a student who only eats dessert when there are
lemon squares on the menu, we might simulate a student who eats
dessert every day, choosing lemon squares when they are available and
cookies when they are not. (Cookies
are always available in this cafeteria.)
The new behavior appears below.
| Example: Two Alternatives |
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
else
student.takeACookie();
student.sitDownAndEat();
|
Note that the else line does not include a conditional
expression. It is unnecessary (because it can be deduced
from the if expression), and, as a result,
it would not be legal Java code.
Concept: Positive Condition
It is often possible to write a conditional expression as either a
positive or negative expression. Often programmers write down the
more common case first, even if that means writing the conditional
expression in the negative, as in the second example below. Positive
expressions are generally easier to read, however, especially if
the conditional expression is somewhat complicated.
| Positive Condition (Preferred) |
Negative Condition |
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
else
student.takeACookie();
student.sitDownAndEat();
|
if ( ! caf.hasLemonSquares() )
student.takeACookie();
else
student.takeALemonSquare();
student.sitDownAndEat();
|
Exercise
- Look through the
AquaFish class. Where is
the fish color set? Modify the class so that the fish color
is set to Color.RED for the first fish (the
fish whose ID is 1), and to Color.BLUE for all
other fish.
(Color.RED and Color.BLUE are
constant Color values defined in the Java Color
class.) Make sure to use a positive conditional expression.
- If your program does not already construct at least three
fish, modify it so that it does. (Where would this
modification be made?)
- Identify in advance what behavior you expect from your
program when you test it. Do you know which fish will be
which color? Do you know how many fish you should get of each
color? Test your program to make sure that your results
are what you expect.
- Just for fun (and only if you have time):
Modify your program so that all fish with odd ID numbers are
red, and all fish with even ID numbers are blue. The
% operator, which calculates the remainder when
doing integer division, is useful for this exercise.
Construct several additional fish to test this modification.
|
Rainbow Fish
Introduction
Why should there only be two colors of fish in our aquarium?
We can use Multiple Alternatives to
add diversity of color when constructing and displaying fish.
Or, to make things more interesting, we could decide on the color of each
fish based on a random number.
We can use the
standard Java Random class to do this.
Concept: Multiple Alternatives
Sometimes there are several alternative actions, only one of which
should be performed, depending on a set of conditions.
For example,
consider another variation on the cafeteria lunch example from above.
This time, we will simulate a student who likes a variety of desserts,
but has strong preferences for some over others. The student's
favorite is lemon squares, followed by apples, followed by apple pie.
If none of these desserts are available, the student always chooses
cookies, which are always available. The table below
summarizes the three different types of conditional statements seen in
this mini-lab, with
the new behavior in the third example (Multiple
Alternatives).
| Simple Conditional |
Two Alternatives |
Multiple Alternatives |
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
student.sitDownAndEat();
|
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
else
student.takeACookie();
student.sitDownAndEat();
|
student.takeASandwich();
student.takeCoffee();
if ( caf.hasLemonSquares() )
student.takeALemonSquare();
else if ( caf.hasApples() )
student.takeAnApple();
else if ( caf.hasApplePie() )
student.takeApplePie();
else
student.takeACookie();
student.sitDownAndEat();
|
Exercise
- Modify your program to create fish with the colors of the rainbow.
Use the fish's ID number to decide which color to use.
| ID Number |
Color |
| 1 |
Color.RED |
| 2 |
Color.ORANGE |
| 3 |
Color.YELLOW |
| 4 |
Color.GREEN |
| 5 |
Color.BLUE |
| 6 |
Color.MAGENTA |
(This table uses Color.MAGENTA
because there are no Color constants for indigo
and violet.)
- Test your program to make sure that your results are what you expect.
(What results were you expecting? What tests are necessary to make
sure the results are what you expect?)
- Make sure that you have updated the program documentation at the
top of the file to reflect your modifications.
- Just for fun (and only if you have time):
Modify your program so that all fish whose ID numbers when
divided by 6 have a remainder of 0 are constructed red, if
they have a remainder of 1 they are constructed orange, etc.
Construct additional fish to test this modification.
|
Leaving the Color to Chance
Introduction
We could get a wider array of fish colors by choosing a random color for
each fish, rather than setting each fish to one of six pre-defined
colors.
Concepts: Random Numbers,
Saving Returned Values in Variables
One of the standard Java packages,
java.util,
includes a Random class that can
be used to create what are known as pseudo-random numbers.
Pseudo-random numbers are as close as computers can get to truly random
numbers; they look and work like random numbers, although they are
generated from computer algorithms and so are not truly random. The
nextInt method in the Random class returns a new
random integer, which can be saved in a variable. For
example, in the following code
Random generator = new Random();
int randNum = generator.nextInt(10);
the random numer generator called generator will randomly
generate one of the 10 numbers between 0 and 9 (because the parameter to
the nextInt method is 10), and save it in the
integer variable called randNum. If the parameter to
the nextInt method were 100, it would
randomly generate one of the 100 numbers between 0 and 99.
Consider modifying the cafeteria lunch example to simulate a student
who eats dessert everyday, randomly choosing from four different
desserts. The code in Example 1 below gets a random number
from 0 to 3, saves it in a variable, and then uses the value to
determine which dessert to take. Each dessert may be chosen with
equal probability. (What does the code in Example 2 do?)
| Example 1 |
Example 2 |
Random generator = new Random();
student.takeASandwich();
student.takeCoffee();
int randNum = generator.nextInt(4);
if ( randNum == 0 )
student.takeALemonSquare();
else if ( randNum == 1 )
student.takeAnApple();
else if ( randNum == 2 )
student.takeApplePie();
else
student.takeACookie();
student.sitDownAndEat();
|
Random generator = new Random();
student.takeASandwich();
student.takeCoffee();
int randNum = generator.nextInt(10);
if ( randNum < 4 )
student.takeALemonSquare();
else if ( randNum < 7 )
student.takeAnApple();
else if ( randNum < 9 )
student.takeApplePie();
student.sitDownAndEat();
|
Concept: Color Representation
Colors are commonly represented as a mixture of a certain
amount of red, a certain amount of green, and
a certain amount of blue (this color representation is called
RGB, for red, green, and blue).
Each individual color amount is represented by a number between 0 and
255: black is (0, 0, 0) because it has no red, green, or blue; white is
(255, 255, 255) because it is made up of the maximum amount of red,
gree, and blue; pure red is represented as (255, 0, 0); while orange,
which is made up of lots of red, quite a bit of green, and no blue, is
represented as (255, 200, 0). New colors can be constructed in Java by
passing the appropriate amounts of red, green, and blue to the
Color constructor, as in the example below:
Color myColor = new Color(255, 0, 255);
Exercise
- In the
AquaFish constructor, create three
variables to represent a random amount of red, green, and
blue, and randomly set each to a value between 0 and
255. (Stop and Think: how many
numbers are there between 0 and 255?)
(Note: Notice that the AquaFish class has an
import java.util.Random; statement above the
class definition; this allows you to use the
Random class without specifying the package it
comes from every time.)
- Set the fish color to a random color constructed from
the three random values for red, green, and blue.
- Test your modifications.
Stop and Think
- In Example 2, for what values of
randNum would the
student take a lemon square? An apple? A slice of apple pie? What
does this code do when randNum has the value 9?
- Overall, what behavior does Example 2 in the table above simulate?
Express your answer in terms of percentages.
- How would the behavior of the code in Example 1 be
different if the first two
else keywords were left out;
in other words, if
it were a sequence of separate, independent conditional statements?
Could you write code that would have the same behavior as Example 1
without any else keywords?
- How would the behavior of the code in Example 2 be
different if both
else keywords were left out;
in other words, if
it were a sequence of separate, independent conditional statements?
Could you write code that would have the same behavior as Example 2
without any else keywords?
- How would the behavior of the code in Example 1 be different if
it repeatedly called the
nextInt method and checked
its return value against different values
instead of calling nextInt once,
saving the random number in a variable,
and checking the variable against different values? In other words,
if there were no randNum variable, and each conditional
expression called the nextInt method instead?
|