refactor solvers
All checks were successful
Linux arm64 / Build (push) Successful in 42s

This commit is contained in:
2025-01-30 18:05:18 +01:00
parent 1f92c49f3c
commit a74bf42e59
11 changed files with 299 additions and 249 deletions

View File

@@ -1,236 +1,47 @@
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 {
public interface Solver {
/**
/**
* Log du Solver, qui garde trace des actions réalisées.
*/
private static final Logger logger = Logger.getLogger("SolverLogger");
public 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");
boolean solve(MultiDoku doku);
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) {
default int countSolution(MultiDoku doku) {
int result = 0;
if (doku.isSolved()) {
return 1;
}
Cell cellToFill = doku.getFirstEmptyCell();
assert(cellToFill != null);
assert (cellToFill != null);
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
for (int symbol : possibleSymbols) {
doku.getStateManager().pushState();
cellToFill.setSymbolIndex(symbol);
if (Solver.solve(doku)) {
if (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;
}
}