package org.fhnw.aigs.TicTacToe.server;
import java.util.Random;
import org.fhnw.aigs.commons.FieldStatus;
import org.fhnw.aigs.commons.Game;
import org.fhnw.aigs.commons.GameMode;
import org.fhnw.aigs.commons.Player;
import org.fhnw.aigs.commons.communication.FieldClickFeedbackMessage;
import org.fhnw.aigs.commons.communication.FieldClickMessage;
import org.fhnw.aigs.commons.communication.GameEndsMessage;
import org.fhnw.aigs.commons.communication.Message;
// -- References to internal packages (of this game)
import org.fhnw.aigs.TicTacToe.commons.TicTacToeFieldChangedMessage;
import org.fhnw.aigs.TicTacToe.commons.TicTacToeSymbol;
/**
* This class represents the game logic part of TicTacToe. The size is defined
* in the variables xFields and yFields which are set in the constructor. <br>
* The game logic contains a multidimensional array of TicTacToeFields which
* represent the fields, see {@link GameLogic#ticTacToeFields}.<br>
* v1.0 Initial release<br>
* v1.1 Functional changes (package)<br>
* v1.1.1 Minor changes
* @author Matthias Stöckli (v1.0)
*/
public class GameLogic extends Game {
/**
* The fields of the TicTacToe game. The first dimension represents the
* x-axis, the second the y-axis. Therefore
* <code>ticTacToeFields[1][1]</code> returns
* the field in the middle of the board.
*/
private TicTacToeField[][] ticTacToeFields;
/**
* The number if fields in the x-axis (3)
*/
private final int xFields;
/**
* The number of fields in the y-axis (3)
*/
private final int yFields;
/**
* Empty constructor which is necessary in order to load the game.<br>
* Don't forgett to set the same game name in the class {@link org.fhnw.aigs.TicTacToe.client.TicTacToeMain} in the package 'client'.
*/
public GameLogic() {
super("TicTacToe", "v1.1.1", 2); // VERY IMPORTANT! The game name must be unique on the server (only onece 'TicTacToe')
xFields = 3;
yFields = 3;
}
/**
* See {@link Game#initialize} for more information about the way this
* method works.<br>
* This implementation checks for the GameMode. If SinglePlayer has been
* chosen, a new player (the AI) will be generated and added to the list of
* players. Afterwards, a random player will be picked, he or she will begin
* the game. then, the game will be started.
*/
@Override
public void initialize() {
setUpFields();
if (gameMode == GameMode.SinglePlayer) {
aiPlayer = new Player("AI", true);
players.add(aiPlayer);
}
setCurrentPlayer(getRandomPlayer());
if (getCurrentPlayer().equals(aiPlayer)) {
aiAction();
}
startGame();
}
/**
* Creates the (logical) fields of the game and fills them into
* {@link GameLogic#ticTacToeFields}.
*/
private void setUpFields() {
ticTacToeFields = new TicTacToeField[xFields][yFields];
for (int i = 0; i < xFields; i++) {
for (int j = 0; j < yFields; j++) {
ticTacToeFields[i][j] = new TicTacToeField(i, j);
}
}
}
/**
* See {@link Game#processGameLogic} for more information about the way this
* method works.<br>
* ProcessGameLogic processes all the messages that control the flow of this
* game.
*
* @param message The message passed from ServerMessageBroker.
* @param sendingPlayer The player who sent the message.
*/
@Override
public void processGameLogic(Message message, Player sendingPlayer) {
if (message instanceof FieldClickMessage) {
int clickedX = ((FieldClickMessage) message).getXPosition(); // X-Coordinates on the game board
int clickedY = ((FieldClickMessage) message).getYPosition(); // Y-Coordinates on the gameBoard
TicTacToeField clickedField = ticTacToeFields[clickedX][clickedY]; // Get the TicTacToeField according to the coordinates
// If the field's "controllingPlayer" value is null, the field is empty, otherwise it is controlled by a player
boolean isClickedFieldEmpty = clickedField.getControllingPlayer() == null;
// A FieldClickFeedBackMessage is prepared to inform the sender of the status of his or her turn
FieldClickFeedbackMessage fieldClickFeedbackMessage;
// If the field is empty...
if (isClickedFieldEmpty == true && sendingPlayer.equals(getCurrentPlayer())) {
clickedField.setControllingPlayer(sendingPlayer); // Set the current player as controlling player
fieldClickFeedbackMessage = new FieldClickFeedbackMessage(clickedX, clickedY, FieldStatus.OK); // Field is still unoccupied
sendMessageToPlayer(fieldClickFeedbackMessage, sendingPlayer); // Send a feedback about the turn to the sending player
// Determine the symbol (Cross or Nought): The first player (index == 0) is Cross, the second one (index != 0) is Nought;
TicTacToeSymbol symbol = players.indexOf(getCurrentPlayer()) == 0 ? TicTacToeSymbol.Cross : TicTacToeSymbol.Nought;
// Set up a FieldChangedMessage to inform all players about the new game situation
TicTacToeFieldChangedMessage fieldChangedMessage = new TicTacToeFieldChangedMessage(clickedX, clickedY, sendingPlayer, symbol);
sendMessageToAllPlayers(fieldChangedMessage);
if (gameMode == GameMode.SinglePlayer) {
passTurnToNextPlayer();
aiAction();
} else if (gameMode == GameMode.Multiplayer) {
passTurnToNextPlayer();
}
} else if (clickedField.hasPlayer() && clickedField.getControllingPlayer().equals(getCurrentPlayer()) == false) {
// Field is blocked by opponent's field
fieldClickFeedbackMessage = new FieldClickFeedbackMessage(clickedX, clickedY, FieldStatus.NoChange);
sendMessageToPlayer(fieldClickFeedbackMessage, sendingPlayer);
} else if (clickedField.hasPlayer()) {
// Field is occupied by the player himself
fieldClickFeedbackMessage = new FieldClickFeedbackMessage(clickedX, clickedY, FieldStatus.Blocked);
sendMessageToPlayer(fieldClickFeedbackMessage, sendingPlayer);
}
}
}
/**
* Checks whether one of the player meets the winning conditions: A full
* row, a full column or three diagonal fields.
*/
@Override
public void checkForWinningCondition() {
for (Player p : players) {
if ( // Check rows
(cfp(0, 0, p) && cfp(0, 1, p) && cfp(0, 2, p))
|| (cfp(1, 0, p) && cfp(1, 1, p) && cfp(1, 2, p))
|| (cfp(2, 0, p) && cfp(2, 1, p) && cfp(2, 2, p))
|| // Check columns
(cfp(0, 0, p) && cfp(1, 0, p) && cfp(2, 0, p))
|| (cfp(0, 1, p) && cfp(1, 1, p) && cfp(2, 1, p))
|| (cfp(0, 2, p) && cfp(1, 2, p) && cfp(2, 2, p))
|| // Check diagonals
(cfp(0, 0, p) && cfp(1, 1, p) && cfp(2, 2, p))
|| (cfp(2, 0, p) && cfp(1, 1, p) && cfp(0, 2, p))) {
GameEndsMessage gameEndsMessage = new GameEndsMessage(p.getName() + " won");
sendMessageToAllPlayers(gameEndsMessage);
return;
}
}
boolean isDraw = calculateDraw();
if (isDraw) {
GameEndsMessage gameEndsMessage = new GameEndsMessage("Draw!");
sendMessageToAllPlayers(gameEndsMessage);
}
}
/**
* Checks whether a player controls a field.
*
* @param x The x coordinate of the field.
* @param y The y coordinate of the field.
* @param p The player to be checked against.
* @return True if the given player controls the field.
*/
private boolean cfp(int x, int y, Player p) {
// Prevent NullPointerException if there is no player
if (ticTacToeFields[x][y].hasPlayer() == true) {
return ticTacToeFields[x][y].getControllingPlayer().equals(p);
} else {
return false;
}
}
private boolean calculateDraw() {
// Calculate draw: If all the fields are occupied, then it is a draw.
// Use binary boolean operators. As soon as a value is false the
// "AND" operator (&) will be set to false thus indicating that
// one field is not occupied.
boolean isOccupied = true;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
isOccupied &= ticTacToeFields[i][j].hasPlayer();
}
}
return isOccupied;
}
/**
* The AI is controlled by this method. In this game the AI does not act
* very intelligent. It simply takes a random field which is still not
* occupied.
*/
private void aiAction() {
boolean turnIsPossible;
// X and Y coordinates.
int randomX;
int randomY;
Random r = new Random();
do {
// Get two random numbers.
randomX = (int) r.nextInt(xFields);
randomY = (int) r.nextInt(yFields);
// Check whether the field is still unoccupied.
turnIsPossible = ticTacToeFields[randomX][randomY].getControllingPlayer() == null;
// Check for draw
if (calculateDraw()) {
return;
}
} while (turnIsPossible == false);
// Determine which symbol is used (Cross or Nought)
TicTacToeSymbol symbol = players.indexOf(aiPlayer) == 0 ? TicTacToeSymbol.Cross : TicTacToeSymbol.Nought;
// Set the AI as the new controlling player.
ticTacToeFields[randomX][randomY].setControllingPlayer(aiPlayer);
// Send a "FieldChangedMessage" to the player.
TicTacToeFieldChangedMessage fieldChangedMessage = new TicTacToeFieldChangedMessage(randomX, randomY, aiPlayer, symbol);
sendMessageToAllPlayers(fieldChangedMessage);
// Pass the turn to the next player.
passTurnToNextPlayer();
}
}