COMP 1006/1406 Winter 2009
Assignment #4: Table Shuffleboard
Due Date: Monday April 6 by 4:00pm
Assignments MUST be submitted
electronically using Raven. The Raven Web site is:
raven.scs.carleton.ca
This assignment will give you practice using Graphics and Menus
in Java.
Assignment Marking:
This assignment is based on 27 specific
design requirements labeled R1.1... R3.1 and we will be awarding 2 marks for
those you are able to satisfy for a total of 54
marks. You will get 2 marks for each design requirement that is satisfied, well
implemented and demonstrated through your testing output. You will get 1 mark
for any requirement only partly met, or met but not well implemented or not
demonstrated in your testing. You get 0 for any requirement not satisfied.
In addition there are some general programming and good practice requirements
that you must satisfy. These requirements are numbered R0.1, R0.2 .... You do
not get marks if you satisfy them, instead you loose 5 marks from your total
score for each one of them not satisfied.
General Programming and Good Practice Requirements.
These requirements pertain regardless of what your
application is supposed to do (i.e. regardless of the design requirements).
These requirements are to ensure that your code is readable and maintainable by
other programmers (or readable by the TA's in our case), and that your program
is robust (It does not crash from bad object references). You will loose 5
marks from your total assignment mark for each of the following requirements
that are not satisfied. If you do not satisfy requirement R0.0 you will get
nothing for the assignment.
R0.0) IMPORTANT Uniqueness Requirement. The
solution and code you submit MUST be unique. That is, it cannot be a copy, or be
too similar, to someone else's assignment, or other code found elsewhere. A mark
of 0 will be assigned to any assignment that is judged by us or the TA's not to
be unique.
R0.1) All of your variables, methods and classes should have
meaningful names that reflect their purpose. Do not follow the convention common
in math courses where they say things like: "let x be the number of customers
and let y be the number of products...". Instead call your variables
numberOfCustomers or
numberOfProducts. Your program
should not have any variables called "x" unless there is a very good reason for
them to be called "x". (One exception: It's OK to call for-loop counters i,j
and k etc. when the context is clear; that is common practice in programming.)
R0.2) All variables in your classes should be
private, unless a specific design
requirements asks for them to be public
(which is unlikely). We will design objects that provide services to
others through their public methods.
How they store their variables is their own
private business. (This will not pertain to assignment #1 since we are
not building classes and objects yet.)
R0.3) Robustness Requirements: Your program should never crash
because of a "null pointer exception". This exception means that you are using a
variable that does not actually refer to an object. We instruct the TA's to try
and crash your program for this reason so guard against it. (The TA's love this
part of their job.)
R0.4) Comments in your code must coincide with what the code
actually does. It is a very common bug in industry for people to modify code and
forget to modify the comments and so you end up with comments that say one thing
and code that actually does another. (By the way, try not to over-comment your
code; instead choose good variable names and method names which make the code
more "self commenting".)
R0.5) Java 1.5 Requirement: Your code should compile with the Java 1.5SDK or
later without warnings. This requirement is to ensure that if you used older
code from elsewhere (e.g. some older demonstration code.) that it has been
updated to compile cleanly with Java 1.5. For example code like the following:
Vector people = new Vector();
...
Person p = (Person) people.elementAt(0);
p.getName();
would probably generate a warning with Java 1.5 compile. You should use template
ArrayLists as follows instead.
ArrayList<Person> people = new ArrayList<Person>();
....
Person p = people.get(0);
p.getName();
VERY IMPORTANT: Any sample code fragments shown below may have
bugs (although none have been put there intentionally). It is part of your task
to identify errors in the requirements.
Rules Of The Game
The following rules
were copied from the web site: http://www.shuffleboard.net. If you
are
totally unfamiliar with what table shuffleboard is you should go out to a pub and play a few games.
The team without the hammer shoots first. The other team shoots second. Players continue to alternate shooting until all weights are used. At this time, points are counted and play continues from the opposite end. The team that scored points on the previous round must shoot first on the next round. If no points are scored on the preceding round (e.g., all weights are knocked off) then the hammer changes. In other words, the team that had the hammer during the round where no points were scored must shoot first the next round. Play continues in this manner until one team reaches 15 points.
Scoring:The team which has their weight closest to the end of the board scores. All of their weights which are ahead of their opponent's deepest weight (closest to the end of the board) are added together for the score for that round.
A weight scores one point if it is located between the short foul line and the "2" line.
Weights completely across the "2" or "3" line count 2-points or 3-points respectively. To judge if a weight is completely over the line it should be viewed from above (i.e., look down over the top of the weight. Again the entire weight must be over the line for it to count as the next higher point value. You should be able to see some wood between the line and the weight.
If any portion of the weight is hanging over the end of the board (not the side) it is called a "hanger" and counts four (4) points. Close calls can be checked by holding a weight so the bottom of the weight is along the back end of the board. The weight is then slid along the back end of the board. If it hits the "disputed" hanger the weight is indeed hanging and is worth 4 points.Miscellaneous rules:
Before a player shoots, the player can dust the board with saw dust if dry spots are
showing.
Note: In tournaments this may be
restricted to the edge of the board.
Shooters must have one
foot behind the playing surface while
they are shooting. Hitting or shaking the table is never
allowed.
Your GUI should provide at least the
following, but you can add more if you want. The close up view above is just for
illustration, you application need not have a close up view.
R1.1) The main view representing the
whole shuffleboard table should be oriented vertically so that shooting is up
from the bottom of your screen towards the top
R1.2) The table top should look as
much like wood as possible (Preferably a light, laminated, maple).
[Special Note: The ones
that look most like wood, as judged by the TA, will get 2 marks. Ones
that look sort-of wood-like get 1 mark. Ones that look unconvincing
get 0. --The picture above would get 0.]
R1.3) The scoring lines should be
drawn on the table along with the designations "1", "2" and "3".
R1.4) The weights should be round and be in two colors to distinguish the two teams. There should be four weights for each team.
R1.5) Shooting a weight should work like shooting the cue ball did in tutorial #4. That is, you should click on the centre of the stone you wish to shoot, drag the mouse back and then release the mouse to shoot. The further you drag back the harder the shot will be. The line from the point where the mouse was released to the center of the stone will determine the direction of the stone when shot. You will need to experiment with the numbers to get the stone to depart with a reasonable speed. The travel down the table should seem "natural" and "correct".
R1.6) The weights should slide down the table in a realistic way when they are shot. You should use a Timer object to animate the motion.
R1.7) You should be able to induce a spin on the stone by dragging from off-center, rather than the center of the stone. The more off-center the more the induced spin should be. Dragging from the right of center of the stone should induce a spin towards the left. That is, it should spin to the opposite side.
R1.8) By inducing a spin on the stone you should be able to get it to curve as it travels up the table. This should allow you, if you shoot well, to position a stone behind another.
R1.9) There should be enough spin so that it is evident and useful to the shooter. You will have to experiment with how you might want to do this. Basically you will have to modify the horizontal velocity of the stone as it travels up the table.
R1.10) When weights collide with each other they
should bounce and deflect in a realistic way. At the end of this
assignment description is an explanation of the mathematics of how to
do this. Note you can do this yourself from scratch or make use of the
Pool Game code in tutorial #4 which is based on this math.
R1.11) The main shuffle board should have are area
around the table which is "out of bounds" or "off the table".
The code must do bounds checking and if a weight falls off
the table it should stop and be drawn more "grayed-out". A weight is
deemed to have fallen off the table once it's center is past the edge
of the table. (Note that weights can go off the end or off the sides of
the table.)
R1.12) Your application window
should also have a scoreboard which shows, for any given configuration of
stones, what the score count should be and for which team it counts.
R1.13) The weights that are
currently counting towards the score should have their score indicated on, or
beside, them on the shuffleboard window. In the
example closeup above the red team is currently scoring 5 with their two weights,
but this needs to be indicated on the actual game GUI.
R1.14) Your code should have a Weight class that represents the weight objects being shot. These objects could be analogous to the Ball objects from tutorial #4 (The Pool Game). Each Weight object should keep track of its own color, position and current velocity.
Menu Requirements
R2.1) Your main GUI window should a
"Game" menu in the menu bar of the main window with the following
options.
R2.2) There should be a "Reset" menu
item that resets the weights all at the bottom of the table ready for
the next shooting round.
R2.3) There should a cascaded submenu
in the "Game" menu called "Speed" and it should have three radio button
menu items labled "Fast", "Regular" and "Slow". These menu items should
cause the table friction to be adjusted so that you can simulate a
fast, or slippery, table, a regular table and a slow, or "sticky"
table. Set the friction values so that the results seem realistic and
fun to play.
R2.4) There should be some menu
items to allow each team to pick from several weight colors.
R2.5) Two teams should not be allowed to choose the same color.
R2.6) There should be an "Exit" item which will cause the game to close.
R2.7) There should be an "Undo" menu item.
R2.8) When the "Undo" item is selected the game position should go back to how it looked before the last shot. That is, go back as though the last shot was never taken.
R2.9) When the "Undo" item is selected the score should also revert back to what it was before the shot being undone.
R2.10) The "Undo" menu should allow the player to repeat a shot
attempt over and over by repeatedly undoing after each shot.
R2.11) There should be a "Replay" menu item.
R2.12) When the "Replay" menu item is selected a replay of the
last shot should be shown. That is it should like and play just like when the
stone was actually shot by the user. A suggestion for doing this is to capture
the stone positions, or state, for each timer event during a shot and then play
them back when the replay item is selected.
Launch Requirements
R3.1) Your code that contains the
main() method should be in a class called
ShuffleBoardApplication. This should be the only class with a
main() method.
In the simple situation above the red weight is
stationary and
the yellow weight hits it. When two weights collide, the line through
their
centers is called the "line of impact". (Two weights collide when their
centers
are two weight radius lengths from each other). Suppose the yellow
weight is traveling with velocity Vy at an angle "a" with the line of
impact.
After collision, the red weight will travel along the line of impact
with
a velocity Ur = Vy*cos(a). The yellow weight will travel away with the
same
angle "a" but on the opposite side of the line of impact, with a
velocity
of Vy*sin(a). -Simple enough right? In the next situation both weights
are
moving.
If both weights are moving the situation is similar but
each imparts some velocity to the other. For example suppose the yellow
weight is traveling with velocity Vy at an angle "a" with the impact
line
and the red weight is traveling with velocity Vr at an angle of "b"
with
the impact line. The yellow weight will bounce off again at an angle
"a"
with the impact line, but with velocity Uy = Vy*sin(a) + Vr*cos(b). The
red weight will bounce off at angle "b" with velocity Ur = Vr*sin(b) +
Vy*cos(a).
Your task is to model these collisions. You will
have
to dust off your high school geometry book if you don't remember sines
and cosines etc. In the pool game code provided in tutorial #4 the
velocity of the balls in the
vertical direction is stored separately from the velocity in the
horizontal
direction. This makes it easy do to the animation, but you will have to
translate the above pictures into that situation or change the way
velocities
are stored in the weight objects. It is entirely up to you, you do not
have
to leave the code we provide as is.
Lets examine the simple situation where weight 1 is
moving
and hits a stationary weight 2.
At the point of collision what do we know? We know the positions of each weight, (x1,y1) and
(x2,y2).
We know horizontal and vertical velocity of each weight, v1x,
v1y,
v2x=0, v2y = 0. We know the weight's centers are 2R apart (R is the
radius
of a weight).
The velocity vector of weight1, called V, has magnitude
sqrt(v1x*v1x + v1y*v1y); Weight2 is stopped so its velocity is 0.
Our objective is to figure out the new vx and vy for the two weights after the collision. Looking at the situation 1 picture above we know the moving weight will depart at the same angle to the line of impact as it arrived -but on the opposite side, and we know the struck weight will depart along the line of impact. So everything we know at the point of collision is shown on the picture below.
Now first off, realize that the the model we are using
is a very simple (first order) approximation. That is, you will notice
V1
= V*sin(a) and V2 = V*cos(a). This is not strictly accurate. It is
based
on the approximation that sin(a) + cos(a) ~= 1. The approximation has it
maximum error at a=45 degrees. A better approximation would be use the
correct
formula sin2(a) + cos2(a) = 1. If you want to use the
more
accurate interpretation for your code that is fine. In that case the
speeds
should be V1=V*sin2(a); V2=V*cos2(a). I will continue
though
with the simpler model as it should suffice for this level of animation.
Now our objective is to figure out the new v1x, v1y,
v2x,
and v2y. Thus we want to resolve all the angles relative to vertical
and
horizontal.
The picture below now shows the important angles and
how they can be found.
Angle a is the angle the moving weight velocity
makes with
the line of impact.
Angle b is the angle that the line of impact makes with
the horizontal.
The angle then that the moving weight would depart
would
be angle c=b-a.
First remember the old velocity
V = sqrt(v1x*v1x + v1y*v1y);
Here is how the angles can be found.
b = arcsin((y2-y1)/2R) //i.e. computed
from
the known positions of the stones.
//In Java Math class there is an asin() method that
does
this
//there also the sin() and cos() methods.
d = arcsin(vx/V); //moving weight's angle with the vertical
a = pi/2 - b -d //i.e. angle a is 90 degrees less angle b and d; remember we have to work in radians so 90 degrees is pi/2.
So then the angle c at which the moving weight departs at relative to the horizon is: c = b - a.
So now we know all the important angles so let's calculate the new speeds.
The new speeds are:
V1 = V*sin(a); V2 = V*cos(a);
Resolve the speeds back into vertical and horizontal components for the two weights.
v1x = V1*cos(c); v1y = V1*sin(c); v2x = V2*cos(b); v2y = V2*sin(b);
So now the weights are on their way with new speeds and directions and will move when the next timers event occurs. There are other details you may have to resolve. To handle two weights moving at once you can work out the additional details. It will involve a few more angles and speeds but won't be much harder. The other way you could do two weights moving is to handle it as two cases. In each case one is stopped and the other moving. Then just add the horizontal and vertical velocities of the two cases together at the end and you will have your answer (this is how the demonstration code for the pool game works.)