From 03f577828b3592b5a5e5c5b235a2bac0c9abb607 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 30 Jan 2025 00:51:22 +0100 Subject: [PATCH 1/2] fix: serialize --- .../main/java/gui/RenderableMultidoku.java | 20 +- app/src/main/java/gui/SudokuRenderer.java | 4 +- .../main/java/sudoku/io/SudokuSerializer.java | 59 +++--- app/src/main/java/sudoku/solver/Solver.java | 43 ++-- .../main/java/sudoku/solver/StupidSolver.java | 5 +- app/src/main/java/sudoku/structure/Cell.java | 53 +++-- .../main/java/sudoku/structure/MultiDoku.java | 85 ++------ .../main/java/sudoku/structure/Sudoku.java | 186 ++---------------- .../java/sudoku/structure/SudokuFactory.java | 2 - .../java/sudoku/SudokuSerializerTest.java | 34 +++- .../test/java/sudoku/solver/SolverTest.java | 121 ++++++------ 11 files changed, 208 insertions(+), 404 deletions(-) diff --git a/app/src/main/java/gui/RenderableMultidoku.java b/app/src/main/java/gui/RenderableMultidoku.java index db07f0b..6e14235 100644 --- a/app/src/main/java/gui/RenderableMultidoku.java +++ b/app/src/main/java/gui/RenderableMultidoku.java @@ -27,10 +27,6 @@ public class RenderableMultidoku { this.doku = doku; } - public boolean isResolved() { - return this.doku.isSolved(); - } - public int getWidth() { return width; } @@ -51,21 +47,7 @@ public class RenderableMultidoku { return cells.get(index); } - public boolean setCellValue(Cell cell, int value) { - for (Sudoku s : doku.getSubGrids()) { - int cellIndex = s.getCells().indexOf(cell); - // la cellule existe - if (cellIndex != -1) { - int cellX = cellIndex % s.getSize(); - int cellY = cellIndex / s.getSize(); - if (!s.canBePlaced(cellX, cellY, value)) { - return false; - } - } - } - cell.setSymbolIndex(value); - return true; - } + private static record PositionConstraint(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) { } diff --git a/app/src/main/java/gui/SudokuRenderer.java b/app/src/main/java/gui/SudokuRenderer.java index ca67ce9..474ff44 100644 --- a/app/src/main/java/gui/SudokuRenderer.java +++ b/app/src/main/java/gui/SudokuRenderer.java @@ -76,8 +76,8 @@ public class SudokuRenderer { } } else { if (ImGui.button(Integer.toString(i + 1), cellSize)) { - this.doku.setCellValue(currentCell, i); - if (this.doku.isResolved()) + currentCell.trySetValue(i); + if (this.doku.getDoku().isSolved()) this.onResolve.emit(); ImGui.closeCurrentPopup(); } diff --git a/app/src/main/java/sudoku/io/SudokuSerializer.java b/app/src/main/java/sudoku/io/SudokuSerializer.java index 52d946b..96ac2c5 100644 --- a/app/src/main/java/sudoku/io/SudokuSerializer.java +++ b/app/src/main/java/sudoku/io/SudokuSerializer.java @@ -1,7 +1,8 @@ package sudoku.io; -import java.io.*; -import java.nio.charset.StandardCharsets; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; @@ -15,16 +16,15 @@ import sudoku.structure.Block; import sudoku.structure.Cell; import sudoku.structure.MultiDoku; import sudoku.structure.Sudoku; -import sudoku.structure.SudokuFactory; public class SudokuSerializer { public static JSONObject serializeSudoku(final MultiDoku multidoku) { - List cellIds = new ArrayList<>(); + List cellIds = new ArrayList<>(); List blockIds = new ArrayList<>(); - JSONObject jsonRoot = new JSONObject(); - JSONArray jsonCells = new JSONArray(); + JSONObject jsonRoot = new JSONObject(); + JSONArray jsonCells = new JSONArray(); JSONArray jsonBlocks = new JSONArray(); JSONArray jsonSudokus = new JSONArray(multidoku.getNbSubGrids()); @@ -33,9 +33,10 @@ public class SudokuSerializer { // init cells for (Cell cell : sudoku.getCells()) { - if (!cellIds.contains(cell)) { - cellIds.add(cell); - } + if (cellIds.contains(cell)) + continue; + + cellIds.add(cell); Block block = cell.getBlock(); if (!blockIds.contains(block)) { @@ -43,6 +44,7 @@ public class SudokuSerializer { } int blockID = blockIds.indexOf(block); + assert(blockID >= 0); int symbolIndex = cell.getSymbolIndex(); JSONObject cellJsonObject = new JSONObject(); @@ -58,16 +60,17 @@ public class SudokuSerializer { // init blocks - for (Block blockId : blockIds) { - JSONObject blockJsonObject = new JSONObject(); - JSONArray cellsJsonArray = new JSONArray(); - for (Cell cell : blockId.getCells()) { - int cellID = cellIds.indexOf(cell); - cellsJsonArray.put(cellID); - } - blockJsonObject.put("cellIDs", cellsJsonArray); - jsonBlocks.put(blockJsonObject); - } + for (Block blockId : blockIds) { + JSONObject blockJsonObject = new JSONObject(); + JSONArray cellsJsonArray = new JSONArray(); + for (Cell cell : blockId.getCells()) { + int cellID = cellIds.indexOf(cell); + assert (cellID >= 0); + cellsJsonArray.put(cellID); + } + blockJsonObject.put("cellIDs", cellsJsonArray); + jsonBlocks.put(blockJsonObject); + } for (int i = 0; i < multidoku.getNbSubGrids(); i++) { // serialise sub grid @@ -82,6 +85,7 @@ public class SudokuSerializer { for (Cell cell : sudoku.getCells()) { int cellID = cellIds.indexOf(cell); + assert (cellID >= 0); cellsJsonArray.put(cellID); } @@ -89,6 +93,7 @@ public class SudokuSerializer { for (Block block : sudoku.getBlocks()) { int blockID = blockIds.indexOf(block); + assert (blockID >= 0); blocksJsonArray.put(blockID); } @@ -114,6 +119,7 @@ public class SudokuSerializer { /** * Save a serialized MultiDoku in a JSON file. + * * @param doku MultiDoku, MultiDoku to save. * @return String, the path of the save. */ @@ -139,6 +145,7 @@ public class SudokuSerializer { /** * Get a MultiDoku from a pre-existing json save file. + * * @param numberSave int, number of the save file to open. * @return MultiDoku, MultoDoku contained in the file. * @throws Exception when the given save file does not exist. @@ -158,20 +165,6 @@ public class SudokuSerializer { throw new Exception("This save does not exist."); } else { fileContent = new String(Files.readAllBytes(Paths.get("save/" + fileName))); - /* - try { - FileReader file = new FileReader(f); - char[] rawFileContent = {}; - int length = 1000; - while (file.read(rawFileContent, 0, length) != -1) { - rawFileContent = new char[]{}; - length = length * 10; - } - fileContent = new String(rawFileContent); - } catch (IOException e) { - throw new Exception("Error reading file."); - } - */ return deserializeSudoku(fileContent); } } diff --git a/app/src/main/java/sudoku/solver/Solver.java b/app/src/main/java/sudoku/solver/Solver.java index 2ab99e3..3a9d615 100644 --- a/app/src/main/java/sudoku/solver/Solver.java +++ b/app/src/main/java/sudoku/solver/Solver.java @@ -1,16 +1,16 @@ package sudoku.solver; -import sudoku.io.SudokuPrinter; -import sudoku.structure.MultiDoku; -import sudoku.structure.Cell; -import sudoku.structure.Sudoku; - 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 { /** @@ -46,7 +46,7 @@ public class Solver { return false; } - List possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); + List possibleSymbols = cellToFill.getPossibleSymbols(); while (!possibleSymbols.isEmpty()) { int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size()); @@ -78,18 +78,13 @@ public class Solver { } Cell cellToFill = doku.getFirstEmptyCell(); - if (cellToFill == null) { - System.out.println("AAAAAAAAAAAAAA"); - return 0; - } + assert(cellToFill != null); - List possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); + List possibleSymbols = cellToFill.getPossibleSymbols(); for (int symbol : possibleSymbols) { cellToFill.setSymbolIndex(symbol); - System.out.println("symbol : "+symbol); - System.out.println("doku.isSolved() || Solver.solve(doku) ? "+ (doku.isSolved() || Solver.solve(doku))); - if (doku.isSolved() || Solver.solve(doku)) { + if (Solver.solve(doku) != null) { result++; } cellToFill.setSymbolIndex(Cell.NOSYMBOL); @@ -103,35 +98,37 @@ public class Solver { * @param doku MultiDoku, MultiDoku à résoudre. * @return boolean, valant true si le MultiDoku est résolu, false sinon. */ - public static boolean solve(MultiDoku doku) { + public static MultiDoku solve(MultiDoku oldDoku) { if (Thread.interrupted()) throw new CancellationException("User wants to stop the solver"); + MultiDoku doku = oldDoku.clone(); + if (doku.isSolved()) { - return true; + return doku; } Cell cellToFill = doku.getFirstEmptyCell(); if (cellToFill == null) { - return false; + return null; } - List possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); + List possibleSymbols = cellToFill.getPossibleSymbols(); if (possibleSymbols.isEmpty()) { - return false; + return null; } for (int symbol : possibleSymbols) { cellToFill.setSymbolIndex(symbol); - if (Solver.solve(doku)) { - return true; + if (Solver.solve(doku) != null) { + return doku; } else { cellToFill.setSymbolIndex(Cell.NOSYMBOL); } } - return false; + return null; } /** @@ -156,7 +153,7 @@ public class Solver { boolean blocked = true; for (Cell cellToFill : cellsToFill) { - List possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); + List possibleSymbols = cellToFill.getPossibleSymbols(); if (possibleSymbols.size() != 1) { continue; } diff --git a/app/src/main/java/sudoku/solver/StupidSolver.java b/app/src/main/java/sudoku/solver/StupidSolver.java index 9d2dec1..82c2200 100644 --- a/app/src/main/java/sudoku/solver/StupidSolver.java +++ b/app/src/main/java/sudoku/solver/StupidSolver.java @@ -24,9 +24,8 @@ public class StupidSolver { if (!sudoku.getCell(index).isMutable()) return solve(sudoku, index + 1); - Coordinate coords = sudoku.toCoords(index); for (int symbol = 0; symbol < sudoku.getSize(); symbol++) { - if (sudoku.tryPlaceCellSymbol(coords.getX(), coords.getY(), symbol)) { + if (sudoku.getCell(index).trySetValue(symbol)) { // on tente de placer sur la case suivante if (solve(sudoku, index + 1)) { return true; @@ -34,7 +33,7 @@ public class StupidSolver { } } // on a tout essayé et rien n'a fonctionné - sudoku.clearCell(coords.getX(), coords.getY()); + sudoku.getCell(index).empty(); return false; } diff --git a/app/src/main/java/sudoku/structure/Cell.java b/app/src/main/java/sudoku/structure/Cell.java index 3889282..eaa4964 100644 --- a/app/src/main/java/sudoku/structure/Cell.java +++ b/app/src/main/java/sudoku/structure/Cell.java @@ -24,28 +24,21 @@ public class Cell { * Il est initialisé à Cell.NOSYMBOL. */ private int symbolIndex = Cell.NOSYMBOL; - /** - * Liste des index de symbole possibles pour cette Cell, - * en fonction des contraintes de sudoku dans lequel elle est. - */ - private final List possibleSymbols; /** * Si cette Cell peut être modififié ou non. */ private boolean isMutable = true; public Cell() { - this.possibleSymbols = new ArrayList<>(); + this(Cell.NOSYMBOL); } public Cell(int symbolIndex) { this.symbolIndex = symbolIndex; - this.possibleSymbols = new ArrayList<>(); } public Cell(int symbolIndex, boolean isMutable) { this.symbolIndex = symbolIndex; - this.possibleSymbols = new ArrayList<>(); this.isMutable = isMutable; } @@ -57,11 +50,6 @@ public class Cell { this.symbolIndex = symbolIndex; } - public void setPossibleSymbols(List possibleSymbols) { - this.possibleSymbols.clear(); - this.possibleSymbols.addAll(possibleSymbols); - } - /** * Rend la Cell immuable. */ @@ -95,14 +83,6 @@ public class Cell { return this.symbolIndex == Cell.NOSYMBOL; } - public void removeSymbolFromPossibilities(int indexSymbol) { - possibleSymbols.remove(indexSymbol); - } - - public List getPossibleSymbols() { - return this.possibleSymbols; - } - /** * Renvoie si la Cell est modifiable * @return boolean, true si elle est modifiable ou false sinon. @@ -120,4 +100,35 @@ public class Cell { this.symbolIndex = Cell.NOSYMBOL; return oldSymbol; } + + public boolean canHaveValue(int value) { + for (Sudoku s :getBlock().getSudokus()) { + int cellIndex = s.getCells().indexOf(this); + // la cellule existe + if (cellIndex != -1) { + int cellX = cellIndex % s.getSize(); + int cellY = cellIndex / s.getSize(); + if (!s.canBePlaced(cellX, cellY, value)) { + return false; + } + } + } + return true; + } + + public List getPossibleSymbols() { + List result = new ArrayList<>(); + for (int i = 0; i < getBlock().getSudokus().get(0).getSize(); i++) { + if (canHaveValue(i)) + result.add(i); + } + return result; + } + + public boolean trySetValue(int newValue) { + if (!canHaveValue(newValue)) + return false; + setSymbolIndex(newValue); + return true; + } } diff --git a/app/src/main/java/sudoku/structure/MultiDoku.java b/app/src/main/java/sudoku/structure/MultiDoku.java index b60c842..5eeca5a 100644 --- a/app/src/main/java/sudoku/structure/MultiDoku.java +++ b/app/src/main/java/sudoku/structure/MultiDoku.java @@ -24,85 +24,57 @@ public class MultiDoku { } public MultiDoku clone() { - //TODO: ahhhhhhhhhhhhhhhhhhhhhhh + // TODO: ahhhhhhhhhhhhhhhhhhhhhhh return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this)); } /** * Renvoie le nombre de sudoku contenu dans ce MultiDoku. + * * @return int */ - public int getNbSubGrids(){ + public int getNbSubGrids() { return subGrids.size(); } /** * Renvoie la ie sudoku contenue dans ce MultiDoku. + * * @param i int, indice du sudoku à renvoyer. * @return Sudoku, ie Sudoku */ - public Sudoku getSubGrid(int i){ + public Sudoku getSubGrid(int i) { return subGrids.get(i); } /** * Renvoie la liste des Cells contenue dans ce MultiDoku, * soit les Cells contenues de chaques sous-Sudoku. + * * @return List */ - public List getCells(){ + public List getCells() { Set cellsSet = new HashSet<>(); - for (Sudoku sudoku : subGrids){ + for (Sudoku sudoku : subGrids) { cellsSet.addAll(sudoku.getCells()); } - return new ArrayList<>(cellsSet); - } - - /** - * Met à jour les symboles possibles de chaque Cell. - * @throws Exception, si ce n'est pas possible. - */ - public void updateSymbolsPossibilities() throws Exception { - for (Sudoku sudoku : subGrids){ - sudoku.updateSymbolsPossibilities(); - } + return new ArrayList<>(cellsSet); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Multidoku {"); - for (Sudoku sudoku : subGrids){ + for (Sudoku sudoku : subGrids) { sb.append("\n\t").append(sudoku.toString()); } sb.append("\n}"); return sb.toString(); } - /** - * Renvoie les symboles possibles d'une Cell donnée. - * @param cellToFill Cell. - * @return List, liste des symboles possible. - */ - public List getPossibleSymbolsOfCell(Cell cellToFill) { - List result = new ArrayList<>(); - boolean hasBeenFill = false; - - for (Sudoku sudoku : this.subGrids) { - if (sudoku.contains(cellToFill)) { - if (!hasBeenFill) { - result.addAll(sudoku.getPossibleSymbolsOfCell(cellToFill)); - hasBeenFill = true; - } else { - result.retainAll(sudoku.getPossibleSymbolsOfCell(cellToFill)); - } - } - } - return result; - } - /** * Renvoie les sous-Sudoku + * * @return List */ public List getSubGrids() { @@ -111,43 +83,26 @@ public class MultiDoku { /** * Check si le MultiDoku est valide, en fonction de ses sous-Sudokus. + * * @return boolean, true s'il est valide et false sinon. */ public boolean isSolved() { - boolean result = true; for (Sudoku sudoku : this.subGrids) { - result = sudoku.isSolved() && result; - } - return result; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof MultiDoku)) { - return false; - } - - if (this.getNbSubGrids() != ((MultiDoku) object).getNbSubGrids()) { - return false; - } - - for (int i = 0; i < this.getNbSubGrids(); i++) { - if (!this.getSubGrid(i).equals(((MultiDoku) object).getSubGrid(i))) { + if (!sudoku.isSolved()) return false; - } } - return true; } /** * Renvoie la 1re Cell vide des sous-Sudoku. + * * @return Cell, une Cell vide, ou null s'il n'y en a pas. */ public Cell getFirstEmptyCell() { for (Sudoku sudoku : this.subGrids) { Cell cellTmp = sudoku.getFirstEmptyCell(); - if (cellTmp != null && cellTmp.isEmpty()) { + if (cellTmp != null) { return cellTmp; } } @@ -156,11 +111,12 @@ public class MultiDoku { /** * Renvoie la liste des Cells préalablement remplies du MultiDoku. + * * @return List, vide si aucune Cell n'est remplie. */ public List getFilledCells() { List result = new ArrayList<>(); - for (Cell cell : this.getCells()){ + for (Cell cell : this.getCells()) { if (!cell.isEmpty()) { result.add(cell); } @@ -170,11 +126,12 @@ public class MultiDoku { /** * Renvoie la liste des Cells vides du MultiDoku. + * * @return List, vide si aucune Cell ne l'est. */ public List getEmptyCells() { List result = new ArrayList<>(); - for (Cell cell : this.getCells()){ + for (Cell cell : this.getCells()) { if (cell.isEmpty()) { result.add(cell); } @@ -182,9 +139,9 @@ public class MultiDoku { return result; } - /** * Vide une Cell donnée. + * * @param cell Cell, à vider. */ public void empty(Cell cell) { @@ -195,6 +152,7 @@ public class MultiDoku { /** * Renvoie le nombre de Cell contenue dans le MultiDoku. + * * @return int, nombre de Cell dans le MultiDoku. */ public int getNbCells() { @@ -210,4 +168,3 @@ public class MultiDoku { } } } - diff --git a/app/src/main/java/sudoku/structure/Sudoku.java b/app/src/main/java/sudoku/structure/Sudoku.java index 2b7560a..3458d40 100644 --- a/app/src/main/java/sudoku/structure/Sudoku.java +++ b/app/src/main/java/sudoku/structure/Sudoku.java @@ -3,6 +3,7 @@ package sudoku.structure; import sudoku.constraint.BlockConstraint; import sudoku.constraint.Constraint; import sudoku.constraint.IConstraint; +import sudoku.io.SudokuPrinter; import java.util.ArrayList; import java.util.List; @@ -89,22 +90,6 @@ public class Sudoku { return true; } - /** - * Tente de placer le symbole value dans la Cell de coordonnées x, y. - * @param x int, abscisse de la Cell voulue. - * @param y int, coordonnée de la Cell voulue; - * @param value int, index du symbole que l'on veut placer. - * @return boolean, true si le symbole a été placé, false sinon - */ - public boolean tryPlaceCellSymbol(int x, int y, int value) { - assert (isValidCoords(x, y)); - if (!canBePlaced(x, y, value)) - return false; - Cell cell = getCell(x, y); - cell.setSymbolIndex(value); - return true; - } - /** * Vide la Cell dotn les coordonnées sont renseignées de son symbole. * @param x int, abscisse de la Cell voulue. @@ -194,7 +179,7 @@ public class Sudoku { } public Cell getCell(int x, int y) { - int index = y * getSize() + x; + int index = toIndex(x, y); assert (isValidCoords(x, y)); try { return this.cells.get(index); @@ -232,62 +217,6 @@ public class Sudoku { return this.cells.contains(cell); } - /** - * Localise la Cell dans le Sudoku. - * @param c Cell, cellule dont on veut les coordonées. - * @return Coordinate, coordonnées de la Cell. - * @throws Exception si la Cell n'appartient pas au Sudoku. - */ - private Coordinate getCoordinateCell(Cell c) throws Exception { - int x = 0, y = 0; - int size = this.getSize(); - - if (!this.contains(c)) { - throw new Exception("The given cell is not in this sudoku."); - } - - // TODO: use this.cells.indexOf(); - - for (Cell cell : this.cells) { - if (cell == c) { - return new Coordinate(x, y); - } - if (x == size - 1) { - y += 1; - x = 0; - } else { - x += 1; - } - } - return new Coordinate(x, y); - } - - /** - * Met à jour les symboles possibles des Cells du Sudoku. - * - */ - public void updateSymbolsPossibilities() { - for (Constraint constraint : constraints) { - List cells = this.getCells(); - for (Cell cell : cells) { - Coordinate coord = null; - try { - coord = this.getCoordinateCell(cell); - } catch (Exception e) { - System.out.println("Cas jamais atteint."); - } - List newPossibleSymbols = cell.getPossibleSymbols(); - newPossibleSymbols.retainAll(constraint.getPossibleSymbols( - this, - coord.getX(), - coord.getY() - )); - - cell.setPossibleSymbols(newPossibleSymbols); - } - } - } - public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Sudoku {"); @@ -315,30 +244,6 @@ public class Sudoku { return null; } - /** - * Renvoie l'index des symboles possibles de la Cell passée en paramètres. - * @param cellToFill Cell, cellule dont on cherche les symboles posisbles. - * @return List, la liste des index des symboles possibles, vide si la Cell n'appartient pas au Sudoku. - */ - public List getPossibleSymbolsOfCell(Cell cellToFill) { - List result = new ArrayList<>(); - Coordinate cellCoordinates; - try { - cellCoordinates = this.getCoordinateCell(cellToFill); - } catch (Exception e) { - return result; - } - for (int i = 0; i < this.constraints.size(); i++) { - Constraint constraint = this.constraints.get(i); - if (i == 0) { - result.addAll(constraint.getPossibleSymbols(this, cellCoordinates.getX(), cellCoordinates.getY())); - } else { - result.retainAll(constraint.getPossibleSymbols(this, cellCoordinates.getX(), cellCoordinates.getY())); - } - } - return result; - } - /** * Vérifie si le Sudoku est résolue, soit complet et cohérent avec ses contraintes. * @return boolean, valant true si le Sudoku est résolu, false sinon. @@ -362,89 +267,20 @@ public class Sudoku { * @return bollean, true si le Sudoku est valide, false sinon */ private boolean isValid() { - for (Cell cell : this.getFilledCells()) { - for (Constraint constraint : this.constraints) { - try { - Coordinate coords = this.getCoordinateCell(cell); + for (int i = 0; i < cells.size(); i++) { + Cell cell = getCell(i); + if (cell.isEmpty()) + continue; - int symbolPlaced = cell.empty(); - List possibleSymbols = constraint.getPossibleSymbols( - this, - coords.getX(), - coords.getY() - ); + Coordinate coordinate = toCoords(i); - cell.setSymbolIndex(symbolPlaced); - if (!possibleSymbols.contains(symbolPlaced)) { - return false; - } - - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - return true; - } - - /** - * Renvoie la liste des Cells remplies. - * @return List - */ - private List getFilledCells() { - List result = new ArrayList<>(); - for (Cell cell : getCells()) { - if (!cell.isEmpty()) { - result.add(cell); - } - } - return result; - } - - /** - * Renvoie la liste des Cells modifiables. - * @return List - */ - private List getEmptyCells() { - List result = new ArrayList<>(); - for (Cell cell : getCells()) { - if (cell.isMutable()) { - result.add(cell); - } - } - return result; - } - - /** - * Renvoie la liste des Cells immuables. - * @return List - */ - private List getImmutableCells() { - List result = new ArrayList<>(); - for (Cell cell : getCells()) { - if (!cell.isMutable()) { - result.add(cell); - } - } - return result; - } - - @Override - public boolean equals(Object object) { - if (!(object instanceof Sudoku)) { - return false; - } - - if (this.getSize() != ((Sudoku) object).getSize()) { - return false; - } - - for (int i = 0; i < this.getSize(); i++) { - if (this.getCell(i).getSymbolIndex() != ((Sudoku) object).getCell(i).getSymbolIndex()) { + int symbolPlaced = cell.empty(); + if (!canBePlaced(coordinate.getX(), coordinate.getY(), symbolPlaced)) { + cell.setSymbolIndex(symbolPlaced); return false; } + cell.setSymbolIndex(symbolPlaced); } - return true; } diff --git a/app/src/main/java/sudoku/structure/SudokuFactory.java b/app/src/main/java/sudoku/structure/SudokuFactory.java index 366bdad..e2f07da 100644 --- a/app/src/main/java/sudoku/structure/SudokuFactory.java +++ b/app/src/main/java/sudoku/structure/SudokuFactory.java @@ -123,7 +123,6 @@ public class SudokuFactory { */ public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception { - System.out.println("nbCellsToEmpty : "+nbCellsToEmpty); if (nbCellsToEmpty >= doku.getCells().size()) { throw new Exception(); } @@ -141,7 +140,6 @@ public class SudokuFactory { int oldSymbol = cellToEmpty.empty(); int nbDokuSultions = Solver.countSolution(doku); - System.out.println("oldSymbol : "+oldSymbol); if (nbDokuSultions == 1) { if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) { return true; diff --git a/app/src/test/java/sudoku/SudokuSerializerTest.java b/app/src/test/java/sudoku/SudokuSerializerTest.java index d08fe19..2723cb5 100644 --- a/app/src/test/java/sudoku/SudokuSerializerTest.java +++ b/app/src/test/java/sudoku/SudokuSerializerTest.java @@ -1,5 +1,8 @@ package sudoku; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + import java.io.File; import java.util.Random; @@ -7,6 +10,7 @@ import org.json.JSONObject; import org.junit.jupiter.api.Test; import sudoku.io.SudokuSerializer; +import sudoku.solver.Solver; import sudoku.structure.MultiDoku; import sudoku.structure.SudokuFactory; @@ -15,19 +19,23 @@ public class SudokuSerializerTest { void testSerializeWithSize(int blockWidth, int blockHeight) { var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, SudokuFactory.DEFAULT_CONSTRAINTS); + Solver.solveRandom(sudoku, new Random()); JSONObject data = SudokuSerializer.serializeSudoku(sudoku); MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); - assert (data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); + assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); } void testSaveWithSize(int blockWidth, int blockHeight) { MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, SudokuFactory.DEFAULT_CONSTRAINTS); + Solver.solveRandom(doku, new Random()); String savePath = SudokuSerializer.saveMultiDoku(doku); MultiDoku otherDoku = null; try { otherDoku = SudokuFactory.fromfile(savePath); assert (otherDoku != null); + + assertEquals(SudokuSerializer.serializeSudoku(doku).toString(), SudokuSerializer.serializeSudoku(otherDoku).toString()); // clean file after test File fileToDelete = new File(savePath); fileToDelete.delete(); @@ -35,23 +43,35 @@ public class SudokuSerializerTest { e.printStackTrace(); assert false; } - assert (doku.equals(otherDoku)); + } + + void testSerializeX(int size) { + var sudoku = SudokuFactory.createBasicXShapedMultidoku(size, SudokuFactory.DEFAULT_CONSTRAINTS); + Solver.solveRandom(sudoku, new Random()); + JSONObject data = SudokuSerializer.serializeSudoku(sudoku); + MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); + + assertTrue(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); } @Test void testSerialize() { Random r = new Random(); - int testCount = 5; + int testCount = 20; for (int i = 0; i < testCount; i++) { - int blockWidth = r.nextInt(10) + 1; - int blockHeight = r.nextInt(10) + 1; + int blockWidth = r.nextInt(4) + 1; + int blockHeight = r.nextInt(4) + 1; testSerializeWithSize(blockWidth, blockHeight); } for (int i = 0; i < testCount; i++) { - int blockWidth = r.nextInt(10) + 1; - int blockHeight = r.nextInt(10) + 1; + int blockWidth = r.nextInt(4) + 1; + int blockHeight = r.nextInt(4) + 1; testSaveWithSize(blockWidth, blockHeight); } + for (int i = 0; i < testCount; i++) { + int size = r.nextInt(2) + 2; + testSerializeX(size); + } } } diff --git a/app/src/test/java/sudoku/solver/SolverTest.java b/app/src/test/java/sudoku/solver/SolverTest.java index ae5d093..f0c61e4 100644 --- a/app/src/test/java/sudoku/solver/SolverTest.java +++ b/app/src/test/java/sudoku/solver/SolverTest.java @@ -2,90 +2,101 @@ package sudoku.solver; import org.junit.jupiter.api.Test; import sudoku.io.SudokuPrinter; +import sudoku.io.SudokuSerializer; import sudoku.structure.Cell; import sudoku.structure.MultiDoku; import sudoku.structure.Sudoku; import sudoku.structure.SudokuFactory; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.util.List; import java.util.Random; class SolverTest { - @Test - void solveTest() { - Random rand = new Random(); + @Test + void solveTest() { + Random rand = new Random(); - MultiDoku dokuToTest = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); - MultiDoku dokuResult = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); + MultiDoku dokuToTest = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); + MultiDoku dokuResult = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); - Sudoku sudokuToTest = dokuToTest.getSubGrid(0); - Sudoku sudokuResult = dokuResult.getSubGrid(0); + Sudoku sudokuToTest = dokuToTest.getSubGrid(0); + Sudoku sudokuResult = dokuResult.getSubGrid(0); - int ns = Cell.NOSYMBOL; - List immutableCells = List.of(ns, ns, 0, ns, ns, 2, 8, ns, 1, - ns, 3, ns, ns, 5, 6, 7, ns, ns, - ns, ns, ns, 8, ns, 7, ns, ns, 6, - 0, ns, 1, ns, ns, ns, ns, ns, ns, - 4, 8, 7, 5, 1, ns, 6, ns, ns, - 6, ns, 3, 2, ns, ns, ns, 8, 0, - ns, ns, 6, ns, ns, 8, ns, 7, 5, - 8, 0, ns, 7, ns, 5, 2, ns, 3, - 5, ns, ns, ns, 3, 1, 0, ns, ns); + int ns = Cell.NOSYMBOL; + List immutableCells = List.of(ns, ns, 0, ns, ns, 2, 8, ns, 1, + ns, 3, ns, ns, 5, 6, 7, ns, ns, + ns, ns, ns, 8, ns, 7, ns, ns, 6, + 0, ns, 1, ns, ns, ns, ns, ns, ns, + 4, 8, 7, 5, 1, ns, 6, ns, ns, + 6, ns, 3, 2, ns, ns, ns, 8, 0, + ns, ns, 6, ns, ns, 8, ns, 7, 5, + 8, 0, ns, 7, ns, 5, 2, ns, 3, + 5, ns, ns, ns, 3, 1, 0, ns, ns); - assert (sudokuToTest.setImmutableCellsSymbol(immutableCells)); + assert (sudokuToTest.setImmutableCellsSymbol(immutableCells)); - SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); + SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); - List correctCells = List.of(7, 6, 0, 3, 4, 2, 8, 5, 1, - 2, 3, 8, 1, 5, 6, 7, 0, 4, - 1, 4, 5, 8, 0, 7, 3, 2, 6, - 0, 2, 1, 6, 8, 3, 5, 4, 7, - 4, 8, 7, 5, 1, 0, 6, 3, 2, - 6, 5, 3, 2, 7, 4, 1, 8, 0, - 3, 1, 6, 0, 2, 8, 4, 7, 5, - 8, 0, 4, 7, 6, 5, 2, 1, 3, - 5, 7, 2, 4, 3, 1, 0, 6, 8); + List correctCells = List.of(7, 6, 0, 3, 4, 2, 8, 5, 1, + 2, 3, 8, 1, 5, 6, 7, 0, 4, + 1, 4, 5, 8, 0, 7, 3, 2, 6, + 0, 2, 1, 6, 8, 3, 5, 4, 7, + 4, 8, 7, 5, 1, 0, 6, 3, 2, + 6, 5, 3, 2, 7, 4, 1, 8, 0, + 3, 1, 6, 0, 2, 8, 4, 7, 5, + 8, 0, 4, 7, 6, 5, 2, 1, 3, + 5, 7, 2, 4, 3, 1, 0, 6, 8); - sudokuResult.setCellsSymbol(correctCells); + sudokuResult.setCellsSymbol(correctCells); - System.out.println("\n****************************Doku Control\n"); - SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); + System.out.println("\n****************************Doku Control\n"); + SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); - assert(dokuResult.isSolved()); + assert (dokuResult.isSolved()); - Solver.solveRandom(dokuToTest, rand); + Solver.solveRandom(dokuToTest, rand); - System.out.println("\n****************************\nDoku solved"); - SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); + System.out.println("\n****************************\nDoku solved"); + SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); + assert (dokuToTest.isSolved()); - assert(dokuToTest.isSolved()); + for (Cell cell : sudokuToTest.getCells()) { + cell.setImmutable(); + } - assert(dokuToTest.equals(dokuResult)); + for (Cell cell : sudokuResult.getCells()) { + cell.setImmutable(); + } - MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); - Sudoku sudokuToTest2 = dokuToTest2.getSubGrid(0); + assertEquals(SudokuSerializer.serializeSudoku(dokuResult).toString(), + SudokuSerializer.serializeSudoku(dokuToTest).toString()); - List immutableCells2 = List.of(ns, ns, 0, ns, ns, 2, 8, ns, 1, - 1, 3, ns, ns, 5, 6, 7, ns, ns, - ns, ns, ns, 8, ns, 7, ns, ns, 6, - 0, ns, 1, ns, ns, ns, ns, ns, ns, - 4, 8, 7, 5, 1, ns, 6, ns, ns, - 6, ns, 3, 2, ns, ns, ns, 8, 0, - ns, ns, 6, ns, ns, 8, ns, 7, 5, - 8, 0, ns, 7, ns, 5, 2, ns, 3, - 5, ns, ns, ns, 3, 1, 0, ns, ns); - sudokuToTest2.setImmutableCellsSymbol(immutableCells2); + MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); + Sudoku sudokuToTest2 = dokuToTest2.getSubGrid(0); - boolean isSolved = Solver.solveRandom(dokuToTest2, rand); + List immutableCells2 = List.of(ns, ns, 0, ns, ns, 2, 8, ns, 1, + 1, 3, ns, ns, 5, 6, 7, ns, ns, + ns, ns, ns, 8, ns, 7, ns, ns, 6, + 0, ns, 1, ns, ns, ns, ns, ns, ns, + 4, 8, 7, 5, 1, ns, 6, ns, ns, + 6, ns, 3, 2, ns, ns, ns, 8, 0, + ns, ns, 6, ns, ns, 8, ns, 7, 5, + 8, 0, ns, 7, ns, 5, 2, ns, 3, + 5, ns, ns, ns, 3, 1, 0, ns, ns); + sudokuToTest2.setImmutableCellsSymbol(immutableCells2); - assert (!isSolved); + boolean isSolved = Solver.solveRandom(dokuToTest2, rand); - MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); + assert (!isSolved); - Solver.solveRandom(dokuToTest3, rand); + MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); - SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3); - } + Solver.solveRandom(dokuToTest3, rand); + + SudokuPrinter.printRectangleSudoku(dokuToTest3.getSubGrid(0), 3, 3); + } } \ No newline at end of file From b9a661796767274a78fd984ee7fa4d7e3b19fda1 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Thu, 30 Jan 2025 09:34:00 +0100 Subject: [PATCH 2/2] feat: add states --- app/src/main/java/sudoku/solver/Solver.java | 24 +++++------ .../main/java/sudoku/structure/MultiDoku.java | 13 +++--- .../java/sudoku/structure/StateManager.java | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/sudoku/structure/StateManager.java diff --git a/app/src/main/java/sudoku/solver/Solver.java b/app/src/main/java/sudoku/solver/Solver.java index 3a9d615..b2e45a1 100644 --- a/app/src/main/java/sudoku/solver/Solver.java +++ b/app/src/main/java/sudoku/solver/Solver.java @@ -68,10 +68,9 @@ public class Solver { * @param oldDoku MultiDoku, MultiDoku dont on veut le nombre de solutions. * @return int, nombre de solutions possibles. */ - public static int countSolution(MultiDoku oldDoku) { + public static int countSolution(MultiDoku doku) { int result = 0; - MultiDoku doku = oldDoku.clone(); if (doku.isSolved()) { return 1; @@ -83,11 +82,12 @@ public class Solver { List possibleSymbols = cellToFill.getPossibleSymbols(); for (int symbol : possibleSymbols) { + doku.getStateManager().pushState(); cellToFill.setSymbolIndex(symbol); - if (Solver.solve(doku) != null) { + if (Solver.solve(doku)) { result++; } - cellToFill.setSymbolIndex(Cell.NOSYMBOL); + doku.getStateManager().popState(); } return result; @@ -98,37 +98,35 @@ public class Solver { * @param doku MultiDoku, MultiDoku à résoudre. * @return boolean, valant true si le MultiDoku est résolu, false sinon. */ - public static MultiDoku solve(MultiDoku oldDoku) { + public static boolean solve(MultiDoku doku) { if (Thread.interrupted()) throw new CancellationException("User wants to stop the solver"); - MultiDoku doku = oldDoku.clone(); - if (doku.isSolved()) { - return doku; + return true; } Cell cellToFill = doku.getFirstEmptyCell(); if (cellToFill == null) { - return null; + return false; } List possibleSymbols = cellToFill.getPossibleSymbols(); if (possibleSymbols.isEmpty()) { - return null; + return false; } for (int symbol : possibleSymbols) { cellToFill.setSymbolIndex(symbol); - if (Solver.solve(doku) != null) { - return doku; + if (Solver.solve(doku)) { + return true; } else { cellToFill.setSymbolIndex(Cell.NOSYMBOL); } } - return null; + return false; } /** diff --git a/app/src/main/java/sudoku/structure/MultiDoku.java b/app/src/main/java/sudoku/structure/MultiDoku.java index 5eeca5a..37b4976 100644 --- a/app/src/main/java/sudoku/structure/MultiDoku.java +++ b/app/src/main/java/sudoku/structure/MultiDoku.java @@ -19,13 +19,11 @@ public class MultiDoku { */ private final List subGrids; + private final StateManager stateManager; + public MultiDoku(List subGrids) { this.subGrids = subGrids; - } - - public MultiDoku clone() { - // TODO: ahhhhhhhhhhhhhhhhhhhhhhh - return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this)); + this.stateManager = new StateManager(this); } /** @@ -167,4 +165,9 @@ public class MultiDoku { filledCell.setImmutable(); } } + + public StateManager getStateManager() { + return stateManager; + } + } diff --git a/app/src/main/java/sudoku/structure/StateManager.java b/app/src/main/java/sudoku/structure/StateManager.java new file mode 100644 index 0000000..8ea9feb --- /dev/null +++ b/app/src/main/java/sudoku/structure/StateManager.java @@ -0,0 +1,41 @@ +package sudoku.structure; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +//TODO: doc +public class StateManager { + + private final Stack> states; + private final MultiDoku doku; + + public StateManager(MultiDoku doku) { + this.states = new Stack<>(); + this.doku = doku; + } + + public void pushState() { + states.add(new HashMap<>()); + saveState(); + } + + public void popState() { + assert (states.size() > 0); + restoreState(); + states.pop(); + } + + private void restoreState() { + for (var entry : this.states.getLast().entrySet()) { + entry.getKey().setSymbolIndex(entry.getValue()); + } + } + + private void saveState() { + for (Cell cell : this.doku.getCells()) { + states.getLast().put(cell, cell.getSymbolIndex()); + } + } + +}