diff --git a/app/save/save.json b/app/save/save.json new file mode 100644 index 0000000..67f4b87 --- /dev/null +++ b/app/save/save.json @@ -0,0 +1,554 @@ +{ + "cells": [ + { + "blockID": 0, + "immutable": true, + "symbolIndex": 5 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 0, + "immutable": true, + "symbolIndex": 6 + }, + { + "blockID": 1, + "immutable": true, + "symbolIndex": 7 + }, + { + "blockID": 1, + "symbolIndex": -1 + }, + { + "blockID": 1, + "immutable": true, + "symbolIndex": 1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 1, + "symbolIndex": -1 + }, + { + "blockID": 1, + "immutable": true, + "symbolIndex": 2 + }, + { + "blockID": 1, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 0, + "immutable": true, + "symbolIndex": 4 + }, + { + "blockID": 0, + "symbolIndex": -1 + }, + { + "blockID": 1, + "symbolIndex": -1 + }, + { + "blockID": 1, + "symbolIndex": -1 + }, + { + "blockID": 1, + "immutable": true, + "symbolIndex": 8 + }, + { + "blockID": 2, + "immutable": true, + "symbolIndex": 7 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 2, + "symbolIndex": -1 + }, + { + "blockID": 3, + "immutable": true, + "symbolIndex": 6 + }, + { + "blockID": 3, + "symbolIndex": -1 + }, + { + "blockID": 3, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 5, + "immutable": true, + "symbolIndex": 8 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 3, + "immutable": true, + "symbolIndex": 1 + }, + { + "blockID": 3, + "immutable": true, + "symbolIndex": 2 + }, + { + "blockID": 3, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 5, + "immutable": true, + "symbolIndex": 6 + }, + { + "blockID": 3, + "immutable": true, + "symbolIndex": 8 + }, + { + "blockID": 3, + "symbolIndex": -1 + }, + { + "blockID": 3, + "symbolIndex": -1 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 4, + "immutable": true, + "symbolIndex": 0 + }, + { + "blockID": 4, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 5, + "symbolIndex": -1 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 6, + "immutable": true, + "symbolIndex": 5 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 7, + "symbolIndex": -1 + }, + { + "blockID": 7, + "immutable": true, + "symbolIndex": 1 + }, + { + "blockID": 7, + "symbolIndex": -1 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 8, + "immutable": true, + "symbolIndex": 7 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 6, + "immutable": true, + "symbolIndex": 8 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 7, + "symbolIndex": -1 + }, + { + "blockID": 7, + "symbolIndex": -1 + }, + { + "blockID": 7, + "immutable": true, + "symbolIndex": 6 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 6, + "symbolIndex": -1 + }, + { + "blockID": 7, + "immutable": true, + "symbolIndex": 4 + }, + { + "blockID": 7, + "immutable": true, + "symbolIndex": 5 + }, + { + "blockID": 7, + "immutable": true, + "symbolIndex": 0 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 8, + "symbolIndex": -1 + }, + { + "blockID": 8, + "symbolIndex": -1 + } + ], + "blocks": [ + {"cellIDs": [ + 0, + 1, + 2, + 9, + 10, + 11, + 18, + 19, + 20 + ]}, + {"cellIDs": [ + 3, + 4, + 5, + 12, + 13, + 14, + 21, + 22, + 23 + ]}, + {"cellIDs": [ + 6, + 7, + 8, + 15, + 16, + 17, + 24, + 25, + 26 + ]}, + {"cellIDs": [ + 27, + 28, + 29, + 36, + 37, + 38, + 45, + 46, + 47 + ]}, + {"cellIDs": [ + 30, + 31, + 32, + 39, + 40, + 41, + 48, + 49, + 50 + ]}, + {"cellIDs": [ + 33, + 34, + 35, + 42, + 43, + 44, + 51, + 52, + 53 + ]}, + {"cellIDs": [ + 54, + 55, + 56, + 63, + 64, + 65, + 72, + 73, + 74 + ]}, + {"cellIDs": [ + 57, + 58, + 59, + 66, + 67, + 68, + 75, + 76, + 77 + ]}, + {"cellIDs": [ + 60, + 61, + 62, + 69, + 70, + 71, + 78, + 79, + 80 + ]} + ], + "multidoku": [{ + "blockWidth": 3, + "cells": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 77, + 78, + 79, + 80 + ], + "blocks": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8 + ], + "constraints": [ + 0, + 1, + 2 + ] + }] +} \ No newline at end of file diff --git a/app/src/main/java/gui/widget/SudokuSelector.java b/app/src/main/java/gui/widget/SudokuSelector.java index fe0f251..e99d87f 100644 --- a/app/src/main/java/gui/widget/SudokuSelector.java +++ b/app/src/main/java/gui/widget/SudokuSelector.java @@ -15,6 +15,7 @@ import sudoku.constraint.IConstraint; import sudoku.structure.Difficulty; import sudoku.structure.MultiDoku; import sudoku.structure.SudokuFactory; +import sudoku.structure.SudokuFiller; public class SudokuSelector { @@ -86,7 +87,8 @@ public class SudokuSelector { this.genThread = new Thread(() -> { try { if (!empty) { - SudokuFactory.fillDoku(doku, Difficulty.values()[difficulty.get()]); + SudokuFiller.fillDoku(doku, Difficulty.values()[difficulty.get()]); + // SudokuFactory.fillDoku(doku, Difficulty.values()[difficulty.get()]); } this.onSelect.emit(this.doku); } catch (Exception e) { diff --git a/app/src/main/java/sudoku/solver/MixedSolver.java b/app/src/main/java/sudoku/solver/MixedSolver.java index e2ad05a..1084583 100644 --- a/app/src/main/java/sudoku/solver/MixedSolver.java +++ b/app/src/main/java/sudoku/solver/MixedSolver.java @@ -1,5 +1,6 @@ package sudoku.solver; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CancellationException; @@ -8,17 +9,27 @@ import sudoku.structure.MultiDoku; public class MixedSolver implements Solver { - private Cell findCellToBacktrack(MultiDoku doku, int maxPossibilities) { + private List findCellToBacktrack(MultiDoku doku, int maxPossibilities) { + int symbolCount = doku.getSubGrid(0).getSize(); + if (maxPossibilities > symbolCount) + return null; + + List backtrackCells = new ArrayList<>(); + for (Cell cell : doku.getCells()) { if (!cell.isMutable() || !cell.isEmpty()) continue; List possibleSymbols = cell.getPossibleSymbols(); if (possibleSymbols.size() == maxPossibilities) { - return cell; + backtrackCells.add(cell); } + + // grille non résolvable + if (possibleSymbols.size() == 0) + return null; } - return null; + return backtrackCells; } /** @@ -34,41 +45,54 @@ public class MixedSolver implements Solver { throw new CancellationException("User wants to stop the solver"); } - while (!doku.isSolved()) { - boolean filledCell = false; - for (Cell cell : doku.getCells()) { - if (!cell.isMutable() || !cell.isEmpty()) - continue; + int symbolCount = doku.getSubGrid(0).getSize(); - List possibleSymbols = cell.getPossibleSymbols(); - if (possibleSymbols.size() == 1) { - cell.setSymbolIndex(possibleSymbols.getFirst()); - addStep(cell, steps); - filledCell = true; - } - } - // on ne peut plus remplir de cases, on tente de backtrack - if (!filledCell) { - int maxPossibilities = 2; - Cell backtrackCell = null; - while (backtrackCell == null) { - backtrackCell = findCellToBacktrack(doku, maxPossibilities); - maxPossibilities++; - } - // on fait du backtracking - List possibilities = backtrackCell.getPossibleSymbols(); - for (int symbol : possibilities) { - doku.getStateManager().pushState(); - backtrackCell.setSymbolIndex(symbol); - if (solve(doku, steps)) - return true; - doku.getStateManager().popState(); - } - } + // on remplit les cases par déduction + for (Cell cell : doku.getCells()) { + if (!cell.isMutable() || !cell.isEmpty()) + continue; + List possibleSymbols = cell.getPossibleSymbols(); + if (possibleSymbols.size() == 1) { + cell.setSymbolIndex(possibleSymbols.getFirst()); + addStep(cell, steps); + } } - return true; + if (doku.isSolved()) + return true; + + // on ne peut plus remplir de cases, on tente de backtrack + for (int maxPossibilities = 2; maxPossibilities <= symbolCount; maxPossibilities++) { + List backtrackCells = new ArrayList<>(); + while (backtrackCells.isEmpty()) { + backtrackCells = findCellToBacktrack(doku, maxPossibilities); + // grille non résolvable + if (backtrackCells == null) { + return false; + } + maxPossibilities++; + } + // on fait du backtracking + for (Cell backtrackCell : backtrackCells) { + List possibilities = backtrackCell.getPossibleSymbols(); + + // System.out.println("Backtraing on " + backtrackCell); + + // on tente de placer chacun des symboles + for (int symbol : possibilities) { + var state = doku.getStateManager().pushState(); + backtrackCell.setSymbolIndex(symbol); + if (solve(doku, steps)) { + // doku.getStateManager().forgetState(); + return true; + } + doku.getStateManager().popState(); + doku.getStateManager().restoreState(state); + } + } + } + return doku.isSolved(); } } diff --git a/app/src/main/java/sudoku/structure/StateManager.java b/app/src/main/java/sudoku/structure/StateManager.java index 5717251..72ee05b 100644 --- a/app/src/main/java/sudoku/structure/StateManager.java +++ b/app/src/main/java/sudoku/structure/StateManager.java @@ -25,6 +25,10 @@ public class StateManager { this.doku = doku; } + public void forgetState() { + states.pop(); + } + public Map pushState() { states.add(saveState()); return states.getLast(); @@ -42,7 +46,7 @@ public class StateManager { return currentState; } - private void restoreState(Map state) { + public void restoreState(Map state) { for (var entry : state.entrySet()) { entry.getKey().setSymbolIndex(entry.getValue()); } diff --git a/app/src/main/java/sudoku/structure/SudokuFiller.java b/app/src/main/java/sudoku/structure/SudokuFiller.java new file mode 100644 index 0000000..b6c533c --- /dev/null +++ b/app/src/main/java/sudoku/structure/SudokuFiller.java @@ -0,0 +1,78 @@ +package sudoku.structure; + +import java.util.List; +import java.util.Random; + +import sudoku.solver.BacktrackingSolver; +import sudoku.solver.HumanSolver; +import sudoku.solver.MixedSolver; +import sudoku.solver.RandomSolver; +import sudoku.solver.Solver; + +public class SudokuFiller { + + private static boolean isPossiblySolvable(MultiDoku doku) { + for (Cell cell : doku.getEmptyCells()) { + if (cell.getPossibleSymbols().size() == 0) + return false; + } + return true; + } + + private static void tryFillRandomCells(MultiDoku doku) { + Random r = new Random(); + while (isPossiblySolvable(doku)) { + int cellCount = doku.getEmptyCells().size(); + if (cellCount == 0) + return; + Cell cellToFill = doku.getEmptyCells().get(r.nextInt(cellCount)); + if (!cellToFill.isEmpty() || !cellToFill.isMutable()) + continue; + + List possibleSymbols = cellToFill.getPossibleSymbols(); + int symbolCount = possibleSymbols.size(); + // la cellule n'a pas de valeur possible + if (symbolCount == 0) + return; + + cellToFill.setSymbolIndex(possibleSymbols.get(r.nextInt(symbolCount))); + } + } + + private static void removeRandomCell(MultiDoku doku) { + Random r = new Random(); + int cellCount = doku.getFilledCells().size(); + doku.getFilledCells().get(r.nextInt(cellCount)).clearCurrentSymbol(); + } + + private static void makeSolvable(MultiDoku doku) { + while (!isPossiblySolvable(doku)) { + removeRandomCell(doku); + } + Solver solver = new BacktrackingSolver(); + boolean solvable = false; + while (!solvable) { + removeRandomCell(doku); + doku.getStateManager().pushState(); + solvable = solver.solve(doku); + doku.getStateManager().popState(); + System.out.println("eeeeeeeeeeeee"); + } + } + + private static void fillDoku(MultiDoku doku) { + tryFillRandomCells(doku); + makeSolvable(doku); + } + + private static void setCellsImmutable(MultiDoku doku) { + for (Cell cell : doku.getFilledCells()) + cell.setImmutable(); + } + + public static void fillDoku(MultiDoku doku, Difficulty difficulty) { + fillDoku(doku); + setCellsImmutable(doku); + } + +}