Lecture 6: The Model
1 An introductory example:   Tic-tac-toe
1.1 Object-oriented analysis
1.2 Designing an interface
1.2.1 How to move
1.2.2 Whose turn is it?
1.2.3 Getting the grid
1.2.4 Finding out the results
1.3 Implementation
2 A bigger example:   Connect $N$
2.1 Object-oriented analysis
2.2 Interface design
2.2.1 How to move?
2.2.2 Querying the configuration
2.2.3 Querying the game state
2.3 Configuring the model
6.5

Lecture 6: The Model

Suppose you wanted to implement a graphical game. Where would you start, and how would you structure your program? A common object-oriented technique for structuring graphical programs is the model—view—controller pattern, which separates the program into three distinct but cooperating components,

It can sometimes be mysterious what this means in practice, and we will get to that eventually. A reasonable place to start, when both designing and learning, is the model, so that is our subject for this lecture.

1 An introductory example: Tic-tac-toe

For the unfamiliar, Tic-tac-toe is a two-player game played on a 3-by-3 grid, where the players alternately mark cells of the grid. A player wins by making a line of three marks, or if the grid fills with no winner then the result is a tie. For example, here a sequence of game states in which player “X” wins:

 │ │     X│ │     X│ │O    X│ │O    X│ │O    X│ │O    X│ │O    X│ │O
─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─
 │ │      │ │      │ │      │ │      │O│      │O│      │O│     X│O│
─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─    ─┼─┼─
 │ │      │ │      │ │      │ │X     │ │X    X│ │X    X│O│X    X│O│X

1.1 Object-oriented analysis

In order to design a model for a Tic-tac-toe game, we need to consider what operations a client of our model will need to perform to manipulate and query the game state. What do we need to be able to do? These operations should suffice:

Because we want the model to enforce the rules of the game, we should also consider ways in which clients should not be able to manipulate the model. Rather than allow the model’s state to be corrupted, it should signal an exception in these cases:

Having completed our analysis, we can now begin to design an interface for the Tic-Tac-Toe model.

1.2 Designing an interface

We have a number of choices for how to realize the operations above as methods in Java. Let’s start with a method or methods for playing moves.

1.2.1 How to move

In one possible design, we could provide separate methods for playing as X and playing as O:

/**
 * Places an X mark in the specified cell.
 *
 * @param column the column of the cell
 * @param row    the row of the cell
 * @throws IllegalStateException if it's Y's turn, if the game is
 * over, or if there is already a mark in the cell.
 * @throws IndexOutOfBoundsException if the cell is out of bounds.
 */
void moveAsX(int column, int row);

/** [mutatis mutandis] */
void moveAsY(int column, int row);

With the above design, the model must throw an exception if its client calls moveAsX when it is Y’s turn or moveAsY when it is X’s turn.

Alternatively, because the model must track whose turn it is, we could simply have one move method, and require the model to move for the correct player in each case:

/**
 * Places an X or O mark in the specified cell. Whether it places an X
 * or O depends on which player's turn it is.
 *
 * @param column the column of the cell
 * @param row    the row of the cell
 * @throws IllegalStateException if the game is over, or if there is
 * already a mark in the cell.
 * @throws IndexOutOfBoundsException if the cell is out of bounds.
 */
void move(int column, int row);

Of course, there are additional possibilities such as a single move method that takes some specification of which player is moving as the parameter. And we may not have specified the method fully, since we haven’t said how the column and row coordinates map onto the grid. (Are they zero- or one-based? How are they oriented?1Tic-tac-toe has both rotational and reflective symmetries, which means that the orientation doesn’t actually matter so long as the UI is consistent.)

1.2.2 Whose turn is it?

In order to allow the client to find out whose turn it is, we also have several choices. One simple way is a boolean method or methods to ask if it is a particular player’s turn:

/**
 * Determines whether it is player X's turn to move.
 *
 * @return whether X can play now
 */
boolean isXsTurn();

/** [mutatis mutandis] */
boolean isYsTurn();

Even within the above approach, we have a design choice: What should the methods do if called once the game is over? They could return false, or they could throw an exception; either way, we ought to document this choice. Which do you prefer, and why?

Another way would be to have the method return some representation of the player whose turn it is, like so:

/**
 * Returns the player whose turn is next.
 *
 * @return the next player
 * @throws IllegalStateException if the game is over
 */
? nextPlayer();

There are a variety of choices we could use for the return type ?. Which of these do you prefer, and why?:

Using type char is a poor design choice, because the char type has many other values that don’t stand for valid players. Java’s type system will not help us in preventing nextPlayer from returning meaningless values—so then what should we do if it does? More to the point, the char type is good for representing textual characters, and (ab)using it for other meanings is unstylish.

Using a String is even worse, because it has all the same drawbacks as using a char, except it has even more possibilities, including null, the empty string, and longer strings. (Important guideline you should always follow: Strings are for representing textual information, where the possible values are many or unlimited and not known ahead of time. If you find yourself using strings to represent some small set of values, or for any kind of internal API communication that is never presented to the user, you are doing it wrong.)

The boolean idea solves the main problems that we saw with char and String: there are exactly two Boolean values, and we need two values. However, boolean could be confusing, because which player is true and which false is non-obvious. In general, we should use booleans only when the value we are trying to represent is a truth value.

Finally, we come to the enumeration, which declares a new class Player having exactly two values, Player.X and Player.Y. This expresses very clearly what the possibilities are and what they mean. One caveat with enumerations, though: switch statements over enumerations always require a default case, even if every current possibility appears in some other case. This is because Java assumes an open world, in which more values can be added to an enumeration at a later time.

1.2.3 Getting the grid

We want our model to enforce the rules of the game. This is not because we expect untrustworthy users to interact with it directly—all their interactions will be mediated by the view and the controller—but because enforcing model integrity within the model class provides a good separation of concerns. The code that represents the state of the game is the right place to write the logic for how that state may evolve, and encapsulating this logic in the model will make the rest of our program simpler and less error-prone.

In order to enforce the rules and prevent client code from corrupting the state of the grid, we need to ensure that all changes to the state happen via our model class’s public methods, which will enforce the rules. Thus, we cannot merely return a reference to our internal mutable representation of the grid to the client, because then the client could change it. However, we can safely return a copy of the grid (which need not be the same type as what we use internally):

/**
 * Returns a two-dimensional array representing the state of the
 * grid. The first index is the column and the second the row.
 * The resulting array is newly allocated and unconnected to the model;
 * thus, mutating it will have no effect on the model or subsequent
 * calls to {@code getGrid}.
 *
 * @return a copy of the grid
 */
Player[][] getGrid();

Alternatively, rather than return some representation of the grid, we could provide some means of querying it:

/**
 * Returns the {@link Player} whose mark is in the cell at the given
 * coordinates, or {@code null} if that cell is empty.
 *
 * @param column the column of the cell
 * @param row    the row of the cell
 * @return a {@code Player} or {@code null}
 * @throws IndexOutOfBoundsException if the cell is out of bounds.
 */
Player getMarkAt(int column, int row);

1.2.4 Finding out the results

Finally, we need some way to find out when the game is over and who, if anyone, won. Unlike the previous operations, there isn’t much room in this design space (though there are a few sensible alternatives). We will have one method to ask whether the game is over and another to ask who has won:

/**
 * Determines whether the game is over.
 *
 * @return whether the game is over
 */
boolean isGameOver();

/**
 * Returns the winner of the game, or {@code null} if the game is a
 * tie.
 *
 * @return the winner or {@code null}
 * @throws IllegalStateException if the game isn't over
 */
Player getWinner();

In a game that does not admit ties, could these methods be combined into one? Is that a good idea?

1.3 Implementation

Tic-tac-toe is a very simple game, yet designing the interface to the model can still be an involved process. Some decisions are consequential, some not, and we might not be able to tell which is which until we start attempting to implement or even use the interface. However, whatever interface design we might choose, much of the hard work is now done, and implementing the interface is a simple matter of programming.

2 A bigger example: Connect $N$

As our second example, we consider the game Connect $N$, which is a generalization of the game Connect Four. In Connect Four, two players take turns dropping tokens (white for one player and red for the other) into a vertical 7-by-6 grid. The first player to form a line of four or more of their tokens, horizontally, vertically, or diagonally, wins the game. For Connect $N$, we will allow the dimensions of the grid, the line length necessary to win, and the number of players to be configurable rather than fixed.

For example, here is a sequence of game states for two-player, 3-by-4 Connect Three in which Player W wins:

│ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │
│ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │
│ │ │ │    │ │ │ │    │ │ │ │    │ │W│ │    │ │W│ │
│ │ │ │    │W│ │ │    │W│R│ │    │W│R│ │    │W│R│R│
└─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘


│ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │
│ │ │ │    │ │ │ │    │ │ │ │    │ │ │W│
│W│W│ │    │W│W│ │    │W│W│R│    │W│W│R│
│W│R│R│    │W│R│R│    │W│R│R│    │W│R│R│
└─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘

2.1 Object-oriented analysis

We begin our design once again by considering what operations the model must provide and what exceptional conditions the operations will detect. The operations are somewhat similar to Tic-tac-toe’s operations. We need ways to:

We need to reject bad moves, including playing out of turn, playing in a non-existent column, or playing in a column that is already full to the top. Additionally, we may want to make it an error to ask for the winner before the game is over, or to ask for the next player after the game is over.

2.2 Interface design

Because several methods will deal with players, one of the first decisions we must make is how to represent the players and their tokens. One simple way would be with integers counting up from 0. However, we will make the model slightly richer by associating a name, represented as a String, with each player. The set of players and their turn order will be represented as a String[], with most methods representing each player as an index into that array.

2.2.1 How to move?

To take a turn, it is sufficient to select which column the current player wants to play in. However, as an extra check, we will have the move method also take the int representing the player who is moving, which the model then checks before allowing the move. Thus:

/**
 * Plays a move. Given the player (whose turn it must be) and the column
 * number (zero-based from the left), attempts to add a token for that
 * player to that column. If this move ends the game then the game state
 * is updated to reflect that. Because it may be useful to the client,
 * this method returns the row number where the token lands.
 *
 * @param who    the player who is moving
 * @param where  which column to play in
 * @return the row where the token ends up
 *
 * @throws IllegalStateException if the game is over
 * @throws IllegalStateException if it isn't {@code who}'s turn
 * @throws IllegalStateException if the requested column is full
 * @throws IndexOutOfBoundsException if the requested column does not exist
 */
int move(int who, int where);

2.2.2 Querying the configuration

Although presumably the client must specify the game configuration when instantiating the game model, it may be useful to allow the client to ask the model for this information. (Why?) Thus, we will have methods that return the width and height of the grid, the goal line length, and the sequence of players:

int getWidth();

int getHeight();

int getGoal();

String[] getPlayers();

It’s important that getPlayers() return a copy of the array held by the model object rather than the array itself, since returning the array itself would then allow the client to modify the model’s private state.

2.2.3 Querying the game state

The game is always in one of three different statuses:

In order to communicate these statuses to the client, we will declare an enumeration within the game model class:

public static enum Status { Playing, Stalemate, Won }
We make the enumeration public so clients can see it, and static because it’s the same for every game instance.2Static classes and enumerations are like static fields and methods, in that they are part of the class rather than part of each object of the class. Then in order to check the status, the client can use the getStatus method; as a convenience we will also define an isGameOver method:

Status getStatus();

/**
 * Determines whether the game is over.
 *
 * @return whether the game is over
 */
boolean isGameOver();

When the game isn’t over, we can ask which player’s turn it is, and when the game has a winner, we can ask who the winner is:

/**
 * Returns the winner of the game.
 *
 * <p><strong>PRECONDITION:</strong> the game is over and has a winner
 *
 * @return the winner
 * @throws IllegalStateException if {@code getStatus() != Status.Won}
 */
int getWinner();

Finally, we need a way for the client to observe the contents of the grid. This time we will provide a way to query the token at any given cell position. Because some cells may be empty of tokens, we need some way to distinguish empty cells from player numbers. The wrapped integer type Integer includes null, so we use Integer instead of int for the result with null representing empty cells.

Additionally, we will include a convenience method3Meaning that the client could do what this method does using the other public methods, but it’s still nice to have. for asking whether a particular column is full.

/**
 * Gets the player whose token is at the given column and row. The
 * coordinates are zero-based and start in the lower left. Returns
 * {@code null} if there is no token in the given position.
 *
 * @param x the column coordinate ({@code 0 <= x < width})
 * @param y the row coordinate ({@code 0 <= y < height})
 * @return the player in the given position, or {@code null}
 * @throws IndexOutOfBoundsException if (x, y) is out of bounds
 */
Integer getPlayerAt(int x, int y);

/**
 * Determines whether the specified column is full and thus cannot be played
 * in.
 *
 * @param which the column to check
 * @return whether column {@code which} is full
 */
boolean isColumnFull(int which);

2.3 Configuring the model

We designed our Connect $N$ game to have some flexibility in its parameters, so it’s worth discussing how a client can select those parameters when instantiating the model. We could, of course offer a constructor that takes all of the parameters that are allowed to vary:

/**
 * Constructs a new game model with the given parameters.
 *
 * @param width    the width of the grid (positive)
 * @param height   the height of the grid (positive)
 * @param goal     the goal line length for the game ({@code > 1})
 * @param players  the array of player names (non-null, non-empty, and
 *                 each element non-null)
 */
public ConnectNModel(int width, int height, int goal, String[] players);
(Note that while we do not say which exceptions we might throw in the Javadoc, it should be clear that violating any of the method preconditions will result in an IllegalArgumentException.)

However, having to provide all four parameters every time is annoying, especially if standard Connect Four is the common case. It’s easy enough to provide a second, nullary constructor that uses the default values:

/**
 * Constructs a new game model for Connect Four with the default parameters.
 */
public ConnectNModel();

However, what if want to change only one parameter—the width, say—while leaving the others at their defaults. With only the above two constructors, we must use the four-argument constructor, passing the width that we want and the default values for the other three parameters. That requires knowing the default values, and it’s tedious. Wouldn’t it be nice if there were a way to only specify how we want to differ from the defaults?

One common way to allow defaulting some of the parameters is to define constructors with different sets of parameters. Unfortunately, this can be confusing and error prone. For example, if there were a constructor that took one argument, an int, which game parameter would you expect that to be?

Another common solution is to provide setters for the configuration parameters, so that the client can create a game model and then configure it by modifying it. However, this creates another problem: What happens if the client tries to reconfigure a game already in progress? Of course it could detect this and throw an exception, but this solution is not satisfactory because it violates two design principles: 1) that it’s better to make bad states or operations unexpressible than to catch them later, and 2) that when we want different behaviors, we should use different classes rather than a bunch of conditionals.

A better solution for handling several optional arguments is the builder pattern. When using the builder pattern, the client does not call the class’s constructor directly—in fact, we will make the constructor private, so that clients must instantiate model instances via the builder. Instead, the client first creates some kind of builder object, which encapsulates the configuration parameters for the game:

/**
 * Constructs a builder for configuring and then creating a game model
 * instance. Defaults to a standard game of Connect Four with players
 * named "White" and "Red".
 *
 * @return the new builder
 */
public static Builder builder();

The builder starts out with the default parameter values, and then provides several methods for changing whichever parameters we choose without saying anything about the others. In this case, that means we need a method for each of the parameters: the width, the height, the goal, and the array of players. Then, the builder provides a method build() that instantiates a game model object using the builder’s current parameters. Here’s the interface:

public static final class Builder {
  /**
   * Sets the width of the game grid.
   *
   * @param width the width (positive)
   * @return {@code this}, for method chaining
   */
  public Builder width(int width) { ... }

  /**
   * Sets the height of the game grid.
   *
   * @param height the height (positive)
   * @return {@code this}, for method chaining
   */
  public Builder height(int height) { ... }

  /**
   * Sets the goal line length for the game.
   *
   * @param goal the goal (positive)
   * @return {@code this}, for method chaining
   */
  public Builder goal(int goal) { ... }

  /**
   * Sets the players for the game. Makes a defensive copy of the
   * player array, so the client cannot change it from under us.
   *
   * @param players the array of player names (non-null, non-empty,
   *                and every element non-null)
   * @return {@code this}, for method chaining
   */
  public Builder players(String... players) { ... }

  /**
   * Builds and returns the specified {@link ConnectNModel}.
   *
   * @return a new {@code ConnectNModel}
   */
  public ConnectNModel build() { ... }
}

Then to create our wide Connect Four instance, we can use the builder object to specify only the parameters we want:

ConnectNGame.builder().width(15).build()

Next time we will begin implementing the ConnectNGame class.

1Tic-tac-toe has both rotational and reflective symmetries, which means that the orientation doesn’t actually matter so long as the UI is consistent.

2Static classes and enumerations are like static fields and methods, in that they are part of the class rather than part of each object of the class.

3Meaning that the client could do what this method does using the other public methods, but it’s still nice to have.