Reading: For this week, review the BouncingBall
demo and Canvas class described on pp. 150-155 of the text and
read the online Java
Tutorial on Graphics2D. Read
Chapter 12 in the Objects
First
text (on Exceptions). Work along
with it, using BlueJ.
HW: We
return to the
JukeBox project to add a graphical component. We
will discuss the design in class. This is complex, so do not put
it off for the last minute.
Key points: The text does not have a chapter on Graphics2D, so
here are some essentials to note when studying the above examples:
Any JPanel can provide a Graphics object, with its getGraphics()
method. The contentPane of a JFrame can be
used for Graphics if you want to fill the frame with
graphics. Usually one uses
a smaller JPanel somewhere lower down in the containment
hierarchy.
You can cast the Graphics object to the newer Graphics2D type
and use the following methods. Some methods immediately draw graphical
objects, e.g., rectangles, ellipses, text, images. Other methods
set attributes which affect subsequent drawing, e.g., color, pen width,
font, and are remembered until changed later. Here are some examples to
look up in the JavaDocs:
- draw(Shape) draws a shape in outline only.
- fill(Shape) draws the shape and fills its interior.
- drawString(String) writes text
- drawImage(Img) shows an image
- setColor(Color) sets the color of the pen used
in following drawing
The Shape type (it is an Interface) includes lines,
rectangles, elliupses, and
arbitrary polygons, which are described by a sequence of vertex
coordinates. Look up Line2D.Double, Ellipse2D.Double,
Polygon, and Rectangle.
[This Canvas approach does
not
involve a repaint() method to
redraw the contents when necessary, e.g., when a window in front of our
window is moved away. And this method does not involve double
buffering, in which a copy of the image is kept to be automatically
redrawn when needed. So the panel will go blank if resized or
uncovered, but that is OK as you will be
continuously repainting over it. Next semester, you will see more
sophisticated ways to do animation, with multiple threads.]
JukeBox Graphics
Here is a sketch of a possible design, which we will discuss in
class. You
may prefer your own design. Anything that works is fine. You do not need to make the switch
animate back and forth, but you can do so for extra credit, so ignore
those parts below unless you have finished everything else.
Constructors for Tube, Bucket, and Switch class
take additional parameters which indicate the (x,y) location, size, and
color
of their graphical representation.
An animate() method to the Switch is called at regular
intervals by the Game, causing it to update to its next
position, among a series of angles from left to right.
There is a Location class which can hold a Ball. Each Ball
knows its Location, and each Location knows what Ball
it contains (or holds null if there is no ball at that location).
Each Location nas a next()
method which returns the following Location. For example, a Tube
may
have five or so Locations along its length, each pointing to the next
lower one, with the last one pointing to the first location of its
outlet. At regular intervals, a move() method in each
Ball causes it to attempt to move to the next location after its
current location, but if that location is not ready, this ball just
waits. Each Location has a isReady() method which returns false
if it already contains a ball or is in a switch that is in motion
between
its left and right angles.
Work with graph paper to lay out your graphical design. One can
generate a Polygon something like the shape of the switches in the
online game like the following, but some sines and cosines would allow
for a rounder shape and this does not have adjustable size pockets:
{
Polygon p = new Polygon();
p.addPoint(0, -50);
p.addPoint(-30, -40);
p.addPoint(-10, -20);
p.addPoint(-20, -10);
p.addPoint(-40, -30);
p.addPoint(-50, 0);
p.addPoint(-40, 40);
p.addPoint(0, 50);
p.addPoint(40, 40);
p.addPoint(50, 0);
p.addPoint(40, -30);
p.addPoint(20, -10);
p.addPoint(10, -20);
p.addPoint(30, -40);
p.addPoint(0, -50);
p.translate(100,160);
return p;
}
You may want methods to scale a polygon, and to translate a polygon by
adding (x,y) to each coordinate. You can write a method to rotate
a polygon about the origin by an angle theta, that calculates for each
(x,y) an new (x', y') by the formulas:
x' = Cos(theta) * x + Sin(theta) * y
y' = Sin(theta) * x - Cos(theta) * x
Addenda:
Here are some design ideas that
can be made to work without too much coding. As always, feel free
to go with your own design ideas if you prefer.
The abstract Receiver class can have two methods: receive(Ball)
and isReady(). It can have four subclasses: Tube, Switch,
Bucket, and Location. The Location class can have
four fields: x, y, a Ball, and a Receiver outlet.
The outlet is where the Ball goes after here, or null if this
is a dead end, e.g., the bottom of a Pocket that faces up.
When a Tube is created, it creates a series of Locations within
it. Each points to the next in line. The last points to the
tube's outlet. When a Tube receives a ball, it just passes it
to the top Location in the Tube. Similarly, when a tube
is asked if it is ready, it just passes along the response from it's
top Location's isReady().
A Bucket should always be ready to receive. When it receives a ball it
doesn't have to keep it. So it can put it in a location (which
should delete it from the previous location) and then set the ball
field to null.
The Switch is the tricky part. It should create two Pockets, each of
which creates a series of one to three Locations, as in a tube. A
pocket has a faceUP() method and a faceDown() method.
The faceUP() method causes each of its locations to set its
outlet to be to the next one below it, except the bottom one has no
outlet. The faceDown() method causes the outlets to go in the
other direction, with the first one pointing to the Pocket's outlet.
When the Switch changes state, the Pocket which was facing up is told
to faceDown and vice versa. You don't need to animate your switch
and pockets (that is extra credit) but indicate somehow, e.g., with
color, what state they are in.
You can have an isEmpty() and an isFull() method for
the Pockets. The switch changes state when the top pocket is full and
the bottom one is empty. The Switch is ready to receive if its
top pocket is ready to receive, and a pocket is ready if it's initial
location is ready.
If you are having trouble, don't animate the Switch class; just have it
change instantly between its LEFT state and its RIGHT state. If
you want the Switch class to be animated, it needs a state field
with four states: L, R, MOVING_L, and MOVING_R. When told to animate,
it should move further if it is moving; it should stop moving if it
reached the maximum angle; it should start moving if the top pocket is
full and the bottom pocket is empty. An angle field,
keeps track of the current angle.
The Ball class has a field for its Location. When a Ball is told
to try to move, it checks to see if its Location has a non-null outlet,
and if so checks if that outlet is ready to receive. If so,
execute the new location's
receive method, causing the
new location to store the ball
and erase the ball from the current location (if any).
Finally, the Game class creates the buckets, tubes, and switches,
giving them their locations, sizes and colors, and putting them into a
list of Paintables. (You should have a Paintable interface which
specifies a paint() method, analogous to how the foxes and
rabbits were collected.) Then it runs an animation loop, which
goes through each of the Paintables and tells them to paint themself;
then it sleeps for a while. Whenever a ball is created, it is added to
the list of paintables, so it too will be drawn. Wihtin this loop,
after painting everything, or every few such times, tell each Ball to
try to move.
Submit your .jar file on May 7.