CS 2 |
Igel Ärgern |
Winter 2012 |
Design, implement, and test a Java application to play the board game Igel Ärgern (commonly translated as "Hedgehogs in a Hurry").
Rules are very easy to find on the web (begin by reading this document as well as others at boardgamegeek.com); and we will demonstrate the game in class. Therefore, we provide only a very brief overview here:
The standard game is played on a grid with 6 rows and 9 columns. Each player has four tokens (representing hedgehogs) that he or she must move from the left column to the right column. Hedgehogs sharing a square stack on top of each other. Only the hedgehog on the top of a stack may move.
On his turn a player:
The game is interesting because:
This document contains a long list of variants. You will be required to implement some of them.
This jar file contains an example completed project. Use it to learn how to play
the game as well as get an idea of how your project may look when complete. To run the demo, download the jar file,
then run java -jar igelArgern.jar
from the command line.
IIgelGame
interface. In addition, your model must have at least these two constructors:
public MyIgelGame(IgelGameParameters)
public MyIgelGame(IgelGameParameters, InputStream)
IgelGameParameters
object. In addition:
IgelGameParameters.obstacleType
specifies
the type for all obstacles for this game. Please make sure your model recognizes the strings
"ConcreteBlock" and "DeepPit". You may also have it recognize additional strings.
IgelGameParameters
object. When this is the case, simply ignore the redundant values in the
IgelGameParameters
.
IIgelGame
(i.e.,
your main game engine) should be the only
public
class in your model package (other than IgelGameParameters
or any other
instructor-supplied code).
Consider the sample presenter below:
public class MVPDemoSidewaysButton { private IIgelView view; private IIgelGame game; public MVPDemoSidewaysButton() { game = new IgelGameKurmas(new IgelGameParameters()); view = new IgelViewKurmas(game.getState()); view.addPassSidewaysMovePressedListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { sidewaysButtonPressed(); } }); } private void sidewaysButtonPressed() { game.passSidewaysMove(); view.update(game.getState()); } public static void main(String[] args) { new MVPDemoSidewaysButton(); } }
The presenter class contains two instance variables: a model and a view. The view looks something like this:
Notice that the view has a button labeled "Pass Sideways Move". The view, however,
does not contain an ActionListener
for this button. Instead,
the presenter creates the ActionListener
and passes it to the view using the method
addPassSidewaysMovePressedListener
. As a result, when the user presses the "Pass Sideways Move"
button, the ActionListener
in the presenter calls the sidewaysButtonPressed
method. This
method knows that when the user presses the "Pass Sideways Move" button, then the model should be told that the
current user has chosen not to make a sideways move (hence, the call to
game.passSidewaysMove()
). The
presenter then queries the model for an updated state (the call to
game.getState()
) and passes that state
back to the view (the call to
view.update(game.getState()
).
Placing the ActionListener
in the presenter instead of the view separates the
code that defines how the view should look (i.e., where different components are placed and
how they are drawn) from the code that defines what should happen in response to a specific user interaction.
This separation has several advantages:
Begin by creating a new project in your favorite IDE. Create a package for this project. (I called my package
igel
.) Inside that package, create two packages: model
and presenter
.
(You don't need to create an view
package because we provide all the code
you'll need in this package.) Download igelArgern.jar
and
import it into your project. This jar file contains support code including the interfaces you must implement
(IIgelGame
,
IGameState
,
ICellState
, etc.)
as well as a complete view for your use (you may,
of course, write your own if you choose).
Next, begin your model class. At a minimum, you need a main game class that implements both
IIgelGame
and
IGameState
and a small hierarchy of classes to represent the cells on the board. The top of your "cell" hierarchy should
implement the
ICellState
interface.
Test and implement just enough of the model so that you can create a game with a board using concrete blocks
and the standard layout (as shown in the pictures above).
Now, experiment with the provided view
(IgelViewKurmas
) to see how it
works. Begin by importing PresenterToDemoView.java
into your presenter
package and running it. (You will need to modify the package
statement
to match your package structure.)
Examine the presenter source code to see how each behavior is implemented.
When you are comfortable with how the provided view works, create your own presenter and begin adding behaviors.
One technique for implementing an MVP design is to use the idea of "user stories". A user story
is
a statement like this: "When the user xxxxx
, then yyyy
". For example:
"When the user clicks on a cell in the left
hand column at the beginning of the game, then a hedgehog should appear in that cell." We suggest that students
iteratively define and implement such stories. For example, to implement the aforementioned story:
CellActionListener
inside the presenter.
addCellActionListener
method.
cellClicked
method so that it places a new hedgehog.(PresenterToDemoView.java
implements these steps.)
You still have a lot of work to do for this feature:
placeHedgehog
method in your model.At this point, completing the project is primarily an iterative process of defining, refining, testing, and implementing stories.
You will probably have to experiment with IgelViewKurmas
a bit to understand exactly
what it does. (We provide PresenterToDemoView.java
to help
with this exploration.) The view's basic purpose is to
However, IgelViewKurmas
view also provides a number of optional features that improve the playability of
the game. When you run igelGameProject.jar
, you can choose "Simple Presenter" or "Complex Presenter".
The complex presenter uses all of the view's features. The simple presenter responds only to mouse clicks. It
does not attempt to prevent the user from making an illegal move or allow the user to drag and drop the hedgehogs.
The complex presenter has about 12% more lines of code.
setProposedMoveSource(row, column)
will temporarily hide the hedgehog at the top of the
stack in a cell [row, column]
. This represents a user having picked up a hedgehog to move it.
Similarly,
setProposedMoveTarget
simulates the user holding a hedgehog over a new square by displaying
a translucent piece. Finally,
clearProposedMove
cancels
the proposed move and sets the view so that it again exactly reflects the game state.
enableSidewaysViewButton
allows the presenter to enable or disable the "Pass Sideways Move" button. It is not necessary to disable the
button, but doing so when appropriate makes for a better user experience. (Otherwise,
there will be times when pressing the button causes nothing to happen, which can be confusing.)
displayDialogMessage
displays a modal dialog box.
close
closes the view. In particular, it closes and destroys the underlying JFrame
.
IgelViewKurmas
is not reconfigurable. The only way to start a new game
or resize the board is to close the current view and create a new one.
CellActionListener
The
CellActionListener
is very similar to the standard Swing MouseListener
. The two key differences are
MouseListener
methods take only a MouseEvent
object.)
cellReleased
method are the location of the cell over which the mouse was
positioned when it was released. In contrast, when Java Swing generates a "mouse released" event,
it attaches that event to the component over which the mouse was positioned when it was pressed. The
non-standard behavior implemented by IgelViewKurmas
allows users to press in a cell to pick a
piece up, then release that piece into a different cell.
ICellState
and IGameState
Notice that the IgelViewKurmas
constructor and
IIgelView.update()
methods both take
IGameState
objects as parameters instead of
IIgelGame
objects. The purpose of this second interface is to prevent the view from "cheating" and modifying the model. The
view can't modify the model because the IGameState
interface doesn't have any mutator methods.
Similarly the use of the
ICellState
interface allows the view to examine the state of individual
board cells without requiring the model to expose its private Cell classes. This design not only prevents the
model from modifying the game state, but it also helps minimize the coupling between the model and view.
IgelViewKurmas
uses objects called Artists to draw each cell.
An Artist
is simply an object that
implements a
draw
method. The
type()
method in ICellState
tells the view which artist to use. The IIgelViewKurmas
constructor
takes an optional Map<String, Artist>
parameter. This Map
maps the strings returned
by ICellState.type()
to the Artist
object used to render the cell. Notice that all cells
of a given type share the same Artist
object. This means that the Artist
shouldn't store
any state. The view of any given cell should be determined solely by it's state (i.e.,
the ICellState
object).
To add another obstacle type, do the following:
Artist
. This step is optional
because one of the existing Artists may work for the new obstacle type. For example,
if the new obstacle doesn't allow hedgehogs inside, then you can simply use the existing
ConcreteBlockArtist
.
IgelViewKurmas.defaultArtistMap()
to obtain the default map.IgelViewKurmas
constructor.Map<String, Artist> artistMap = IgelViewKurmas.defaultArtistMap(); artistMap.put("FinishCell", new SmileyArtist()); // Create a view configured for the specified game. final IIgelView view = new IgelViewKurmas(model.getState(), artistMap);
IgelViewKurmas
comes with the following artists: "Cell Type" is the string that is mapped to that
artist.
Name | Cell Type | Description |
---|---|---|
CellPanelArtist |
"Cell" | Draws a "regular" cell: A cell containing a stack of hedgehogs. |
FinishCellArtist |
"FinishCell" | Draws the cells in the rightmost column (i.e., the "finish" column). Works just like a
CellPanelArtist ,
except it draws the finish line on the left side of the cell.
|
DeepPitArtist |
"DeepPit" | Draws "deep pit" obstacles. These cells can contain a stack of hedgehogs. The artist draws a "regular" cell with a black background and a red border. |
ConcreteBlockArtist |
"ConcreteBlock" | Simply draws a solid black rectangle. |
If you look at the
IIgelGame
documentation,
you will see it contains a
GameEventListener
. This interface allows the model to report events back to the presenter. You
are not required to implement and utilize a GameEventListener
, but doing so can simplify your code.
Consider the die. Without the GameEventListener
, your presenter must query the model for the die
value after every interaction that may result in a die roll. In contrast, the GameEventListener
provides a mechanism for the model to tell the presenter that the die value has changed. The presenter can react
to this event instead of "polling" the model.
The provided GameEventListener
is just an example. You may use some,
or none of these events. You can also add your own events by defining your own subclass of
GameEventListener
.
ICellState
object correctly implements the
type()
method. Unless you have
added
your own Artist
s or artist map, the type
method should return only those values
listed in the "Cell Type" column of this table.
You must demonstrate your program. It is your responsibility to find a time for the demonstration. Programs that are not personally demonstrated may not be graded.
Electronically submit
igelArgern.jar
: All necessary demo and support code.PresenterToDemoView.jar
: A sample presenter to demonstrate
various features of IgelViewKurmas
.