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
Often enforces information integrity
For games, this means enforcing the rules
It's where the action is!
It's where we'll start
│ │
─┼─┼─
│ │
─┼─┼─
│ │
│ │ X│ │
─┼─┼─ ─┼─┼─
│ │ │ │
─┼─┼─ ─┼─┼─
│ │ │ │
│ │ X│ │ X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │
─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │
│ │ X│ │ X│ │O X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │X
│ │ X│ │ X│ │O X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │X
X│ │O
─┼─┼─
│O│
─┼─┼─
│ │X
│ │ X│ │ X│ │O X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │X
X│ │O X│ │O
─┼─┼─ ─┼─┼─
│O│ │O│
─┼─┼─ ─┼─┼─
│ │X X│ │X
│ │ X│ │ X│ │O X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │
─┼─┼─ ─┼─┼─ ─┼─┼─ ─┼─┼─
│ │ │ │ │ │ │ │X
X│ │O X│ │O X│ │O
─┼─┼─ ─┼─┼─ ─┼─┼─
│O│ │O│ │O│
─┼─┼─ ─┼─┼─ ─┼─┼─
│ │X X│ │X X│O│X
│ │ 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
│ │ 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
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)
Play out of turn
Play in an occupied cell
Play after the game has ended
Play in a cell that doesn't exist
/**
* 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);
/** [mutatis mutandis] */
void moveAsY(int column, int row);
/**
* 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);
/**
* Determines whether it's player X's turn to move.
*
* @return whether X can play now
*/
boolean isXsTurn();
/** [mutatis mutandis] */
boolean isYsTurn();
/**
* Returns the player whose turn is next.
*
* @return the next player
* @throws IllegalStateException if the game is over
*/
❓ nextPlayer();
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 }
/**
* 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();
/**
* 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)
* @param row the row of the cell (zero-based)
* @return the {@code Player} or {@code null}
* @throws IndexOutOfBoundsException
* if the cell is out of bounds
*/
Player getMarkAt(int column, int row);
/**
* 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();
Generalize:
the grid dimensions
the goal (line length to win)
the number of players
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │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│
└─┴─┴─┘ └─┴─┴─┘ └─┴─┴─┘ └─┴─┴─┘
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)
/**
* 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);
int getWidth();
int getHeight();
int getGoal();
String[] getPlayers();
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
enum Status { Playing, Stalemate, Won }
There are exactly three values of type Status
:
Status.Playing
Status.Stalemate
Status.Won
Status getStatus();
/**
* Determines whether the game is over.
*
* @return whether the game is over
*/
boolean isGameOver();
/**
* 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();
/**
* 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);