package eu.semberal.migmang.logic;
import eu.semberal.migmang.enums.GameColor;
import eu.semberal.migmang.enums.PlayerType;
import eu.semberal.migmang.events.MigmangGameEvent;
import eu.semberal.migmang.exceptions.BadSaveGameException;
import eu.semberal.migmang.graphics.Graphics;
import java.io.File;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JOptionPane;
/**
* Controll class is responsible for the game loop. Receives events from the
* graphics.
*
* @author lukas.sembera
*/
public class Controller implements WindowEventsListener {
private Graphics view;
private GameStatus gameStatus;
private Thread gameThread;
/**
* MoveGeneratorInterface will store moves here
*/
private MoveContainer moveContainer;
/**
* Timer activated in case of replay
*/
private Timer replayTimer;
private boolean replayMode = false;
/**
* Indicates whether the timer is running. If not, the replay is paused.
*/
private boolean replayTimeActive = false;
@Override
public GameStatus getGameStatus() {
return gameStatus;
}
public Controller() {
view = new Graphics();
view.attachWindowEventsListener(this);
}
@Override
public void onGameStarted(MigmangGameEvent e) {
if (isGameInProgress()) {
onGameClosed();
}
gameStatus = new GameStatus(e.getWhitePlayerType(), e.getBlackPlayerType(), e.getWhitePlayerDifficulty(), e.getBlackPlayerDifficulty(), view, view);
view.redraw(gameStatus.getCurrentBoard());
view.onStartGame(false);
view.showMessage("New game has started");
doMoveLoop();
}
/**
* Game loop. Asks players to move, receivers and validates moves, etc...
*/
private void doMoveLoop() {
gameThread = new Thread(new Runnable() {
public void run() {
while (!(gameThread.isInterrupted()) & Referee.isGameEnded(gameStatus.getCurrentBoard()) == null) {
moveContainer = new MoveContainer();
new Thread(new Runnable() {
public void run() {
if (gameStatus.isWhiteOnTurn()) {
gameStatus.getWhitePlayer().makeMove(gameStatus.getCurrentBoard(), moveContainer);
} else {
gameStatus.getBlackPlayer().makeMove(gameStatus.getCurrentBoard(), moveContainer);
}
}
}).start();
/*
* Thread will wait until minimax or player store move in
* the container
*/
while (moveContainer.getMove() == null) {
Thread.yield();
}
synchronized (moveContainer) {
Move move = moveContainer.getMove();
if (Referee.isValidMove(gameStatus.getCurrentBoard(), move, gameStatus.isWhiteOnTurn())) {
Board board = gameStatus.getCurrentBoard();
Referee.addCaptureInformationToMove(board, move);
board.addMoveWithCaptures(move);
gameStatus.addMove(move);
view.addMove(move);
gameStatus.setWhiteOnTurn(!gameStatus.isWhiteOnTurn());
view.showMessage(move.toString());
} else {
//this condition is here to not to report messages when the move is incorrect on purpuse (because of ending of minimax, etc...)
if (!move.isIncorrectMove()) {
view.showMessage("Incorrent move");
}
}
}
view.redraw(gameStatus.getCurrentBoard());
}
if (Referee.isGameEnded(gameStatus.getCurrentBoard()) != null) {
view.showImportantMessage("Game end", "Game end", JOptionPane.INFORMATION_MESSAGE);
}
}
});
gameThread.start();
}
private void setPausedGame() {
if (!isGameInProgress() || isGamePaused()) {
return;
}
gameThread.interrupt();
synchronized (moveContainer) {
moveContainer.setMove(Move.createIncorrectMove()); //puts incorrect move, so that the game loop continues and is not waiting anymore
}
view.disableMoves();
while (gameThread.getState() != Thread.State.TERMINATED) { //waits until the thread stops
Thread.yield();
}
}
private void setGameResumed() {
if (!isGameInProgress() || !isGamePaused()) {
return;
}
doMoveLoop();
}
@Override
public void onGameClosed() {
setPausedGame();
timerCancel();
replayMode = false;
replayTimer = null;
gameStatus = null;
view.showMessage("Game has ended");
view.redraw(null);
view.onQuitGame();
}
private boolean isGamePaused() {
if (gameThread == null) {
return true;
}
return gameThread.getState() == Thread.State.TERMINATED;
}
private boolean isGameInProgress() {
return gameStatus != null;
}
@Override
public void onGameDataChanged(MigmangGameEvent e) {
if (!isGameInProgress()) {
return;
}
boolean wasPaused = isGamePaused();
setPausedGame();
gameStatus.getWhitePlayer().setDifficulty(e.getWhitePlayerDifficulty());
gameStatus.getBlackPlayer().setDifficulty(e.getBlackPlayerDifficulty());
gameStatus.getWhitePlayer().setPlayerType(e.getWhitePlayerType());
gameStatus.getBlackPlayer().setPlayerType(e.getBlackPlayerType());
if (!wasPaused) {
setGameResumed();
}
view.showMessage("Game preferences have been changed");
}
@Override
public void onSaveGame(File kam) {
boolean wasPaused = isGamePaused();
setPausedGame();
SavedGame game = new SavedGame(
gameStatus.getMovesHistoryWithoutCapturingInfo(),
gameStatus.getWhitePlayer().getDifficulty(),
gameStatus.getBlackPlayer().getDifficulty(),
gameStatus.getWhitePlayer().getPlayerType(),
gameStatus.getBlackPlayer().getPlayerType(),
gameStatus.getUndoedMovesCount());
try {
OperationsIO.saveGame(kam, game);
} catch (Exception e) {
view.showImportantMessage("Error", "Game has NOT been saved successfully, an error occured", JOptionPane.ERROR_MESSAGE);
}
if (!wasPaused) {
setGameResumed();
}
}
@Override
public void onLoadGame(File file) {
loadGameReguested(file, true);
}
private void loadGameReguested(File file, boolean standardGame) {
if (file == null) {
return;
}
try {
SavedGame loadedGame = OperationsIO.loadGame(file);
if (isGameInProgress()) {
onGameClosed();
}
int counter = 1;
gameStatus = new GameStatus(loadedGame.getWhitePlayerType(), loadedGame.getBlackPlayerType(), loadedGame.getWhiteDifficulty(), loadedGame.getBlackDifficulty(), view, view);
for (Move move : loadedGame.getMoveHistory()) {
if (move.getCapturedIndexes().length != 0
|| !Referee.isValidMove(gameStatus.getCurrentBoard(), move, gameStatus.isWhiteOnTurn())) {
throw new BadSaveGameException();
}
Referee.addCaptureInformationToMove(gameStatus.getCurrentBoard(), move);
gameStatus.getCurrentBoard().addMoveWithCaptures(move);
gameStatus.addMove(move);
gameStatus.setWhiteOnTurn(!gameStatus.isWhiteOnTurn());
view.addMove(move);
}
for (int i = 0; i < loadedGame.getUndoesMovesCount(); i++) {
moveBackRequested(false);
}
view.redraw(gameStatus.getCurrentBoard());
if (standardGame) {
view.showMessage("Game has been successfully loaded. Unpause it to continue");
view.onStartGame(false);
} else {
view.showMessage("Replay has been successfully loaded");
}
} catch (BadSaveGameException e) {
view.showImportantMessage("Error", "Game data are inconsistent, game has not been successfully loaded", JOptionPane.ERROR_MESSAGE);
onGameClosed();
} catch (Exception e) {
view.showImportantMessage("Error", "File read error, game has not been successfully loaded", JOptionPane.ERROR_MESSAGE);
onGameClosed();
}
}
@Override
public void onBestMoveHintRequest() {
if (Referee.isGameEnded(gameStatus.getCurrentBoard()) != null) {
view.showMessage("Game is already over");
return;
}
if (replayMode) {
view.showMessage("Game is in replay mode");
return;
}
boolean humanOnMove = (gameStatus.isWhiteOnTurn() ? gameStatus.getWhitePlayer().getPlayerType() == PlayerType.Human : gameStatus.getBlackPlayer().getPlayerType() == PlayerType.Human);
if (!humanOnMove) {
view.showMessage("It is computer's turn now, best move hints are disabled");
return;
}
boolean wasPaused = isGamePaused();
setPausedGame();
Minimax minimax = new Minimax(2);
MoveContainer container = new MoveContainer();
minimax.move(gameStatus.getCurrentBoard(), gameStatus.isWhiteOnTurn() ? GameColor.White : GameColor.Black, container);
view.showMessage("Best move: " + container.getMove());
if (!wasPaused) {
setGameResumed();
}
}
/**
* Go half-move back in the move history
*
* @param handlePausing Whether pausing should be handled. This method is either called by the user in GUI. In such case
* it is necessary to pause the game and do halfmove back. But this method might be also called many times when rewind to initial
* board position was requested. In such case it is not necessary to pause the game because pausing has already been taken care of.
* @return Whether is was possible to move back (not yet on the beginning)
*/
public synchronized boolean moveBackRequested(boolean handlePausing) {
if (!isGameInProgress()) {
return false;
}
boolean wasPaused = true;
if (handlePausing) {
wasPaused = isGamePaused();
setPausedGame();
}
try {
gameStatus.moveBackward();
view.dropLastMove();
view.redraw(gameStatus.getCurrentBoard());
} catch (Exception e) {
return false;
} finally {
if (handlePausing && !wasPaused) {
setGameResumed();
}
}
return true;
}
@Override
public void onMoveBackward() {
moveBackRequested(true);
}
/**
* Move half-move forward in the history
* @see moveBackRequested()
*/
private synchronized boolean moveForwardRequested(boolean handlePausing) {
if (!isGameInProgress()) {
return false;
}
boolean wasPaused = true;
if (handlePausing) {
wasPaused = isGamePaused();
setPausedGame();
}
try {
Move t = gameStatus.moveForward();
view.redraw(gameStatus.getCurrentBoard());
view.addMove(t);
} catch (Exception e) {
return false;
} finally {
if (handlePausing && !wasPaused) {
setGameResumed();
}
}
return true;
}
@Override
public void onMoveForward() {
moveForwardRequested(true);
}
@Override
public void onMoveToStartRequested() {
boolean wasPaused = isGamePaused();
setPausedGame();
while (moveBackRequested(false)) {
}
if (!wasPaused) {
setGameResumed();
}
}
@SuppressWarnings("empty-statement")
@Override
public void moveToEndRequested() {
boolean wasPaused = isGamePaused();
setPausedGame();
while (moveForwardRequested(false));
if (!wasPaused) {
setGameResumed();
}
}
@Override
public void onTogglePausedState() {
if (replayMode) {
if (replayTimeActive) {
view.showMessage("Replay has been paused");
timerCancel();
} else {
view.showMessage("Replay has been resumed");
timerStart();
}
} else {
if (isGamePaused()) {
view.showMessage("Game has been resumed");
setGameResumed();
} else {
view.showMessage("Game has been paused");
setPausedGame();
}
}
}
@Override
public void loadReplayRequested(File file) {
loadGameReguested(file, false);
if (gameStatus == null) {
return;
}
/*
* If loaded game is not a replay => quit
*/
if (Referee.isGameEnded(gameStatus.getCurrentBoard()) == null) {
view.showImportantMessage("Load Replay error", "Saved game is not a replay", JOptionPane.WARNING_MESSAGE);
this.onGameClosed();
return;
}
onMoveToStartRequested();
replayMode = true;
timerStart();
view.onStartGame(true);
}
@Override
public boolean quitReplayMode() {
if (!replayMode) {
return false;
}
timerCancel();
replayTimer = null;
replayMode = false;
doMoveLoop();
return true;
}
private void timerStart() {
replayTimer = new Timer();
replayTimeActive = true;
replayTimer.schedule(new TimerSchedule(), 0, 1500);
}
private void timerCancel() {
if (!replayTimeActive) {
return;
}
replayTimeActive = false;
replayTimer.cancel();
}
private class TimerSchedule extends TimerTask {
@Override
public void run() {
synchronized (gameStatus) {
boolean canMoveForward = moveForwardRequested(false);
if (!canMoveForward) { //if it cannot move forward anymore, quit the timer
timerCancel();
}
}
}
}
}