/**
* Copyright (C) 2010, LGPL
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* @author Matfyz, fyzmat@gmail.com
*/
package mines;
import com.trolltech.qt.QSignalEmitter;
import com.trolltech.qt.QSignalEmitter.Signal1;
import java.util.List;
import mines.MineSolver.ComputationAbortedException;
import mines.MineSolver.FIELD_SOLVE_RESULT;
import mines.MineSolver.MinesSolverInternalError;
import mines.MinefieldContainer.Coord;
import mines.MinesUtils.MineSweeperData;
/**
* Thread for making the solver computations. Started by a thread runner.
*/
public class MineSolverThread extends QSignalEmitter implements Runnable {
/**
* Mode for running the solver.
*/
public enum MODE {
/** The marked clear fields are revealed immedeately */
SOLVE_AND_REVEAL,
/** The solver only marks clear and mined fields. */
SOLVE_ONLY
}
/** Identical reference as mines.MineSweeperLogic.mRealMinefield. */
MinefieldContainer<GUI_FIELD> mRealMinefield = null;
/** Identical reference as mines.MineSweeperLogic.mViewedMinefield. */
MinefieldContainer<GUI_FIELD> mViewedMinefield = null;
/** Identical reference as mines.MineSweeperLogic.mSolver. */
MineSolver mSolver = null;
/** Current way of solving the minefield*/
MODE mMode = MODE.SOLVE_ONLY;
/** Signals, that specified field should be revealed */
public Signal1<Coord> mFieldRevealedSignal = new Signal1<Coord>();
/** Signals, that specified field should be marked as clear */
public Signal1<Coord> mFieldMarkClearSignal = new Signal1<Coord>();
/** Signals, that specified field should be marked as clear */
public Signal1<Coord> mFieldMarkMineSignal = new Signal1<Coord>();
/**
* Changes the working content of the thread.
*
* @param realMinefield Container of current minefield (uncovered fields).
* @param viewedMinefield Container of current minefield (fields, which the user see).
* @param solver The solver object.
*/
public void setContent(MinefieldContainer<GUI_FIELD> realMinefield,
MinefieldContainer<GUI_FIELD> viewedMinefield, MineSolver solver) {
mRealMinefield = realMinefield;
mViewedMinefield = viewedMinefield;
mSolver = solver;
}
/**
* Sets the mode of evaluating solver results.
* @param mode Mode of the solver thread.
*/
public void setMode(MODE mode) {
mMode = mode;
}
/**
* Main function of this thread. Runs the solver evaluation.
*/
public void run() {
try {
runSolverTest();
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* Saves current minefield to file for debugging purposes.
* @param filePath Filename, where the minefield will be saved.
*/
void saveCurrentMinefieldState(String filePath) {
MineSweeperData data = new MineSweeperData();
data.mRealContent = mRealMinefield;
data.mViewedContent = mViewedMinefield;
MinesUtils.saveMinefieldToFileSafely(data, filePath);
}
/**
* Signals to gui thread, that some changes should be made on specified field,
* and waits for response of GUI thread.
*
* @param signal Signal to be emitted.
* @param argument Coordinates of field, which should be changed.
*/
void signalToGUI(Signal1<Coord> signal, Coord argument) {
try {
synchronized(this) {
signal.emit(argument);
this.wait();
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
/**
* Runs the solver until the solver has some determinable field to mark.
*/
void runSolverTest(){
//mSolver.enableLogging(true);
MinefieldContainer<SOLVER_FIELD> solverData =
MinesUtils.convertGUIToSolverMinefield(mViewedMinefield);
mSolver.setContent(solverData);
// continue to run the solver until there has been some change
while(true) {
List<Coord> coveredCoords = mViewedMinefield.getAllFieldsIndexes(GUI_FIELD.COVERED_FLAG);
boolean continueTesting = solveFields(coveredCoords);
if(!continueTesting) {
break;
}
}
mSolver.enableLogging(false);
}
/**
* Emits the signal, that specified covered field should be marked as mined.
* @param coveredCoord Coordinates of field to mark.
*/
void markMine(Coord coveredCoord) {
GUI_FIELD realValue = mRealMinefield.get(coveredCoord);
if (!realValue.equals(GUI_FIELD.MINE)) {
throw new MinesSolverInternalError("Bad evaluation of mine "
+ " at coord " + coveredCoord);
}
signalToGUI(mFieldMarkMineSignal, coveredCoord);
}
/**
* Emits the signal, that specified covered field should be marked as cleared.
* @param coveredCoord Coordinates of field to mark.
*/
void markClearField(Coord coveredCoord) {
GUI_FIELD realValue = mRealMinefield.get(coveredCoord);
if (realValue.equals(GUI_FIELD.MINE)) {
throw new MinesSolverInternalError("Bad evaluation of clear "
+ " at coord " + coveredCoord);
}
signalToGUI(mFieldMarkClearSignal, coveredCoord);
}
/**
* Emits the signal, that specified covered field should be revealed.
* @param coveredCoord Coordinates of field to reveal.
*/
void revealClearField(Coord coveredCoord) {
GUI_FIELD realValue = mRealMinefield.get(coveredCoord);
if (realValue.equals(GUI_FIELD.MINE)) {
throw new MinesSolverInternalError("Bad evaluation of clear "
+ " at coord " + coveredCoord);
}
signalToGUI(mFieldRevealedSignal, coveredCoord);
}
/**
* Decides, what to do with field, which has been determined as cleared.
* @param coveredCoord Coordinates of field, which has been marked.
*/
void handleClearField(Coord coveredCoord) {
switch(mMode) {
case SOLVE_ONLY:
markClearField(coveredCoord);
break;
case SOLVE_AND_REVEAL:
revealClearField(coveredCoord);
break;
default:
throw new Error("Unexpected solver thread mode: " + mMode);
}
}
/**
* For each covered field asks the solver, if the field is unknown, mined or
* numbered.
*
* @param coveredCoords All covered fields, which will be checked.
* @return False, if the solver has not discovered something new or,
* if the computation was aborted.
*/
boolean solveFields(List<Coord> coveredCoords) {
boolean continueTesting = false;
try {
for(Coord coveredCoord : coveredCoords) {
FIELD_SOLVE_RESULT result = mSolver.solveOneField(coveredCoord);
switch(result) {
case UNKNOWN:
break;
case MUST_BE_MINE:
markMine(coveredCoord);
mSolver.findApparentFields();
continueTesting = true;
break;
case CANNOT_BE_MINE:
handleClearField(coveredCoord);
mSolver.findApparentFields();
continueTesting = true;
break;
default:
throw new MinesSolverInternalError("Unexpected value " + result);
}
}
} catch (ComputationAbortedException abortEx) {
return false;
} catch (MinesSolverInternalError internalEx) {
internalEx.printStackTrace();
saveCurrentMinefieldState("./corruptedMinefieldState.msd");
return false;
}
return continueTesting;
}
}