From c16f2b8f5ad2fa2b54ca064bd14ed516a2f0f1ee Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Wed, 29 Jan 2025 17:19:44 +0100 Subject: [PATCH] feat: dynamic constraints (Fixes #8) --- app/src/main/java/gui/SudokuRenderer.java | 27 +++++- app/src/main/java/gui/SudokuSelector.java | 49 +++++++--- .../main/java/gui/menu/MultiPlayerView.java | 5 +- app/src/main/java/sudoku/Main.java | 33 ++++--- .../java/sudoku/constraint/Constraint.java | 52 ++++++++++ .../sudoku/constraint/DiagonalConstraint.java | 6 +- app/src/main/java/sudoku/structure/Block.java | 14 +-- .../java/sudoku/structure/Difficulty.java | 16 +++- .../main/java/sudoku/structure/Sudoku.java | 76 ++++++++++----- .../java/sudoku/structure/SudokuFactory.java | 94 ++++++++++++------- .../java/sudoku/SudokuSerializerTest.java | 10 +- .../test/java/sudoku/solver/SolverTest.java | 78 +++++++-------- 12 files changed, 311 insertions(+), 149 deletions(-) create mode 100644 app/src/main/java/sudoku/constraint/Constraint.java diff --git a/app/src/main/java/gui/SudokuRenderer.java b/app/src/main/java/gui/SudokuRenderer.java index f1a670c..ca67ce9 100644 --- a/app/src/main/java/gui/SudokuRenderer.java +++ b/app/src/main/java/gui/SudokuRenderer.java @@ -1,8 +1,10 @@ package gui; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import common.Signal; import gui.ColorGenerator.Color; @@ -11,10 +13,11 @@ import imgui.ImVec2; import imgui.ImVec4; import imgui.flag.ImGuiCol; import imgui.flag.ImGuiStyleVar; -import imgui.flag.ImGuiWindowFlags; +import sudoku.constraint.Constraint; import sudoku.structure.Block; import sudoku.structure.Cell; import sudoku.structure.MultiDoku; +import sudoku.structure.Sudoku; public class SudokuRenderer { @@ -24,13 +27,28 @@ public class SudokuRenderer { private static final ImVec4 BLACK = new ImVec4(0, 0, 0, 1); private static final ImVec4 TRANSPARENT = new ImVec4(); + private static final ImVec4 WHITE = new ImVec4(1.0f, 1.0f, 1.0f, 1.0f); private static final ImVec2 cellSize = new ImVec2(50, 50); + private final Set diagonals = new HashSet<>(); + public final Signal onResolve = new Signal(); public SudokuRenderer(MultiDoku doku) { this.doku = RenderableMultidoku.fromMultidoku(doku); this.colorPalette = initColors(); + initDiagonals(); + } + + private void initDiagonals() { + for (Sudoku sudoku : this.doku.getDoku().getSubGrids()) { + if (sudoku.hasConstraint(Constraint.Diagonal)) { + for (int i = 0; i < sudoku.getSize(); i++) { + this.diagonals.add(sudoku.getCell(i, i)); + this.diagonals.add(sudoku.getCell(sudoku.getSize() - i - 1, i)); + } + } + } } private Map initColors() { @@ -92,12 +110,15 @@ public class SudokuRenderer { ImGui.pushStyleColor(ImGuiCol.Button, TRANSPARENT); ImGui.button("##" + index, cellSize); } else { - ImGui.pushStyleColor(ImGuiCol.Border, BLACK); + if (diagonals.contains(cell)) { + ImGui.pushStyleColor(ImGuiCol.Border, WHITE); + } else { + ImGui.pushStyleColor(ImGuiCol.Border, BLACK); + } int symbol = cell.getSymbolIndex(); Color blockColor = colorPalette.get(cell.getBlock()); if (!cell.isMutable()) { blockColor = new Color(blockColor.r - 0.20f, blockColor.g - 0.20f, blockColor.b - 0.20f); - } else { } ImGui.pushStyleColor(ImGuiCol.Button, new ImVec4(blockColor.r, blockColor.g, blockColor.b, 1.0f)); String cellText = ""; diff --git a/app/src/main/java/gui/SudokuSelector.java b/app/src/main/java/gui/SudokuSelector.java index a53456d..343a47f 100644 --- a/app/src/main/java/gui/SudokuSelector.java +++ b/app/src/main/java/gui/SudokuSelector.java @@ -1,10 +1,15 @@ package gui; +import java.util.ArrayList; +import java.util.List; + import common.Signal; import imgui.ImGui; import imgui.extension.imguifiledialog.ImGuiFileDialog; import imgui.extension.imguifiledialog.flag.ImGuiFileDialogFlags; +import imgui.type.ImBoolean; import imgui.type.ImInt; +import sudoku.constraint.Constraint; import sudoku.structure.Difficulty; import sudoku.structure.MultiDoku; import sudoku.structure.SudokuFactory; @@ -19,7 +24,7 @@ public class SudokuSelector { private final ImInt sudokuType = new ImInt(0); private final ImInt difficulty = new ImInt(Difficulty.Medium.ordinal()); - private final String[] difficulties; + private final List contraints = new ArrayList<>(); private static final String[] sudokuTypes = { "Carré", "Rectangle", "Multidoku" }; private static final int SQUARE = 0, RECTANGLE = 1, MULTIDOKU = 2; @@ -31,10 +36,21 @@ public class SudokuSelector { public SudokuSelector(boolean canGenEmptyGrid) { this.canGenEmptyGrid = canGenEmptyGrid; - Difficulty[] diffs = Difficulty.values(); - difficulties = new String[diffs.length]; - for (int i = 0; i < diffs.length; i++) { - difficulties[i] = diffs[i].getDisplayName(); + initConstraints(); + } + + private List getConstraints() { + List constraints = new ArrayList<>(); + for (int i = 0; i < this.contraints.size(); i++) { + if (this.contraints.get(i).get()) + constraints.add(Constraint.values()[i]); + } + return constraints; + } + + private void initConstraints() { + for (Constraint cons : Constraint.values()) { + contraints.add(new ImBoolean(SudokuFactory.DEFAULT_CONSTRAINTS.contains(cons))); } } @@ -71,15 +87,21 @@ public class SudokuSelector { public void render() { ImGui.combo("Type de Sudoku", sudokuType, sudokuTypes); - ImGui.combo("Difficulté", difficulty, difficulties); + ImGui.combo("Difficulté", difficulty, Difficulty.getDifficultyNames()); + if (ImGui.treeNode("Constraintes")) { + for (Constraint cons : Constraint.values()) { + ImGui.checkbox(cons.getDisplayName(), contraints.get(cons.ordinal())); + } + ImGui.treePop(); + } switch (sudokuType.get()) { case SQUARE: ImGui.inputInt("Taille", sudokuSize); if (ImGui.button("Résoudre un sudoku")) { - selectSudoku(SudokuFactory.createBasicEmptySquareSudoku(sudokuSize.get()), false); + selectSudoku(SudokuFactory.createBasicEmptySquareDoku(sudokuSize.get(), getConstraints()), false); } if (canGenEmptyGrid && ImGui.button("Générer une grille vide")) { - selectSudoku(SudokuFactory.createBasicEmptySquareSudoku(sudokuSize.get()), true); + selectSudoku(SudokuFactory.createBasicEmptySquareDoku(sudokuSize.get(), getConstraints()), true); } break; @@ -88,22 +110,25 @@ public class SudokuSelector { ImGui.inputInt("Longueur", sudokuWidth); if (ImGui.button("Résoudre un sudoku")) { selectSudoku( - SudokuFactory.createBasicEmptyRectangleSudoku(sudokuWidth.get(), sudokuHeight.get()), + SudokuFactory.createBasicEmptyRectangleDoku(sudokuWidth.get(), sudokuHeight.get(), + getConstraints()), false); } if (canGenEmptyGrid && ImGui.button("Générer une grille vide")) { selectSudoku( - SudokuFactory.createBasicEmptyRectangleSudoku(sudokuWidth.get(), sudokuHeight.get()), true); + SudokuFactory.createBasicEmptyRectangleDoku(sudokuWidth.get(), sudokuHeight.get(), + getConstraints()), + true); } break; case MULTIDOKU: ImGui.inputInt("Taille", sudokuSize); if (ImGui.button("Résoudre un sudoku")) { - selectSudoku(SudokuFactory.createBasicXShapedMultidoku(sudokuSize.get()), false); + selectSudoku(SudokuFactory.createBasicXShapedMultidoku(sudokuSize.get(), getConstraints()), false); } if (canGenEmptyGrid && ImGui.button("Générer une grille vide")) { - selectSudoku(SudokuFactory.createBasicXShapedMultidoku(sudokuSize.get()), true); + selectSudoku(SudokuFactory.createBasicXShapedMultidoku(sudokuSize.get(), getConstraints()), true); } default: diff --git a/app/src/main/java/gui/menu/MultiPlayerView.java b/app/src/main/java/gui/menu/MultiPlayerView.java index 09afd83..9a0ee2e 100644 --- a/app/src/main/java/gui/menu/MultiPlayerView.java +++ b/app/src/main/java/gui/menu/MultiPlayerView.java @@ -17,7 +17,8 @@ public class MultiPlayerView extends BaseView { this.client = client; this.server = server; this.client.onDisconnect.connect(this::onDisconnect); - this.client.onGameStarted.connect(() -> this.stateMachine.pushState(new MultiPlayerDokuView(stateMachine, client, server))); + this.client.onGameStarted + .connect(() -> this.stateMachine.pushState(new MultiPlayerDokuView(stateMachine, client, server))); } @Override @@ -36,7 +37,7 @@ public class MultiPlayerView extends BaseView { } else { if (ImGui.button("Démarrer")) { // temp - MultiDoku doku = SudokuFactory.createBasicXShapedMultidoku(3); + MultiDoku doku = SudokuFactory.createBasicXShapedMultidoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); this.server.startGame(doku); } } diff --git a/app/src/main/java/sudoku/Main.java b/app/src/main/java/sudoku/Main.java index ca6a820..c91aa66 100644 --- a/app/src/main/java/sudoku/Main.java +++ b/app/src/main/java/sudoku/Main.java @@ -18,30 +18,29 @@ public class Main { int blockWidth = 2; int blockHeight = 2; - var multidoku = SudokuFactory.createBasicEmptyRectangleSudoku(blockWidth, blockHeight); + var multidoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, + SudokuFactory.DEFAULT_CONSTRAINTS); var sudoku = multidoku.getSubGrid(0); - if(!sudoku.setCellsSymbol(Arrays.asList(0,1,2,3, 2,3,1,1, 1,0,3,2, 3,2,1,1))){ + if (!sudoku.setCellsSymbol(Arrays.asList(0, 1, 2, 3, 2, 3, 1, 1, 1, 0, 3, 2, 3, 2, 1, 1))) { System.out.println("At least one of those values does not respect the constraints."); } + // sudoku.setCellSymbol(8,3,0); - //sudoku.setCellSymbol(8,3,0); - - SudokuPrinter.printRectangleSudoku(multidoku.getSubGrid(0), blockWidth , blockHeight); + SudokuPrinter.printRectangleSudoku(multidoku.getSubGrid(0), blockWidth, blockHeight); /* - Solver solver = new Solver(); - ArrayList constraints = new ArrayList<>(); - constraints.add(new LineConstraint()); - constraints.add(new ColumnConstraint()); - constraints.add(new BlockConstraint()); - try { - solver.solve(multidoku, constraints); - } catch (Exception e) { - System.out.println(e); - } - */ - + * Solver solver = new Solver(); + * ArrayList constraints = new ArrayList<>(); + * constraints.add(new LineConstraint()); + * constraints.add(new ColumnConstraint()); + * constraints.add(new BlockConstraint()); + * try { + * solver.solve(multidoku, constraints); + * } catch (Exception e) { + * System.out.println(e); + * } + */ } } diff --git a/app/src/main/java/sudoku/constraint/Constraint.java b/app/src/main/java/sudoku/constraint/Constraint.java new file mode 100644 index 0000000..14b7c3d --- /dev/null +++ b/app/src/main/java/sudoku/constraint/Constraint.java @@ -0,0 +1,52 @@ +package sudoku.constraint; + +import java.util.List; + +import sudoku.structure.Sudoku; + +public enum Constraint { + + Block("Bloc", new BlockConstraint()), + Column("Colonne", new ColumnConstraint()), + Line("Ligne", new LineConstraint()), + Diagonal("Diagonale", new DiagonalConstraint()); + + String displayName; + IConstraint constraint; + + private Constraint(String displayName, IConstraint contraint) { + this.constraint = contraint; + this.displayName = displayName; + } + + public boolean canBePlaced(Sudoku s, int x, int y, int newValue) { + return getConstraint().canBePlaced(s, x, y, newValue); + } + + public List getPossibleSymbols(final Sudoku s, int x, int y) { + return getConstraint().getPossibleSymbols(s, x, y); + } + + public String getDisplayName() { + return displayName; + } + + public IConstraint getConstraint() { + return constraint; + } + + private static final String[] constraintNames; + + static { + Constraint[] cons = Constraint.values(); + constraintNames = new String[cons.length]; + for (int i = 0; i < cons.length; i++) { + constraintNames[i] = cons[i].getDisplayName(); + } + } + + public static String[] getConstraintNames() { + return constraintNames; + } + +} diff --git a/app/src/main/java/sudoku/constraint/DiagonalConstraint.java b/app/src/main/java/sudoku/constraint/DiagonalConstraint.java index 63f9ad6..95d6842 100644 --- a/app/src/main/java/sudoku/constraint/DiagonalConstraint.java +++ b/app/src/main/java/sudoku/constraint/DiagonalConstraint.java @@ -9,12 +9,12 @@ public class DiagonalConstraint implements IConstraint { if (x == y) { for (int i = 0; i < s.getSize(); i++) { if (s.getCell(i, i).getSymbolIndex() == newSymbolIndex) - return false; + return false; } } else if (s.getSize() - x == y) { for (int i = 0; i < s.getSize(); i++) { - if (s.getCell(s.getSize() - i, i).getSymbolIndex() == newSymbolIndex) - return false; + if (s.getCell(s.getSize() - i - 1, i).getSymbolIndex() == newSymbolIndex) + return false; } } // not in diagonal diff --git a/app/src/main/java/sudoku/structure/Block.java b/app/src/main/java/sudoku/structure/Block.java index 356b20b..98acdbd 100644 --- a/app/src/main/java/sudoku/structure/Block.java +++ b/app/src/main/java/sudoku/structure/Block.java @@ -5,7 +5,8 @@ import java.util.List; /** * Class qui représente les block de chaque sudoku, - * Un block étant un ensemble de cellule avec une contrainte de block qui lui ait associé + * Un block étant un ensemble de cellule avec une contrainte de block qui lui + * ait associé */ public class Block { @@ -18,14 +19,15 @@ public class Block { * List de sudoku qui contiennent le block * Pour un acces plus rapide aux sudokus */ - private List sudokus; + private final List sudokus; public Block(List cells) { this.cells = cells; + this.sudokus = new ArrayList<>(); } public Block() { - this.cells = new ArrayList<>(); + this(new ArrayList<>()); } public List getCells() { @@ -34,6 +36,7 @@ public class Block { /** * Ajoute une Cell au Block + * * @param newCell Cell, à ajouter */ void addCell(Cell newCell) { @@ -42,6 +45,7 @@ public class Block { /** * Cherche si le Block contient déjà un symbole donné. + * * @param symbolIndex int, un index de symbole * @return boolean, true s'il contient le symbole et false sinon */ @@ -65,8 +69,4 @@ public class Block { public List getSudokus() { return sudokus; } - - void setSudokus(List sudokus) { - this.sudokus = sudokus; - } } diff --git a/app/src/main/java/sudoku/structure/Difficulty.java b/app/src/main/java/sudoku/structure/Difficulty.java index 0952255..9f0b225 100644 --- a/app/src/main/java/sudoku/structure/Difficulty.java +++ b/app/src/main/java/sudoku/structure/Difficulty.java @@ -8,7 +8,7 @@ public enum Difficulty { double factor; String displayName; - Difficulty(String displayName, double factor) { + private Difficulty(String displayName, double factor) { this.factor = factor; this.displayName = displayName; } @@ -21,4 +21,18 @@ public enum Difficulty { return factor; } + private static final String[] difficultyNames; + + static { + Difficulty[] diffs = Difficulty.values(); + difficultyNames = new String[diffs.length]; + for (int i = 0; i < diffs.length; i++) { + difficultyNames[i] = diffs[i].getDisplayName(); + } + } + + public static String[] getDifficultyNames() { + return difficultyNames; + } + } diff --git a/app/src/main/java/sudoku/structure/Sudoku.java b/app/src/main/java/sudoku/structure/Sudoku.java index 4aff373..b664a68 100644 --- a/app/src/main/java/sudoku/structure/Sudoku.java +++ b/app/src/main/java/sudoku/structure/Sudoku.java @@ -1,5 +1,7 @@ package sudoku.structure; +import sudoku.constraint.BlockConstraint; +import sudoku.constraint.Constraint; import sudoku.constraint.IConstraint; import java.util.ArrayList; @@ -22,13 +24,14 @@ public class Sudoku { /** * Liste des contraintes (TODO) du Sudoku. */ - private final List constraints; + private final List constraints; /** - * Largeur des Blocks s'ils sont rectangulaires, valant 0 si ce n'est pas le cas. + * Largeur des Blocks s'ils sont rectangulaires, valant 0 si ce n'est pas le + * cas. */ private int blockWidth; - public Sudoku(List cells, List blocks, List constraints) { + public Sudoku(List cells, List blocks, List constraints) { this.cells = cells; this.blocks = blocks; this.constraints = constraints; @@ -41,11 +44,12 @@ public class Sudoku { * @return Coordinate, correspondante à l'index donné. */ public Coordinate toCoords(int index) { - return new Coordinate( index % getSize(), index / getSize() ); + return new Coordinate(index % getSize(), index / getSize()); } /** * Transforme des coordonées d'une Cell en index. + * * @param x int, abscisse. * @param y int, ordonnée. * @return int, index correspondant. @@ -56,7 +60,9 @@ public class Sudoku { /** * Vérifie que des coordonnées correspondent bien à une Cell dans le Sudoku. - * @return boolean, valant true si les coordonnées sont dans les bornes du Sudoku, false sinon. + * + * @return boolean, valant true si les coordonnées sont dans les bornes du + * Sudoku, false sinon. */ public boolean isValidCoords(int x, int y) { int index = toIndex(x, y); @@ -65,21 +71,25 @@ public class Sudoku { /** * Vérifie que l'index correspond bien à une Cell dans le Sudoku. - * @return boolean, valant true si l'index est dans les bornes du Sudoku, false sinon. + * + * @return boolean, valant true si l'index est dans les bornes du Sudoku, false + * sinon. */ public boolean isValidCoords(int index) { return index < getSize() * getSize(); } /** - * Teste si on peut placer la value dans la Cell aux coordonnées x, y d'après les contraintes du Sudoku. - * @param x int, abscisse de la Cell voulue. - * @param y int, ordonnée de la Cell voulue. + * Teste si on peut placer la value dans la Cell aux coordonnées x, y d'après + * les contraintes du Sudoku. + * + * @param x int, abscisse de la Cell voulue. + * @param y int, ordonnée de la Cell voulue. * @param value int, index du symbole qu'on veut placer. * @return boolean, true si on peut la placer et false sinon. */ public boolean canBePlaced(int x, int y, int value) { - for (IConstraint constraint : this.constraints) { + for (Constraint constraint : this.constraints) { if (!constraint.canBePlaced(this, x, y, value)) { return false; } @@ -89,8 +99,9 @@ public class Sudoku { /** * 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 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 */ @@ -105,6 +116,7 @@ public class Sudoku { /** * Vide la Cell dotn les coordonnées sont renseignées de son symbole. + * * @param x int, abscisse de la Cell voulue. * @param y int, coordonnée de la Cell voulue. */ @@ -132,14 +144,15 @@ public class Sudoku { /** * Place le symbole d'index value dans la Cell de coordonnées précisées. - * @param x int, abscisse de la Cell voulue. - * @param y int, coordonnée de la Cell voulue. + * + * @param x int, abscisse de la Cell voulue. + * @param y int, coordonnée de la Cell voulue. * @param value int, index du symbole à placer. * @return Cell, la Cell qui a été modifiée. */ public Cell setCellSymbol(int x, int y, int value) { assert (isValidCoords(x, y)); - for (IConstraint constraint : this.constraints) { + for (Constraint constraint : this.constraints) { if (!constraint.canBePlaced(this, x, y, value)) { return null; } @@ -151,6 +164,7 @@ public class Sudoku { /** * Place les symboles d'index contenus dans values dans les cases du Sudoku. + * * @param values List, liste des index des symboles à placer. * @return boolean, vaut true si les symboles ont été placés, false sinon. */ @@ -168,7 +182,9 @@ public class Sudoku { } /** - * Place les symboles d'index contenus dans values dans les cases du Sudoku et rend ces cases immuables. + * Place les symboles d'index contenus dans values dans les cases du Sudoku et + * rend ces cases immuables. + * * @param values List, liste des index des symboles à placer. * @return boolean, vaut true si les symboles ont été placés, false sinon. */ @@ -205,7 +221,7 @@ public class Sudoku { return this.cells.get(i); } - public List getConstraints() { + public List getConstraints() { return constraints; } @@ -223,6 +239,7 @@ public class Sudoku { /** * Vérifie si une Cell appartient au Sudoku. + * * @param cell Cell, cellule dont on veut vérifier l'appartenance au Sudoku. * @return boolean, vaut true si la Cell appartient au Sudoku. */ @@ -232,6 +249,7 @@ public class Sudoku { /** * 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. @@ -264,8 +282,8 @@ public class Sudoku { * Met à jour les symboles possibles des Cells du Sudoku. * */ - public void updateSymbolsPossibilities(){ - for (IConstraint constraint : constraints) { + public void updateSymbolsPossibilities() { + for (Constraint constraint : constraints) { List cells = this.getCells(); for (Cell cell : cells) { Coordinate coord = null; @@ -275,7 +293,8 @@ public class Sudoku { System.out.println("Cas jamais atteint."); } List newPossibleSymbols = cell.getPossibleSymbols(); - newPossibleSymbols.retainAll(constraint.getPossibleSymbols(this, coord.getX(), coord.getY())); + newPossibleSymbols + .retainAll(constraint.getConstraint().getPossibleSymbols(this, coord.getX(), coord.getY())); cell.setPossibleSymbols(newPossibleSymbols); } @@ -298,6 +317,7 @@ public class Sudoku { /** * Renvoie la 1re Cell vide du Sudoku. + * * @return Cell, une Cell vide, ou null s'il n'y en a pas. */ public Cell getFirstEmptyCell() { @@ -311,8 +331,10 @@ public class Sudoku { /** * 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. + * @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<>(); @@ -323,7 +345,7 @@ public class Sudoku { return result; } for (int i = 0; i < this.constraints.size(); i++) { - IConstraint constraint = this.constraints.get(i); + Constraint constraint = this.constraints.get(i); if (i == 0) { result.addAll(constraint.getPossibleSymbols(this, cellCoordinates.getX(), cellCoordinates.getY())); } else { @@ -335,7 +357,9 @@ public class Sudoku { /** * Vérifie que le Sudoku est cohérent avec ses contraintes. - * @return boolean, valant true si le Sudoku est cohérent avec ses contraintes, false sinon. + * + * @return boolean, valant true si le Sudoku est cohérent avec ses contraintes, + * false sinon. */ public boolean isValid() { for (Cell cell : this.cells) { @@ -343,7 +367,7 @@ public class Sudoku { if (cell.isEmpty()) { return false; } - for (IConstraint constraint : this.constraints) { + for (Constraint constraint : this.constraints) { Coordinate coords; try { int symbolPlaced = cell.getSymbolIndex(); @@ -389,4 +413,8 @@ public class Sudoku { this.blockWidth = blockWidth; } + public boolean hasConstraint(Constraint constraint) { + return this.constraints.contains(constraint); + } + } diff --git a/app/src/main/java/sudoku/structure/SudokuFactory.java b/app/src/main/java/sudoku/structure/SudokuFactory.java index 0013bbb..183785e 100644 --- a/app/src/main/java/sudoku/structure/SudokuFactory.java +++ b/app/src/main/java/sudoku/structure/SudokuFactory.java @@ -11,6 +11,8 @@ import java.util.Random; import sudoku.constraint.BlockConstraint; import sudoku.constraint.ColumnConstraint; +import sudoku.constraint.Constraint; +import sudoku.constraint.DiagonalConstraint; import sudoku.constraint.IConstraint; import sudoku.constraint.LineConstraint; import sudoku.io.SudokuSerializer; @@ -27,10 +29,12 @@ public class SudokuFactory { * Liste des contraintes par défaut d'un Multi- ou Sudoku. * Comprend les contraintes de blocs, de lignes, et de colonnes. */ - public static List DEFAULT_CONSTRAINTS = Arrays.asList(new BlockConstraint(), new LineConstraint(), new ColumnConstraint()); + public static List DEFAULT_CONSTRAINTS = Arrays.asList(Constraint.Block, Constraint.Column, + Constraint.Line); /** * Créée des Cells et les met dans une liste de taille size. + * * @param size int, nombre de Cells à initialiser. * @return List, liste des Cells initialisées. */ @@ -43,9 +47,11 @@ public class SudokuFactory { } /** - * Créée des Blocks de taille width par height à partir des cellules données, et les met dans une liste. - * @param cells List, liste des Cells à découper en Blocks. - * @param width int, largeur des Blocks à créer. + * Créée des Blocks de taille width par height à partir des cellules données, et + * les met dans une liste. + * + * @param cells List, liste des Cells à découper en Blocks. + * @param width int, largeur des Blocks à créer. * @param height int, hauteur des Blocks à créer. * @return List, liste des Blocks créés. */ @@ -73,29 +79,36 @@ public class SudokuFactory { } /** - * Créée un MultiDoku vide dont les Blocks sont de taille widthBlock par heightBlock. - * @param widthBlock int, largeur des Blocks. + * Créée un MultiDoku vide dont les Blocks sont de taille widthBlock par + * heightBlock. + * + * @param widthBlock int, largeur des Blocks. * @param heightBlock int, hauteur des Blocks. * @return MultiDoku, MultiDoku vide. */ - public static MultiDoku createBasicEmptyRectangleSudoku(int widthBlock, int heightBlock) { - Sudoku s = createRectangleSudoku(widthBlock, heightBlock); - return new MultiDoku(Arrays.asList(s)); + public static MultiDoku createBasicEmptyRectangleDoku(int widthBlock, int heightBlock, + List constraints) { + return new MultiDoku(Arrays.asList(createRectangleSudoku(widthBlock, heightBlock, constraints))); } /** * Créée un MultiDoku vide dont les Blocks sont carrés de longueur size. + * * @param size int, taille des Blocks. * @return MultiDoku, MultiDoku vide. */ - public static MultiDoku createBasicEmptySquareSudoku(int size) { - return createBasicEmptyRectangleSudoku(size, size); + public static MultiDoku createBasicEmptySquareDoku(int size, List constraints) { + return new MultiDoku(Arrays.asList(createSquareSudoku(size, constraints))); } /** - * Place des Cells immutables de valeurs fournies, aux Coordinate fournies dans le MultiDoku doku fourni. - * @param doku MultiDoku, MultiDoku à remplir. - * @param immutableCells Map, association de Coordinate coordonnées et Integer valeurs, correspondant aux cases à remplir. + * Place des Cells immutables de valeurs fournies, aux Coordinate fournies dans + * le MultiDoku doku fourni. + * + * @param doku MultiDoku, MultiDoku à remplir. + * @param immutableCells Map, association de Coordinate + * coordonnées et Integer valeurs, correspondant aux cases + * à remplir. */ public static void setImmutableCells(MultiDoku doku, Map immutableCells) { immutableCells.forEach((coordinate, symbol) -> { @@ -114,10 +127,12 @@ public class SudokuFactory { * * @param doku MultiDoku, MultiDoku dont on doit vider des Cells. * @param nbCellsToEmpty int, nombre de cases à retirer. - * @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. + * @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. */ - public static boolean newDokuFromFilledOne (MultiDoku doku, int nbCellsToEmpty) throws Exception { + public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception { if (nbCellsToEmpty > doku.getCells().size()) { throw new Exception(); @@ -149,34 +164,41 @@ public class SudokuFactory { } /** - * Créée un Sudoku vide dont les Blocks sont de taille widthBlock par heightBlock. - * @param widthBlock int, largeur des Blocks. + * Créée un Sudoku vide dont les Blocks sont de taille widthBlock par + * heightBlock. + * + * @param widthBlock int, largeur des Blocks. * @param heightBlock int, hauteur des Blocks. * @return Sudoku, Sudoku vide. */ - private static Sudoku createRectangleSudoku(int widthBlock, int heightBlock) { + private static Sudoku createRectangleSudoku(int widthBlock, int heightBlock, List constraints) { int symbolCount = widthBlock * heightBlock; List cases = initCells(symbolCount); List blocs = initRectangleBlocs(cases, widthBlock, heightBlock); - Sudoku s = new Sudoku(cases, blocs, DEFAULT_CONSTRAINTS); + Sudoku s = new Sudoku(cases, blocs, constraints); + for (Block block : s.getBlocks()) { + block.getSudokus().add(s); + } s.setBlockWidth(widthBlock); return s; } - /** + /** * Créée un Sudoku vide dont les Blocks sont carrés de longueur size. + * * @param size int, taille des Blocks. * @return Sudoku, Sudoku vide. */ - private static Sudoku createSquareSudoku(int size) { - return createRectangleSudoku(size, size); + private static Sudoku createSquareSudoku(int size, List constraints) { + return createRectangleSudoku(size, size, constraints); } /** * Connecte deux Sudokus selon la décalage offset fourni. + * * @param sudoku1 Sudoku, premier sudoku à connecter. * @param sudoku2 Sudoku, second sudoku à connecter. - * @param offset Coordinate, décalage entre les deux Sudokus. + * @param offset Coordinate, décalage entre les deux Sudokus. */ private static void linkSquareSudokus(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) { int blockWidth = sudoku1.getBlockWidth(); @@ -193,6 +215,7 @@ public class SudokuFactory { // on remplace le bloc sudoku2.getBlocks().set(block2Y * blockWidth + block2X, block1); + block1.getSudokus().add(sudoku2); // on remplace les cellules for (int i = 0; i < block1.getCells().size(); i++) { @@ -208,24 +231,27 @@ public class SudokuFactory { } /** - * Créée un MultiDoku de Blocks carrés de taille size composé de cinq Sudokus, dont un central qui partage chacun de ses Blockss d'angle avec un autre Sudoku. + * Créée un MultiDoku de Blocks carrés de taille size composé de cinq Sudokus, + * dont un central qui partage chacun de ses Blockss d'angle avec un autre + * Sudoku. + * * @param size int, largeur des Blocks unitraires des Sudokus à crééer. * @return MultiDoku, MultiDoku de forme X. */ - public static MultiDoku createBasicXShapedMultidoku(int size) { + public static MultiDoku createBasicXShapedMultidoku(int size, List constraints) { assert (size > 1); /* * 2 3 - * 1 + * 1 * 4 5 */ - Sudoku sudoku1 = createSquareSudoku(size); - Sudoku sudoku2 = createSquareSudoku(size); - Sudoku sudoku3 = createSquareSudoku(size); - Sudoku sudoku4 = createSquareSudoku(size); - Sudoku sudoku5 = createSquareSudoku(size); + Sudoku sudoku1 = createSquareSudoku(size, constraints); + Sudoku sudoku2 = createSquareSudoku(size, constraints); + Sudoku sudoku3 = createSquareSudoku(size, constraints); + Sudoku sudoku4 = createSquareSudoku(size, constraints); + Sudoku sudoku5 = createSquareSudoku(size, constraints); linkSquareSudokus(sudoku1, sudoku2, new Coordinate(1 - size, 1 - size)); linkSquareSudokus(sudoku1, sudoku3, new Coordinate(size - 1, 1 - size)); @@ -237,7 +263,7 @@ public class SudokuFactory { public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception { Solver.solveRandom(doku, random); - int nbCellsToEmpty = (int)(difficulty.getFactor()*doku.getNbCells()); + int nbCellsToEmpty = (int) (difficulty.getFactor() * doku.getNbCells()); boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty); if (!successfull) { throw new Exception("Canno't create this doku with this difficulty"); diff --git a/app/src/test/java/sudoku/SudokuSerializerTest.java b/app/src/test/java/sudoku/SudokuSerializerTest.java index 8871b32..d08fe19 100644 --- a/app/src/test/java/sudoku/SudokuSerializerTest.java +++ b/app/src/test/java/sudoku/SudokuSerializerTest.java @@ -13,14 +13,16 @@ import sudoku.structure.SudokuFactory; public class SudokuSerializerTest { void testSerializeWithSize(int blockWidth, int blockHeight) { - var sudoku = SudokuFactory.createBasicEmptyRectangleSudoku(blockWidth, blockHeight); + var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, + SudokuFactory.DEFAULT_CONSTRAINTS); JSONObject data = SudokuSerializer.serializeSudoku(sudoku); MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); - assert(data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); + assert (data.toString().equals(SudokuSerializer.serializeSudoku(multiDoku).toString())); } void testSaveWithSize(int blockWidth, int blockHeight) { - MultiDoku doku = SudokuFactory.createBasicEmptyRectangleSudoku(blockWidth, blockHeight); + MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, + SudokuFactory.DEFAULT_CONSTRAINTS); String savePath = SudokuSerializer.saveMultiDoku(doku); MultiDoku otherDoku = null; try { @@ -33,7 +35,7 @@ public class SudokuSerializerTest { e.printStackTrace(); assert false; } - assert(doku.equals(otherDoku)); + assert (doku.equals(otherDoku)); } @Test diff --git a/app/src/test/java/sudoku/solver/SolverTest.java b/app/src/test/java/sudoku/solver/SolverTest.java index 85a38c4..88ea0c3 100644 --- a/app/src/test/java/sudoku/solver/SolverTest.java +++ b/app/src/test/java/sudoku/solver/SolverTest.java @@ -16,78 +16,72 @@ class SolverTest { void solveTest() { Random rand = new Random(); - MultiDoku dokuToTest = SudokuFactory.createBasicEmptySquareSudoku(3); - MultiDoku dokuResult = SudokuFactory.createBasicEmptySquareSudoku(3); + 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); 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)); + 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)); 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); - System.out.println("\n****************************Doku Control\n"); SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); - - assert(dokuResult.isValid()); + assert (dokuResult.isValid()); Solver.solveRandom(dokuToTest, rand); - System.out.println("\n****************************\nDoku solved"); SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); + assert (dokuToTest.isValid()); - assert(dokuToTest.isValid()); + assert (dokuToTest.equals(dokuResult)); - assert(dokuToTest.equals(dokuResult)); - - MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareSudoku(3); + MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); Sudoku sudokuToTest2 = dokuToTest2.getSubGrid(0); - 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); + 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); boolean isSolved = Solver.solveRandom(dokuToTest2, rand); - assert(!isSolved); + assert (!isSolved); - MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareSudoku(3); + MultiDoku dokuToTest3 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); Solver.solveRandom(dokuToTest3, rand);