Mini-Lab: More Fish!

Using Vectors

This set of Mini-Lab Exercises is the fourth 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, (usually) abridged versions of one or more Patterns that will be useful in solving the problem or completing the task, and an Exercise.

In the exercises that precede this one, students will have created three fish of various colors that move randomly back and forth in an aquarium, being careful not to hit the sides. Students should be familiar with basic for loops, simple selection statements, prompting for input, and the RandGen class.

We Want More!


A more realistic simulation of an aquarium would have more than two or three fish. We could modify the simulation so that it supports four fish, five fish, or twelve fish, but if we "hard-code" the number of fish into program in this way, then we must modify and re-compile the program to change the number of fish in the aquarium. We must also repeat statements, such as the code to display and move fish, four, five, or twelve times.

A better alternative would be to ask the user how many fish to place in the aquarium and then store them in a collection object. We can use a Linear Indexed Data Structure to store the collection of fish.


Prompt the user for the number of fish they want in the aquarium. Replace the three fish you constructed earlier with a Fixed-Size Linear Data Structure of the right size to store the number of fish the user wants. What does this data structure represent? Choose a Role-Indicating Name for it. Don't forget to include the appropriate header file (apvector.h).

Stop and Think

Where should you construct the data structure representing the collection of fish? Do you need to know the size when you construct it? When do you know the size?

It is always difficult to test modified code part-way through the change. At this point, you have constructed a variable-sized collection of fish rather than three specific fish, but the rest of your program still expects three named fish. In order to test that you have not introduced any syntax errors into your program, "comment out" the code that displays and moves the three fish (who no longer exist) by placing the appropriate code in comments. For example,

        d.Show (fish1);
        .    more code to display and move fish
        d.Show (fish3);
(Don't be surprised when your program no longer moves or displays fish!)

Fish Out of Water


Until now, we've been creating our fish in the context of an aquarium (so that they have a valid position). We've done this by passing the aquarium object to the AquaFish constructor. The individual objects in a Fixed-Size Linear Data Structure, however, are constructed with the default constructor, that is, the constructor that doesn't take any parameters. This means that our fish aren't in an aquarium and there are a number of AquaFish methods that we can't use with them. (We see this by looking at the precondition on the AquaFish XCoord, YCoord, MoveForward, and ChangeDir methods; fish must be in an aquarium before these methods are called.)

To solve this problem, we will replace the default AquaFish in the apvector with properly constructed AquaFish. Then we will partially test our modified code by displaying some fish. Rather than trying to replace and display all the fish in the aquarium right away, though, we will start by concentrating on the first and last fish in the collection. We can no longer refer to each fish by name, but we can use an Indexed Random Access to refer to any particular fish in our Linear Indexed Data Structure.


The default fish in the new apvector of fish are useless to us because they are not in an aquarium. You will want to replace them immediately with fish created with the appropriate constructor. Rather than replacing them all, however, we will focus on just replacing the first and last fish in the collection in order to practice using Indexed Random Access into a Linear Indexed Data Structure.

Modify your program to replace the first and last fish in your collection with a properly constructed and initialized AquaFish with appropriate constructor parameters. You can do this using the following syntax (assuming that you named your collection fishList):

       fishList[0] = AquaFish (aqua, "Red");
(Question: What is the index of the last fish in the collection?)

Next, remove the comments around the initial display of the aquarium and fish. Replace the statements displaying the three named fish with statements displaying the first and last fish in the collection. If you are using a graphics display, you may want to change your call to Pause back to WaitNClear. (Or just comment out the call to Pause, since you will probably want to go back to it later.)

NOTE: Keep the comments around the code that moves fish and redisplays them.

Home, Sweet Home


Now let's replace all the fish. We can use a Linear Indexed Traversal to step through and process each item in our Linear Indexed Data Structure.


Step through the Linear Indexed Data Structure of fish using Linear Indexed Traversal, and set each one to be a newly constructed fish, initialized with both the aquarium and a color. (For now, you may set all the fish to a single color.)

Research the Display interface to discover how to display an indexed collection of fish. Update the initial display of fish in the aquarium to display all the fish in the collection. Test that the initial display works.

A School in Motion


Now let's think about how to move and display all the fish for as many time steps as the user wants.

It seems clear that we will want to loop through the steps in the simulation, as we are already doing. It also seems clear that we will want to loop through all the fish. The question is:

Processing all the fish and all the steps in the simulation are not independent of one another. That is, we can't process all the fish and then process all the simulation steps, or vice versa. Either processing all the fish is part of what we do in one step of the simulation, or running all the steps of the simulation is part of what we do for each fish. Thus, we will need to nest one of the loops inside the other.

To decide which loop gets nested inside of which, consider the following algorithmic structures in which we assume that we have 25 fish in the aquarium and we want to run the simulation 100 times.

For each fish in the collection:
        Move 100 times and display the aquarium.
For each step in the simulation:
        Move the 25 fish once and display the aquarium.

We do not want the first fish to move 100 times, followed by the second fish moving 100 times, followed by the third fish moving 100 times. Instead, we want all 25 fish to move once, then all 25 fish to move again. This corresponds to the second solution above.

Another question we have to consider is where the display of the aquarium should be relative to the fish movement. Do we want to display the fish and aquarium in the outer loop (as part of each simulation step), or in the inner loop (as part of processing each fish)? The following table illustrates these two options.

For each step in the simulation:
        For each fish in the collection:
                Move, possibly changing direction.
        Display aquarium & fish.
For each step in the simulation:
        For each fish in the collection:
                Move, possibly changing direction.
                Display aquarium & fish.

Which behavior do you wish to implement?



Remove the remaining comments around the code that runs through the steps of the simulation, moving and displaying the fish in the aquarium.

Inside the simulation loop, replace the code that moves the three named fish with a loop that will move all the fish in your collection. (Each will still change direction when it has to or when it randomly chooses to.) Use a Linear Indexed Traversal through your Linear Indexed Data Structure of fish.

Display all the fish in the aquarium with a single statement, as you did at the beginning of the program. Where does this statement (along with the redisplay of the actual aquarium and the call to Pause or WaitNClear) belong? Should it be part of the inner or outer loop?

Test your modifications.

Houdini Fish


As you have tested the programs you have written, you have undoubtedly encountered syntax errors. These are errors that the compiler can catch for you, such as misspelled variable names or missing parentheses and semi-colons. It is also possible for a program to have logic errors. A program with logic errors will correctly compile, and will even correctly do what you programmed it to do, but what you programmed it to do is not what you meant for it to do.

Our aquarium program has a subtle logic error that may have gone unnoticed when we had fewer fish. Fish in this program are always constructed facing right, so any fish constructed at the left wall of the aquarium will not be facing the wall. Thus, it will not automatically change direction before moving forward. Like any other fish that is not facing a wall, though, it may randomly choose to change direction before moving forward, in which case it will turn around and swim right through the left aquarium wall. We need to fix the logic in our program to prevent such "Houdini fish" from escaping.


Run your program several times, until you witness the logic error described above. For example, run the simulation for 3 - 5 steps (or even 1 step) with 25 fish. You may see the error immediately, or you may have to run the program 5 - 10 times. (If you always get the same 25 fish in the same locations, then you have initialized your random number generator with a seed. Double-check the RandGen interface, and modify your program to construct your random number generator without a seed.)

Modify your program to correct the logic that controls whether a fish changes direction before moving forward. A fish should change direction if either of the following conditions is true.

  • It is facing a wall and, thus, cannot move forward in the current direction.
  • It is not against a wall and it randomly chooses to change direction.
The logic for this condition embodies both an OR condition and an AND condition, so you will need to be careful about how you construct the selection statement and the logical expressions in it.

Copyright Alyce Faulstich Brady, 1999.