CS 161 Lab #4

February 8th

Goals:

This week you'll spend time with your old friend Boulder to get lots more practice with conditionals (if statements) and a bit of experience with object references at the end. You can refer to the Conditionals code from class for lots of examples of if statements, and this code for the work we did this week with object references.

Getting Started

  1. Take a moment to introduce yourself to your partner(s). After social pleasantries are complete, pick one member of the team to be the "typer". They'll share their screen while editing the lab code in BlueJ. (I think BlueJ works better for these interactions than Eclipse. It's easier to see when sharing screens, and makes it easier to quickly test individual methods than Eclipse does.) Group members should contribute equally while working through the problems below and discuss all code to be written, though only the "typer" will be able to edit code. Resist the temptation to have both members work simultaneously in BlueJ — you're much more likely to "drift apart" over the course of the lab if you do so. The goal here is to have a partner who's engaged on exactly the same step of the lab as you are.

    Consider letting the less experienced member of the group do the typing if it seems like there's a mismatch in experience or comfort levels. That will help make sure they don't get left behind. Worst case, flip a coin to see who does the typing. Or have Java generate a random boolean value. (Type "(new java.util.Random()).nextBoolean()" in the codepad without the double quotes.)

    At the conclusion of the lab, make arrangements for the typer to share a copy of the code with the other member(s) of the group if desired. (E.g. email it, or put it on a shared Google drive, etc.) My solutions to the lab will get posted as well.

  2. Download the BouncingBoulders2 project and extract its contents, then start BlueJ and open the project.
  3. Take a moment to familiarize yourself with the Java code in the Boulder class. It looks a lot like the solution to the last lab, but with some extra if statements in some of the methods.

Fancier Status Reports

  1. Take a look at the printStatus method. It's been updated so that it prints a warning if xVelocity or yVelocity are greater than 50. Do some tests to make sure you understand how that code works. For example, create a "slow" Boulder and verify that neither warning appears. See if you can create a Boulder where only the warning about horizontal speed appears. Finally, note that it's possible to get two warnings for the same boulder if both velocities are too high:
    > Boulder b = new Boulder(300,300,75,75,40);
    > b.printStatus();
    Boulder is on a canvas that's 800 by 600
    It's at 300.0, 300.0 with velocity 75.0, 75.0
    Horizontal speed is pretty high!
    Vertical speed is pretty high!
    
  2. Try to rewrite the code in printStatus so that at most one warning message ever prints, but if both velocities are too high you get a special message to that effect. For example, in the case shown above you'd get a message like "Both the horizontal and vertical speeds are high!" instead of two separate messages, but if only one of the velocities is too high you get the same messages as before. Below is a diagram showing one possible way to organize the new code, though you'll need to use the && operator to perform the logical and of two comparisons.

    Note: On the final case in the bottom-right corner of the diagram, there's nothing to print. That suggests that the if statment checking whether yVelocity is too high doesn't really need an else, though it would work if you had an else with no statements in its body. Once you've modified the code, test it thoroughly to make sure it does what it's supposed to do:

    > Boulder b = new Boulder(300,300,75,75,40);
    > b.printStatus();
    Boulder is on a canvas that's 800 by 600
    It's at 300.0, 300.0 with velocity 75.0, 75.0
    Both the horizontal and vertical speeds are high!
    > b = new Boulder(150,150,75,40,40);
    > b.printStatus();
    Boulder is on a canvas that's 800 by 600
    It's at 150.0, 150.0 with velocity 75.0, 40.0
    Horizontal speed is pretty high!
    > b = new Boulder(150,150,40,75,40);
    > b.printStatus();
    Boulder is on a canvas that's 800 by 600
    It's at 150.0, 150.0 with velocity 40.0, 75.0
    Vertical speed is pretty high!
    
  3. One more step and then we'll leave poor printStatus alone: Add some additional code that reports whether the boulder is visible or not. You can access the isVisible field to help you make that decision. Your new code should always print exactly one line of additional output — either a line of output stating that the boulder is visible, or a line stating that it's not visible. Think about a plan before adding the code: Will you need two separate if statements to add this new functionality? One if statement with an else? And how will this new code fit in with the conditionals you just added in the step above? Does this need to be "else'd on" to the if statements you already wrote? Tucked into one of their cases? Or should this be its own independent chunk of code?
    > Boulder b = new Boulder(300,300,75,75,40);
    > b.printStatus();
    Boulder is on a canvas that's 800 by 600
    It's at 300.0, 300.0 with velocity 75.0, 75.0
    Both the horizontal and vertical speeds are high!
    The Boulder is not visible
    

Code Red

That was some good practice! Let's do some more. The next goal is to change a Boulder's color to red if it gets too close to the wall, and back to black again once it's at a safe distance. I've gotten a start on the code, but I need your help...
  1. Open the Boulder class and take a look at the updatePosition method. Before you do anything else, take a look at the code that's causing boulders to wrap around after they move off of the edge of the screen. It's good practice to read some if statements and understand their logic. Why does the outer if have an else, but the inner if doesn't? How does that influence what the code does? Why is there a separate if statement for adjusting yPosition rather than joining it to the first if statement with an else? Ponder these questions with your partner before proceeding. Ask for help if you want someone to come talk it through with you.
  2. Scroll down and look at the third if statement in the method. It's my attempt to change the color if the boulder gets within 100 pixels of the edges of the screen, but it's only checking the X position right now, not the Y position. Once you digest the code, run some tests to verify that the colors change as expected, or run the test I wrote for you in BouncingBoulders: Create a BouncingBoulders object and run its runTest() method. Watch what happens when boulders get too close to the edges.
  3. Before proceeding, take a moment to simplify the color-changing if statement. Notice that it changes the color to red if xPosition is too close to the left wall, or if the xPosition is too close to the right wall. That should give you an idea... You could use the Boolean or operator (||) to build a more complext test that evaluates to true if the xPosition is too close to the left wall or the right wall. Then the if statement would only need two cases instead of three. Test your improved version before proceeding, and ask for help if you're having trouble making the changes.
  4. As mentioned above, the current version of the code only watches the X position when considering the color, but it really should change color based on the Y value as well. For example, if we get too close to the top of the window the boulder should change red even if it's nowhere near the left or right edge of the window. As a first attempt at adding this behavior, copy your current color-setting if statement and paste in a duplicate copy right below it (but still above the draw() call). Edit the new if statement so that it considers the Y position instead of X. Ask for help if you get stuck or, if you require immediate gratification, you can peek here for a spoiler. Run the runTest() method again and see what happens. Watch it carefully...
  5. You probably noticed that the boulders now misbehave as they approach the right wall. They "flash", but stay black even when they get close to the right edge of the window. If you watch long enough, you'll see they eventually turn red, but that's only once they get close to the top or bottom of the screen! Go back and look at your code. It used to work properly as boulders approached the right wall, and the code for checking the X position hasn't changed. Can you see what's gone wrong? Ask for help or confirmation if you're not sure.
  6. Now that you've figured out the problem, try to fix it! There are several possible ways to do that. One involves migrating the Y-checking if statement inside the X-checking if statement. The logic would look something like this:

    Or, you could simplify this like you did at the start of this section: Use an || to create a more complex test, and reduce the three cases in the diagram above to the two cases below:

Comparing Boulders

Now let's practice writing methods that take other Boulder objects as inputs, and get a bit more practice with conditionals while we're at it. We haven't talked about passing objects as parameters in class yet, but there's not all that much to it.
  1. Go find the overlaps() method and make sure you understand how it works: It takes a reference to a Boulder object and returns true if that boulder overlaps us on the screen. You can see the syntax for taking an object reference as a parameter — you just use Boulder as the type of the parameter. Also note how we "look inside" the other boulder: Just use the . operator to "follow" the object reference and refer to an item inside it. Try it out to make sure you know how to call such a method. (Note, in the codepad interactions below, that there's no ; at the end of the calls to overlaps. If you add a ; it will hide the returned result.)
    > Boulder b1 = new Boulder(100,100,10,10,30);
    > Boulder b2 = new Boulder(110,90,10,10,30);
    > b1.overlaps(b2)
        true   (boolean)
    > b2.overlaps(b1)
        true   (boolean)
    
  2. Now find the similar() method. Like overlaps(), it takes a reference to another boulder as an input, though at the moment it just returns false. As a warmup, change the definition of similar() so that it returns true if the other boulder's diameter is the same as ours (and false if they differ). Test your method before continuing.
  3. Now modify the definition of similar() so that it returns true if two out of three of these are the same: xPosition, yPosition, and diameter. Make a plan before you start writing code, and don't forget about && and ||.

Extras

If time permits, try some of the following exercises:


Brad Richards, 2024