package org.fhnw.aigs.Minesweeper.server;
import java.security.SecureRandom;
import java.util.HashSet;
import org.fhnw.aigs.commons.Game;
import org.fhnw.aigs.commons.Player;
import org.fhnw.aigs.Minesweeper.commons.BoardChangeMessage;
import org.fhnw.aigs.Minesweeper.commons.MarkFieldMessage;
import org.fhnw.aigs.Minesweeper.commons.MarkFieldStatusMesage;
import org.fhnw.aigs.Minesweeper.commons.MinesweeperField;
import org.fhnw.aigs.Minesweeper.commons.RestartMessage;
import org.fhnw.aigs.Minesweeper.commons.SetUpBoardMessage;
import org.fhnw.aigs.commons.communication.FieldClickMessage;
import org.fhnw.aigs.commons.communication.GameEndsMessage;
import org.fhnw.aigs.commons.communication.Message;
/**
* This class represents the game logic part of Minesweeper.<br>
* v1.0 Initial release<br>
* v1.1 Minor changes in Handling
* @author Matthias Stöckli (v1.1)
* @version v1.1
*/
public class GameLogic extends Game {
MinesweeperField[] fields;
/**
* Number of fields in the y-axis.
*/
int fieldsX;
/**
* Number of fields in the y-axis.
*/
int fieldsY;
/**
* Number of hidden mines. Controlled by the client.
*/
int totalMines;
/**
* number of mines minus number of flags
*/
int minesLeft;
boolean gameEnded = false;
/**
* 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.Minesweeper.client.Main} in the package 'client'.
*/
public GameLogic() throws Exception {
super("Minesweeper", "v1.1", 1);
// setUpFields will be called after "SetmineCountMessage" arrived.
// See processGameLogic.
}
@Override
public void initialize() {
startGame(); // Starts the game
}
/**
* When all fields which do not contain a mine are uncovered, the game ends.
*/
@Override
public void checkForWinningCondition() {
// If one of the uncovered fields still doesn't contain a mine,
// set this boolean to false.
boolean hasWon = true;
for (MinesweeperField f : fields) {
// As soon as there is a field which is uncovered and is NOT a mine
// the game goes on
if (f.getIsUncovered() == false && f.getContainsMine() == false) {
hasWon = false;
}
}
if (hasWon) {
// If the game has already ended (player won or lost) the winning conditions
// are irrelevant.
if (gameEnded == false) {
gameEnded = true;
GameEndsMessage gameEndsMessage = new GameEndsMessage("WON");
sendMessageToPlayer(gameEndsMessage, getCurrentPlayer());
}
}
}
/**
* The game logic of Minesweeper takes place in this method..
*
* @param message
* @param player
*/
@Override
public void processGameLogic(Message message, Player player) {
if (message instanceof SetUpBoardMessage) {
setUpBoard(message);
} else if (message instanceof RestartMessage) {
restart();
} else if (message instanceof MarkFieldMessage) {
markField(message);
} else if (message instanceof FieldClickMessage) {
handleFieldClick(message, player);
}
}
/**
* Creates the logical entities of minesweeper fields
*/
private void setUpFields() {
for (int i = 0; i < fieldsX; i++) {
for (int j = 0; j < fieldsY; j++) {
fields[(i * fieldsX) + j] = new MinesweeperField();
fields[(i * fieldsX) + j].setxPosition(i);
fields[(i * fieldsX) + j].setyPosition(j);
}
}
// Create a random number generator
SecureRandom random = new SecureRandom();
MinesweeperField field = null;
/**
* Create a new mine on a random position. If a position has already
* been occupied by another mine, try again.
*/
for (int i = 0; i < totalMines; i++) {
boolean setField = false;
while (setField == false) {
int randomIndex = random.nextInt((fieldsX * fieldsY) - 1);
field = fields[randomIndex];
if (field.getContainsMine()) {
continue;
} else {
field.setContainsMine(true);
setField = true;
}
}
}
calculateSurroundingMines();
}
/**
* Gets all fields without mines around the specified field. The borders are marked by fields with mines in proximity
* @param field Field to be checked (center)
* @return HashSet of all fields without mines around the specified field
*/
private HashSet<MinesweeperField> coverUpConnectedFields(MinesweeperField field) {
HashSet<MinesweeperField> nonMineFields = new HashSet<MinesweeperField>();
MinesweeperField[] checkFields = new MinesweeperField[4];
checkFields[0] = getFieldTranslatedByDirection(field, Direction.UPPER);
checkFields[1] = getFieldTranslatedByDirection(field, Direction.LEFT);
checkFields[2] = getFieldTranslatedByDirection(field, Direction.RIGHT);
checkFields[3] = getFieldTranslatedByDirection(field, Direction.LOWER);
for (MinesweeperField f : checkFields) {
if (f != null && f.getContainsMine() == false && f.hasNoSurroundingMines() && f.getIsUncovered() == false) {
// Check all blank fields again
f.setIsUncovered(true);
nonMineFields.add(f);
nonMineFields.addAll(coverUpConnectedFields(f));
} else if (f != null && f.getContainsMine() == false && f.hasNoSurroundingMines() == false) {
// Don't check non-blank fields.
nonMineFields.add(f);
f.setIsUncovered(true);
}
}
return nonMineFields;
}
/**
* Method to set up the minesweeper board
* @param message message to process
*/
private void setUpBoard(Message message) {
SetUpBoardMessage setUpBoardMessage = (SetUpBoardMessage) message; // Cast
this.totalMines = setUpBoardMessage.getTotalMines();
this.minesLeft = totalMines;
fieldsX = setUpBoardMessage.getxFields();
fieldsY = setUpBoardMessage.getyFields();
fields = new MinesweeperField[fieldsX * fieldsY];
setUpFields();
}
/**
* Restarts the game
*/
private void restart() {
gameEnded = false;
this.minesLeft = totalMines;
setUpFields();
}
/**
* Marks a filed as uncovered (nothing to do), as flagged or as unflagged
* @param message Message to process
*/
private void markField(Message message) {
MarkFieldMessage markFieldMessage = (MarkFieldMessage) message; // Cast
boolean isFlagged;
int x = markFieldMessage.getPositionX();
int y = markFieldMessage.getPositionY();
MinesweeperField field = fields[x * fieldsX + y];
if (field.getIsUncovered()) {
return;
}
if (field.getHasFlag()) {
isFlagged = false;
field.setHasFlag(false);
minesLeft++;
} else {
isFlagged = true;
field.setHasFlag(true);
minesLeft--;
}
MarkFieldStatusMesage markFieldStatusMesage = new MarkFieldStatusMesage(x, y, isFlagged, minesLeft);
sendMessageToPlayer(markFieldStatusMesage, getCurrentPlayer());
}
/**
* Handles the user action when clicking on a flied
* @param message Message to process
* @param player Player who clicked
*/
private void handleFieldClick(Message message, Player player) {
FieldClickMessage fieldClickMessage = (FieldClickMessage) message; // Cast
int x = fieldClickMessage.getXPosition();
int y = fieldClickMessage.getYPosition();
MinesweeperField field = fields[x * fieldsX + y];
// Ignore the move if the field was already uncovered or flagged
if (field.getIsUncovered() == true || field.getHasFlag()) {
return;
}
if (field.getContainsMine()) {
handleGameEnd(player);
} else if (field.getSurroundingMinesCount() > 0) {
uncoverDangerousField(field, player);
} else {
uncoverHarmlessField(field, player);
}
}
/**
* Iterates through every field, then check the surrounding fields for mines
* and add one to the minecount of the field for every surrounding mine.
*/
private void calculateSurroundingMines() {
MinesweeperField field;
// Iteration
for (int i = 0; i < fields.length; i++) {
field = fields[i];
MinesweeperField[] checkFields = new MinesweeperField[8];
// Look up fields
checkFields[0] = getFieldTranslatedByDirection(field, Direction.UPPER_LEFT);
checkFields[1] = getFieldTranslatedByDirection(field, Direction.UPPER);
checkFields[2] = getFieldTranslatedByDirection(field, Direction.UPPER_RIGHT);
checkFields[3] = getFieldTranslatedByDirection(field, Direction.LEFT);
checkFields[4] = getFieldTranslatedByDirection(field, Direction.RIGHT);
checkFields[5] = getFieldTranslatedByDirection(field, Direction.LOWER_LEFT);
checkFields[6] = getFieldTranslatedByDirection(field, Direction.LOWER);
checkFields[7] = getFieldTranslatedByDirection(field, Direction.LOWER_RIGHT);
// If there IS a field (not null) and there is a mine, add one to count.
for (MinesweeperField f : checkFields) {
if (f != null && f.getContainsMine()) {
field.addOneToMineCount();
}
}
}
}
/**
* Finds one of the surrounding fields of another field.
*
* @param field The field to start.
* @param direction The direction in which direction the field to be found
* lies.
* @return
*/
private MinesweeperField getFieldTranslatedByDirection(MinesweeperField field, Direction direction) {
int x = field.getxPosition();
int y = field.getyPosition();
/**
* Every {@link Direction} translates the fields. Basis of the
* translation is the one dimensional array in which the fields are
* stored.
*/
switch (direction) {
case UPPER_LEFT:
x--;
y--;
break;
case UPPER:
y--;
break;
case UPPER_RIGHT:
x++;
y--;
break;
case LEFT:
x--;
break;
case RIGHT:
x++;
break;
case LOWER_LEFT:
x--;
y++;
break;
case LOWER:
y++;
break;
case LOWER_RIGHT:
x++;
y++;
break;
}
// Is out of bounds, don't try
if (x < 0 || x > fieldsX || y < 0 || y > fieldsY) {
return null;
}
// Go through all fields and return the one with the calculated coordinates
for (MinesweeperField f : fields) {
if (f.getxPosition() == x && f.getyPosition() == y) {
return f;
}
}
return null;
}
/**
* If the player clicked on a mine, the game will end. All fields will be
* uncovered. The user will be asked whether he wants to restart the game.
*
* @param player A reference to the player.
*/
private void handleGameEnd(Player player) {
gameEnded = true;
// first send a BoardChangedMessage (client will then see all fields
for (int i = 0; i < fields.length; i++) {
fields[i].setIsUncovered(true);
}
BoardChangeMessage boardChangeMessage = new BoardChangeMessage(fields);
sendMessageToPlayer(boardChangeMessage, player);
GameEndsMessage gameEndMessage = new GameEndsMessage("LOST");
sendMessageToPlayer(gameEndMessage, player);
}
/**
* Uncovers a field which is surrounded by one or more mines.
*
* @param field The clicked field.
* @param player A reference to the player.
*/
private void uncoverDangerousField(MinesweeperField field, Player player) {
HashSet<MinesweeperField> uncoveredMinesweeperFields = new HashSet<MinesweeperField>();
field.setIsUncovered(true);
uncoveredMinesweeperFields.add(field);
// Set all uncovered fields as unflagged
for (MinesweeperField f : uncoveredMinesweeperFields) {
if (field.getHasFlag()) {
field.setHasFlag(false);
minesLeft++;
}
};
// Send a message with all the uncovered fields.
BoardChangeMessage boardChangeMessage = new BoardChangeMessage(uncoveredMinesweeperFields.toArray(new MinesweeperField[uncoveredMinesweeperFields.size()]));
sendMessageToPlayer(boardChangeMessage, player);
}
/**
* Uncover all fileds without a mine
* @param field Field where player clicked
* @param player Player who clicked
*/
private void uncoverHarmlessField(MinesweeperField field, Player player) {
HashSet<MinesweeperField> uncoveredMinesweeperFields = coverUpConnectedFields(field);
// Set all uncovered fields as unflagged
if (field.getHasFlag()) {
field.setHasFlag(false);
minesLeft++;
}
// Additionally add the clicked field to the list of all uncovered fields.
uncoveredMinesweeperFields.add(field);
// Send a message with all the uncovered fields.
BoardChangeMessage boardChangeMessage = new BoardChangeMessage(uncoveredMinesweeperFields.toArray(new MinesweeperField[uncoveredMinesweeperFields.size()]));
sendMessageToPlayer(boardChangeMessage, player);
}
/**
* This enumeration is just a shorthand for the surrounding mines algorithm.
* See {@link GameLogic#getFieldTranslatedByDirection}.
*/
public enum Direction {
UPPER_LEFT,
UPPER,
UPPER_RIGHT,
LEFT,
RIGHT,
LOWER_LEFT,
LOWER,
LOWER_RIGHT
}
}