In this lab and the next you will implement a simple game in which coconuts fall on a beach and a crab shoots them with its laser eyes. You will use the Observer Pattern in your implementation. This is a group lab.
The crab sits on the beach and moves right and left. Coconuts are
introduced at the top and fall downwards. The intended rules for the game
are that the crab would shoot lasers straight up at coconuts, destroying
them. The laser beam should move slowly enough the user can see it move and
the crab can move to the side to escape being hit if the laser misses. You
might add a random probability for the laser destroying the coconut. The
number of coconuts destroyed by a laser beam are counted, as are the number
of coconuts reaching the beach. If a coconut hits the crab, no more
coconuts are generated. The game ends after the crab is destroyed and all
remaining coconuts have hit the beach. The game starts when the user
presses the space bar. If the game is already running, pressing the space
bar pauses it, and then pressing it again resumes game play. The up arrow
key must fire the laser, but you can add actions for other keys as well.
You are being given an initial implementation of the game, either through
GitHub Classroom or coconuts.zip
. You may
change most of the elements you are given, but preserve the following:
Do not change the keystrokes or their effect. Right and left arrows move the players object and space starts/pauses/resumes.
Beach
, Coconut
, Crab
, IslandObject
, HittableIslandObject
, LaserBeam
, and OhCoconuts
are
all domain classes. All IslandObject
instances implement the following
operations that can be used to detect which type of object is which type:
public boolean isHittable(); // is object hittable by any other object
protected int hittable_height(); // the y coordinate where it can be hit
public boolean isGroundObject(); // does the object exist on the ground
public boolean isFalling(); // is the object one that falls
public void step(); // make the object move one step
public boolean canHit(IslandObject other); // can this hit other?
public boolean isTouching(IslandObject other); // is this object touching the other object?
Feel free to change which classes are derived from
HittableIslandObject
. In particular, you may find it helpful to add
additional abstract classes.
Objects are considered to be “touching” (that is, colliding) if the appropriate y values are the same and the center of one object is between the left and right coordinates of the other. For falling objects, the appropriate y value is the bottom of the object. For the crab, beach, and laser, the y value is the top.
All IslandObject
instances have an ImageView
capturing how to display
the item. To move an object, change the x
and y
values and then call
display
. These classes (as for all domain classes) should have minimal
interactions with JavaFX.
Methods like isGroundObject
make it possible to determine which objects
represent things like being a crab or a falling coconut. You can change
these methods, but keep the basic concept: having these methods mean you
do not need to call instanceof
in your code. Using instanceof
leads
to content cohesion since it is based on Java implementation features
rather than domain-level concepts. Do not use instanceof
in your
code.
GameController
is the JavaFX controller. It keeps track of the primary
JavaFX elements such as the Pane
for the game. It also contains a
Timeline
object that is used to periodically move other objects in the
game. Every few milliseconds, this calls OhCoconutsGameManager
methods
to move the game objects for one “tick”. You may modify GameController
,
but keep as much of the game play functionality in the
OhCoconutsGameManager
and domain classes.
OhCoconutsGameManager
contains the lists of island objects and calls the
appropriate methods to make each move on a tick. It also calls methods to
check for collisions. This class interacts with JavaFX, but the
interactions are limited.
You may adjust the constants controlling how fast objects move.
There are likely other things you need to know about the provided code; ask!
Add a scoreboard class to your game to track the number of coconuts that are destroyed by lasers and the number of coconuts that hit the beach. You can track additional information as well.
Introduce the Observer Pattern to handle one object hitting another. In
particular, create a subject class that responds to hit events and
multiple observer classes that capture the effects of various hit
events. For example, the score board might need updating, objects may
need to disappear, or the game may terminate as a result of various hit
events. You can decide how much and what information to pass to the
corresponding update
operation. You will likely find it helpful to
distinguish between different hit events.
You can use the Observer Pattern in lots of ways, but at a minimum you must use it for the following:
To ensure you have used the Observer Pattern correctly, check you have done the following:
Subject
attach
, detatch
, and notifyAll
Observer
update
notifyAll
update
method does the primary work that happens
in response to the state change captured by the subjectYour instructor may provide small amounts of extra credit for additional features. See Canvas.
There is a coconut tree picture in the images folder. Feel free to add it to the game screen along with other elements. Important: keep the images in the images folder to minimize problems for your instructor when they try to run your code.
Start by getting the provided code to run. You will probably need to add a run configuration:
Open the Run menu and select *Edit Configurations…
Click on *Add new run configuration… and select Application
Click on the browse button at the far right of the box titled Main class, wait a second for options to update, and select Main of coconuts. This will fill in coconuts.Main for the build and run entry.
Click on Modify Options and select Build VM Options
In the VM Options box enter
--module-path "C:\Program Files\Java\javafx-sdk-24\lib" --add-modules=javafx.controls,javafx.fxml
You may need to update the path to the FX library to match your machine.
Build and run.
Review the code and note how GameController
sets up a Timeline
object that calls theGame.advanceOneTick()
after
moving the coconuts. Review both tryDropCoconut
and advanceOneTick
in
OhCoconutsGameManager
. Note how objects are removed from the game and how
the game ends. Then read the documentation for HitEvent
and
HittableIslandObject
. This should give you enough information to see how
crabs and coconuts move. Possible next steps include adding code to
terminate the game when the crab is hit, then removing coconuts when they
are hit by the laser.
Once you have objects moving around the screen, introduce the score
board. This contains a subtle element: changing the score at run time
typically introduces Java exceptions. This is because of the fact that a
Java GUI is “multi-threaded”: multiple pieces of code are executing at the
same time. The fix is to do the same as for the WeatherStation example
introduced in class: if method xxx
in a class needs to update the text on
the screen, call it by
Platform.runLater(this::xxx);
See TemperatureDisplay
in the WeatherStation code.
You will have two weeks to work on this lab. Your instructor may specify deliverables in the first week. In the second week, you will need to submit a Minimal Solution Diagram showing how you applied the pattern, discussion about your experiences with the pattern, and screen shots showing your game in various stages. As for previous labs, do not use a reverse-engineer tool to create the diagram; it must be drawn by hand using Enterprise Architect.
To help you get started with the minimal solution diagram, we have created
the EA file island.qea
. You may modify this file however
you like, including moving classes, changing assocations and
generalizations, adding new classes, and removing classes you do not need.
Consistent with the requirements for mimimal solution diagrams,
the diagram properties have been set so type information is not shown.
isHittable
, isGroundObject
, isFalling
, canHit
, and other IslandObject
methods are correct for each class.instanceof
. instanceof
is an example of content coupling. You may find it helpful to add additional predicates to your hierarchy.Node
(including ImageView
) is 0,0. This means y=0 is the top of an image. This is important when writing isTouching
. You may find it helpful to write a targetY
method that defines where an object touches another.