/* An instance of this class keeps track of the state of a Tic-tac-toe game. ** ** Developed during final class meeting of CMPS 134, Fall 2019, with ** some improvements made later. */ public class TicTacToeGame { // class constant // -------------- public static final char X = 'X'; // X and O represent the two marks public static final char O = 'O'; // that get placed into square, as well public static final char EMPTY = ' '; // as the two players. public static final char DRAW = 'D'; private static int N; // # of rows and # of columns // instance variables // ------------------ private char whoseTurn; // either X or O private char[][] board; // each element contains X, O, or EMPTY private int numSquaresMarked; // # of squares occupied by either X or O // constructor // ----------- /* Establishes this TicTacToe game as one in which the board has ** the standard 3-by-3 size, all the squares are initially empty, ** and player X is set to make the first move. */ public TicTacToeGame() { this(X, 3); } /* Establishes this TicTacToe game as one in which the board has ** the specified # of rows and columns, all squares are initially ** empty, and the first move is to be made by the specified player. ** pre: 0 < boardSize && (whoGoesFirst == X || whoGoesFirst == O) */ public TicTacToeGame(char whoGoesFirst, int boardSize) { this.N = boardSize; this.whoseTurn = whoGoesFirst; this.numSquaresMarked = 0; this.board = new char[N][N]; makeBoardEmpty(); } // observers // --------- /* Identifies the player whose turn it is (X or O). */ public char whoseTurnIsIt() { return whoseTurn; } /* Returns the mark occupying the specified cell ** (so the result is either X, O, or EMPTY). ** pre: 0 <= row < N && 0 <= col < N */ public char markIn(int row, int col) { return board[row][col]; } /* Answers the question, "Is the game over?" A game is considered to ** be over iff either it has been won or all squares have been marked. ** A more sophisticated version of this method would recognize at least ** some cases in which neither player can win despite the fact that not ** every square has been marked. */ public boolean isOver() { return isWinner(X) || isWinner(O) || numSquaresMarked == N*N; } /* Answers the question: Has the game been won by the specified player. ** pre: mark == X || mark == O */ public boolean isWinner(char mark) { return wonViaSomeRow(mark) || wonViaSomeCol(mark) || wonViaSlash(mark) || wonViaBackSlash(mark); } /* Returns either X, O, or DRAW, according to whether the game ** was won by X, by O, or ended in a draw, respectively. ** pre: isOver() */ public char winner() { if (isWinner(X)) { return X; } else if (isWinner(O)) { return O; } else { return DRAW; } } /* Displays the board. */ public void printBoard() { // print column headings System.out.print(" "); for (int j = 0; j != N; j++) { System.out.printf("%d ", j); } System.out.println(); // print each row, preceded by a row heading for (int i=0; i != N; i++) { System.out.printf("%d ", i); for (int j=0; j != N; j++) { System.out.printf("%c ", board[i][j]); } System.out.println(); } } // mutators // -------- /* Places into the specified square the mark corresponding to ** the player whose turn it is. ** If the specified square is not empty, an exception is thrown. ** If the specified square does not exist, an exception is thrown. */ public void markSquare(int row, int col) { if (row < 0 || row >= N || col < 0 || col >= N) { throw new IllegalArgumentException("Square coordinates out of range"); } if (board[row][col] != EMPTY) { throw new IllegalArgumentException("Square is already marked"); } board[row][col] = whoseTurn; whoseTurn = opposite(whoseTurn); numSquaresMarked++; } // private methods // --------------- /* Returns the "opposite" of the given mark. ** pre: mark == X || mark == O */ private char opposite(char mark) { if (mark == X) { return O; } else { return X; } } /* Makes empty each of the squares on the board. */ private void makeBoardEmpty() { for (int i=0; i != N; i++) { for (int j=0; j != N; j++) { board[i][j] = EMPTY; } } } /* Reports whether or not the specified player has won the ** game by having marked all squares in some row of the board. */ private boolean wonViaSomeRow(char mark) { boolean result = false; for (int i=0; i != N; i++) { if (wonViaRow(i, mark)) { result = true; } } return result; } /* Reports whether or not the specified player has won the ** game by having marked all squares in some column of the board. */ private boolean wonViaSomeCol(char mark) { boolean result = false; for (int j = 0; j != N; j++) { if (wonViaCol(j, mark)) { result = true; } } return result; } /* Reports whether or not the specified player has won by ** having marked every square in the specified row. */ private boolean wonViaRow(int rowNum, char mark) { boolean goodSoFar = true; for (int j = 0; j != N; j++) { if (board[rowNum][j] != mark) { goodSoFar = false; } } return goodSoFar; } /* Reports whether or not the specified player has won by ** having marked every square in the specified column. */ private boolean wonViaCol(int colNum, char mark) { boolean goodSoFar = true; for (int i = 0; i != N; i++) { if (board[i][colNum] != mark) { goodSoFar = false; } } return goodSoFar; } /* Reports whether or not the specified player has won by ** having marked every square in the "backslash" diagonal. */ private boolean wonViaBackSlash(char mark) { boolean goodSoFar = true; for (int i = 0; i != N; i++) { if (board[i][i] != mark) { goodSoFar = false; } } return goodSoFar; } /* Reports whether or not the specified player has won by ** having marked every square in the "slash" diagonal. */ private boolean wonViaSlash(char mark) { boolean goodSoFar = true; for (int i = 0; i != 3; i++) { if (board[i][2-i] != mark) { goodSoFar = false; } } return goodSoFar; } }