You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
482 lines
10 KiB
482 lines
10 KiB
2 years ago
|
package model;
|
||
|
|
||
|
/**
|
||
|
* This class represents the Connect4 game model.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
public class Connect4Model extends java.util.Observable implements java.io.Serializable {
|
||
|
|
||
|
/**
|
||
|
* Java generated serialization ID.
|
||
|
*/
|
||
|
private static final long serialVersionUID = 1L;
|
||
|
|
||
|
/**
|
||
|
* The maximum size for each rows.
|
||
|
*/
|
||
|
private final int ROWS_NUM = 7;
|
||
|
|
||
|
/**
|
||
|
* The maximum size of each column.
|
||
|
*/
|
||
|
private final int COLS_NUM = 6;
|
||
|
|
||
|
/**
|
||
|
* The number of consecutive pieces either horizontally, vertically, or
|
||
|
* diagonally to win the game.
|
||
|
*/
|
||
|
private final int CONNECT_SIZE = 4;
|
||
|
|
||
|
/**
|
||
|
* The default character representation of the player.
|
||
|
*/
|
||
|
private final char humanChar = 'X';
|
||
|
|
||
|
/**
|
||
|
* The default character representation of the computer player.
|
||
|
*/
|
||
|
private final char computerChar = 'O';
|
||
|
|
||
|
/**
|
||
|
* The default character representation of an empty cell.
|
||
|
*/
|
||
|
private final char blankChar = '_';
|
||
|
|
||
|
/**
|
||
|
* The object which represents the computer's strategy for playing the game.
|
||
|
*/
|
||
|
private transient ComputerPlayer computerPlayer;
|
||
|
|
||
|
/**
|
||
|
* Encapsulate move information to send over to the views.
|
||
|
*/
|
||
|
private transient Connect4MoveMessage moveMsg;
|
||
|
|
||
|
/**
|
||
|
* Indicates if it's currently the human turn.
|
||
|
*/
|
||
|
private boolean isHumanMove;
|
||
|
|
||
|
/**
|
||
|
* Counts the total number of moves that have been made.
|
||
|
*/
|
||
|
private int moveMaked;
|
||
|
|
||
|
/**
|
||
|
* Representation of the board.
|
||
|
*/
|
||
|
private char[][] board;
|
||
|
|
||
|
/**
|
||
|
* Ctor for Connect4Model.
|
||
|
*/
|
||
|
public Connect4Model() {
|
||
|
|
||
|
board = new char[ROWS_NUM][COLS_NUM];
|
||
|
for (int i = 0; i < ROWS_NUM; i++) {
|
||
|
for (int j = 0; j < COLS_NUM; j++) {
|
||
|
board[i][j] = blankChar;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Human moves first by default.
|
||
|
isHumanMove = true;
|
||
|
computerPlayer = new Minimax_AlphaBeta();
|
||
|
moveMaked = 0;
|
||
|
moveMsg = null;
|
||
|
|
||
|
setChanged();
|
||
|
notifyObservers(moveMsg);
|
||
|
}
|
||
|
|
||
|
public void setComputerPlayer(ComputerPlayer another) {
|
||
|
this.computerPlayer = another;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return a character on the board at row x and column y.
|
||
|
*
|
||
|
* @param x The row number.
|
||
|
* @param y The column number.
|
||
|
* @return A character on the board at row x and column y.
|
||
|
* @throws IllegalArgumentException when x is not between 0 and ROWS_NUM or y is
|
||
|
* not between 0 and COLS_NUM.
|
||
|
*/
|
||
|
public char getObjectAt(final int x, final int y) throws model.IllegalArgumentException {
|
||
|
|
||
|
if ( (x >= this.ROWS_NUM || x < 0) ||
|
||
|
(y >= this.COLS_NUM || y < 0)) {
|
||
|
|
||
|
throw new model.IllegalArgumentException("Invalid arguments.");
|
||
|
}
|
||
|
|
||
|
return this.board[x][y];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set an object at cell (x, y) to a character.
|
||
|
*
|
||
|
* @param x stands for row
|
||
|
* @param y stands for column
|
||
|
* @param obj The character representation of any player (can be blank).
|
||
|
* @throws IllegalArgumentException when x is not between 0 and ROWS_NUM or y is
|
||
|
* not between 0 and COLS_NUM.
|
||
|
*/
|
||
|
public void setObjectAt(final int x, final int y, final char obj) throws model.IllegalArgumentException {
|
||
|
if ((x >= this.ROWS_NUM || x < 0) || (y >= this.COLS_NUM || y < 0)) {
|
||
|
|
||
|
throw new model.IllegalArgumentException("Invalid arguments.");
|
||
|
}
|
||
|
|
||
|
this.board[x][y] = obj;
|
||
|
|
||
|
if (obj == this.getComputerChar()) {
|
||
|
|
||
|
moveMsg = new Connect4MoveMessage(this.board[0].length - y - 1, x , 2);
|
||
|
} else {
|
||
|
|
||
|
moveMsg = new Connect4MoveMessage(this.board[0].length - y - 1, x , 1);
|
||
|
}
|
||
|
|
||
|
setChanged();
|
||
|
notifyObservers(moveMsg);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a cell is empty or not.
|
||
|
*
|
||
|
* @param x stands for row
|
||
|
* @param y stand for column
|
||
|
* @return true if the cell (x,y) is empty.
|
||
|
*/
|
||
|
public boolean isBlank(final int x, final int y) {
|
||
|
return this.board[x][y] == blankChar;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the maximum row of the board.
|
||
|
*
|
||
|
* @return The maximum row of the board.
|
||
|
*/
|
||
|
public int getMaxRow() {
|
||
|
return this.ROWS_NUM;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the maximum column of the board.
|
||
|
*
|
||
|
* @return The maximum column of the board.
|
||
|
*/
|
||
|
public int getMaxCol() {
|
||
|
return this.COLS_NUM;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Accessor to the model's character representation of the player.
|
||
|
*
|
||
|
* @return A character representing the player.
|
||
|
*/
|
||
|
public char getHumanChar() {
|
||
|
return humanChar;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Accessor to the model's character representation of the computer player.
|
||
|
*
|
||
|
* @return A character representing the computer.
|
||
|
*/
|
||
|
public char getComputerChar() {
|
||
|
return computerChar;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Accessor to the model's character representation of an empty slot.
|
||
|
*
|
||
|
* @return A blank character.
|
||
|
*/
|
||
|
public char getBlankChar() {
|
||
|
return blankChar;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An accessor to isHumanMove.
|
||
|
*
|
||
|
* @return Return true if it's currently the human's move.
|
||
|
*/
|
||
|
public boolean isHumanMove() {
|
||
|
return this.isHumanMove;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the flag for human turn.
|
||
|
*
|
||
|
* @param isHumanTurn Set isHumanMove to true if this is true and false otherwise.
|
||
|
*/
|
||
|
public void setHumanTurn(final boolean isHumanTurn) {
|
||
|
this.isHumanMove = isHumanTurn;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A helper method to switch turn in a single game.
|
||
|
*/
|
||
|
public void switchTurn() {
|
||
|
|
||
|
this.moveMaked++;
|
||
|
isHumanMove = !isHumanMove;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check the winning condition by column.
|
||
|
*
|
||
|
* @param playerChar A character that represents the player.
|
||
|
* @return True if playerChar wins the game by column and false otherwise.
|
||
|
*/
|
||
|
public boolean wonByCol(final char playerChar) {
|
||
|
|
||
|
boolean won = false;
|
||
|
|
||
|
for (int i = 0; i < ROWS_NUM; i++) {
|
||
|
|
||
|
int colSum = 0;
|
||
|
for (int j = 0; j < COLS_NUM; j++) {
|
||
|
|
||
|
if (board[i][j] == playerChar) {
|
||
|
|
||
|
colSum++;
|
||
|
if (colSum == CONNECT_SIZE) {
|
||
|
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
colSum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return won;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check the winning condition by row.
|
||
|
*
|
||
|
* @param playerChar A character that represents the player.
|
||
|
* @return True if playerChar wins the game by row and false otherwise.
|
||
|
*/
|
||
|
public boolean wonByRow(final char playerChar) {
|
||
|
|
||
|
boolean won = false;
|
||
|
|
||
|
for (int i = 0; i < COLS_NUM; i++) {
|
||
|
|
||
|
int rowSum = 0;
|
||
|
for (int j = 0; j < ROWS_NUM; j++) {
|
||
|
|
||
|
if (board[j][i] == playerChar) {
|
||
|
|
||
|
rowSum++;
|
||
|
if (rowSum == CONNECT_SIZE) {
|
||
|
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
|
||
|
rowSum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return won;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check the winning condition diagonally, both left and right.
|
||
|
*
|
||
|
* @param playerChar A character that represents the player.
|
||
|
* @return True if playerChar wins the game diagonally and false otherwise.
|
||
|
*/
|
||
|
public boolean wonByDiagonal(final char playerChar) {
|
||
|
int sum = 0;
|
||
|
boolean won = false;
|
||
|
|
||
|
// Covering from left to right.
|
||
|
for (int i = 3; i < ROWS_NUM; i++) {
|
||
|
sum = 0;
|
||
|
for (int j = 0; j < COLS_NUM && j <= i; j++) {
|
||
|
if (board[i - j][j] == playerChar) {
|
||
|
|
||
|
sum++;
|
||
|
if (sum == CONNECT_SIZE) {
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
sum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int j = 1; j < 3; j++) {
|
||
|
|
||
|
sum = 0;
|
||
|
for (int k = 0; j + k < COLS_NUM; k++) {
|
||
|
|
||
|
if (board[ROWS_NUM - 1 - k][j + k] == playerChar) {
|
||
|
sum++;
|
||
|
if (sum == CONNECT_SIZE) {
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
sum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Covering from right to left.
|
||
|
for (int i = 3; i >= 0; i--) {
|
||
|
sum = 0;
|
||
|
for (int j = 0; j < COLS_NUM && (i + j) < ROWS_NUM; j++) {
|
||
|
if (board[i + j][j] == playerChar) {
|
||
|
sum++;
|
||
|
if (sum == CONNECT_SIZE) {
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
sum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int j = 1; j < 3; j++) {
|
||
|
sum = 0;
|
||
|
for (int k = 0; j + k < COLS_NUM; k++) {
|
||
|
if (board[0 + k][j + k] == playerChar) {
|
||
|
sum++;
|
||
|
if (sum == CONNECT_SIZE) {
|
||
|
won = true;
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
sum = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return won;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a player win the game either by row, column, or diagonal.
|
||
|
*
|
||
|
* @param playerChar A character representing the player who won.
|
||
|
* @return True if playerChar win the game in some ways.
|
||
|
*/
|
||
|
public boolean didWin(char playerChar) {
|
||
|
return wonByRow(playerChar) || wonByCol(playerChar) || wonByDiagonal(playerChar);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check the tie condition of the game. Tie happens when nobody wins and there's
|
||
|
* no move left.
|
||
|
*
|
||
|
* @return True if it's a tie and false otherwise.
|
||
|
*/
|
||
|
public boolean isTied() {
|
||
|
// Tie condition: Out of moves AND nobody wins.
|
||
|
return (moveMaked == ROWS_NUM * COLS_NUM && !(didWin(computerChar) || didWin(humanChar)));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prompts the computer to make a move.
|
||
|
*
|
||
|
* @param c The character token that will belong to the move.
|
||
|
*/
|
||
|
public void computerMove(char c, boolean max) {
|
||
|
boolean foundAMove = false;
|
||
|
while (!foundAMove) {
|
||
|
int colMove = computerPlayer.getMove(this, max);
|
||
|
if (colMove == -1)
|
||
|
return;
|
||
|
for (int i = 0; i < COLS_NUM; i++) {
|
||
|
if (board[colMove][i] == blankChar) {
|
||
|
board[colMove][i] = c;
|
||
|
if (c == this.computerChar)
|
||
|
moveMsg = new Connect4MoveMessage(board[0].length - i - 1, colMove , 2);
|
||
|
else
|
||
|
moveMsg = new Connect4MoveMessage(board[0].length - i - 1, colMove, 1);
|
||
|
foundAMove = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
setChanged();
|
||
|
notifyObservers(moveMsg);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @return A 2d characters array that represents the board.
|
||
|
*/
|
||
|
public char[][] getBoard() {
|
||
|
return this.board;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
* @return True if the game is already over.
|
||
|
*/
|
||
|
public boolean isOver() {
|
||
|
return this.didWin(this.getComputerChar()) || this.didWin(this.getHumanChar()) || this.isTied();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return A move message that is used to update the observers.
|
||
|
*/
|
||
|
public Connect4MoveMessage getMoveMsg() {
|
||
|
return this.moveMsg;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
*
|
||
|
*/
|
||
|
public void printRawBoard() {
|
||
|
for (int i = 0; i < board.length; i++) {
|
||
|
for (int j = 0; j < board[i].length; j++) {
|
||
|
System.out.print(board[i][j] + " ");
|
||
|
}
|
||
|
System.out.println();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the reference of the character array board to this.board. Its only
|
||
|
* purpose is to self assign a given board to model's subclass, in which
|
||
|
* this.board is not visible.
|
||
|
*
|
||
|
* @param board A character array board.
|
||
|
*/
|
||
|
public void setBoard(char[][] board) {
|
||
|
this.board = board;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the move made for this object. Similar to setBoard(), only to do a deep
|
||
|
* copy of model's subclass object. It shouldn't be called in other cases.
|
||
|
*
|
||
|
* @param m The number of moves that should be assigned to this instance's move.
|
||
|
*/
|
||
|
public void setMoveMaked(int m) {
|
||
|
this.moveMaked = m;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return The number of moves that have been made in this model.
|
||
|
*/
|
||
|
public int getMoveMaked() {
|
||
|
return moveMaked;
|
||
|
}
|
||
|
|
||
|
}
|