The Model
1 The Model

The Model

CS 3500, Fall 2024

Structuring a GUI

Model–View–Controller

A common OO technique for structuring graphical programs:

  • Controller: takes input from the user and decides what to do

  • View: knows how to display the interface to the user

  • Model: the domain information that the user manipulates

MVC: Cohesion

MVC: Coupling

The Model

  • Often enforces information integrity

  • For games, this means enforcing the rules

  • It's where the action is!

  • It's where we'll start

Tic-tac-toe

An example game

 │ │
─┼─┼─
 │ │
─┼─┼─
 │ │

An example game

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

An example game

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

An example game

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

An example game

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


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

An example game

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


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

An example game

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


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

An example game: X wins!

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


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

Another example: stalemate

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


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

Object-oriented analysis

What does our Tic-tac-toe model need to do?

  • Play a move as X

  • Play a move as O

  • Find out whose turn it is

  • Find out the contents of the grid

  • Find out whether the game is over

  • Find out who the winner is (if any)

More analysis: error conditions

  • Play out of turn

  • Play in an occupied cell

  • Play after the game has ended

  • Play in a cell that doesn't exist

Designing an interface: moves

/**
 * 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 O'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);

/** ...same as above, but for O... */
void moveAsO(int column, int row);

  • Is this Javadoc any good?

  • What's ambiguous about it?

Designing an interface: moves

/**
 * 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's already a mark
 *     in the cell
 * @throws IndexOutOfBoundsException
 *     if the cell is out of bounds
 */
void move(int column, int row);

Interface: whose turn?

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

/** ...same as above, but for O... */
boolean isOsTurn();

Interface: whose turn?

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

Representing players

  • type char, with 'X' for X and 'O' for O

  • class String, with "X" for X and "O" for O

  • type boolean, with true for X and false for O

  • type boolean, with true for O and false for X

  • an enumeration defined as enum Player { X, O }

Interface: the grid

/**
 * 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, mutat-
 * ing it will have no effect on the model or subequent
 * calls to {@code getGrid}.
 *
 * @return a copy of the grid
 */
Player[][] getGrid();

Interface: the grid

/**
 * 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 (zero-based from the left)
 * @param row    the row of the cell (zero-based from the top)
 * @return       the {@code Player} or {@code null}
 * @throws IndexOutOfBoundsException
 *     if the cell is out of bounds
 */
Player getMarkAt(int column, int row);

Interface: the results

/**
 * 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();

Connect $N$

A generalization of Connect Four

Generalize:

  • the grid dimensions

  • the goal (line length to win)

  • the number of players

Example: 2-player, 3-by-4 Connect Three



│ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │
│ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │    │ │ │ │
│ │ │ │    │ │ │ │    │ │ │ │    │ │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│
└─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘    └─┴─┴─┘

Object-oriented analysis

  • Play a move

  • Find out about the configuration parameters:

    • dimensions

    • goal line length

    • players

  • Find out about the state of the game:

    • whose turn

    • grid contents

    • whether the game is over

    • who the winner is (if any)

Moving

Moving

/**
 * Plays a move. Given the player and the column number,
 * attempts to add a token for that player to that column.
 *
 * @param who    the player who is moving (must be current)
 * @param where  which column to play in (0-based from left)
 * @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);

Querying the configuration

int getWidth();

int getHeight();

int getGoal();

String[] getPlayers();

The game status

The game is always in one of three different statuses:

  • Playing, when the game isn't over yet

  • Stalemate, when the game has ended in a tie

  • Won, when the game has ended with a winner

Representing the status

enum Status { Playing, Stalemate, Won }

There are exactly three values of type Status:

  • Status.Playing

  • Status.Stalemate

  • Status.Won

Getting the status

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

Getting the winner

/**
 * 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();

Observing the grid

/**
 * 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 oob
 */
Integer getPlayerAt(int x, int y);