Planning Larger Projects

Writers and Consumers of Code

If there are several people working on different parts of a larger project, they can be both a writer and a consumer of code at different times:

The public interface between classes

Since a consumer is likely to be a user of a class other than the one she or he is writing, the methods of the other classes must be public, allowing different classes to interface/connect/relate. (This is a different use of the word interface than the Java key word!)

The user of a class does not need the private internals of the class, but does need to know how to use public methods. In particular, for a public method, what they need to know and what consumer/public documentation needs to make clear is:

Good identifier names helps here, but extra specification is often useful. I have try informally to give this kind of data with lots of my methods. Much more sophisticated documentation can be generated automatically by the Javadoc utility, if the internal method and class documentation follows various formatting rules.

Documentation in the same form is also good for private methods, helping support the class implementers.

Cohesion, Coupling, and Separation of Concerns

There are three important ideas in organizing your code into classes and methods:

Cohesion of code is how focused a portion of code is on a unified purpose. This applies to both individual methods and to a class. The higher the cohesion, the easier it is for you to understand a method or a class. Also a cohesive method is easy to reuse in combination with other cohesive methods. A method that does several things is not useful to you if you only want to do one of the things later.

Separation of concerns allows most things related to a class to take place in the class where they are easy to track, separated from other classes. Data most used by one class should probably reside in that class. Cohesion is related to separation of concerns: The data and methods most used by one class should probably reside in that class, so you do not need to go looking elsewhere for important parts. Also, if you need to change something, where concerns are separated into cohesive classes, with the data and logic in one place, it is easier to see what to change, and the changes are likely to be able to be kept internal to the class, affecting only its internal implementation, not its public interface.

Some methods are totally related to the connection between classes, and there may not be a clear candidate for a class to maximize the separation of concerns. One thing to look at is the number of references to different classes. It is likely that the most referred to class is the one where the method should reside.

Coupling is the connections between classes. If there were no connections to a class, no public interface, it would be useless except all by itself. The must be some coupling between classes, where one class uses another, but with increased cohesion and strong separation of concerns you are likely to be able to have looser coupling. Limiting coupling makes it easier to follow your code. There is less jumping around. More important, it is easier to modify the code. There will be less interfacing between classes, so if you need to change the public interface of a class, there are fewer places in other classes that need to be changed to keep in sync.

Aim for strong cohesion, clear separation of concerns, and loose coupling. Together they make your code clearer, easier to modify, and easier to debug.

The Game of Zuul

Planning A Class Structure for Zuul

The screen input/output interchange below illustrates an idea for a skeleton of a text (adventure?) game, built on the ideas of Michael Kolling and David J. Barnes. It could be the basis of a later group project. It does not have much in it yet, but it can be planned in terms of classes. Classes with instances correspond to nouns you would be using, particularly nouns used in more than one place with different state data being remembered. Verbs associated with nouns that you use tend to be methods. Think how you might break this down, looking at what is happening in the sequence below.

The parts appearing after the '>' prompt are entries by the user. Other lines are computer responses:

Welcome to Loyola!
This is a pretty boring game, unless you modify it.
Type 'help' if you need help.

You are outside the main entrance of the university that prepares people for
extraordinary lives.  It would help to be prepared now....
Exits: east south west
> help
You are lost. You are alone.
You wander around at the university.

Your command words are:
   help go quit

Enter
    help command
for help on the command.
> help go
Enter
    go direction
to exit the current place in the specified direction.
The direction should be in the list of exits for the current place.
> go west
You are in the campus pub.
Exits: east
> go east
You are outside the main entrance of the university that prepares people for
extraordinary lives.  It would help to be prepared now....
Exits: east south west
> go south
You are in a computing lab.
Exits: north east
> go east
You are in the computing admin office.
Exits: west
> bye
I don't know what you mean...
> quit
Do you really want to quit? yes
Thank you for playing.  Good bye.

Think and discuss how to organize things first, mostly from a high level, thinking of the pubic interfaces....

As you think how to break this game into parts (classes), also think how the classes interact (public methods). This is a good place for the start of a classroom discussion.

Implementation Design

Continue after thinking and hopefully discussing the ideas above.

Clearly one would want to elaborate Zuul before spending much time playing it!

The classic text adventure game setup is to have the user give text commands and have them change the game state and give some feedback to the user.

Even without any more details of the game, we can see several things that need to be included for a new command:

  • The basic help string with all the command names needs to be changed.
  • There needs to be provision for the more detailed help for "help <command>".
  • The program must distinguish the new command when analyzing the next user input, and then execute the right code for that command.

With a naive approach, this means at least three places, likely widely separated in the program, need to be located and changed for each new command. That is not making it easy for a program modifier.

We can look at the generality of this problem another way: These are exactly the three things we need to do for each command. Rather than spreading the code to do these things all over the program, the details for each command can be in a single, coherent class. While they will be totally different classes, the common abstraction of what they need to accomplish means we can unify their treatment with an interface!

You will have to wait a bit to see, but this will mean that other than a new class for a new kind of response to the user, only one single statement in the program needs to be changed to include the new behavior (other than whatever added state the Game object itself needs).

Each new action will be a response to the user, so let's call our interface Response. We will have a part for each of the bulleted items above, elaborated below:

  • To construct a string of all command names, each Response needs to have an associated command name.
  • We need to produce the extended help string for the specific command.
  • We need to actually execute the actions required for the command each time the user asks for it. A subtle issue here is that a command could at some point be all that is needed to end the game - either winning or losing. While the command can indicate the outcome to the user on the screen, the program also needs to know to stop at that point.

So here is the interface. (Documentation strings are encouraged!)

/**
 * Object that responds to a user's command
 */
public interface Response
{
    /**
     * Execute a command.
     * @param  tokens  The token entered by the user.
     * @return true if the game is over; false otherwise
     */
    boolean execute(String[] tokens);

    /** @return the command name */
    String getCommandName();

    /** @return the help string */
    String help();
}

The source code for all of Zuul is in the examples zuul folder. There are also zip files of source and of the class files and data file needed for execution, and a jdocs folder.

IntelliJ Idea will make complete, hyperlinked documentation for you with Tools -> Generate Javadocs. This folder was generated automatically, based on the Javadoc comments that I included!

In the original version of the game, there are three commands, so we need to write three classes to support them, all implementing Response. I named them Goer, Helper and Quitter. More could be added. You can check how they do satisfy the interface.

To manage the commands as an aggregate we need some code that I put in the class CommandMapper. Its principal duty will be to map a command name entered by the user to the appropriate command to execute, via a HashMap responses.

Here is the code. More discussion follows:

import java.util.HashMap;

/**
 * This class is part of the "World of Zuul" application.
 * "World of Zuul" is a very simple, text based adventure game.
 *
 * This version holds mapping from command words to Responses
 */

public class CommandMapper
{
    private static HashMap<String, Response> responses; //responses to commands
    private static String allCmds;

    /**
     * Initialize the command response mapping
     * @param game The game being played.
     * @param helpIntroStr World data for help intro.
     */
    public static void init(Game game, String helpIntroStr) {
        responses = new HashMap<String, Response>();
        Response[] responseArray =    // insert yours in the sequence!
                     {new Goer(game),
                      new Helper(responses, helpIntroStr),
                      new Quitter()
                     };
        allCmds = "Your command words are:";
        for (Response r: responseArray) {
            String name = r.getCommandName();
            allCmds += " " + name; // in same order as responseArray
            responses.put(name, r);
        }
    }

    /**
     * Check whether a given String is a valid command word.
     * @param aString The possible command word.
     * @return true if it is, false if it isn't.
     */
    public static boolean isCommand(String aString)
    {
        return responses.get(aString) != null;
    }

    /**
     * Return the command associated with a command word.
     * @param cmdWord The command word.
     * @return the Response for the command.
     */
    public static Response getResponse(String cmdWord)
    {
        return responses.get(cmdWord);
    }

    /**
     * @return a string containing all valid commands.
     */
    public static String allCommands()
    {
        return allCmds;
    }
}

The class level data is all static, so it just has static methods. In place of a constructor, we have init.

Note that the only place a reference to a specific one of the command types gets included is in init, initializing the commandArray with a sequence of Response class constructors. The constructors can refer to whatever data is needed for the particular command's needs.

Note the cohesion of this class: all methods deal with all the commands together: checking if a name matches an actual command, mapping a name to the proper command, getting a string of all the commands. To set this up, only the interface methods are used in the different classes implementing Response.

The main class is Game, which also has the central object, the Game, which gets played. Note that the short central play and processCommand methods make no reference to specific commands - they just work on some Response.

That is the end of the overview of the importance of the interface in the game. We will look at one further feature mostly:

Supplying Canned Data

A game like this needs a huge amount of canned data describing the world, to go in specific strings and in assorted lists and mappings. All this could be hard-coded in the program with literal strings and maybe numbers used to set a numbing assortment of assignment statements and additions to lists and maps.

This game code approaches this issue differently: No data for the world is hard-coded: It is all read from a single text file, in this case from startData.txt. This is a different sort of cohesion! Just by replacing that file you could have a game for a very different world. Though I am still calling the game Zuul, like it was originally, clearly I have totally replaced the world data.

You can check out the utility class FileUtil that supports the file-reading.

Most of the data is for the places in the world (Rooms as I call them), with their descriptions, and connections/exits to other Rooms. There is a major static method in the Room class, createRooms, that initializes all the rooms from the bulk of the data file.

There is a lot more code, but not a lot of new ideas in the implementations, while giving lots of examples of topics discussed in the course. If you do want to base a game on this code, then getting familiar with the rest is important.

Here is the code link again: the src sub-folder of example folder zuul.