Lecture 11

Agenda

Announcements

Back to Pong

Back to Pong for just a quick look.

Demo: Pong

The MovingBall class is included on the above page. Some things to notice, in case they weren't clear at the end of last class:

More on loops

Now that we have seen how important loops are in ActiveObjects, we step back and discuss more complex loops.

Here's an example where there is more than one variable can be changing in a loop. Consider the code to a constructor to draw a laundry basket, and demo of that basket:

Demo: BasketDemo

Notice that because botLineX increases more slowly than topLineX, the lines are not parallel.

The program below generates a picture of a scarf wherever the user clicks.

Demo: Knitting a Scarf

If you look carefully at the pictures generated, you will see that the scarf is formed by overlapping circles. It is easiest to develop this by first writing code to generate a row.

    // draws a scarf with upper left at point of click
    public void onMouseClick( Location point )
    {
        double x = point.getX();
        double y = point.getY();    // x and y positions of the next "stitch"
        
        int numCols = 0;
        
        while (numCols < SCARF_WIDTH) {
          new FramedOval(x, y, DIAMETER, DIAMETER, 
                         canvas).setColor(Color.green);
          x = x + X_DISP;
          numCols = numCols + 1;
        }
    }

Each time through the loop we increase the value of x as well as bump up numCols.

That wasn't too hard, but now we'd like to create successive rows. Each time we start a new row, there are a number of things that we will need to take care of:

  1. We need to reset the value of x so that we start drawing at the beginning of the row rather than where we left off.
  2. We to increase the value of y so that rows won't be drawn on top of each other.
  3. We need to reset numCols back to 0 so that it will keep the correct count when we restart drawing a row.
  4. We need to bump up numRows each time through.

Now all we need to do is to repeatedly execute the code for drawing a row by placing it inside an enclosing while loop. We've marked the new lines in the code below by marking them with "// new!".

    // draws a scarf with upper left at point of click
    public void onMouseClick( Location point )
    {
        double x = point.getX();
        double y = point.getY();    // x and y positions of the next "stitch"
        
        int numRows = 0;                 // new!
        int numCols = 0;
        
        while (numRows < SCARF_T) {      // new!
          while (numCols < SCARF_WIDTH) {
              new FramedOval(x, y, DIAMETER, DIAMETER, 
                             canvas).setColor(Color.green);
              x = x + X_DISP;
             numCols = numCols + 1;
          }
            
          x = point.getX();           // new!
          y = y + Y_DISP;             // new!
          numCols = 0;                // new!
          numRows = numRows + 1;      // new!
        }
    }

Exercises:

  1. Determine what would go wrong if each of the items listed above were omitted from the code.
  2. Modify the program so that the first row only has one circle, the second has 2, the third has 3, etc.

Suppose we wish to design a class to draw an American flag.

Demo: American Flag

A few things of note, some of which we have seen, others we have not:

  1. We use "++" to indicate that a variable's value should be incremented by one. Thus in drawStripes, each time through the loop, stripeNum++ increases the value of stripeNum by one.

    In general, x++; abbreviates x = x+1; if x is an int variable. A common mistake is to type x=x++;, which is wrong! This will end up not incrementing x, since x++ is a statement that increments x but returns is previous value! This means you increment x, then take the return value of x++ (which is the original x value) and assign that to x. So you haven't changed x after all!

  2. Private methods: The methods drawStripes and drawStars are called from inside the Flag constructor. They are not designed to be accessible from outside the class. They are designed only to be useful in breaking down the constructor into easier to understand pieces. drawStripes, is especially useful because it allows us to avoid duplicating code. Notice that it is used twice inside the constructor. Once to draw short stripes, and once to draw long stripes. Because we provide different parameters to it each time, it produces different results. If we did not use this private method, we would have to repeat the code in the method twice, once for each collection of values of the parameters.
  3. The drawStars method includes a nested while loop, with several items changing each time through the loop. In the outer while loop in drawStars, we must reinitialize both col and x each time through the loop.

Debugging Tips

As your labs become more complicated and your knowledge of the syntax of Java grows, you will discover that the type of errors you spend most of your time dealing with will change. The problems caused by errors involving the rules of Java grammar will decrease and the problems caused by subtle logical difference between what you told the computer to do and what you meant to tell it will increase.

The process of removing such logical errors is called debugging. In recognition of its growing importance we want to give you three tips on how to approach it. The first is a way to avoid complicated debugging problems in the first place, the second is a deep, guiding principle that us useful in debugging, and the third is a simple but very handy practical technique.

  1. Write and test your programs incrementally.

    Compile and test your code after adding small parts. Never wait until you have typed in several long methods before you start testing. This will make finding syntax errors easier, and often can give clues to potential logical errors while the problem is still reasonably small. If you do this often, you will know what you have changed recently, which helps make the next item easier.

  2. Localize the problem.

    The minute your program misfunctions, you will probably have some idea what is wrong. Suppose for example, that while you are working on Boxball, you start out by trying to write the code for the WindowController extension first (saving the Box and Ball for later). You would eventually reach a point where the game rectangle, the "drop" line and the level buttons were all drawn on the screen and the code to make the "easy", "medium" and "hard" buttons move the drop line was written. Suppose, however, that when you click on the buttons, the line does not move.

    You might attack the problem by carefully rereading the code you have written. This is a good idea, but it is often ineffective. When we write incorrect code, the error often springs from a basic misunderstanding of how our instructions will be interpreted. When we reread them, we stick with our misunderstanding and conclude that they should work as we had intended. In order to escape our misunderstandings, we have to uncover irrefutable proof that some small section of our code is just not doing what we expected. Then, we will have to admit that something about our understanding was wrong.

    The trick is to find the small section of code that will force one to recognize the mistake quickly.

    In the case of the Boxball example, there are several sorts of mistakes that could cause the problem.

    This is too many possibilities. As long as there are so many possibilities, the debugger will tend, while looking at the code related to one possibility, to overlook the error and assume on of the other possibilities must be at fault. Narrowing things down to a single possibility is generally the best way to force oneself to admit a misunderstanding.

    This brings us to the simple technique...

  3. The use of "System.out.println"

    Consider the BoxBall problem again. How can we narrow the search for the error down from several possibilities to just one? The answer is to try to rule out one possibility at a time.

    In this example, the easiest possibility to eliminate is the third -- that the correct "move" instruction is being executed but the wrong object is being moved. How can we tell if this is the case? One approach is to add another line to our program which a) will produce some visible output, and b) will be executed exactly when the move is executed. Item (b) can usually be accomplished by putting the two lines right next to one another in the program.

    Producing visible output isn't too hard either. We could construct a FilledRect or a Text. An even easier thing to do, however is to simply ask Java to print a line of text. Java has a separate window from the "canvas" in which such text appears. Each line displayed is just added to the bottom of the window. You don't have to specify a Location or a canvas.

    The syntax of the command used to display such text is:

    System.out.println("Move the line");
    

    To make this concrete, consider the following (very incorrect) attempt to write the button handling code.

          public void onMouseClick(Location point)
          
            //checks to see if the click is inside the court
            if (court.contains (point));
            
                    // Make sure the user clicked above the starting line
                    if (point.getY() < clickHeight);
                    
                            // create a ball
                    
            
    
            // Check if the user clicked on a button
            if (hard.contains(point));
            
                    clickHeight =  HARD_HEIGHT;
                    startingLine.moveTo (COURT_LEFT, clickHeight);
            
            if (medium.contains(point));
            
                    clickHeight = MEDIUM_HEIGHT;
                    startingLine.moveTo (COURT_LEFT, clickHeight);
            
            if (easy.contains(point));
            
                    clickHeight = EASY_HEIGHT;
                    startingLine.moveTo (COURT_LEFT, clickHeight);
            
          
    

    To begin to track down the problem with this program, we could add a simple "println" to the if that is supposed to handle a click on the "HARD" button:

            if (hard.contains(point));
            
                System.out.println("Moving to HARD level");
                    clickHeight =  HARD_HEIGHT;
                    startingLine.moveTo (COURT_LEFT, clickHeight);
            
    
    

    and then run the program and click on the HARD button to see what happens.

    If you do this, you will discover that a new window appears on the screen containing the message "Moving to HARD level". This is proof that the program did get to the move instruction as desired. Now, we get to work on eliminating other possibilities.

    For example, the value of clickHeight might be wrong. we can investigate this option by chaning the println to:

    System.out.println("Moving to height " + clickHeight);
    
    Now, the message displayed will include the value of clickHeight.

    Be prepared to use this trick while working on upcoming labs. It can be quite helpful.