Comp201: Principles of Object-Oriented Programming I
Spring 2008 -- Final Project: BallWar   


(A summary of the students' final projects can be found here).

Project Description

For our final project, we will return to BallWorld, but this time we will put it on steroids (legally, of course!).

The final project will be to take the upgraded BallWorld framework (rename "BallWar") and to build a game of your own design from it.

You are allowed to work in teams of at most two people for this project. Upload a copy of the finished product to EACH PARTNER's submission.

(Note that there is a lab that involves this project)

Ground Rules

  1. The BallWar system must be used as a base platform.
  2. Any modifications of this platform may be made, subject to the other criteria.
  3. At least 15 changes to the supplied framework must be performed. These changes include, but are not limited to
    1. Enhancing an existing feature
    2. Fixing a limitation of an existing feature
    3. Fixing a bug in an existing feature.
    4. Etc.
  4. The removal of a feature does not qualify as a change, even though it may be necessary for your application!
  5. Of those 15 changes, at least 5 of the changes must be completely new behaviors for the system. These additions include, but are not limited to:
    1. Adding an ability to actually "win" or "lose" the game.
    2. Adding the ability to define custom movement keys.
    3. Adding strategy-specific pop-up configuration panels (ala the ShapeCalculator).
    4. Whatever else comes to your fertile imagination!
  6. Full documentation required. This documentation is to include all of the following:
    1. Full javadocs, including for all private members (be sure to set DrJava to include private members when it creates the javadocs!).
    2. A complete user manual that describes
      1. what the game is,
      2. how you play it and
      3. how you win/lose it.
    3. A complete description of at least 15 of the changes you made above which must include at least 5 of the additions made. The descriptions should include:
      1. What the change was.
      2. Why the change was made.
      3. Where (in what classes) the change can be found. Note that a change may span multiple files.
  7. The supplied code has been designed to be fairly flexible, extensible, robust and correct.
    1. It is not perfect however, however your code is expected to be at least as flexible, extensible, robust and correct.
    2. Along these same lines, the supplied code is designed to maximally decouple the view from the model, the ball from its environment, and the strategies from their contexts. Your code is expected to maintain at least the supplied level of decoupling.
  8. The supplied framework is designed to illustrate a number of techniques that you might want to use in your game.
    1. You are not required to utilize all the features or techniques present in the supplied code.
    2. Not all techniques are used consistently throughout the supplied code. This is so that multiple techniques can be demonstrated. A viable change would be to regularize the techniques used throughout

Check out the demo! (may take a little while to load, includes a short "users manual")

Documentation for BallWar

Download the code for BallWar (collision detection deactivated--see accompanying lab)

Click here to learn more about the new BallSwarm code base

By Monday April 21, 2008, you must e-mail to comp201, a brief description of your proposed game, providing at least the following information in your e-mail (use a bulleted format please!)

Strong recommendation: Keep It SIMPLE!! You can always add features later if you've built it right in the first place. You are not being graded on the elaborateness of your game.

Your game description information will be posted to the Comp201 web site under the Final Project Student Project Descriptions

Grading criteria:

When you have completed your final project, zip up the entire directory, including all non-JVM packages needed to run your system and all documentation and upload it from the usual upload site.

Be sure that both you and your partner's names are in the documentation!!

Background Information:

Here is some background information on the architecture of the supplied code, which you are free to change, subject to the above guidelines.

Paint Strategies

The BallWar system uses paint strategies to paint the desired shapes and images corresponding to a Ball on the screen.   This is very similar to the way that update strategies are used for the Ball's behaviors.    Please carefully read the detailed web page on painting strategies.

Important note regarding image files:   In the demo, the image files are in the directory ballwar\model\paint\images in the src directory.   This directory must be manually added to the classes directory structure--DrJava will not automatically copy it from the src directory to the classes directory.   Also, when creating a jar file using DrJava., this directory will not be put into the jar file.   You will need to use a zip tool such as WinZip or WinRAR to open the jar file up and insert the images directory in the correct location.     If you configured your system to read files solely from the file system on the disk, then the images will need to be stored outside of the jar file.    See the internal code documentation in ImagePaintStrategy.java for more information.

 

Component-Framework Systems:

The BallWar system is what is referred to as a "component-framework" system. A framework is a the invariant portion of a system that provides services for and management of the variant portions of the system, the components. We can think of components as "plugging into" the framework. Examples of component-framework systems are Netscape (the framework) and it plugins (e.g. RealPlayer, the components), an XBox console (the framework) and its game cartridges (components), a computer motherboard (the framework) and its memory chips (components), and the electric power delivery system in a house (the framework) and all the appliances plugged into it (the components).

Component-framework systems are important because they enable a complex system to be stable, but yet to grow and be modified, even at run-time. The invariant framework only knows about the abstract component, and thus can handle any concrete component that is installed at any time. Components know that they can rely on an invariant framework to provide management, communications and other services and are thus freed from those tasks. Components remain decoupled from each other by the framework, reducing the internal complexity of the system making it more understandable, more extensible, more flexible and more robust.

But "variant" and "invariant" are relative terms. What is variant at on level of abstraction may be invariant when viewed from another, name from a level of lower abstraction. BallWar thus has several component-framework systems imbedded within it, some nested within each other:

  1. The BallModel is a framework for the component Balls.
  2. The Ball is a framework for the component IUpdateStrategies.
  3. The Ball is a framework for the component IBallCmds.
  4. The BallGUI is a framework for the component Components (duh!), especially the OptionPanels.

See if you can find more examples of component-framework systems buried in the BallWar system!

Command Dispatching:

The BallWar system uses a "command dispatching" system for communicating with all the balls that are in the system. This system is an extension of the Observer-Observable design pattern. The Observer-Observable pattern is used when GUI components (Observerables) have listeners (Observers) added to them to respond to mouse clicks, key strokes, etc.

The BallModel has a Dispatcher object (the "Observable")to which all the balls ("Observers") have been added, as they all implement the necessary Observer interface. When the Dispatcher's notifyAll method is called, each of the ball's update methods are called. The balls are called one at a time and in no guaranteed sequence.

The Dispatcher's notifyAll method allows a parameter to be passed to each ball's update method. In order to make this simple pattern allow us to do anything we wish with the balls, we pass an abstract IBallCmd object (Command design pattern) to the ball. All the ball's update method does is to call the command's apply method, handing itself as the context. The command object's apply method now has the ability to call whatever methods it desires on the ball and accomplish whatever task it so desires.

The two IBallCmd commands that are currently used by the system are for updating the state of each ball and for detecting collisions between any given ball and all the other balls.

Collision Detection and Processing:

Collisions are handled in a straightforward, brute-force and not particularly efficient manner in the current BallWar system. Essentially, when a ball moves, it may collide with another ball.  While the ball may be painted using any arbitrary shape or image, the collision system simply approximates the ball to be...a ball.   That is, collision detection is calculated as if the ball were painted as a circle of the ball's radius, even though it's actual painted representation may be quire different.    More sophisticated collision detection is possible but beyond the scope of this class.

A collision is defined as the centers of two balls being less than the sum of their radii. Note that since the system involves discrete-sized movements, a ball may move will inside of the collision radius before being detected. Since the dispatcher processes only one ball at a time, we are assured that only one ball has moved and thus possibly collided with another. It is thus important to maintain a set protocol as to which ball is the one that was moved at that moment.

The dispatcher can be used to send a IBallCmd command out to each ball that can be used to check if a given ball (the one that is in the middle of updating its state) has collided with another ball. Ball provides three methods for this purpose:

From a design standpoint, notice how each method only takes care of very specific tasks and delegates to other methods to perform tasks that are beyond its scope. For instance, checkCollide only does the checking and collidedWith only deals with modifying the original, "context" ball while collidedWithHelp only deals with modifying the target ball. This "separation of responsibility" is crucial in creating complex systems that are understandable and maintainable.

Model-View-Controller Architecture:

The BallWar system uses a Model-View-Controller design pattern ("MVC") to separate the user interface (the "view") from the underlying processing (the "model"). This is because,the view the is a variant with respect to the model while the model is a variant with respect to the view. That is, for a given way of processing something, there are many different ways of presenting its input and output to the user. Likewise, for any given way of presenting input and output of a computation to the user, there are many ways in which that computation can be implemented.

In general, we replace concrete variants with abstract invariants, so we do that with both sides, on the model side looking towards the view and from the view side looking towards the model. If you look at the UML diagram labeled "Model-View-Controller" in the documentation, you will see that the model and the view do not communicate directly, but rather through "adapters". Each adapter, supports an interface that is invariant with respect to whomever is using it. Thus when the model needs to communicate to the view, it only talks to what it thinks is an invariant interface, IViewCtrlAdapter. Likewise, when the view needs to communicate to the model, it talks only to invariant interfaces, here separated into 3 interfaces grouped according to functionality: IGameCtrlAdapter, IBallCreateAdapter,and IBallCtrlAdapter. The adapters do no significant processing themselves and serve chiefly to translate the call being made from the model (view) to the methods available on the view (model). Thus the model and the view are decoupled from each other.

The role of the "controller" (BallControl) is to be the one and only one object in the system that knows about both the concrete model and the concrete view. The controller will instantiate both the model and the view, instantiate all the necessary adapters and connect the model and the view using those adapters. Since the controller knows both the model and the view, it can translate the calls to the adapters to the calls to the respective model or view. Do not think of the controller as a "mediator" but rather as a "setter upper". Once the controller has instantiated and connected the model and the view, its job is done. One thing to note that the controller does take advantage however is that since it is using anonymous inner classes to create the adapters, it can utilize the closure of those anonymous inner classes to enable types of communication that would otherwise be very difficult.

In an MVC system, if one changes the model or the view, one only need to change the controller and not all the rest of the system. This greatly enhances maintainability of the system.

Composing Factories On The Fly

One quandary that was encountered while designing the BallWar system (actually, this was in the original BallWorld system as well) was that the model expects to be given IUpdateStrategies with which to construct new Balls. On the other hand, the BallGUI has no and should not have any knowledge of IUpdateStrategies. The naive solution is to simply pass a String with the strategy's class name from the view to the model and let the model dynamically load the strategy from that name. But the system inherently supports dynamically composed strategies (e.g. using MultiStrategy) and since the model treats these composed strategies identically to uncomposed ones, there should not be any special methods on the model to deal with such special cases. But the view doesn't know about composed strategies any more than it knows about any other strategy. So we have a dilemma: the view needs functionality that the model doesn't support.

Here is where the controller, BallControl, with its intimate knowledge of both the model and the view steps in. The controller defines a private factory for instantiating strategies, IStrategyFac. This factory takes a string name and instantiates the corresponding strategy when its make method is called. Let's see how this simple little interface with very small amounts of code neatly solves our problem. (If this doesn't hook you on factories, nothing will!)

Please reference the code for BallControl and BallGUI while reading the following sections.

  1. The user inputs a name (a string) of a strategy that he/she would like to dynamically add to the system.
  2. The view takes that string and sends it to the model, or so it thinks, expecting some sort of object back. It doesn't know what sort of object is coming back, and quite frankly, doesn't care.
  3. Unbeknownst to the view, the request never went to the model. Instead, the controller (actually, the IBallCreateAdapter adapter instantiated by the controller) has simply taken the given string and created, using an anonymous inner class, a factory to instantiate the corresponding strategy.
  4. The IBallCreateAdapter adapter returns the IStrategyFac object to the view.
  5. The view blithely adds the factory, which is just a random object so far as it is concerned, to the drop lists.
  6. To display its choices, the drop lists use the toString method of the factory objects it was given, as it does which any object added to the drop list., But the factory's toString just happens to return the very name it uses to instantiate the desired strategy. (Cute trick, eh?)
  7. When the user requests that a ball with a selected strategy is made, the view simply yanks the factory from the drop list and hands it to the view, or so it thinks. Notice that the view never needed to know that it was dealing with a factory.
  8. The IBallCreateAdapter once again intercepts the call, takes the object given to it, which it knows is a factory, run the make method of the factory, instantiating the desired strategy.
  9. The new instance of the desired strategy is handed to the model, which is what it wants.
  10. Ta da!

So what about composed strategies? No problem:

  1. When the user wants to compose two strategies, one strategy is selected from each of the two identical drop lists.
  2. Since the drop lists (unknowingly) hold the strategy factories, the view hands two factories to the model via the IBallCreateAdapter.
  3. The IBallCreateAdapter takes the two factories and instantiates a third factory. When this factory's make method is called, it instantiates a MultiStrategy, whose constructor takes the strategies made by the two given factories! Of course, this is all done with anonymous inner classes.
  4. IBallCreateAdapter then returns that new factory to the view.
  5. The view adds that "multi"-factory into the drop lists as normal. The new factory's toString method returns the names of the two original factories with a "-" sign in between them.
  6. When the user requests that a ball be made with the combination strategy, the situation is no different than any other strategy being selected. The factory is passed to the IBallCreateAdapter who instantiates the strategy and gives it to the model.
  7. You've got to love it.

Feel the power of OOP. This is why we do it.

Players and Moveable Objects:

Players add unique challenges to the BallWar system because they exert global influence across the entire system. Players have a score associated with them that may be updated due to interactions between specific balls which, to the system, are equivalent to any other ball. Likewise, a player is associated with a particular set of keys used to control movements, etc of the balls and those keys may only affect particular balls in the system.

The pieces used to implement players are as follows:

To create a player:

  1. At stat up, the controller populates a drop list in the view (using an OptionPanel) with a number of available movement key sets. As always, these sets are just plain Objects to the view.
  2. When the user wishes to create a new player, a name for that player is typed into a textfield and a "Make Player" button is clicked.
  3. The view sends the name to the model. The controller removes the currently selected movement key set from its drop list and sends the two pieces of information to the model.
  4. The model instantiates a new IPlayer object with the given name, whose movement keys are the given set. The model also adds a command to the view (via its ViewCtrlAdapter) to display that player's score on the view's status label.
  5. The model returns the IPlayer object to the view, who places it into the drop list of available players. IPlayer.toString() returns the player's name, so it appears on the drop list as such.
  6. Whenever a player is selected from the drop list, the view simply pulls the IPlayer object out and returns it. Once again, the view never needs to know either the type or the usage of the objects being stored in the drop lists and is thus still decoupled from the model.

To create a "moveable" ball:

  1. Whenever a strategy that either is or is composed with MoveStrategy is selected to be used in the creation of a ball, the MoveStrategy's init method will be called as the ball initializes itself.
  2. MoveStrategy's init can obtain the currently selected player from the context ball's environment (which obtains it from its IViewCtrlAdapter which obtains it from the view).
  3. MoveStrategy instantiates an IMoveable object using an anonymous inner class whose methods modify the velocity of the context ball.
  4. MoveStrategy asks the ball's environment (the model, BallModel) to register the set of movement keys from the player with the IMoveable object it just made.
  5. Thus a player, through their associated movement keys, can now control the movement of the ball using the MoveStrategy.

While slick, this mechanism could use improvements. For instance, at the moment a player can only control one ball at a time and only a freshly made ball at that. Get your thining caps on!

 

JavaDocs For Documentation:

The Java Development Kit (JDK) comes with a tool called JavaDoc.  This tool will generate documentation for Java source code with comments written in accordance with the Java documentation style.   The following links show more examples.  You do need to spend time outside of the lab to study them.

Use DrJava to look at both the code and the comments in the BallWar system, which follow the Javadoc conventions.  The following is a very short summary of the Javadoc conventions.

Creating javadoc using DrJava:

In DrJava, 

Now change the javadoc Access level in the javadoc Preferences to private and generate javadoc again.  What is  the difference?

 

 


Last Revised Thursday, 03-Jun-2010 09:50:22 CDT

©2008 Stephen Wong and Dung Nguyen