CS 2

Igel Ärgern

Winter 2012

Objectives

  1. Provide experience managing a moderately large project
  2. Provide experience with the semester's key topics, including

Due Dates:

Problem Statement

Design, implement, and test a Java application to play the board game Igel Ärgern (commonly translated as "Hedgehogs in a Hurry").

Image of box cover Image of board Image of board

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:

  1. rolls the die,
  2. optionally moves one of his hedgehogs up or down one row (we call this a "sideways" move, because the hedgehog is moved sideways relative to the direction of travel toward the finish line), and
  3. chooses one hedgehog (either his or another player's) in the row indicated by the die roll and moves it one space to the right (i.e., "forward").

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.

Project Requirements

Implement and test the game logic for Igel Ärgern. (We will provide the GUI. More on that later.) In addition to playing a basic game of Igel Ärgern, your program must meet the following requirements:
  1. Each obstacle may have a different behavior. (This is the inheritance/polymorphism part of the project.)
  2. Users should be able to load a board configuration from a file. The file must specify
  3. When beginning a game, users must be able to specify To specify the board configuration, users either choose a file (as described above) or specify the number of rows, number of columns, and the obstacle type used for this game. When not using a file, you may use the default location for obstacles and have all obstacles be of the same type. (You of course may optionally provide users a means of specifying the location and type of each obstacle. This could be a good "bonus feature".)
  4. By default, obstacles are deep pits. A hedgehog that falls into a pit is stuck there until every other hedgehog has caught up. In addition to deep pits, you must implement "Concrete Blocks" and at least two other obstacle behaviors. When the obstacles are concrete blocks, hedgehogs simply cannot enter that square. (This is variant 37). You may choose your other two behaviors from the rule variants, or invent one of your own. Here are some ideas:
  5. You must have some automated mechanism for testing the game engine. This need not be JUnit.
  6. You must either:
    1. Implement your own user interface (text or GUI), or
    2. Implement a set of instructor-approved "bonus features". Your bonus features can be rule variants (other than simply adding another obstacle type), an AI that other users can play against, or almost any other set of interesting features that are worth at least 10% of the project grade.

Required Design Elements

In order to meet the objectives for this project,

Overview of Model-View-Presenter

We suggest using a design pattern called Model-View-Presenter. With MVP, the application is divided into three pieces: picture of MVP

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:

picture of provided view

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:

Suggested Approach

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:

  1. Define a CellActionListener inside the presenter.
  2. Pass the listener to the view using the addCellActionListener method.
  3. Finally, implement the listener's 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:

At this point, completing the project is primarily an iterative process of defining, refining, testing, and implementing stories.

Using the provided view

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

  1. draw a picture of the current state of the game (the locations of all the hedgehogs, the current die value, and the most recent message generated by the game engine) and
  2. report UI events to the presenter.

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.

Show moves "in progress"
Specifically, calling 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.
Enable/disable the "Pass sideways move" button
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.)
Displaying a modal dialog box
Calling displayDialogMessage displays a modal dialog box.
Closing the view
Calling 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

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.

Artists

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:

  1. Optionally, create a new 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.
  2. Call IgelViewKurmas.defaultArtistMap() to obtain the default map.
  3. Add an entry to this map for your new obstacle:
  4. Pass the updated map as the second parameter to the 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.

Model-generated Events

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.

Hints and Reminders

Grading / Rubric

Here is the grading rubric.

Academic Honesty

You may not under any circumstances look at the source of another person's Igel Ärgern game. In particular, you may not download implementations of Igel Ärgern from the web and look at the source code (whether original or decompiled). High-level help from classmates is allowed and encouraged, but looking at their specific implementation is not.

Deliverables / Submission

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

Instructor Supplied Resources


Valid HTML 4.01!