diff --git a/app/src/main/java/sudoku/Block.java b/app/src/main/java/sudoku/Block.java index df287e4..d7ededc 100644 --- a/app/src/main/java/sudoku/Block.java +++ b/app/src/main/java/sudoku/Block.java @@ -31,4 +31,12 @@ public class Block { return false; } + public boolean containsCell(Cell cell) { + for (Cell cellTmp : this.cells) { + if (cellTmp.equals(cell)) { + return true; + } + } + return false; + } } diff --git a/app/src/main/java/sudoku/Cell.java b/app/src/main/java/sudoku/Cell.java index 6cb1847..d66013e 100644 --- a/app/src/main/java/sudoku/Cell.java +++ b/app/src/main/java/sudoku/Cell.java @@ -1,98 +1,74 @@ package sudoku; -import org.checkerframework.checker.units.qual.C; - import java.util.ArrayList; import java.util.List; -public abstract class Cell { +public class Cell { - protected static int NOSYMBOL = -1; - protected int symbolIndex; - protected Block block = null; - //protected List blockContainers = null; - //protected List sudokuContainers = null; + public static int NOSYMBOL = -1; + + private Block blockContainer; + private int symbolIndex = Cell.NOSYMBOL; + private final List possibleSymbols; + private boolean isMutable = true; + + public Cell() { + this.possibleSymbols = new ArrayList<>(); + } public Cell(int symbolIndex) { this.symbolIndex = symbolIndex; + this.possibleSymbols = new ArrayList<>(); } - /** - * @brief Default constructor, empty square - */ - public Cell() { - this(NOSYMBOL); - } - - /* - - public Cell(Cell cell) { - this.symbolIndex = cell.symbolIndex; - this.sudokuContainers = cell.sudokuContainers; - this.blockContainers = cell.blockContainers; - } - - public Cell(List sudokuContainers, List blockContainers) { - super(); - this.sudokuContainers = new ArrayList<>(sudokuContainers); - this.blockContainers = new ArrayList<>(blockContainers); - } - - */ - public int getSymbolIndex() { - return symbolIndex; + return this.symbolIndex; + } + + public void setSymbolIndex(int symbolIndex) { + this.symbolIndex = symbolIndex; + } + + public void setPossibleSymbols(List possibleSymbols) { + this.possibleSymbols.clear(); + this.possibleSymbols.addAll(possibleSymbols); + } + + public void setImmutable() { + this.isMutable = false; } public Block getBlock() { - return this.block; + return this.blockContainer; } - /* - public List getBlockContainers() { - return this.blockContainers; - } - - public void setBlockContainers(List block) { - this.blockContainers = block; - } - - public List getSudokuContainers() { - return this.sudokuContainers; - } -*/ - // only SudokuFactory and SudokuSerializer should access this public void setBlock(Block block) { - this.block = block; + this.blockContainer = block; } - /* - public void setSudokuContainers(List sudoku) { - this.sudokuContainers = sudoku; + /** + * Remove the current symbolIndex and returns it + * @return integer symbolIndex cleared + */ + public int clearCurrentSymbol() { + int i = this.symbolIndex; + setSymbolIndex(NOSYMBOL); + return i; } - */ public boolean isEmpty() { return this.symbolIndex == Cell.NOSYMBOL; } - public boolean equalsValue(Cell otherCell) { - return otherCell.getSymbolIndex() == this.getSymbolIndex(); + public void removeSymbolFromPossibilities(int indexSymbol) { + possibleSymbols.remove(indexSymbol); } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("| "); - if (this.symbolIndex != NOSYMBOL){ - sb.append(this.symbolIndex); - } - else { - sb.append(" "); - } - - sb.append(" |"); - return sb.toString(); + public List getPossibleSymbols() { + return this.possibleSymbols; } + public boolean isMutable() { + return this.isMutable; + } } diff --git a/app/src/main/java/sudoku/Coordinate.java b/app/src/main/java/sudoku/Coordinate.java index 668dd85..c00b8ad 100644 --- a/app/src/main/java/sudoku/Coordinate.java +++ b/app/src/main/java/sudoku/Coordinate.java @@ -5,9 +5,9 @@ public class Coordinate { private int x; private int y; - public Coordinate(int row, int col) { - this.x = row; - this.y = col; + public Coordinate(int x, int y) { + this.x = x; + this.y = y; } public int getX() { diff --git a/app/src/main/java/sudoku/MultiDoku.java b/app/src/main/java/sudoku/MultiDoku.java index 59a170d..1c65754 100644 --- a/app/src/main/java/sudoku/MultiDoku.java +++ b/app/src/main/java/sudoku/MultiDoku.java @@ -1,7 +1,5 @@ package sudoku; -import sudoku.constraint.IConstraint; - import java.util.ArrayList; import java.util.List; @@ -26,12 +24,12 @@ public class MultiDoku { return subGrids.get(i); } - public List getMutableCells(){ - List mutableCells = new ArrayList<>(); + public List getCells(){ + List cells = new ArrayList<>(); for (Sudoku sudoku : subGrids){ - mutableCells.addAll(sudoku.getMutableCells()); + cells.addAll(sudoku.getCells()); } - return mutableCells; + return cells; } public void updateSymbolsPossibilities() throws Exception { @@ -51,17 +49,17 @@ public class MultiDoku { return sb.toString(); } - public MutableCell getFirstEmptyMutableCell() { + public Cell getFirstEmptyCell() { for (Sudoku sudoku : this.subGrids) { - MutableCell cellTmp = sudoku.getFirstEmptyMutableCell(); - if (cellTmp.isEmpty()) { + Cell cellTmp = sudoku.getFirstEmptyCell(); + if (cellTmp != null && cellTmp.isEmpty()) { return cellTmp; } } return null; } - public List getPossibleSymbolsOfCell(MutableCell cellToFill) { + public List getPossibleSymbolsOfCell(Cell cellToFill) { for (Sudoku sudoku : this.subGrids) { if (sudoku.contains(cellToFill)) { return sudoku.getPossibleSymbolsOfCell(cellToFill); @@ -69,4 +67,36 @@ public class MultiDoku { } return new ArrayList<>(); } + + public List getSubGrids() { + return this.subGrids; + } + + public boolean isValid() { + boolean result = true; + for (Sudoku sudoku : this.subGrids) { + result = sudoku.isValid() && 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))) { + return false; + } + } + + return true; + } } + diff --git a/app/src/main/java/sudoku/MutableCell.java b/app/src/main/java/sudoku/MutableCell.java deleted file mode 100644 index 9673b8a..0000000 --- a/app/src/main/java/sudoku/MutableCell.java +++ /dev/null @@ -1,47 +0,0 @@ -package sudoku; - -import java.util.ArrayList; -import java.util.List; - -public class MutableCell extends Cell{ - - private final List possibleSymbols; - - public MutableCell() { - super(); - this.possibleSymbols = new ArrayList<>(); - } - - public MutableCell(int symbolIndex) { - super(symbolIndex); - this.possibleSymbols = new ArrayList<>(); - } - - public void setSymbolIndex(int symbolIndex) { - this.symbolIndex = symbolIndex; - } - - public void setPossibleSymbols(List possibleSymbols) { - this.possibleSymbols.clear(); - this.possibleSymbols.addAll(possibleSymbols); - } - - /** - * Remove the current symbolIndex and returns it - * @return integer symbolIndex cleared - */ - public int clearCurrentSymbol() { - int i = this.symbolIndex; - setSymbolIndex(NOSYMBOL); - return i; - } - - public void removeSymbolFromPossibilities(int indexSymbol) { - possibleSymbols.remove(indexSymbol); - } - - public List getPossibleSymbols() { - return this.possibleSymbols; - } - -} diff --git a/app/src/main/java/sudoku/Sudoku.java b/app/src/main/java/sudoku/Sudoku.java index 0ee1776..ccd4f75 100644 --- a/app/src/main/java/sudoku/Sudoku.java +++ b/app/src/main/java/sudoku/Sudoku.java @@ -14,6 +14,7 @@ public class Sudoku { private final List blocks; private final List cells; private final List constraints; + private boolean isMutable; public Sudoku(List cells, List blocks, List constraints) { this.cells = cells; @@ -32,24 +33,36 @@ public class Sudoku { /** * Try to place a cell at the given coordinate * - * @return false if it can't be done + * @return Cell created or null if it can't be done */ - public boolean setCellSymbol(int x, int y, int value) { + public Cell setCellSymbol(int x, int y, int value) { assert (isValidCoords(x, y)); + /* for (IConstraint constraint : this.constraints) { if (!constraint.canBePlaced(this, x, y, value)) { - return false; + return null; } } + */ Cell cell = getCell(x, y); - if (cell instanceof MutableCell mCell) { - mCell.setSymbolIndex(value); - return true; - } - return false; + cell.setSymbolIndex(value); + return cell; } public boolean setCellsSymbol(List values) { + if (values.size() > this.cells.size()) { + return false; + } + for (int i = 0; i < values.size(); i++) { + int x = i % this.blocks.size(); + int y = (i-x) / this.blocks.size(); + int value = values.get(i); + this.setCellSymbol(x, y, value); + } + return true; + } + + public boolean setImmutableCellsSymbol(List values) { if (values.size() > this.cells.size()) { return false; } @@ -57,18 +70,27 @@ public class Sudoku { int x = i%this.blocks.size(); int y = (i-x)/this.blocks.size(); int value = values.get(i); - if (!this.setCellSymbol(x, y, value)) { - return false; + if (value != Cell.NOSYMBOL) { + Cell cellPlaced = this.setCellSymbol(x, y, value); + if (cellPlaced == null) { + continue; + } + cellPlaced.setImmutable(); } + } return true; - } + } public Cell getCell(int x, int y) { int index = y * getSize() + x; assert (isValidCoords(x, y)); - return this.cells.get(index); + try { + return this.cells.get(index); + } catch (IndexOutOfBoundsException e) { + return null; + } } public Cell getCell(int i) { @@ -91,16 +113,6 @@ public class Sudoku { return this.blocks; } - public List getMutableCells() { - List mutableCells = new ArrayList<>(); - for (Cell cell : this.cells) { - if (cell instanceof MutableCell) { - mutableCells.add((MutableCell) cell); - } - } - return mutableCells; - } - public boolean contains(Cell cell) { return this.cells.contains(cell); } @@ -129,8 +141,8 @@ public class Sudoku { public void updateSymbolsPossibilities() throws Exception { for (IConstraint constraint : constraints) { - List mutableCells = this.getMutableCells(); - for (MutableCell cell : mutableCells) { + List cells = this.getCells(); + for (Cell cell : cells) { Coordinate coord = null; try { coord = this.getCoordinateCell(cell); @@ -163,8 +175,8 @@ public class Sudoku { return sb.toString(); } - public MutableCell getFirstEmptyMutableCell() { - for (MutableCell cell : this.getMutableCells()) { + public Cell getFirstEmptyCell() { + for (Cell cell : this.cells) { if (cell.isEmpty()) { return cell; } @@ -172,7 +184,7 @@ public class Sudoku { return null; } - public List getPossibleSymbolsOfCell(MutableCell cellToFill) { + public List getPossibleSymbolsOfCell(Cell cellToFill) { List result = new ArrayList<>(); Coordinate cellCoordinates; try { @@ -189,4 +201,51 @@ public class Sudoku { } return result; } + + public boolean isValid() { + for (Cell cell : this.cells) { + if (cell.isMutable()) { + if (cell.isEmpty()) { + return false; + } + for (IConstraint constraint : this.constraints) { + Coordinate coords; + try { + int symbolPlaced = cell.getSymbolIndex(); + coords = this.getCoordinateCell(cell); + + cell.setSymbolIndex(Cell.NOSYMBOL); + List possibleSymbols = constraint.getPossibleSymbols(this, coords.getX(), coords.getY()); + cell.setSymbolIndex(symbolPlaced); + if (possibleSymbols.size() != 1 || possibleSymbols.get(0) != symbolPlaced) { + return false; + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + } + return true; + } + + @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()) { + return false; + } + } + + return true; + } } diff --git a/app/src/main/java/sudoku/SudokuFactory.java b/app/src/main/java/sudoku/SudokuFactory.java index 61c4887..87f5c30 100644 --- a/app/src/main/java/sudoku/SudokuFactory.java +++ b/app/src/main/java/sudoku/SudokuFactory.java @@ -7,13 +7,14 @@ import sudoku.constraint.LineConstraint; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class SudokuFactory { private static List initCells(int size) { List cells = new ArrayList<>(size * size); for (int i = 0; i < size * size; i++) { - cells.add(new MutableCell()); + cells.add(new Cell()); } return cells; } @@ -59,4 +60,15 @@ public class SudokuFactory { return createBasicEmptyRectangleSudoku(size, size); } + public static void setIMMutableCells(MultiDoku doku, Map immutableCells) { + immutableCells.forEach((coordinate, symbol) -> { + for (Sudoku sudoku : doku.getSubGrids()) { + Cell cell = sudoku.getCell(coordinate.getX(), coordinate.getY()); + if (cell != null) { + cell.setSymbolIndex(symbol); + cell.setImmutable(); + } + } + }); + } } diff --git a/app/src/main/java/sudoku/constraint/BlockConstraint.java b/app/src/main/java/sudoku/constraint/BlockConstraint.java index 943fc97..caa1667 100644 --- a/app/src/main/java/sudoku/constraint/BlockConstraint.java +++ b/app/src/main/java/sudoku/constraint/BlockConstraint.java @@ -1,14 +1,15 @@ package sudoku.constraint; import sudoku.Block; +import sudoku.Cell; import sudoku.Sudoku; public class BlockConstraint implements IConstraint{ @Override public boolean canBePlaced(final Sudoku s, int x, int y, int newSymbolIndex) { - Block bloc = s.getCell(x, y).getBlock(); - return !bloc.containsSymbol(newSymbolIndex); + Block block = s.getCell(x, y).getBlock(); + return !block.containsSymbol(newSymbolIndex); } } diff --git a/app/src/main/java/sudoku/constraint/ColumnConstraint.java b/app/src/main/java/sudoku/constraint/ColumnConstraint.java index 881faab..7045b00 100644 --- a/app/src/main/java/sudoku/constraint/ColumnConstraint.java +++ b/app/src/main/java/sudoku/constraint/ColumnConstraint.java @@ -7,7 +7,7 @@ public class ColumnConstraint implements IConstraint { @Override public boolean canBePlaced(final Sudoku s, int x, int y, int newSymbolIndex) { for (int i = 0; i < s.getSize(); i++) { - if (s.getCell(x, newSymbolIndex).getSymbolIndex() == newSymbolIndex) + if (s.getCell(x, i).getSymbolIndex() == newSymbolIndex) return false; } return true; diff --git a/app/src/main/java/sudoku/constraint/LineConstraint.java b/app/src/main/java/sudoku/constraint/LineConstraint.java index 2f9a81e..b568928 100644 --- a/app/src/main/java/sudoku/constraint/LineConstraint.java +++ b/app/src/main/java/sudoku/constraint/LineConstraint.java @@ -7,7 +7,7 @@ public class LineConstraint implements IConstraint { @Override public boolean canBePlaced(final Sudoku s, int x, int y, int newSymbolIndex) { for (int i = 0; i < s.getSize(); i++) { - if (s.getCell(newSymbolIndex, y).getSymbolIndex() == newSymbolIndex) + if (s.getCell(i, y).getSymbolIndex() == newSymbolIndex) return false; } return true; diff --git a/app/src/main/java/sudoku/io/SudokuSerializer.java b/app/src/main/java/sudoku/io/SudokuSerializer.java index b0ab311..0fdd96a 100644 --- a/app/src/main/java/sudoku/io/SudokuSerializer.java +++ b/app/src/main/java/sudoku/io/SudokuSerializer.java @@ -10,17 +10,16 @@ import sudoku.Block; import sudoku.Cell; import sudoku.ImmutableCell; import sudoku.MultiDoku; -import sudoku.MutableCell; import sudoku.Sudoku; public class SudokuSerializer { public static String 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()); @@ -55,17 +54,16 @@ public class SudokuSerializer { // init blocks - for (int i = 0; i < blockIds.size(); i++) { - JSONObject blockJsonObject = new JSONObject(); - JSONArray cellsJsonArray = new JSONArray(); - Block block = blockIds.get(i); - for (Cell cell : block.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); + cellsJsonArray.put(cellID); + } + blockJsonObject.put("cellIDs", cellsJsonArray); + jsonBlocks.put(blockJsonObject); + } for (int i = 0; i < multidoku.getNbSubGrids(); i++) { // serialise sub grid @@ -115,7 +113,7 @@ public class SudokuSerializer { if (entry.has("immutable")) { cells.add(new ImmutableCell(symboleIndex)); } else { - cells.add(new MutableCell(symboleIndex)); + cells.add(new Cell(symboleIndex)); } } diff --git a/app/src/main/java/sudoku/solver/Solver.java b/app/src/main/java/sudoku/solver/Solver.java index 6c933ad..c541309 100644 --- a/app/src/main/java/sudoku/solver/Solver.java +++ b/app/src/main/java/sudoku/solver/Solver.java @@ -1,13 +1,10 @@ package sudoku.solver; import sudoku.MultiDoku; -import sudoku.MutableCell; -import sudoku.constraint.IConstraint; +import sudoku.Cell; +import sudoku.io.SudokuPrinter; -import java.util.ArrayList; import java.util.List; -import java.util.Random; -import java.util.Stack; public class Solver { @@ -18,11 +15,15 @@ public class Solver { * @return boolean, true s'il est résolut ou false s'il ne l'est pas. */ public static boolean solve(MultiDoku doku) { - MutableCell cellToFill = doku.getFirstEmptyMutableCell(); - if (cellToFill == null) { + if (doku.isValid()) { return true; } + Cell cellToFill = doku.getFirstEmptyCell(); + if (cellToFill == null) { + return false; + } + List possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); if (possibleSymbols.isEmpty()) { return false; @@ -30,7 +31,11 @@ public class Solver { for (int symbol : possibleSymbols) { cellToFill.setSymbolIndex(symbol); - return Solver.solve(doku); + if (Solver.solve(doku)) { + return true; + } else { + cellToFill.setSymbolIndex(Cell.NOSYMBOL); + } } return false; } diff --git a/app/src/test/java/sudoku/solver/SolverTest.java b/app/src/test/java/sudoku/solver/SolverTest.java new file mode 100644 index 0000000..cd974a4 --- /dev/null +++ b/app/src/test/java/sudoku/solver/SolverTest.java @@ -0,0 +1,67 @@ +package sudoku.solver; + +import org.junit.jupiter.api.Test; +import sudoku.*; +import sudoku.io.SudokuPrinter; + +import java.util.List; + +class SolverTest { + + @Test + void solveTest() { + + MultiDoku dokuToTest = SudokuFactory.createBasicEmptySquareSudoku(3); + MultiDoku dokuResult = SudokuFactory.createBasicEmptySquareSudoku(3); + + 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)); + + + 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); + + sudokuResult.setCellsSymbol(correctCells); + + + System.out.println("\n****************************Doku Control\n"); + SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); + + + assert(dokuResult.isValid()); + + boolean isSolvable = Solver.solve(dokuToTest); + + + System.out.println("\n****************************\nDoku solve"); + SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); + + + assert(dokuToTest.isValid()); + + assert(dokuToTest.equals(dokuResult)); + } +} \ No newline at end of file