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 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 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 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 cellsToFill = doku.getEmptyCells(); if (cellsToFill.isEmpty()) { return false; } for (Cell cellToFill : cellsToFill) { List 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 cellsToFill = doku.getEmptyCells(); if (cellsToFill.isEmpty()) { return false; } //Règles de déduction for (Cell cellToFill : cellsToFill) { List 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 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; } }