CS 261 Assignment #2

Due Friday, February 10th by 11:59pm
3% penalty per late day instead of 5%
(Not accepted after 2/12)

Introduction

This time around you'll get more practice with interfaces — both writing classes that implement an interface, and code that uses the interface to allow polymorphism. We'll make use of some of the code from the last lab (our "Lost in the Dark" game, the controller that let us interact with it, and the Controllable interface that they shared), and add some new classes to the project that work with the interface.

Teams

You are required to work in pairs on this assignment. (Team assignments are below.) Do not start writing any code until you've met with your partner and discussed the assignment and possible approaches. As on the lab, 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. Ideally, your team would work together using the same pair programming approach as used on the lab, but however you choose to complete the assignment, I will assume that each member of the team has contributed equally to the project. If that assumption isn't true, please contact me privately. Please respect the privacy and scheduling preferences of your partner when finding times to work! Don't ask for a phone number if your partner isn't comfortable sharing it. When setting up times to work, it's probably best to aim for spaces where folks will be comfortable meeting with relative strangers (e.g. the library). Please be professional in your conduct!
Spencer Racca-Gwozdzik and Noah McCullough
Brendan Bell and Noah Sprenger
Bonacic Bonacic and Grey Roppolo
Asa Bonner and Devan Meyer
Liam Bradley and Cian Monaghan
Jinwoo Choi and Rohan Crossland
Brett Garon and Ethan Spence
Lucas Calaff and Ella Slattery
Jacob Endrina and Lucas Takiff
Gabriel Guinn and Noah Zimmer
Mackenzie Leibee and Stephen Rice

Overview

The class diagram below shows the relationship between the classes in the finished version of the project. The AdventureGame is our old friend "Lost in the Dark". It implements the same Controllable interface we used in lab, and can therefore be used with the graphical Controller we worked with in lab. The only difference here is that the game gives away the top-secret location of the flashlight when the game starts. This will make it easier to test some of the new code you write. There's a Tester class again that, at the moment, creates an AdventureGame instance being controlled by a Controller object. Note that the Controllable interface helps the game and the controller "fit together". It provides the "contract" that both classes use to ensure that the controller can operate the game. To drive home this concept, we'll write two new classes — one that implements the interface (like AdventureGame does), and another that can interact with Controllable objects like the Controller does.

Final Class Diagram

Automatically Finding Flashlights

  1. Start by downloading InterfaceAsmt.zip and unzipping the archive to get the starter code. You can create a project in Eclipse from the .java files, or open the project in BlueJ. Familiarize yourselves with the code that's there. (It should look awfully familiar, since you just worked with most of the starter code in lab.) Try running the runControllers() method in Tester and verify that you can play Lost in the Dark via the graphical controller.
  2. As you discovered in lab, it can take a while to find that darned flashlight. We're all computer scientists, so we'll do what all good computer scientists do in a situation like this: write a program to automate the process. In fact, since we're really good computer scientists, we're going to solve a more general problem. We'll write an automatic search tool that works with any Controllable object.

    If we wanted to find the flashlight manually, we could use the graphical controller to do a methodical search starting at position 0,0, and keep trying new positions until "You stumbled across the flashlight!!!" appeared in the display. But the text that appears in the display window of the controller comes from the Controllable object's toString() method, so we don't even need the graphical controller to do our search! We can call right(), up(), etc, on an object until its toString() returns the appropriate results. For example, in BlueJ's codepad we could create an instance of the game and "manually" call direction methods and toString() until we find the flashlight. The sample interactions below show me getting unreasonably lucky in finding it, but you get the picture:

    > AdventureGame game = new AdventureGame();
    > game.right();
    > game.toString()
       "Nope"   (String)
    > game.right();
    > game.toString()
       "Not yet"   (String)
    > game.right();
    > game.toString()
       "Bummer"   (String)
    > game.up();
    > game.toString()
       "Nope"   (String)
    > game.left();
    > game.toString()
       "You stumbled across the flashlight!!!"   (String)
    

    Of course, we don't want to do it manually — we want to write a program to carry out those steps for us automatically. The search space is a grid, with position 0,0 at the lower left-hand corner, but our program can't just access it like an array. The only thing it can do is use right(), up(), left(), and down() to navigate through the space. To do a methodical search it'll have to do a zig-zagging path like the one shown below. The picture shows a flashlight being found at postion 5,3, using a search pattern like the one you should implement. (Note that it makes an assumption about how wide the search area is.)

    Search pattern

    Now, finally, the code-writing part! Define a new class called "Autopilot". Inside the class, write a single static method called find. The find method should take four inputs: A reference to the Controllable object that we're going to run our search on, a String containing a search term we're looking for (e.g. "You stumbled across the flashlight!!!"), and two integers specifying the width and height of the search pattern that should be run. The method should do a methodical search like the one in the picture above, checking the toString() results for each position. If the toString() result contains the search term, the method should print where the string was found and then return. For full credit, your code should ensure that the object being controlled does not stray outside of the search area. If the entire search space has been investigated without success, it should print a message reporting failure as shown below. (In the interactions below, the results would be printed to the terminal window, not displayed in the codepad — I'm showing them all here for simplicity.) Since the flashlight is placed randomly, you might get different results, but if you search a small enough area you can test the case where the flashlight's not found.

    > AdventureGame game = new AdventureGame();
    > Autopilot.find(game, "stumbled", 9, 7);
    Found it at 5,3: "You stumbled across the flashlight!!!"
    > game = new AdventureGame(); 
    > Autopilot.find(game, "stumbled", 2, 3);
    Autopilot didn't find "stumbled"
    

    Your find method should assume that the Controllable object it's passed is currently at position 0,0, and that the search doesn't need to consider locations with negative coordinates. (We could write a fancier find method if we wanted to consider negative coordinates.) Once your method is written, you can test your code by calling the testAutopilot() method in Tester after uncommenting the first two lines.

Prospecting on the Sea Floor: Another Controllable Class

It's nice that the Autopilot can find flashlights, but it's actually capable of much more general search tasks. It can "pilot" any object that implements the Controllable interface, and look for things other than flashlights. To prove the point, let's create another class for Autopilot to "drive": A Remotely Operated Vehicle that prowls around the sea floor prospecting for minerals. When you open the ROV class you'll see that it already contains a 2D array of values to be used to supply the simulated readings on a portion of the sea floor. It's hard to read in the Java code, but it corresponds to the "map" below:

ROV data
  1. Finish implementing the ROV class, making sure that it implements the Controllable interface. You'll want to add code that keeps track of the position of the ROV, and updates that position when up/down/left/right are called. Whenever toString() is called, it should return a simulated reading for the ROV's current position. (If there's a value at the corresponding position in the 2D array of values, use a string containing that value as the simulated reading, otherwise use 0.) It should be possible to "drive" your ROV into negative coordinates, at which point its readings should be "0". More detailed information on these methods is available in the documentation.

    Your ROV should also keep a running total of the readings from all of positions it has visited (whether toString() was called there or not). That'll give us a sense of how "valuable" the area it has explored is. The function() method should print a message containing the current total. The interactions below show how your ROV should work. (Here again, the printed messages from function() would appear in the terminal window, not the codepad, but I'm showing them here so you can see which commands would produce output.)

    > ROV sub = new ROV();
    > sub.toString()
       "4"   (String)
    > sub.function();
    Sum of all readings is 4
    > sub.right();
    > sub.right();
    > sub.up();
    > sub.toString()
       "3"   (String)
    > sub.function();
    Sum of all readings is 10
    

  2. Once you're convinced your ROV is ready to roll, uncomment the last two lines in runControllers() in the Tester class. Try running the runControllers() method and it should now create two graphical controllers — one driving the ROV, and one playing the game. This is the power of interfaces! The controller didn't know anything about the ROV class you were going to create, but it can control it since it implements the Controllable interface. You can use the controller to further stress-test your ROV and make sure it behaves as expected.
  3. Like the controller, the Autopilot's find() should work with the ROV too! Uncomment the remaining code in testAutopilot() in the Tester class, and verify that it can navigate an ROV to find the 10 — and that if the search area doesn't include the 10, that it fails as expected.

Style Guide

Before you submit your assignment, go through the checklist below and make sure your code conforms to the style requirements.

Grading

This assignment will be graded out of a total of 80 points:

Submitting

Before submitting, test each of your methods thoroughly and double check for comments (including @param and @return directives) above each method. When you're convinced it's ready to go, zip up your project folder and submit it via the Canvas submission tool for Assignment #2. (Please contact me if you need help with the submission process.) Only one member of the team should submit the project, but make sure all names are in the comments at the top of each class.


Brad Richards, 2023