PI: Laboratory 5: Polymorphism and Graphics

Overview

We are going to try an experiment in this lab. Since we
cancelled the pre-lab work, the actual layout of lab this week will be
in a series of stations. You will start at the pre-lab station,
where you will review graphics and painting. Once you are
familiar with those ideas and comfortable writing code, you will move
to the lab stations. There, you will work on today's target
exercise. The third (optional) station is one in which you can
work on extensions to the target exercise. Finally, you will
move to the post-lab (checkout) station, where we will make sure
that you've covered the material that you will need later in this
course.

The main themes of today's lab are polymorphism and some graphical
user interface concepts.

Contents

Pre-Lab: Painting on Graphics

To begin, we'll rewrite the code we dashed off frantically at the end of
class on Thursday. That code consisted of two classes, a Main class
and a Canvas. If you are comfortable re-writing the code from
class (or have it handy and feel that you understand it), you can skip
ahead a bit in this lab to the section on painting behavior.

Build a Main class.

You should begin by creating a class whose sole purpose is to be
the place that Java starts executing. CodeWarrior will help you to do
this in its own special way if you open a new project. Be sure to
tell CodeWarrior that you intend for this to be a Java project (J2SE,
application):

On the main toolbar, click the second button from the left.
Select j2se stationery.
Then select application.

This will give you a file with a public static void main(String[]
args) method. Remember that Java begins by creating a single
instruction-follower (aka a Thread) that will begin its execution at
this method in the class that is your Java target. Save this file and
compile it. Look, it's Hello World!

Create a Frame.

at the top of your main class. Now you can refer to classes like
java.awt.Frame by their shorter names (e.g., Frame).

Modify your main method so that you create a Frame. The Frame
constructor optionally takes a String as an argument; it uses this
String to label the Frame it creates. You may also want to use the
Frame's setSize method, which takes two ints as arguments. You will
definitely want to use the Frame's show method (no arguments), too.
Try this and compile and run it.

What happens if you forget to invoke the Frame's show method?

What happens if you vary the arguments to setSize?

Create a specialized Canvas.

Now create another class and add it to your project. This class
should extend java.awt.Canvas. (You can simply call it Canvas if you
import java.awt.*; at the top of the file.)

A Canvas is a Component that can live inside a Frame. You can add
an instance of your extended Canvas class to the Frame you created in
your main method. (Every Frame has an add method that takes a
Component as an argument. Fortunately, a Canvas is a kind of
Component.)

If you extend Canvas and then add one of these extended Canvases to
your Frame, you probably won't see much difference. The reason for
putting the Canvas there in the first place is so that you can make it
do something interesting. For this, we'll resort to the paint
method.

Every java.awt.Component (including java.awt.Canvas) has a paint
method that takes one argument, a java.awt.Graphics. That Graphics is
handed to the Component any time it's time to display the Component.
The Graphics can be asked to do all sorts of cool stuff. To begin
with, try writing a paint method that takes a Graphics as an argument
(don't forget to give it a name!) and calls the fillOval method of
that Graphics. fillOval takes four ints as arguments:

public void paint( Graphics g )
{
g.fillOval( 50, 100, 150, 200 );
}

Try varying the ints to see what each one does. Where is the
origin of this coordinate system? In which directions to the axes
run?

More paint tricks

Look up the documentation for java.awt.Graphics in the Java api
docs. Learn at least two more Graphics methods and get your Canvas to
do something cool. Can you create other shapes? Change pen colors?
(Check out java.awt.Color.)

Painting behavior

If you've followed our instructions, your class will look the same
each time that it is painted. For example, if you drag another window
over your Frame and then away, you'll see that the Canvas and Frame
repaint themselves to look just the same as before. (Resizing your
frame also causes them to repaint, but it doesn't change anything
'cause they paint just the same way.) But what about something that
looks different at different times?

Add a little bit of state to your Canvas class. (Hint: field.)
Depending on the value of this state, make your paint method do
something (slightly) different. Try resizing (or hiding and showing)
your window repeatedly to see your Canvas change.

Get the Point (and the Dimension)

In addition to the classes you just played with, take a look at
java.awt.Point and java.awt.Dimension. These are classes that merge
the (x, y) coordinates or (length, width) coordinates, respectively.
Although it is generally not nice style to reach in and inspect the
fields of another class directly, in these two cases it can be
necessary to do so. For example, a java.awt.Canvas has a getSize()
method that returns a java.awt.Dimension: how big is the Canvas? You
can directly access that Dimension's fields, e.g., to look at the
Canvas' width:

if c is a Canvas,

c.getSize().width is an int representing c's width in pixels.

Be sure that you know how to create a Point -- e.g., (40, 75) --
and to read its fields; Dimension, too. Then, use the getSize()
method of the Canvas to make a simple shape (like an oval) stay in the
middle of the Canvas as you resize it.

Congratulations. You are now ready to move from the pre-lab
station to the lab station. You should actually pick your stuff up
and move to the next table, because we are going to try to keep track
of what people are working on using your physical locations.

Welcome to Lab.

Get the code.

At this point, you need to grab the project for today's lab. It
should be waiting for you at \\stufps01\stufac\pi\breakout. Be
sure to copy the whole breakout directory as there are some important
configuration files there.

See the docs

Make it compile and run.

When you load the project, you will discover that we have not given
you a way to start it. That's because we've omitted the all-important
target file. If you want to run the program, you will need to build
one for yourself. You should create a file that contains a class with
that one special method that Java will begin executing. The body of
that method should create a breakout.GameFrame using GameFrame's
no-args constructor. (Note that you can call breakout.GameFrame by
its shorter name -- GameFrame -- if you are inside package breakout;
or if you import breakout.*; ) Unlike the raw java.awt.Frame that you
used earlier, you won't need to set the dimensions of the
breakout.GameFrame or to tell it to show().

Be sure that you add your new class with its special method to your
project and also be sure to tell Java that this class is its target.
If you're not sure how to do this, ask one of your classmates or one
of us.

Once you run your code, you will have the opportunity to load a
board. Click the load button and, when you see the dialog box, select
the boards directory and choose the first board (default.brd). Play a
quick game of breakout. Ahhh, that was nice.

Build your own Brick type.

Your next job is to create a class that can be a breakout brick.
This class will need to implement the BreakoutComponent interface that
we have provided. A breakout.BreakoutComponent is a bit like a
java.awt.Component, although a BreakoutComponent is not actually a
Component in the java.awt sense. Take a look at the BreakoutComponent
interface now.

The BreakoutComponent interface begins with some methods to
manipulate shape and size. We will be giving you a rudimentary
implementation of these methods, though you have the option of
creating specialized BreakoutComponents that have more sophisticated
versions of these methods.

Next, the BreakoutComponent interface has three methods that deal
with breakout game interactions: kill(), which is invoked whenever
that component should simply die off; isDead(), which lets other
objects find out whether this object has died off; and hitBy, which is
invoked whenever another object (like the ball!) hits this one. You
will be writing the hitBy behavior for (at least) the most basic brick
type.

Finally, the BreakoutComponent method has a paint method that is
always invoked with a java.awt.Graphics, just as though it were a
java.awt.Component. You will also be responsible for providing the
paint behavior of your BreakoutComponent.

(Strictly speaking, there's one more method -- isTransient() -- but
you can ignore this one too.)

To make your job easier, we've provided you with a partial
implementation of BreakoutComponent. This is an abstract class called
DefaultBreakoutComponent. DefaultBreakoutComponent provides
implementations of all BreakoutComponent methods except hitBy and
paint.

Your task is to implement a SimpleBrick. A SimpleBrick should be destroyed
when hit, and render itself as something brick-like. You will probably want
to subclass DefaultBreakoutComponent to implement it. In order to operate
with the provided code, your constructor takes a Point for location
and a Dimension
for size in that order.

Before you can implement these two methods, you should know about
the constructors for your class. DefaultBreakoutComponent provides
two constructors. One takes a java.awt.Point (representing the upper
left-hand corner location of this component inside the GameFrame) and
a java.awt.Dimension (reflecting the length and width of the
component). The other constructor takes those two arguments as well
as a World, should someone happen to want to set this component up
with one. You should implement these same two constructors as well,
making appropriate use of the superclass constructor. (If you are not
sure how to do this, ask someone else at the table or one of us.)

Now that you've got constructors, think about the two methods you
need to implement:

To paint yourself, you need to tell the Graphics what to do. The
origins of the coordinate system for your DefaultBreakoutComponent
will be in the upper left-hand corner of the component itself. To
start with, you probably just want to fill this component in. (Note:
In contrast to the Canvas's paint method, which got called only when
the Frame it was sitting in got resized, your
DefaultBreakoutComponent's paint method will be called on a regular
basis even if nothing much is happening, so if your paint method
changes its behavior, you should see action on your screen.)

Finally, decide what this component should do when it has been hit.
Do something really simple (and visibly obvious) now. You can come up
with all sorts of interesting ideas for later.

Add your component.

There are two ways to get a BreakoutComponent (like the one you've
just implemented) into your GameFrame. You are going to use the
simpler one now, but there's a nicer one that we'll get to in a
bit.

The simplest way thing to do is to modify your main method. In
that method, create an instance of your BreakoutComponent class.
Construct it with a location (Point) and a size (Dimension) that make
it visible in the GameFrame. (You will want to make these components
positive and smaller than World.MAXX and World.MAXY, respectively.)
Finally, use the GameFrame's add method, which takes a
BreakoutComponent as an argument, to add your component to the game.

Try this. Compile it and run it. Play the game. Then try again,
this time creating multiple instances of your class. Cool! This is
today's target exercise, and you can now move to the post-lab table if
you'd like.

But of course, there's more. Even if you're planning to keep
hacking on your breakout game, please move to the Gravy table so that
we know you've finished the target exercise.

Gravy

Handy dandy hint: Using the loader

You may remember that when you started the application originally,
you were able to load a board configuration file -- and a whole bunch
of bricks -- directly. Take a look at
that file now. The purpose of that file is to let you load a board
configuration. See if you can adapt it to load your brick type. If
you're not sure how, ask Ben, who promised to make everything clear.

Going to town

First, you may want to take a look at the World.
The World is the container in which the BreakoutComponents live. It also
understands how to detect overlapping components and rebound components off
each other. In order to implement a simple brick, you didn't need to use
World at all, but more complicated bricks (like those that release balls
when they are destroyed) will need to interact with it.

Ooh, there's lots you can do. Make a few more different kinds of
bricks, like one that can be hit multiple times before it dies or one
that changes color each time it's hit before finally dying. Make a
ball that can only hit one thing before it dies. Or build a better
paddle. (You may have noticed that the paddle we gave you shoots
balls, but the one whose code we gave you doesn't. You can build the
shooter paddle, but you'll also need the ball that can only hit
something once before it dies.) But these are just our ideas, and
we're sure yours will be much cooler.

Post-Lab

There's no explicit post-lab writeup today, but we do want you to get checked
out and you will need to make sure your code is in pretty good shape.
Please join us at the checkout table to discuss where you got and how
you feel about your code. Be sure to save this code as, well, you
do remember that code is never done, right?