Lab 7
Cat and Mouse

Out October 22, due by midnight, Friday October 28

Download the helper library for part 2

Introduction

Last week, you built a simple game using the Model/View/Controller paradigm. This week, you'll work with another classmate to build a networked application based on last week's project.

This week emphasizes the following topics:

You should read through the entire assignment before beginning any of it.

Typically, at this point, we mention that you're encouraged to collaborate on this assignment. This time, it's kind of pointless, as you're required to work with someone else to complete the assignment. On the other hand, your team should feel free to discuss any aspect of the task with other teams. Your team's work should be its own, and each team member should turn in his or her own code. Some of the code must be identical between teammates, but comments and documentation within the java file should be your own.

This assignment is due by midnight on Friday, October 28, to sd-psets@lists.olin.edu, or as hardcopy to Katie.


Contents


Overview

When this lab is complete, you'll have modified your original Mouse lab to be a cat-and-mouse game of tag, played across two computers. To begin, make sure you've got one other person in the class to work with. If you don't have a partner, please send mail to the course staff as soon as possible, so we can find you someone to work with.

Building this lab will require close coordination with your partner. Don't try to get too far ahead of each other, and make sure that you're both in sync. If you end up implementing different interfaces, your programs won't be able to talk to each other!

The task

When you're done with this week's lab, you'll have a pair of applications that run on different computers. One of the applications will control the Mouse from last week, while the other will control a new object, the Cat. When you move your pointer, your application will not only set the location of the Mouse (or the Cat), but will also send the location across the network to the other computer, which will update the location of the Cat (or Mouse). The end result will be two moving characters on each screen, one controlled by each member of the team.

For a communication channel, we'll use an ObjectStream, which allows you to send entire Objects from one computer to another. Part of your task will be to design the object that you send back and forth.

In order to build your program, you'll need several parts. More on each of these below.

The code on each machine will look nearly identical, but there will be a few subtle differences between the Cat application and the Mouse application.

Pre-Lab

Before you start your lab, you must get together with your partner. Share your code from last week, and make sure you both understand what's going on. If you'd like to pick the better of the two code samples and both start from the same place, that's fine. However, it should be possible for each person to start from his or her own code.

Talk with your partner to figure out what information needs to be shared. Obviously, a location is important; is there any other data that need to be shared between the two computers? Remember, the goal of this communication is to get both machines to have the same information in each of their models. Look carefully at your models. What information do they contain?

An initial simplification for your task might be to place the Hole and the Cheese in fixed locations. That way, you don't have to communicate their positions. But think about what other information you might need in your message if you'd like to move them around later.

Sit down with your partner and design a class that contains all the information you need to update the models from one application to the other. Make sure you have all the information you need, but don't waste too much space on any fields that aren't strictly necessary. You'll be sending a lot of these things back and forth.

The message object should be immutable. An immutable object is one that doesn't allow you to change its contents programmatically. Once it's been created, it keeps its values for all time. You do this by making sure that all the fields are private, and that you only have get methods, not any set methods. In order to get the correct values in the first place, the constructor must have a parameter for each of the private fields. Once the constructor is run, nothing else in the object can change.

Java already knows how to turn any object into bits that can be reconstructed later into the original object. In order to permit Java to make the transformation, you must declare that your class implements Serializable. The Serializable interface is kind of goofy in that it doesn't actually require you to write any more methods! In fact, the Serializable interface is completely empty. It's simply there to tag the class and mark it as serializable.

Make sure you understand what you need to do in-lab, so you don't waste the first hour of lab time. We'd like to start checking people out at 6pm.

In-Lab

Setting up

Download the cs101comm.jar file. In NetBeans, right-click on Libraries in the project pane, and add the jar file to your project. The javadoc for the jar file should tell you everything you need to know about it.

Adding the Cat

The Cat class is really simple. The Cat doesn't appear and disappear like the Mouse, so you can just use the Cheese or the Hole as a reference. Just like last week, start with a simple geometric shape with a distinct color. Once you've got everything else working, you can make it look more cat-like.

Now that you've added the Cat, you'll need to tell the World about it. Open World.java.

  1. Make a new field for your Cat.
  2. In the initialize() method, initialize your cat field to be a new Cat().
  3. Also in initialize(), add the cat to the list of displayables, just like the mouse, cheese, and hole are.
  4. Make a method called getCat() so that your controller can get at it.

Run your code now, and make sure that your Cat shows up (in the top left corner) and everything else still works as it did last week.

Creating your messaging object

You should have your message object all ready to go from the pre-lab. Create the class, mark it as serializable by adding implements Serializable, make sure all your fields are private, build the constructor, and add get___() accessor methods for each of the fields.

Client and Server

Up until this point, both partners should have been adding identical code to their applications. At this point, you need to decide which person will be writing the Server (Mouse), and which will be writing the Client (Cat). The code for each will still be very similar, but will contain subtle differences.

Open up your Controller class. Notice how you have a method called mouseMoved that receives an event every time the pointer moves. You'll add another method called messageReceived that takes your messaging object as a parameter. Think about this. Local events (your pointer movements) will cause the mouseMoved method to be called. We'll now set it up so that remote events (your partner's pointer movements) cause the messageReceived method to be called.

Aside: whose job is it to determine what happened to the Mouse? There are three choices. We could have each application independently determine whether the Mouse was caught, we could have the Cat application decide when the Mouse was caught and inform the Mouse application about it, or we could have the Mouse application decide, and inform the Cat application about it.

In this case, since the Mouse application is already telling the Cat application about the state of the Mouse, we'll pick the third choice for simplicity. The Cat application won't do any bounds checking at all.

If you're writing the Cat application, your messageReceived method should update the location of the Mouse. If you're writing the Mouse application, it should update the location of the Cat.

Writing the Communicator

We'll put the communication code in its own class to keep the Controller from getting too messy. Create a new class called Communicator. This will be a somewhat tricky class. It has three tasks. The first is to set up the communication (either a Client for the Cat, or a server for the Mouse). Second, it must provide a method for the Controller to write the messaging object to the other machine. Finally, it must read messaging objects from the remote machine and call the Controller's messageReceived method.

Let's tackle the setup first. The following step could be done in the constructor of your Communicator, or you could separate it out into a method that's called from the constructor.

Now let's write a sendMessage method. This method will be called by the Controller whenever the local user moves the pointer. You have an ObjectOutputStream available, and your Controller will have created an instance of your messaging object. That's all the help we'll give for this part: you figure out the rest. Once you figure out what the parameter to this method needs to be, it's just one line of code in the body.

Now for the tricky part. Messages will be coming in from the remote machine at odd times, and the call to readObject in an ObjectInputStream blocks until a message is available. This means that you'll need a new thread to deal with reading from the stream.

Make your Communicator implement Runnable and write the run method. The run method should be an infinite while loop (while(true)) that reads an object from the ObjectInputStream, casts it to the correct messaging object class and....

... and what? What method do you need to call? In what object is that method? How can you get hold of that object? Hint: who creates the Communicator class? Make sure you and your partner have discussed this question and have an answer before you come to lab.

Now that your Communicator is runnable, you can start the run method in its own thread by saying

Thread anim = new Thread(this);
anim.start();

Be careful: you must start the thread after you've set up your ObjectInputStream.

Almost there....

If you're sitting in lab now typing your code in, you'll notice that there are a ton of exceptions that need to be handled. Almost all of them are IOExceptions. For now, you can just handle the exceptions by printing out a stack trace:

try {
   ....
} catch (IOException ioe) {
   ioe.printStackTrace();
}

... but you should think about how each exception might actually occur and what a more appropriate action might be to take. Once you run your code, you'll get a better sense of where the exceptions are being thrown and why.

Okay. One last bit of code and we can run this sucker! In the mouseMoved method of your Controller, go ahead and create a messaging object and call the sendMessage method of the Communicator.

Running the code

Go ahead and start the Mouse application on one machine and the Cat application on the other. The Mouse application should pop up a window indicating the IP address and port number it's listening on. Type the address and port into the Cat application. Now move your pointer around on the screen. Your partner should see the movement as well. Debug, rinse, and repeat as necessary.

Common pitfalls:
The other person's character moves when you move your pointer, but not when you don't.
You've probably forgotten a call to repaint in your messageReceived method.
You're getting lots of NullPointerExceptions before the other person joins
You're probably trying to send updates to the remote computer before the Client or Server has connected. Don't try to send messages until the Communicator object exists.

Now take a deep breath. Congratulations. You've finished the last regular lab of the year.

Unless, of course, you want to go on to the...

Gravy


What to turn in