This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
package gui.menu;
|
package gui.menu;
|
||||||
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
import gui.SudokuRenderer;
|
import gui.SudokuRenderer;
|
||||||
import imgui.ImGui;
|
import imgui.ImGui;
|
||||||
import imgui.ImGuiStyle;
|
import imgui.ImGuiStyle;
|
||||||
import sudoku.io.SudokuSerializer;
|
import sudoku.io.SudokuSerializer;
|
||||||
|
import sudoku.solver.BacktrackingSolver;
|
||||||
|
import sudoku.solver.HumanSolver;
|
||||||
|
import sudoku.solver.MixedSolver;
|
||||||
import sudoku.solver.Solver;
|
import sudoku.solver.Solver;
|
||||||
import sudoku.structure.MultiDoku;
|
import sudoku.structure.MultiDoku;
|
||||||
|
|
||||||
@@ -66,41 +68,29 @@ public class SudokuView extends BaseView {
|
|||||||
stopResolve();
|
stopResolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startSolve(Solver solver) {
|
||||||
|
resolveThread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
solver.solve(this.doku);
|
||||||
|
} catch (CancellationException e) {
|
||||||
|
System.out.println("The user is bored !");
|
||||||
|
}
|
||||||
|
stopResolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void renderSolvePopup() {
|
private void renderSolvePopup() {
|
||||||
if (ImGui.beginPopup("solve")) {
|
if (ImGui.beginPopup("solve")) {
|
||||||
if (ImGui.button("Résoudre avec backtrace")) {
|
if (ImGui.button("Résoudre avec backtrace")) {
|
||||||
resolveThread = new Thread(() -> {
|
startSolve(new BacktrackingSolver());
|
||||||
try {
|
|
||||||
Random rand = new Random();
|
|
||||||
Solver.randomSolve(doku, rand);
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
System.out.println("The user is bored !");
|
|
||||||
}
|
|
||||||
stopResolve();
|
|
||||||
});
|
|
||||||
ImGui.closeCurrentPopup();
|
ImGui.closeCurrentPopup();
|
||||||
}
|
}
|
||||||
if (ImGui.button("Résoudre avec déduction")) {
|
if (ImGui.button("Résoudre avec déduction")) {
|
||||||
resolveThread = new Thread(() -> {
|
startSolve(new HumanSolver());
|
||||||
try {
|
|
||||||
Solver.humanSolve(doku);
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
System.out.println("The user is bored !");
|
|
||||||
}
|
|
||||||
stopResolve();
|
|
||||||
});
|
|
||||||
ImGui.closeCurrentPopup();
|
ImGui.closeCurrentPopup();
|
||||||
}
|
}
|
||||||
if (ImGui.button("Résoudre avec déduction et backtrace")) {
|
if (ImGui.button("Résoudre avec déduction et backtrace")) {
|
||||||
resolveThread = new Thread(() -> {
|
startSolve(new MixedSolver());
|
||||||
try {
|
|
||||||
Random rand = new Random();
|
|
||||||
Solver.mixedSolve(doku, rand);
|
|
||||||
} catch (CancellationException e) {
|
|
||||||
System.out.println("The user is bored !");
|
|
||||||
}
|
|
||||||
stopResolve();
|
|
||||||
});
|
|
||||||
ImGui.closeCurrentPopup();
|
ImGui.closeCurrentPopup();
|
||||||
}
|
}
|
||||||
ImGui.endPopup();
|
ImGui.endPopup();
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
package sudoku.io;
|
package sudoku.io;
|
||||||
|
|
||||||
import sudoku.constraint.*;
|
import java.util.ArrayList;
|
||||||
import sudoku.solver.Solver;
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import sudoku.constraint.Constraint;
|
||||||
|
import sudoku.solver.RandomSolver;
|
||||||
import sudoku.structure.Difficulty;
|
import sudoku.structure.Difficulty;
|
||||||
import sudoku.structure.MultiDoku;
|
import sudoku.structure.MultiDoku;
|
||||||
import sudoku.structure.Sudoku;
|
import sudoku.structure.Sudoku;
|
||||||
import sudoku.structure.SudokuFactory;
|
import sudoku.structure.SudokuFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Scanner;
|
|
||||||
|
|
||||||
public class ConsoleInterface {
|
public class ConsoleInterface {
|
||||||
public Scanner reader = new Scanner(System.in);
|
public Scanner reader = new Scanner(System.in);
|
||||||
|
|
||||||
@@ -133,7 +132,7 @@ public class ConsoleInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateFullDoku(MultiDoku doku) {
|
private void generateFullDoku(MultiDoku doku) {
|
||||||
Solver.randomSolve(doku, new Random());
|
new RandomSolver().solve(doku);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
49
app/src/main/java/sudoku/solver/BacktrackingSolver.java
Normal file
49
app/src/main/java/sudoku/solver/BacktrackingSolver.java
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package sudoku.solver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
|
import sudoku.structure.Cell;
|
||||||
|
import sudoku.structure.MultiDoku;
|
||||||
|
|
||||||
|
public class BacktrackingSolver implements Solver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public 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 (this.solve(doku)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
55
app/src/main/java/sudoku/solver/HumanSolver.java
Normal file
55
app/src/main/java/sudoku/solver/HumanSolver.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package sudoku.solver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import sudoku.io.SudokuPrinter;
|
||||||
|
import sudoku.structure.Cell;
|
||||||
|
import sudoku.structure.MultiDoku;
|
||||||
|
import sudoku.structure.Sudoku;
|
||||||
|
|
||||||
|
public class HumanSolver implements Solver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean solve(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 this.solve(doku);
|
||||||
|
}
|
||||||
|
|
||||||
|
return doku.isSolved();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
82
app/src/main/java/sudoku/solver/MixedSolver.java
Normal file
82
app/src/main/java/sudoku/solver/MixedSolver.java
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package sudoku.solver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import sudoku.io.SudokuPrinter;
|
||||||
|
import sudoku.structure.Cell;
|
||||||
|
import sudoku.structure.MultiDoku;
|
||||||
|
import sudoku.structure.Sudoku;
|
||||||
|
|
||||||
|
public class MixedSolver implements Solver{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean solve(MultiDoku doku) {
|
||||||
|
Random rand = new Random();
|
||||||
|
|
||||||
|
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 this.solve(doku);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (this.solve(doku)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
||||||
|
possibleSymbols.remove(nextPossibleSymbolIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
63
app/src/main/java/sudoku/solver/RandomSolver.java
Normal file
63
app/src/main/java/sudoku/solver/RandomSolver.java
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package sudoku.solver;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import sudoku.io.SudokuPrinter;
|
||||||
|
import sudoku.structure.Cell;
|
||||||
|
import sudoku.structure.MultiDoku;
|
||||||
|
import sudoku.structure.Sudoku;
|
||||||
|
|
||||||
|
public class RandomSolver implements Solver {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean solve(MultiDoku doku) {
|
||||||
|
Random rand = new Random();
|
||||||
|
|
||||||
|
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 (this.solve(doku)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
cellToFill.setSymbolIndex(Cell.NOSYMBOL);
|
||||||
|
possibleSymbols.remove(nextPossibleSymbolIndex);
|
||||||
|
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,67 +1,19 @@
|
|||||||
package sudoku.solver;
|
package sudoku.solver;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.CancellationException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import sudoku.io.SudokuPrinter;
|
|
||||||
import sudoku.structure.Cell;
|
import sudoku.structure.Cell;
|
||||||
import sudoku.structure.MultiDoku;
|
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.
|
* 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");
|
||||||
|
|
||||||
/**
|
boolean solve(MultiDoku doku);
|
||||||
* 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.
|
* Compte le nombre de solutions possibles au MultiDoku passé en paramètres.
|
||||||
@@ -69,168 +21,27 @@ public class Solver {
|
|||||||
* @param doku MultiDoku, MultiDoku dont on veut le nombre de solutions.
|
* @param doku MultiDoku, MultiDoku dont on veut le nombre de solutions.
|
||||||
* @return int, nombre de solutions possibles.
|
* @return int, nombre de solutions possibles.
|
||||||
*/
|
*/
|
||||||
public static int countSolution(MultiDoku doku) {
|
default int countSolution(MultiDoku doku) {
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
|
|
||||||
if (doku.isSolved()) {
|
if (doku.isSolved()) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cell cellToFill = doku.getFirstEmptyCell();
|
Cell cellToFill = doku.getFirstEmptyCell();
|
||||||
assert(cellToFill != null);
|
assert (cellToFill != null);
|
||||||
|
|
||||||
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
|
||||||
|
|
||||||
for (int symbol : possibleSymbols) {
|
for (int symbol : possibleSymbols) {
|
||||||
doku.getStateManager().pushState();
|
doku.getStateManager().pushState();
|
||||||
cellToFill.setSymbolIndex(symbol);
|
cellToFill.setSymbolIndex(symbol);
|
||||||
if (Solver.solve(doku)) {
|
if (solve(doku)) {
|
||||||
result++;
|
result++;
|
||||||
}
|
}
|
||||||
doku.getStateManager().popState();
|
doku.getStateManager().popState();
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package sudoku.solver;
|
|||||||
|
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
|
|
||||||
import sudoku.structure.Coordinate;
|
|
||||||
import sudoku.structure.MultiDoku;
|
import sudoku.structure.MultiDoku;
|
||||||
import sudoku.structure.Sudoku;
|
import sudoku.structure.Sudoku;
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import sudoku.io.SudokuSerializer;
|
|
||||||
|
|
||||||
import sudoku.constraint.Constraint;
|
import sudoku.constraint.Constraint;
|
||||||
|
import sudoku.io.SudokuSerializer;
|
||||||
|
import sudoku.solver.RandomSolver;
|
||||||
import sudoku.solver.Solver;
|
import sudoku.solver.Solver;
|
||||||
|
|
||||||
public class SudokuFactory {
|
public class SudokuFactory {
|
||||||
@@ -116,7 +116,7 @@ public class SudokuFactory {
|
|||||||
* @return boolean, valant true si un MultiDoku de difficulté donnée peut être créée, false sinon.
|
* @return boolean, valant true si un MultiDoku de difficulté donnée peut être créée, false sinon.
|
||||||
* @throws Exception si la difficulté n'est pas compatible avec la taille du MultiDoku.
|
* @throws Exception si la difficulté n'est pas compatible avec la taille du MultiDoku.
|
||||||
*/
|
*/
|
||||||
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception {
|
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty, Solver solver) throws Exception {
|
||||||
|
|
||||||
if (nbCellsToEmpty >= doku.getCells().size()) {
|
if (nbCellsToEmpty >= doku.getCells().size()) {
|
||||||
throw new Exception();
|
throw new Exception();
|
||||||
@@ -134,9 +134,9 @@ public class SudokuFactory {
|
|||||||
|
|
||||||
int oldSymbol = cellToEmpty.empty();
|
int oldSymbol = cellToEmpty.empty();
|
||||||
|
|
||||||
int nbDokuSultions = Solver.countSolution(doku);
|
int nbDokuSultions = solver.countSolution(doku);
|
||||||
if (nbDokuSultions == 1) {
|
if (nbDokuSultions == 1) {
|
||||||
if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) {
|
if (newDokuFromFilledOne(doku, --nbCellsToEmpty, solver)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ public class SudokuFactory {
|
|||||||
cellsThatCanBeEmptied.remove(cellToEmpty);
|
cellsThatCanBeEmptied.remove(cellToEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newDokuFromFilledOne(doku, --nbCellsToEmpty);
|
return newDokuFromFilledOne(doku, --nbCellsToEmpty, solver);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,9 +272,10 @@ public class SudokuFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
|
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
|
||||||
Solver.randomSolve(doku, random);
|
Solver solver = new RandomSolver();
|
||||||
|
solver.solve(doku);
|
||||||
int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells());
|
int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells());
|
||||||
boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty);
|
boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty, solver);
|
||||||
if (!successfull) {
|
if (!successfull) {
|
||||||
throw new Exception("Canno't create this doku with this difficulty");
|
throw new Exception("Canno't create this doku with this difficulty");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import org.json.JSONObject;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import sudoku.io.SudokuSerializer;
|
import sudoku.io.SudokuSerializer;
|
||||||
import sudoku.solver.Solver;
|
import sudoku.solver.RandomSolver;
|
||||||
import sudoku.structure.MultiDoku;
|
import sudoku.structure.MultiDoku;
|
||||||
import sudoku.structure.SudokuFactory;
|
import sudoku.structure.SudokuFactory;
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ public class SudokuSerializerTest {
|
|||||||
void testSerializeWithSize(int blockWidth, int blockHeight) {
|
void testSerializeWithSize(int blockWidth, int blockHeight) {
|
||||||
var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
|
var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
|
||||||
SudokuFactory.DEFAULT_CONSTRAINTS);
|
SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||||
Solver.randomSolve(sudoku, new Random());
|
new RandomSolver().solve(sudoku);
|
||||||
JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
|
JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
|
||||||
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);
|
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);
|
||||||
assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString()));
|
assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString()));
|
||||||
@@ -28,14 +28,15 @@ public class SudokuSerializerTest {
|
|||||||
void testSaveWithSize(int blockWidth, int blockHeight) {
|
void testSaveWithSize(int blockWidth, int blockHeight) {
|
||||||
MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
|
MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
|
||||||
SudokuFactory.DEFAULT_CONSTRAINTS);
|
SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||||
Solver.randomSolve(doku, new Random());
|
new RandomSolver().solve(doku);
|
||||||
String savePath = SudokuSerializer.saveMultiDoku(doku);
|
String savePath = SudokuSerializer.saveMultiDoku(doku);
|
||||||
MultiDoku otherDoku = null;
|
MultiDoku otherDoku = null;
|
||||||
try {
|
try {
|
||||||
otherDoku = SudokuFactory.fromfile(savePath);
|
otherDoku = SudokuFactory.fromfile(savePath);
|
||||||
assert (otherDoku != null);
|
assert (otherDoku != null);
|
||||||
|
|
||||||
assertEquals(SudokuSerializer.serializeSudoku(doku).toString(), SudokuSerializer.serializeSudoku(otherDoku).toString());
|
assertEquals(SudokuSerializer.serializeSudoku(doku).toString(),
|
||||||
|
SudokuSerializer.serializeSudoku(otherDoku).toString());
|
||||||
// clean file after test
|
// clean file after test
|
||||||
File fileToDelete = new File(savePath);
|
File fileToDelete = new File(savePath);
|
||||||
fileToDelete.delete();
|
fileToDelete.delete();
|
||||||
@@ -47,7 +48,7 @@ public class SudokuSerializerTest {
|
|||||||
|
|
||||||
void testSerializeX(int size) {
|
void testSerializeX(int size) {
|
||||||
var sudoku = SudokuFactory.createBasicXShapedMultidoku(size, SudokuFactory.DEFAULT_CONSTRAINTS);
|
var sudoku = SudokuFactory.createBasicXShapedMultidoku(size, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||||
Solver.randomSolve(sudoku, new Random());
|
new RandomSolver().solve(sudoku);
|
||||||
JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
|
JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
|
||||||
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);
|
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data);
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class SolverTest {
|
|||||||
|
|
||||||
assert (dokuResult.isSolved());
|
assert (dokuResult.isSolved());
|
||||||
|
|
||||||
Solver.randomSolve(dokuToTest, rand);
|
new RandomSolver().solve(dokuToTest);
|
||||||
|
|
||||||
System.out.println("\n****************************\nDoku solved");
|
System.out.println("\n****************************\nDoku solved");
|
||||||
SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3);
|
SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3);
|
||||||
@@ -89,13 +89,13 @@ class SolverTest {
|
|||||||
5, ns, ns, ns, 3, 1, 0, ns, ns);
|
5, ns, ns, ns, 3, 1, 0, ns, ns);
|
||||||
sudokuToTest2.setImmutableCellsSymbol(immutableCells2);
|
sudokuToTest2.setImmutableCellsSymbol(immutableCells2);
|
||||||
|
|
||||||
boolean isSolved = Solver.randomSolve(dokuToTest2, rand);
|
boolean isSolved = new RandomSolver().solve(dokuToTest2);
|
||||||
|
|
||||||
assert (!isSolved);
|
assert (!isSolved);
|
||||||
|
|
||||||
MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
|
MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
|
||||||
|
|
||||||
Solver.randomSolve(dokuToTest3, rand);
|
new RandomSolver().solve(dokuToTest3);
|
||||||
|
|
||||||
SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3);
|
SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user