# Conflicts: # app/src/main/java/sudoku/structure/MultiDoku.java # app/src/main/java/sudoku/structure/Sudoku.java # app/src/main/java/sudoku/structure/SudokuFactory.java # app/src/test/java/sudoku/solver/SolverTest.java
268 lines
9.1 KiB
Java
268 lines
9.1 KiB
Java
package sudoku.structure;
|
|
|
|
import java.io.IOException;
|
|
import java.nio.file.Files;
|
|
import java.nio.file.Paths;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
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.solver.Solver;
|
|
|
|
public class SudokuFactory {
|
|
|
|
/**
|
|
* Générateur de nombre aléatoire.
|
|
*/
|
|
private static final Random random = new Random();
|
|
|
|
/**
|
|
* Liste des contraintes par défaut d'un Multi- ou Sudoku.
|
|
* Comprend les contraintes de blocs, de lignes, et de colonnes.
|
|
*/
|
|
public static List<Constraint> 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<Cell>, liste des Cells initialisées.
|
|
*/
|
|
private static List<Cell> initCells(int size) {
|
|
List<Cell> cells = new ArrayList<>(size * size);
|
|
for (int i = 0; i < size * size; i++) {
|
|
cells.add(new Cell());
|
|
}
|
|
return cells;
|
|
}
|
|
|
|
/**
|
|
* Créée des Blocks de taille width par height à partir des cellules données, et les met dans une liste.
|
|
* @param cells List<Cell>, 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<Block>, liste des Blocks créés.
|
|
*/
|
|
private static List<Block> initRectangleBlocs(List<Cell> cells, int width, int height) {
|
|
List<Block> blocs = new ArrayList<>();
|
|
int size = width * height;
|
|
for (int i = 0; i < size; i++) {
|
|
Block newBlock = new Block();
|
|
int blockX = i % height;
|
|
int blockY = i / height;
|
|
for (int y = 0; y < height; y++) {
|
|
for (int x = 0; x < width; x++) {
|
|
int index = ((y + blockY * height) * size + (x + blockX * width));
|
|
Cell blockCell = cells.get(index);
|
|
blockCell.setBlock(newBlock);
|
|
// List<Block> blockContainers = new ArrayList<>();
|
|
// blockContainers.add(newBlock);
|
|
// blockCell.setBlockContainers(blockContainers);
|
|
newBlock.addCell(blockCell);
|
|
}
|
|
}
|
|
blocs.add(newBlock);
|
|
}
|
|
return blocs;
|
|
}
|
|
|
|
/**
|
|
* 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 createBasicEmptyRectangleDoku(int widthBlock, int heightBlock,
|
|
List<Constraint> 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 createBasicEmptySquareDoku(int size, List<Constraint> 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<Coordinate, Integer>, association de Coordinate coordonnées et Integer valeurs, correspondant aux cases à remplir.
|
|
*/
|
|
public static void setImmutableCells(MultiDoku doku, Map<Coordinate, Integer> 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();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Créée un MultiDoku de difficulté difficulty à partir d'un MultiDoku fourni.
|
|
*
|
|
* @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.
|
|
*/
|
|
public static boolean newDokuFromFilledOne(MultiDoku doku, int nbCellsToEmpty) throws Exception {
|
|
|
|
System.out.println("nbCellsToEmpty : "+nbCellsToEmpty);
|
|
if (nbCellsToEmpty >= doku.getCells().size()) {
|
|
throw new Exception();
|
|
}
|
|
|
|
if (nbCellsToEmpty == 0) {
|
|
return true;
|
|
}
|
|
|
|
List<Cell> cellsThatCanBeEmptied = doku.getFilledCells();
|
|
|
|
while (!cellsThatCanBeEmptied.isEmpty()) {
|
|
int index = random.nextInt(cellsThatCanBeEmptied.size());
|
|
Cell cellToEmpty = cellsThatCanBeEmptied.get(index);
|
|
|
|
int oldSymbol = cellToEmpty.empty();
|
|
|
|
int nbDokuSultions = Solver.countSolution(doku);
|
|
System.out.println("oldSymbol : "+oldSymbol);
|
|
if (nbDokuSultions == 1) {
|
|
if (newDokuFromFilledOne(doku, --nbCellsToEmpty)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
cellToEmpty.setSymbolIndex(oldSymbol);
|
|
cellsThatCanBeEmptied.remove(cellToEmpty);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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, List<Constraint> constraints) {
|
|
int symbolCount = widthBlock * heightBlock;
|
|
List<Cell> cases = initCells(symbolCount);
|
|
List<Block> blocs = initRectangleBlocs(cases, widthBlock, heightBlock);
|
|
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, List<Constraint> 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.
|
|
*/
|
|
private static void linkSquareSudokus(Sudoku sudoku1, Sudoku sudoku2, Coordinate offset) {
|
|
int blockWidth = sudoku1.getBlockWidth();
|
|
for (int dx = 0; dx < blockWidth; dx++) {
|
|
for (int dy = 0; dy < blockWidth; dy++) {
|
|
int block1X = dx + offset.getX();
|
|
int block1Y = dy + offset.getY();
|
|
int block2X = dx;
|
|
int block2Y = dy;
|
|
|
|
if ((block1X < blockWidth) && (block1X >= 0) && (block1Y >= 0) && (block1Y < blockWidth)) {
|
|
Block block1 = sudoku1.getBlocks().get(block1Y * blockWidth + block1X);
|
|
Block block2 = sudoku2.getBlocks().get(block2Y * blockWidth + block2X);
|
|
|
|
// 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++) {
|
|
Cell newCell = block1.getCells().get(i);
|
|
Cell oldCell = block2.getCells().get(i);
|
|
|
|
int oldCellIndex = sudoku2.getCells().indexOf(oldCell);
|
|
sudoku2.getCells().set(oldCellIndex, newCell);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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, List<Constraint> constraints) {
|
|
assert (size > 1);
|
|
|
|
/*
|
|
* 2 3
|
|
* 1
|
|
* 4 5
|
|
*/
|
|
|
|
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));
|
|
linkSquareSudokus(sudoku1, sudoku4, new Coordinate(1 - size, size - 1));
|
|
linkSquareSudokus(sudoku1, sudoku5, new Coordinate(size - 1, size - 1));
|
|
|
|
return new MultiDoku(Arrays.asList(sudoku1, sudoku2, sudoku3, sudoku4, sudoku5));
|
|
}
|
|
|
|
public static void fillDoku(MultiDoku doku, Difficulty difficulty) throws Exception {
|
|
Solver.solveRandom(doku, random);
|
|
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");
|
|
}
|
|
doku.setFilledCellsImmutable();
|
|
}
|
|
|
|
public static MultiDoku fromfile(String filePath) {
|
|
try {
|
|
String content = Files.readString(Paths.get(filePath));
|
|
MultiDoku doku = SudokuSerializer.deserializeSudoku(content);
|
|
return doku;
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return null;
|
|
}
|
|
}
|
|
}
|