CS 261 Lab #2

January 24th
Zoom meeting room: 988 439 5089

Goals:

This week you'll get some intense interactions with inheritance, then proceed to prodigious practice with polymorphism. You'll work with a family of related Die classes that model different flavors of dice, first exploring issues with constructors, then experimenting with the "types as contracts" idea from class, and finally working with polymorphic methods in the DieRoller class.

Partners

In each lab this semester you will work with a randomly assigned partner. (I'll have Zoom randomly set up breakout rooms.) Please be kind in your interactions with your partners! Keep in mind that students in this class have a range of previous programming experience, and that some have been college students for longer than others. We're all in this together, and you have something to learn from your partner, no matter who they are or what their previous experiences have been. I expect that group members will collaborate and work together on each step of the lab. Please consider leaving cameras on to aid in communication and help humanize your collaborator(s).

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.)

    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. (E.g. email it, or put it on a shared Google drive, etc.) My solutions to the lab will get posted as well.

  2. Have the typer download the DieInheritance project and extract its contents, then start BlueJ and open the project.
  3. Take a moment to familiarize yourselves with the classes in the project. The BasicDie class contains the basic functionality of a die — you can create a Die with any number of sides, then roll() it to get a random result. The HistoryDie class extends BasicDie such that it maintains a history (it counts how many times each output value has been produced). CrookedDie introduces new behavior: Every third roll, an instance of this class "cheats" and rolls the die's maximum value. Create some instances of these classes and experiment with them until you're familiar with their behavior.

Predicting Behavior

Before we write any code, let's try to predict how some code involving the Die classes will behave, based on what we know about subtyping. For each of the sections below, decide what you think the expected behavior will be. Then, once done with a section, test them in the codepad to see if you were right. Assume that the following three variables have been declared:

BasicDie basic;
HistoryDie history;
CrookedDie crooked;

  1. Which of the following assignment statements are legal? (That is, which of them would compile, or run in the codepad without error?) If in doubt, you can always try them out in BlueJ, but make sure you understand why things came out the way they did!
    Code Legal?
    basic = new BasicDie(6); 
    history = new CrookedDie(6); 
    crooked = new BasicDie(6); 
    crooked = new HistoryDie(6); 
    basic = new CrookedDie(6); 
    history = new BasicDie(6); 

  2. If we set basic = new HistoryDie(6); which of the following method invocations are legal?
    Code Legal?
    basic.getNumSides() 
    basic.printHistory(); 
    basic.getNumLies() 
    basic.toString() 
  3. If we set history = new CrookedDie(6); which of the following method invocations are legal?
    Code Legal?
    history.getNumLies() 
    history.roll() 
    history.printHistory(); 
    history.getNumSides() 
  4. If we set crooked = new CrookedDie(6); which of the following method invocations are legal?
    Code Legal?
    crooked.roll() 
    crooked.printHistory(); 
    crooked.getNumLies() 
    crooked.getNumSides() 

Constructors

  1. Add a print statement at the bottom of each of the constructors in the three Die classes so that you can see when each of them runs. (Make them unique, so you can tell exactly which constructor is being called.) Try creating instances of each of the three Die classes. Do the expected constructors run in each case?
  2. Comment out the constructor in HistoryDie and recompile the code. Can you still create an instance of HistoryDie now? Do other constructors run as well? Un-comment the constructor when you're done with this step, so that the code is back to its original state.
  3. Comment out the constructor in CrookedDie and recompile the code. Can you still create an instance of CrookedDie now? How is this scenario different from the previous exercise with HistoryDie? Un-comment the constructor when you're done with this step, so that the code is back to its original state.
  4. We can create a CrookedDie via its one-argument constructor — the one that takes the desired number of sides as a parameter — but it's not possible to create a CrookedDie using a no-argument (default) constructor. Modify the class so CrookedDie also has a no-argument constructor that creates a CrookedDie with six sides. Feel free to modify other classes as well, but try to accomplish the goal without making numSides public or protected in the BasicDie class.

Polymorphism

  1. Open the DieRoller class and take a look at the rollRepeatedly method. We know from our discussion of subtyping that while the method's parameter is of type BasicDie, that really means it will accept an instance of BasicDie or any of its subtypes. Using parameters in this way is called "polymorphism", and we'll talk more about it in class. Convince yourself that rollRepeatedly truly is polymorphic by passing it instances of each die class. Below I'm showing what I got when I passed the method a CrookedDie, a HistoryDie, and a BasicDie, in turn. (Lines beginning with ">" are lines I typed into the codepad, with the corresponding outputs below them.)

    > DieRoller dr = new DieRoller();
    > dr.rollRepeatedly(new CrookedDie(6), 100)
        430   (int)
    > dr.rollRepeatedly(new HistoryDie(6), 100)
        372   (int)
    > dr.rollRepeatedly(new BasicDie(6), 100)
        369   (int)
    
  2. Add your own polymorphic method, compareDice, to the DieRoller class. It should take two die instances as arguments, roll each of them 1000 times, and compare the results. Your method should print out the results, including which die instance "won". (In my code I'm including the output from the winner's toString() method when reporting the winner.)

    > DieRoller roller = new DieRoller();
    > BasicDie bd = new BasicDie(6);
    > CrookedDie cd = new CrookedDie(6);
    > roller.compareDice(bd, cd);
    The score is 3461 to 4338
    Die 2 wins: Die has 6 sides and has rolled 1000 times
    > roller.compareDice(cd, bd);
    The score is 4286 to 3447
    Die 1 wins: Die has 6 sides and has rolled 2000 times 
    
If time permits, consider trying one of the following exercises:


Brad Richards, 2023