Download the helper library for part 2
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.
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!
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.
Cat
model. This is fairly straightforward; you can pretty much copy the Cheese
or Hole
(the Cat
doesn't appear and disappear like the Mouse
).
Cat
to the World
model.
Mouse
side) and a Client (on the Cat
side). Your job will be to send pointer location changes on the ObjectOutputStream
and listen for location messages on the ObjectInputStream
.
Cat
and Mouse
working on both computers, you can add some logic to handle when the Cat catches the Mouse.
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.
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.
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
.
Cat
.
initialize()
method, initialize your cat
field to be a new Cat()
.
initialize()
, add the cat
to the list of displayables
, just like the mouse
, cheese
, and hole
are.
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.
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.
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.
Server
. From the Server
, you can call its getConnection
method to wait for a connection from a Client. Once you have a Connection
, you can get an ObjectInputStream
and an ObjectOutputStream
from it. You should save both of these into fields of your Communicator
class.
Client
instead of a Server
(duh). It turns out that a Client
is already a Connection
, so you can get the ObjectInputStream
and ObjectOutputStream
directly from the Client
. Once again, you should save both of the streams into fields of the Communicator
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 IOException
s. 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.
repaint
in your messageReceived
method.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...
Mouse
disappears at the appropriate times on the Cat application.
Mouse
disappears when it moves on top of the Cat
and when the Cat
moves on top of the Mouse
.
Cat
.
instanceof
before you perform a cast.
Cat
, Communicator
, Controller
, your messaging object, and any other files you changed. You should clearly indicate what is new from this week, and what was present last week.