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.io.SudokuSerializer; 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 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. */ private static List initCells(int size) { List 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, 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. */ private static List initRectangleBlocs(List cells, int width, int height) { List 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 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 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 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. */ 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(); } } }); } /** * 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 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, List constraints) { int symbolCount = widthBlock * heightBlock; List cases = initCells(symbolCount); List 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 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 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; } } }