Files
Sudoku/app/src/main/java/sudoku/structure/SudokuFactory.java
Melvyn a221233c06
All checks were successful
Linux arm64 / Build (push) Successful in 53s
fix : création MultiDoku à résoudre avec cell imuable
2025-01-29 11:26:36 +01:00

268 lines
9.1 KiB
Java

package sudoku.structure;
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.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();
/**
* Difficulté avec le ration des cases qui seront vides.
*/
private static final double VERY_EASY = 0.1;
private static final double EASY = 0.25;
private static final double MEDIUM = 0.5;
private static final double HARD = 0.75;
/**
* Liste des contraintes par défaut d'un Multi- ou Sudoku.
* Comprend les contraintes de blocs, de lignes, et de colonnes.
*/
public static List<IConstraint> DEFAULT_CONSTRAINTS = Arrays.asList(new BlockConstraint(), new LineConstraint(), new ColumnConstraint());
/**
* 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 createBasicEmptyRectangleSudoku(int widthBlock, int heightBlock) {
Sudoku s = createRectangleSudoku(widthBlock, heightBlock);
return new MultiDoku(Arrays.asList(s));
}
/**
* 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);
}
/**
* 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 {
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();
if (Solver.countSolution(doku) == 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) {
int symbolCount = widthBlock * heightBlock;
List<Cell> cases = initCells(symbolCount);
List<Block> blocs = initRectangleBlocs(cases, widthBlock, heightBlock);
Sudoku s = new Sudoku(cases, blocs, DEFAULT_CONSTRAINTS);
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);
}
/**
* 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);
// 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) {
assert (size > 1);
/*
* 2 3
* 1
* 4 5
*/
Sudoku sudoku1 = createSquareSudoku(size);
Sudoku sudoku2 = createSquareSudoku(size);
Sudoku sudoku3 = createSquareSudoku(size);
Sudoku sudoku4 = createSquareSudoku(size);
Sudoku sudoku5 = createSquareSudoku(size);
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 MultiDoku createBasicRectangleDokuToSolve(int width, int height, double difficulty) throws Exception {
MultiDoku doku = createBasicEmptyRectangleSudoku(width, height);
Solver.solveRandom(doku, random);
int nbCellsToEmpty = (int)(difficulty*doku.getNbCells());
boolean successfull = newDokuFromFilledOne(doku, nbCellsToEmpty);
if (!successfull) {
throw new Exception("Canno't create this doku with this difficulty");
}
doku.setFilledCellsImmutable();
return doku;
}
public static MultiDoku createBasicSquareDokuToSolve(int size, double difficulty) throws Exception {
return createBasicRectangleDokuToSolve(size, size, difficulty);
}
public static MultiDoku createBasicXShapedMultiDokuToSolve(int size, double difficulty) throws Exception {
MultiDoku doku = createBasicXShapedMultidoku(size);
Solver.solveRandom(doku, random);
int nbCellsToEmpty = (int)(difficulty*doku.getNbCells());
boolean successful = newDokuFromFilledOne(doku, nbCellsToEmpty);
if (!successful) {
throw new Exception("Cannot create this Doku with this difficulty");
}
doku.setFilledCellsImmutable();
return doku;
}
}