237 lines
7.4 KiB
Java
237 lines
7.4 KiB
Java
package sudoku.solver;
|
|
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
import java.util.concurrent.CancellationException;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import sudoku.io.SudokuPrinter;
|
|
import sudoku.structure.Cell;
|
|
import sudoku.structure.MultiDoku;
|
|
import sudoku.structure.Sudoku;
|
|
|
|
public class Solver {
|
|
|
|
/**
|
|
* Log du Solver, qui garde trace des actions réalisées.
|
|
*/
|
|
private static final Logger logger = Logger.getLogger("SolverLogger");
|
|
|
|
/**
|
|
* Résout, si possible, le multidoku passé en paramètre
|
|
* en testant toutes les possibilités, de manière aléatoire, avec un algorithme
|
|
* de backtracking.
|
|
*
|
|
* @param doku Multidoku, à résoudre
|
|
* @param rand Random, pour tester aléatoirement les symboles
|
|
* @return boolean, true s'il est résolu ou false s'il ne l'est pas.
|
|
*/
|
|
public static boolean randomSolve(MultiDoku doku, Random rand) {
|
|
if (Thread.interrupted())
|
|
throw new CancellationException("User wants to stop the solver");
|
|
|
|
Sudoku sudoku = doku.getSubGrid(0);
|
|
logger.log(Level.FINE,
|
|
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth()));
|
|
|
|
if (doku.isSolved()) {
|
|
return true;
|
|
}
|
|
|
|
Cell cellToFill = doku.getFirstEmptyCell();
|
|
if (cellToFill == null) {
|
|
return false;
|
|
}
|
|
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
|
|
while (!possibleSymbols.isEmpty()) {
|
|
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
|
|
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
|
|
|
|
cellToFill.setSymbolIndex(nextSymbol);
|
|
if (Solver.randomSolve(doku, rand)) {
|
|
return true;
|
|
}
|
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
|
possibleSymbols.remove(nextPossibleSymbolIndex);
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Compte le nombre de solutions possibles au MultiDoku passé en paramètres.
|
|
*
|
|
* @param doku MultiDoku, MultiDoku dont on veut le nombre de solutions.
|
|
* @return int, nombre de solutions possibles.
|
|
*/
|
|
public static int countSolution(MultiDoku doku) {
|
|
int result = 0;
|
|
|
|
|
|
if (doku.isSolved()) {
|
|
return 1;
|
|
}
|
|
|
|
Cell cellToFill = doku.getFirstEmptyCell();
|
|
assert(cellToFill != null);
|
|
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
|
|
for (int symbol : possibleSymbols) {
|
|
doku.getStateManager().pushState();
|
|
cellToFill.setSymbolIndex(symbol);
|
|
if (Solver.solve(doku)) {
|
|
result++;
|
|
}
|
|
doku.getStateManager().popState();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Résout le MultiDoku passé en paramètre, avec backtracking.
|
|
*
|
|
* @param doku MultiDoku, MultiDoku à résoudre.
|
|
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
|
|
*/
|
|
public static boolean solve(MultiDoku doku) {
|
|
if (Thread.interrupted())
|
|
throw new CancellationException("User wants to stop the solver");
|
|
|
|
if (doku.isSolved()) {
|
|
return true;
|
|
}
|
|
|
|
Cell cellToFill = doku.getFirstEmptyCell();
|
|
if (cellToFill == null) {
|
|
return false;
|
|
}
|
|
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
if (possibleSymbols.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
for (int symbol : possibleSymbols) {
|
|
|
|
cellToFill.setSymbolIndex(symbol);
|
|
if (Solver.solve(doku)) {
|
|
return true;
|
|
} else {
|
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
|
}
|
|
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Résout le MultiDoku passé en paramètre, avec règles de déduction.
|
|
*
|
|
* @param doku MultiDoku, MultiDoku à résoudre.
|
|
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
|
|
*/
|
|
public static boolean humanSolve(MultiDoku doku) {
|
|
if (Thread.interrupted())
|
|
throw new CancellationException("User wants to stop the solver");
|
|
|
|
Sudoku sudoku = doku.getSubGrid(0);
|
|
logger.log(Level.FINE,
|
|
'\n' + SudokuPrinter.toStringRectangleSudoku(sudoku,
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth()));
|
|
|
|
if (doku.isSolved()) {
|
|
return true;
|
|
}
|
|
|
|
List<Cell> cellsToFill = doku.getEmptyCells();
|
|
if (cellsToFill.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
for (Cell cellToFill : cellsToFill) {
|
|
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
if (possibleSymbols.size() != 1) {
|
|
continue;
|
|
}
|
|
|
|
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
|
|
|
|
return Solver.humanSolve(doku);
|
|
}
|
|
|
|
return doku.isSolved();
|
|
}
|
|
|
|
/**
|
|
* Résout le MultiDoku passé en paramètre, avec règles de déduction et backtracking.
|
|
*
|
|
* @param doku MultiDoku, MultiDoku à résoudre.
|
|
* @param rand Random, pour tester aléatoirement les symboles, lors du backtracking.
|
|
* @return boolean, valant true si le MultiDoku est résolu, false sinon.
|
|
*/
|
|
public static boolean mixedSolve(MultiDoku doku, Random rand) {
|
|
if (Thread.interrupted()) {
|
|
throw new CancellationException("User wants to stop the solver");
|
|
}
|
|
|
|
Sudoku sudoku = doku.getSubGrid(0);
|
|
logger.log(Level.FINE,
|
|
'\n' + SudokuPrinter.toStringRectangleSudoku(
|
|
sudoku,
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getBlockWidth(),
|
|
sudoku.getBlockWidth() == 0 ? sudoku.getSize() : sudoku.getSize() / sudoku.getBlockWidth())
|
|
);
|
|
|
|
if (doku.isSolved()) {
|
|
return true;
|
|
}
|
|
|
|
List<Cell> cellsToFill = doku.getEmptyCells();
|
|
if (cellsToFill.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
//Règles de déduction
|
|
for (Cell cellToFill : cellsToFill) {
|
|
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
if (possibleSymbols.size() != 1) {
|
|
continue;
|
|
}
|
|
|
|
cellToFill.setSymbolIndex(possibleSymbols.getFirst());
|
|
|
|
return Solver.mixedSolve(doku, rand);
|
|
}
|
|
|
|
//Si ça ne marche pas
|
|
//On fait du backtracking
|
|
Cell cellToFill = doku.getRandomEmptyCell(rand);
|
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
|
|
|
while (!possibleSymbols.isEmpty()) {
|
|
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
|
|
int nextSymbol = possibleSymbols.get(nextPossibleSymbolIndex);
|
|
|
|
cellToFill.setSymbolIndex(nextSymbol);
|
|
if (Solver.mixedSolve(doku, rand)) {
|
|
return true;
|
|
}
|
|
|
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
|
possibleSymbols.remove(nextPossibleSymbolIndex);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|