2 Commits

Author SHA1 Message Date
67da77af2e feat: add states
Some checks failed
Linux arm64 / Build (push) Has been cancelled
2025-01-30 11:54:23 +01:00
c4becf2d55 fix: serialize 2025-01-30 11:54:21 +01:00
12 changed files with 254 additions and 415 deletions

View File

@@ -27,10 +27,6 @@ public class RenderableMultidoku {
this.doku = doku; this.doku = doku;
} }
public boolean isResolved() {
return this.doku.isSolved();
}
public int getWidth() { public int getWidth() {
return width; return width;
} }
@@ -51,21 +47,7 @@ public class RenderableMultidoku {
return cells.get(index); 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) { private static record PositionConstraint(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) {
} }

View File

@@ -76,8 +76,8 @@ public class SudokuRenderer {
} }
} else { } else {
if (ImGui.button(Integer.toString(i + 1), cellSize)) { if (ImGui.button(Integer.toString(i + 1), cellSize)) {
this.doku.setCellValue(currentCell, i); currentCell.trySetValue(i);
if (this.doku.isResolved()) if (this.doku.getDoku().isSolved())
this.onResolve.emit(); this.onResolve.emit();
ImGui.closeCurrentPopup(); ImGui.closeCurrentPopup();
} }

View File

@@ -1,7 +1,8 @@
package sudoku.io; package sudoku.io;
import java.io.*; import java.io.File;
import java.nio.charset.StandardCharsets; import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
@@ -15,7 +16,6 @@ import sudoku.structure.Block;
import sudoku.structure.Cell; import sudoku.structure.Cell;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku; import sudoku.structure.Sudoku;
import sudoku.structure.SudokuFactory;
public class SudokuSerializer { public class SudokuSerializer {
@@ -33,9 +33,10 @@ public class SudokuSerializer {
// init cells // init cells
for (Cell cell : sudoku.getCells()) { for (Cell cell : sudoku.getCells()) {
if (!cellIds.contains(cell)) { if (cellIds.contains(cell))
continue;
cellIds.add(cell); cellIds.add(cell);
}
Block block = cell.getBlock(); Block block = cell.getBlock();
if (!blockIds.contains(block)) { if (!blockIds.contains(block)) {
@@ -43,6 +44,7 @@ public class SudokuSerializer {
} }
int blockID = blockIds.indexOf(block); int blockID = blockIds.indexOf(block);
assert(blockID >= 0);
int symbolIndex = cell.getSymbolIndex(); int symbolIndex = cell.getSymbolIndex();
JSONObject cellJsonObject = new JSONObject(); JSONObject cellJsonObject = new JSONObject();
@@ -63,6 +65,7 @@ public class SudokuSerializer {
JSONArray cellsJsonArray = new JSONArray(); JSONArray cellsJsonArray = new JSONArray();
for (Cell cell : blockId.getCells()) { for (Cell cell : blockId.getCells()) {
int cellID = cellIds.indexOf(cell); int cellID = cellIds.indexOf(cell);
assert (cellID >= 0);
cellsJsonArray.put(cellID); cellsJsonArray.put(cellID);
} }
blockJsonObject.put("cellIDs", cellsJsonArray); blockJsonObject.put("cellIDs", cellsJsonArray);
@@ -82,6 +85,7 @@ public class SudokuSerializer {
for (Cell cell : sudoku.getCells()) { for (Cell cell : sudoku.getCells()) {
int cellID = cellIds.indexOf(cell); int cellID = cellIds.indexOf(cell);
assert (cellID >= 0);
cellsJsonArray.put(cellID); cellsJsonArray.put(cellID);
} }
@@ -89,6 +93,7 @@ public class SudokuSerializer {
for (Block block : sudoku.getBlocks()) { for (Block block : sudoku.getBlocks()) {
int blockID = blockIds.indexOf(block); int blockID = blockIds.indexOf(block);
assert (blockID >= 0);
blocksJsonArray.put(blockID); blocksJsonArray.put(blockID);
} }
@@ -114,6 +119,7 @@ public class SudokuSerializer {
/** /**
* Save a serialized MultiDoku in a JSON file. * Save a serialized MultiDoku in a JSON file.
*
* @param doku MultiDoku, MultiDoku to save. * @param doku MultiDoku, MultiDoku to save.
* @return String, the path of the 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. * Get a MultiDoku from a pre-existing json save file.
*
* @param numberSave int, number of the save file to open. * @param numberSave int, number of the save file to open.
* @return MultiDoku, MultoDoku contained in the file. * @return MultiDoku, MultoDoku contained in the file.
* @throws Exception when the given save file does not exist. * @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."); throw new Exception("This save does not exist.");
} else { } else {
fileContent = new String(Files.readAllBytes(Paths.get("save/" + fileName))); 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); return deserializeSudoku(fileContent);
} }
} }

View File

@@ -1,16 +1,16 @@
package sudoku.solver; 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.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CancellationException; import java.util.concurrent.CancellationException;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import sudoku.io.SudokuPrinter;
import sudoku.structure.Cell;
import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku;
public class Solver { public class Solver {
/** /**
@@ -46,7 +46,7 @@ public class Solver {
return false; return false;
} }
List<Integer> possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
while (!possibleSymbols.isEmpty()) { while (!possibleSymbols.isEmpty()) {
int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size()); int nextPossibleSymbolIndex = rand.nextInt(possibleSymbols.size());
@@ -68,31 +68,26 @@ public class Solver {
* @param oldDoku MultiDoku, MultiDoku dont on veut le nombre de solutions. * @param oldDoku MultiDoku, MultiDoku dont on veut le nombre de solutions.
* @return int, nombre de solutions possibles. * @return int, nombre de solutions possibles.
*/ */
public static int countSolution(MultiDoku oldDoku) { public static int countSolution(MultiDoku doku) {
int result = 0; int result = 0;
MultiDoku doku = oldDoku.clone();
if (doku.isSolved()) { if (doku.isSolved()) {
return 1; return 1;
} }
Cell cellToFill = doku.getFirstEmptyCell(); Cell cellToFill = doku.getFirstEmptyCell();
if (cellToFill == null) { assert(cellToFill != null);
System.out.println("AAAAAAAAAAAAAA");
return 0;
}
List<Integer> possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
for (int symbol : possibleSymbols) { for (int symbol : possibleSymbols) {
doku.getStateManager().pushState();
cellToFill.setSymbolIndex(symbol); cellToFill.setSymbolIndex(symbol);
System.out.println("symbol : "+symbol); if (Solver.solve(doku)) {
System.out.println("doku.isSolved() || Solver.solve(doku) ? "+ (doku.isSolved() || Solver.solve(doku)));
if (doku.isSolved() || Solver.solve(doku)) {
result++; result++;
} }
cellToFill.setSymbolIndex(Cell.NOSYMBOL); doku.getStateManager().popState();
} }
return result; return result;
@@ -116,7 +111,7 @@ public class Solver {
return false; return false;
} }
List<Integer> possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.isEmpty()) { if (possibleSymbols.isEmpty()) {
return false; return false;
} }
@@ -156,7 +151,7 @@ public class Solver {
boolean blocked = true; boolean blocked = true;
for (Cell cellToFill : cellsToFill) { for (Cell cellToFill : cellsToFill) {
List<Integer> possibleSymbols = doku.getPossibleSymbolsOfCell(cellToFill); List<Integer> possibleSymbols = cellToFill.getPossibleSymbols();
if (possibleSymbols.size() != 1) { if (possibleSymbols.size() != 1) {
continue; continue;
} }

View File

@@ -24,9 +24,8 @@ public class StupidSolver {
if (!sudoku.getCell(index).isMutable()) if (!sudoku.getCell(index).isMutable())
return solve(sudoku, index + 1); return solve(sudoku, index + 1);
Coordinate coords = sudoku.toCoords(index);
for (int symbol = 0; symbol < sudoku.getSize(); symbol++) { 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 // on tente de placer sur la case suivante
if (solve(sudoku, index + 1)) { if (solve(sudoku, index + 1)) {
return true; return true;
@@ -34,7 +33,7 @@ public class StupidSolver {
} }
} }
// on a tout essayé et rien n'a fonctionné // on a tout essayé et rien n'a fonctionné
sudoku.clearCell(coords.getX(), coords.getY()); sudoku.getCell(index).empty();
return false; return false;
} }

View File

@@ -24,28 +24,21 @@ public class Cell {
* Il est initialisé à Cell.NOSYMBOL. * Il est initialisé à Cell.NOSYMBOL.
*/ */
private int symbolIndex = 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<Integer> possibleSymbols;
/** /**
* Si cette Cell peut être modififié ou non. * Si cette Cell peut être modififié ou non.
*/ */
private boolean isMutable = true; private boolean isMutable = true;
public Cell() { public Cell() {
this.possibleSymbols = new ArrayList<>(); this(Cell.NOSYMBOL);
} }
public Cell(int symbolIndex) { public Cell(int symbolIndex) {
this.symbolIndex = symbolIndex; this.symbolIndex = symbolIndex;
this.possibleSymbols = new ArrayList<>();
} }
public Cell(int symbolIndex, boolean isMutable) { public Cell(int symbolIndex, boolean isMutable) {
this.symbolIndex = symbolIndex; this.symbolIndex = symbolIndex;
this.possibleSymbols = new ArrayList<>();
this.isMutable = isMutable; this.isMutable = isMutable;
} }
@@ -57,11 +50,6 @@ public class Cell {
this.symbolIndex = symbolIndex; this.symbolIndex = symbolIndex;
} }
public void setPossibleSymbols(List<Integer> possibleSymbols) {
this.possibleSymbols.clear();
this.possibleSymbols.addAll(possibleSymbols);
}
/** /**
* Rend la Cell immuable. * Rend la Cell immuable.
*/ */
@@ -95,14 +83,6 @@ public class Cell {
return this.symbolIndex == Cell.NOSYMBOL; return this.symbolIndex == Cell.NOSYMBOL;
} }
public void removeSymbolFromPossibilities(int indexSymbol) {
possibleSymbols.remove(indexSymbol);
}
public List<Integer> getPossibleSymbols() {
return this.possibleSymbols;
}
/** /**
* Renvoie si la Cell est modifiable * Renvoie si la Cell est modifiable
* @return boolean, true si elle est modifiable ou false sinon. * @return boolean, true si elle est modifiable ou false sinon.
@@ -120,4 +100,35 @@ public class Cell {
this.symbolIndex = Cell.NOSYMBOL; this.symbolIndex = Cell.NOSYMBOL;
return oldSymbol; 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<Integer> getPossibleSymbols() {
List<Integer> 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;
}
} }

View File

@@ -19,90 +19,60 @@ public class MultiDoku {
*/ */
private final List<Sudoku> subGrids; private final List<Sudoku> subGrids;
private final StateManager stateManager;
public MultiDoku(List<Sudoku> subGrids) { public MultiDoku(List<Sudoku> subGrids) {
this.subGrids = subGrids; this.subGrids = subGrids;
} this.stateManager = new StateManager(this);
public MultiDoku clone() {
//TODO: ahhhhhhhhhhhhhhhhhhhhhhh
return SudokuSerializer.deserializeSudoku(SudokuSerializer.serializeSudoku(this));
} }
/** /**
* Renvoie le nombre de sudoku contenu dans ce MultiDoku. * Renvoie le nombre de sudoku contenu dans ce MultiDoku.
*
* @return int * @return int
*/ */
public int getNbSubGrids(){ public int getNbSubGrids() {
return subGrids.size(); return subGrids.size();
} }
/** /**
* Renvoie la ie sudoku contenue dans ce MultiDoku. * Renvoie la ie sudoku contenue dans ce MultiDoku.
*
* @param i int, indice du sudoku à renvoyer. * @param i int, indice du sudoku à renvoyer.
* @return Sudoku, ie Sudoku * @return Sudoku, ie Sudoku
*/ */
public Sudoku getSubGrid(int i){ public Sudoku getSubGrid(int i) {
return subGrids.get(i); return subGrids.get(i);
} }
/** /**
* Renvoie la liste des Cells contenue dans ce MultiDoku, * Renvoie la liste des Cells contenue dans ce MultiDoku,
* soit les Cells contenues de chaques sous-Sudoku. * soit les Cells contenues de chaques sous-Sudoku.
*
* @return List<Cell> * @return List<Cell>
*/ */
public List<Cell> getCells(){ public List<Cell> getCells() {
Set<Cell> cellsSet = new HashSet<>(); Set<Cell> cellsSet = new HashSet<>();
for (Sudoku sudoku : subGrids){ for (Sudoku sudoku : subGrids) {
cellsSet.addAll(sudoku.getCells()); cellsSet.addAll(sudoku.getCells());
} }
return new ArrayList<>(cellsSet); 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();
}
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Multidoku {"); sb.append("Multidoku {");
for (Sudoku sudoku : subGrids){ for (Sudoku sudoku : subGrids) {
sb.append("\n\t").append(sudoku.toString()); sb.append("\n\t").append(sudoku.toString());
} }
sb.append("\n}"); sb.append("\n}");
return sb.toString(); return sb.toString();
} }
/**
* Renvoie les symboles possibles d'une Cell donnée.
* @param cellToFill Cell.
* @return List<Integer>, liste des symboles possible.
*/
public List<Integer> getPossibleSymbolsOfCell(Cell cellToFill) {
List<Integer> 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 * Renvoie les sous-Sudoku
*
* @return List<Sudoku> * @return List<Sudoku>
*/ */
public List<Sudoku> getSubGrids() { public List<Sudoku> getSubGrids() {
@@ -111,43 +81,26 @@ public class MultiDoku {
/** /**
* Check si le MultiDoku est valide, en fonction de ses sous-Sudokus. * Check si le MultiDoku est valide, en fonction de ses sous-Sudokus.
*
* @return boolean, true s'il est valide et false sinon. * @return boolean, true s'il est valide et false sinon.
*/ */
public boolean isSolved() { public boolean isSolved() {
boolean result = true;
for (Sudoku sudoku : this.subGrids) { for (Sudoku sudoku : this.subGrids) {
result = sudoku.isSolved() && result; if (!sudoku.isSolved())
}
return result;
}
@Override
public boolean equals(Object object) {
if (!(object instanceof MultiDoku)) {
return false; 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; return true;
} }
/** /**
* Renvoie la 1re Cell vide des sous-Sudoku. * Renvoie la 1re Cell vide des sous-Sudoku.
*
* @return Cell, une Cell vide, ou null s'il n'y en a pas. * @return Cell, une Cell vide, ou null s'il n'y en a pas.
*/ */
public Cell getFirstEmptyCell() { public Cell getFirstEmptyCell() {
for (Sudoku sudoku : this.subGrids) { for (Sudoku sudoku : this.subGrids) {
Cell cellTmp = sudoku.getFirstEmptyCell(); Cell cellTmp = sudoku.getFirstEmptyCell();
if (cellTmp != null && cellTmp.isEmpty()) { if (cellTmp != null) {
return cellTmp; return cellTmp;
} }
} }
@@ -156,11 +109,12 @@ public class MultiDoku {
/** /**
* Renvoie la liste des Cells préalablement remplies du MultiDoku. * Renvoie la liste des Cells préalablement remplies du MultiDoku.
*
* @return List<Cell>, vide si aucune Cell n'est remplie. * @return List<Cell>, vide si aucune Cell n'est remplie.
*/ */
public List<Cell> getFilledCells() { public List<Cell> getFilledCells() {
List<Cell> result = new ArrayList<>(); List<Cell> result = new ArrayList<>();
for (Cell cell : this.getCells()){ for (Cell cell : this.getCells()) {
if (!cell.isEmpty()) { if (!cell.isEmpty()) {
result.add(cell); result.add(cell);
} }
@@ -170,11 +124,12 @@ public class MultiDoku {
/** /**
* Renvoie la liste des Cells vides du MultiDoku. * Renvoie la liste des Cells vides du MultiDoku.
*
* @return List<Cell>, vide si aucune Cell ne l'est. * @return List<Cell>, vide si aucune Cell ne l'est.
*/ */
public List<Cell> getEmptyCells() { public List<Cell> getEmptyCells() {
List<Cell> result = new ArrayList<>(); List<Cell> result = new ArrayList<>();
for (Cell cell : this.getCells()){ for (Cell cell : this.getCells()) {
if (cell.isEmpty()) { if (cell.isEmpty()) {
result.add(cell); result.add(cell);
} }
@@ -182,9 +137,9 @@ public class MultiDoku {
return result; return result;
} }
/** /**
* Vide une Cell donnée. * Vide une Cell donnée.
*
* @param cell Cell, à vider. * @param cell Cell, à vider.
*/ */
public void empty(Cell cell) { public void empty(Cell cell) {
@@ -195,6 +150,7 @@ public class MultiDoku {
/** /**
* Renvoie le nombre de Cell contenue dans le MultiDoku. * Renvoie le nombre de Cell contenue dans le MultiDoku.
*
* @return int, nombre de Cell dans le MultiDoku. * @return int, nombre de Cell dans le MultiDoku.
*/ */
public int getNbCells() { public int getNbCells() {
@@ -209,5 +165,9 @@ public class MultiDoku {
filledCell.setImmutable(); filledCell.setImmutable();
} }
} }
}
public StateManager getStateManager() {
return stateManager;
}
}

View File

@@ -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<Map<Cell, Integer>> 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());
}
}
}

View File

@@ -3,6 +3,7 @@ package sudoku.structure;
import sudoku.constraint.BlockConstraint; import sudoku.constraint.BlockConstraint;
import sudoku.constraint.Constraint; import sudoku.constraint.Constraint;
import sudoku.constraint.IConstraint; import sudoku.constraint.IConstraint;
import sudoku.io.SudokuPrinter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -89,22 +90,6 @@ public class Sudoku {
return true; 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. * Vide la Cell dotn les coordonnées sont renseignées de son symbole.
* @param x int, abscisse de la Cell voulue. * @param x int, abscisse de la Cell voulue.
@@ -194,7 +179,7 @@ public class Sudoku {
} }
public Cell getCell(int x, int y) { public Cell getCell(int x, int y) {
int index = y * getSize() + x; int index = toIndex(x, y);
assert (isValidCoords(x, y)); assert (isValidCoords(x, y));
try { try {
return this.cells.get(index); return this.cells.get(index);
@@ -232,62 +217,6 @@ public class Sudoku {
return this.cells.contains(cell); 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<Cell> 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<Integer> newPossibleSymbols = cell.getPossibleSymbols();
newPossibleSymbols.retainAll(constraint.getPossibleSymbols(
this,
coord.getX(),
coord.getY()
));
cell.setPossibleSymbols(newPossibleSymbols);
}
}
}
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Sudoku {"); sb.append("Sudoku {");
@@ -315,30 +244,6 @@ public class Sudoku {
return null; 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<Integer>, la liste des index des symboles possibles, vide si la Cell n'appartient pas au Sudoku.
*/
public List<Integer> getPossibleSymbolsOfCell(Cell cellToFill) {
List<Integer> 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. * 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. * @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 * @return bollean, true si le Sudoku est valide, false sinon
*/ */
private boolean isValid() { private boolean isValid() {
for (Cell cell : this.getFilledCells()) { for (int i = 0; i < cells.size(); i++) {
for (Constraint constraint : this.constraints) { Cell cell = getCell(i);
try { if (cell.isEmpty())
Coordinate coords = this.getCoordinateCell(cell); continue;
Coordinate coordinate = toCoords(i);
int symbolPlaced = cell.empty(); int symbolPlaced = cell.empty();
List<Integer> possibleSymbols = constraint.getPossibleSymbols( if (!canBePlaced(coordinate.getX(), coordinate.getY(), symbolPlaced)) {
this,
coords.getX(),
coords.getY()
);
cell.setSymbolIndex(symbolPlaced); cell.setSymbolIndex(symbolPlaced);
if (!possibleSymbols.contains(symbolPlaced)) {
return false; return false;
} }
cell.setSymbolIndex(symbolPlaced);
} catch (Exception e) {
throw new RuntimeException(e);
} }
}
}
return true;
}
/**
* Renvoie la liste des Cells remplies.
* @return List<Cell>
*/
private List<Cell> getFilledCells() {
List<Cell> result = new ArrayList<>();
for (Cell cell : getCells()) {
if (!cell.isEmpty()) {
result.add(cell);
}
}
return result;
}
/**
* Renvoie la liste des Cells modifiables.
* @return List<Cell>
*/
private List<Cell> getEmptyCells() {
List<Cell> result = new ArrayList<>();
for (Cell cell : getCells()) {
if (cell.isMutable()) {
result.add(cell);
}
}
return result;
}
/**
* Renvoie la liste des Cells immuables.
* @return List<Cell>
*/
private List<Cell> getImmutableCells() {
List<Cell> 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()) {
return false;
}
}
return true; return true;
} }

View File

@@ -123,7 +123,6 @@ public class SudokuFactory {
*/ */
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception { public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception {
System.out.println("nbCellsToEmpty : "+nbCellsToEmpty);
if (nbCellsToEmpty >= doku.getCells().size()) { if (nbCellsToEmpty >= doku.getCells().size()) {
throw new Exception(); throw new Exception();
} }
@@ -141,7 +140,6 @@ public class SudokuFactory {
int oldSymbol = cellToEmpty.empty(); int oldSymbol = cellToEmpty.empty();
int nbDokuSultions = Solver.countSolution(doku); int nbDokuSultions = Solver.countSolution(doku);
System.out.println("oldSymbol : "+oldSymbol);
if (nbDokuSultions == 1) { if (nbDokuSultions == 1) {
if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) { if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) {
return true; return true;

View File

@@ -1,5 +1,8 @@
package sudoku; package sudoku;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.File; import java.io.File;
import java.util.Random; import java.util.Random;
@@ -13,24 +16,26 @@ import sudoku.structure.SudokuFactory;
public class SudokuSerializerTest { public class SudokuSerializerTest {
void testSerializeWithSize(int blockWidth, int blockHeight, Random r) { void testSerializeWithSize(int blockWidth, int blockHeight) {
MultiDoku sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, var sudoku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
SudokuFactory.DEFAULT_CONSTRAINTS); SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.solveRandom(sudoku, r); Solver.solveRandom(sudoku, new Random());
JSONObject data = SudokuSerializer.serializeSudoku(sudoku); JSONObject data = SudokuSerializer.serializeSudoku(sudoku);
MultiDoku multiDoku = SudokuSerializer.deserializeSudoku(data); 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, Random r) { void testSaveWithSize(int blockWidth, int blockHeight) {
MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight, MultiDoku doku = SudokuFactory.createBasicEmptyRectangleDoku(blockWidth, blockHeight,
SudokuFactory.DEFAULT_CONSTRAINTS); SudokuFactory.DEFAULT_CONSTRAINTS);
Solver.solveRandom(doku, r); Solver.solveRandom(doku, new Random());
String savePath = SudokuSerializer.saveMultiDoku(doku); String savePath = SudokuSerializer.saveMultiDoku(doku);
MultiDoku otherDoku = null; MultiDoku otherDoku = null;
try { try {
otherDoku = SudokuFactory.fromfile(savePath); otherDoku = SudokuFactory.fromfile(savePath);
assert (otherDoku != null); assert (otherDoku != null);
assertEquals(SudokuSerializer.serializeSudoku(doku).toString(), SudokuSerializer.serializeSudoku(otherDoku).toString());
// clean file after test // clean file after test
File fileToDelete = new File(savePath); File fileToDelete = new File(savePath);
fileToDelete.delete(); fileToDelete.delete();
@@ -38,27 +43,35 @@ public class SudokuSerializerTest {
e.printStackTrace(); e.printStackTrace();
assert false; 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 @Test
void testSerialize() { void testSerialize() {
Random r = new Random(); Random r = new Random();
testSerializeWithSize(3, 3, r); int testCount = 20;
/**
int testCount = 5;
for (int i = 0; i < testCount; i++) { for (int i = 0; i < testCount; i++) {
int blockWidth = r.nextInt(10) + 1; int blockWidth = r.nextInt(4) + 1;
int blockHeight = r.nextInt(10) + 1; int blockHeight = r.nextInt(4) + 1;
testSerializeWithSize(blockWidth, blockHeight, r); testSerializeWithSize(blockWidth, blockHeight);
} }
for (int i = 0; i < testCount; i++) { for (int i = 0; i < testCount; i++) {
int blockWidth = r.nextInt(10) + 1; int blockWidth = r.nextInt(4) + 1;
int blockHeight = r.nextInt(10) + 1; int blockHeight = r.nextInt(4) + 1;
testSaveWithSize(blockWidth, blockHeight, r); testSaveWithSize(blockWidth, blockHeight);
}
for (int i = 0; i < testCount; i++) {
int size = r.nextInt(2) + 2;
testSerializeX(size);
} }
*/
} }
} }

View File

@@ -2,11 +2,14 @@ package sudoku.solver;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import sudoku.io.SudokuPrinter; import sudoku.io.SudokuPrinter;
import sudoku.io.SudokuSerializer;
import sudoku.structure.Cell; import sudoku.structure.Cell;
import sudoku.structure.MultiDoku; import sudoku.structure.MultiDoku;
import sudoku.structure.Sudoku; import sudoku.structure.Sudoku;
import sudoku.structure.SudokuFactory; import sudoku.structure.SudokuFactory;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@@ -52,17 +55,25 @@ class SolverTest {
System.out.println("\n****************************Doku Control\n"); System.out.println("\n****************************Doku Control\n");
SudokuPrinter.printRectangleSudoku(sudokuResult, 3, 3); 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"); System.out.println("\n****************************\nDoku solved");
SudokuPrinter.printRectangleSudoku(dokuToTest.getSubGrid(0), 3, 3); 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();
}
assertEquals(SudokuSerializer.serializeSudoku(dokuResult).toString(),
SudokuSerializer.serializeSudoku(dokuToTest).toString());
MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS); MultiDoku dokuToTest2 = SudokuFactory.createBasicEmptySquareDoku(3, SudokuFactory.DEFAULT_CONSTRAINTS);
Sudoku sudokuToTest2 = dokuToTest2.getSubGrid(0); Sudoku sudokuToTest2 = dokuToTest2.getSubGrid(0);