This is a two-part lab. For the first part, you will be working with the Model/View/Controller paradigm, and building a very simple GUI interface. For the second part, you will work with another classmate to build a networked application based on your first week's project.
The first week emphasizes the following topics:
You should read through the entire assignment before beginning any of it.
As usual, you are encouraged to collaborate on this assignment. Hopefully by now you've found someone in the class that you feel comfortable working with. The second part of this lab will require close collaboration with another person, so you should think about who you'd like to work with and practice this week. Of course, your final work should be your own, and you must acknowledge the contributions of your blah blah blah. You've done this before. Just include the names of the people you worked with for this assignment, and how you collaborated.
This assignment is due by midnight on Friday, October 21, to sd-psets@lists.olin.edu, or as hardcopy to Katie.
In this lab, you are going to create a very simple game. You'll use your mouse (the pointing device) to guide a Mouse (a gray circle) from its square mousehole to a block of cheese. The Mouse will appear when your mouse touches the mouse hole, and disappear again when you touch the cheese. Yes, I know. It's not very interesting. But the framework you set up will enable you to turn your program next week into a networked game of tag. Now doesn't that sound cool?
We'll try to be disciplined as we design this project, and use the Model/View/Controller technique, which separates the information about an object from its presentation to the user. The model holds the simple data representation of an object, and typically contans some simple methods to manipulate that data. There can be more than one class that makes up the model. The view holds a reference to the model, and uses the model's data to display information on the screen. The controller listens for changes on the model to update the view, and listens for user events from the view to update the model.
We've written some of the code for you. Your job will be to write
There are three things displayed in this program: the Mouse, the
Hole, and the Cheese. Each will have its own model, but we also
need something that will hold them all together. We'll
call it a World
. The World
object is
the main model object, and it contains the methods that the View
needs to display the state of the world, and that the Controller
needs to manipulate the state of the world. For the most part,
these are just methods to get access to the Cheese, the Hole, and
the Mouse, but we've also thrown in something to keep track of a
score for kicks. Unless you start adding code in the gravy section,
you shouldn't need to change the World
code.
As far as the View is concerned, there's not that much of a
difference between a Mouse, a Hole and the Cheese. They're all just
things that have a size and a location, and need to get drawn on the
screen. To make life easier for the View, we can abstract the three
displayable pieces into an interface. Let's call it, oh, say,
Displayable
.
The World
has a method for pulling together all
of the Displayable objects in a List
and another method
that gives the View an Iterator
over the list so
that it can draw all the parts.
public interface Displayable { public Rectangle getBounds(); public void setLocation(int x, int y); public void paint(Graphics g); }
Why do we have a setLocation
method
rather than individual setX
and setY
methods?
Hint: think back to the Etch-a-sketch lab. What might
go wrong with individual setX
and setY
methods?
In fact, most Displayable
s will probably be identical
except for the paint
code. Just because we're feeling
nice, we've written an AbstractDisplayable
that
implements the getBounds
and setLocation
methods for you. If you just want to have fun with graphics, you
can extend this class and not worry about keeping track of the bounds.
Look closely at the code for
AbstractDisplayable. Notice
the constructor takes two int
arguments. When you extend
this class, you'll have to do something special in your new class's
constructor. What is it? Write down the constructor for Mouse,
assuming you want the Mouse to be 48 pixels wide and 40 pixels tall.
You'll be writing three classes: one each for the Hole, the Cheese
and the Mouse. Later on, you can go all out drawing pretty pictures,
but initially, you should just use the simpler methods of the
Graphics
class. These include fillRect(int left, int top, int width, int height)
and fillOval(int left, int top, int width, int height)
.
You'll also want to set a color before you do the drawing. The
Graphics method is called setColor
and it takes a
Color
object. The Color
class defines several
constants for colors, like Color.black
and Color.yellow
.
You can also create brand new colors by specifying the red green and blue
color values in the constructor for Color
.
Are you feeling
uncomfortable about putting drawing code into what's supposed to be a
model? Why isn't the drawing code part of the view instead? In part,
you can think of the drawing code part of the model as being just
another type of data, like the model's position. Also, Remember
that one of the purposes of having a separate model is so that multiple
views can display the model data at the same time. It turns out
that you still can show multiple views, because each View gives
its own Graphics
object to the paint
method. Thus, the same model can draw the same image into different
views.
So now you've got a Displayable
that would draw a nice gray
blob if someone were to give it a Graphics
and call
paint
. How does that happen? It happens in the
View
. If you look at the code,
you'll notice that the View extends a JPanel
. This means
that whenever part of the window is exposed, or whenever anyone calls
repaint()
on the View, Java will schedule a paint
call.
The paint
method clears the panel by filling it with white,
then steps through each Displayable, setting up the Graphics object and
calling paint
on the Displayable. If you look closely, you'll
notice that the clip is set and the Graphics
state is
translated just before each Displayable's paint
method is called.
This is done so that the Displayable's paint
method can only
mark up the part of the screen within its bounds
, and so that
the top left corner of the Displayable's bounds looks to the paint
method as if it were at (0, 0). After each paint
method
returns, the graphics is restored to its original state.
You may have noticed the extra check just before the Graphics is set up.
Our View
class checks to see if the Displayable
it's looking at also happens to implement the Hidable
interface.
If so, and if the object wants to be hidden, the paint
method skips that Displayable
and doesn't paint it.
This means that if you want to hide the Mouse
, you'll
have to make the Mouse
class also implement Hideable
What extra fields will you need for the Hidable
interface? What methods are required? Write them down, and take a stab
at writing the code to implement the Hidable
interface
before you come to lab.
Finally, we come to the controller. In its current state, it creates
a World
and a View, and builds and shows a
window to hold the View
, but nothing much else happens. If you
were to run the code without updating the controller, you'd see all three
of your Displayables drawn on top of each other in the top left corner.
Why are they all up there in the top left corner?
Can you find the place where the displayable is initialized? Explain
why that initialization causes the display to be in the upper left corner.
What code would fix it so that each Displayable appeared in a random
location? Write down two possible places to put the code for initializing
the positions of the Cheese
and Hole
and
explain why one of them is better than the other.
The controller doesn't pay any attention to the mouse at the moment.
You need to implement a MouseMotionListener
. See the Java API
for the required methods.
Just for starters, ignore the Hole
and the Cheese
,
and just write the MouseMotionListener
method(s) so that the
Mouse
(the screen graphic) always follows the mouse (the pointing
device).
Once you've written the methods, you'll need to register the listener
with the object that produces the MouseMotion events. Think carefully about
what object produces the events, and call its addMouseMotionListener
method to add your listener.
Now you can rewrite the Controller methods so that the Mouse
is hidden initially, is revealed when the mouse is inside the Hole
,
and gets hidden again when the mouse is inside the Cheese
.
For this, you'll want to look at the contains
method of
Rectangle
.
This is the finishing target for this lab. What follows is just Gravy.
status
JLabel to display the score. Alternatively, you can
create your own label for the score and use the status
label as a hint for what to do next.
Hole
and the Cheese
every few times the Mouse
reaches the Cheese
.
Cheese
and
Hole
to change.
Wall
that the Mouse
must avoid
touching. Implement it in such a way that you can have several
Wall
s in the World at the same time. Make sure that all
your collision detection code lives in the Controller.
synchronized
blocks, or trick the AWT
thread into executing your update code. There's a static method called
SwingUtilities.invokeLater
that is designed specifically
for this task: it takes a Runnable
and schedules it to
be executed by the AWT thread just like the event handlers and repaint
calls.
Read the entire lab, and go over the code to make sure you understand what everything does. Answer all the questions marked with a Q, and ask your potential partner about anything you don't understand.
Most of all, don't panic. In lab, there are only four classes you have to write, and three of them are nearly identical. Be sure that you come prepared to lab -- trying to understand the lab in the first two hours and trying to complete it in the third doesn't work very well.
In-lab, you'll be creating three Displayable
classes
and editing the Controller
class. First, download the project files and open it with
NetBeans. The project won't compile, because it is missing
Mouse
, Hole
, and Cheese
files.
Write them, using simple shapes for now. Make each one about 40
pixels in size for clarity. Remember to draw each shape as if the top
left corner of the bounds is at (0, 0). If you try to draw it
anywhere else, it won't show up.
To test them (and to see them all at once instead of scrunched up in the top left corner, you'll have to fix the initialization issue. You can assign them to fixed locations, or make their locations be random.
Next, see if you can get anything to respond to the mouse movement.
You can try printing someting out first (use System.out.println()
)
to make sure your listener is set up properly. Don't forget to tell
the event producer about your listener! Once that works, make the
Mouse
location follow the mouse pointer.
Finally, hide and show the Mouse
as the mouse pointer
encounters the Hole
and the Cheese
.
When everything works, you can get checked out, or continue on to some of the gravy suggestions.
Mouse
, Cheese
, Hole
and Controller
, along with any other classes you might
have changed while making gravy.