From f002667e5424223017b63344fc15802c31aa8f3d Mon Sep 17 00:00:00 2001 From: Morph01 Date: Sat, 8 Feb 2025 17:34:46 +0100 Subject: [PATCH] feat: better shared contraints and backtrace working ! --- app/src/main/java/sudoku/Grille.java | 16 + app/src/main/java/sudoku/Multidoku.java | 416 ++++++++++++------ app/src/main/java/sudoku/Sudoku.java | 21 + .../test/java/sudoku/TestMultidokuBloc.java | 46 +- 4 files changed, 330 insertions(+), 169 deletions(-) diff --git a/app/src/main/java/sudoku/Grille.java b/app/src/main/java/sudoku/Grille.java index 07d0dd7..c0d57cf 100644 --- a/app/src/main/java/sudoku/Grille.java +++ b/app/src/main/java/sudoku/Grille.java @@ -14,6 +14,7 @@ public class Grille { private final ArrayList blocs; private ArrayList symbolesPossibles; private final Sudoku sudoku; + private Multidoku multidoku; // Référence à Multidoku private List generatedColors; public Grille(int taille, Sudoku sudoku) { @@ -34,6 +35,10 @@ public class Grille { } } + public void setMultidoku(Multidoku multidoku) { + this.multidoku = multidoku; + } + /** * Génère une palette de couleurs en fonction du nombre de blocs attendus. * Pour un sudoku classique, la grille est de taille n², et il y a n² blocs, @@ -78,6 +83,17 @@ public class Grille { } } + // public void setCaseAvecPropagation(int ligne, int colonne, Symbole symbole) { + // // Affecte la valeur dans la grille courante + // setCase(ligne, colonne, symbole); + + // // Si cette case est partagée et qu'il y a un multidoku, on propage + // // immédiatement la valeur + // if (multidoku != null && multidoku.isSharedCase(getCase(ligne, colonne))) { + // multidoku.propagateSharedCase(getCase(ligne, colonne), symbole); + // } + // } + public Case getCase(int ligne, int colonne) { return cases[ligne][colonne]; } diff --git a/app/src/main/java/sudoku/Multidoku.java b/app/src/main/java/sudoku/Multidoku.java index f8cfd1a..bcdcdbb 100644 --- a/app/src/main/java/sudoku/Multidoku.java +++ b/app/src/main/java/sudoku/Multidoku.java @@ -1,27 +1,286 @@ package sudoku; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class Multidoku { private List sudokus; private List placements = new ArrayList<>(); - private List> casesPartagees; - private List contraintesPartagees; - - // Pour cet exemple, nous allons associer à chaque sudoku un offset [offsetY, - // offsetX]. - // Par exemple, le premier sudoku est affiché à (0,0) et le second à (ligne, - // colonne) donnée. + private List contraintesPartagees; public Multidoku() { this.sudokus = new ArrayList<>(); - this.casesPartagees = new ArrayList<>(); this.contraintesPartagees = new ArrayList<>(); } public void ajouterSudoku(Sudoku sudoku, int offsetLigne, int offsetColonne) { placements.add(new SudokuPlacement(sudoku, offsetLigne, offsetColonne)); + sudoku.getGrille().setMultidoku(this); // Passer la référence de Multidoku à Grille + } + + // Dans la classe Multidoku, modifier la méthode ajouterCasesPartagees : + public void ajouterCasesPartagees(List cases) { + ContrainteCasePartagee contrainte = new ContrainteCasePartagee(cases, this); + contraintesPartagees.add(contrainte); + + // Vérifier immédiatement la validité après l'ajout + if (!contrainte.estRespectee(null, null)) { + throw new IllegalStateException( + "La contrainte de partage n'est pas respectée après l'ajout des cases partagées"); + } + } + + public boolean resoudreMultidoku(boolean afficherEtape) { + List globalCells = collecterCellulesVides(); + return resoudreGlobal(globalCells, 0, afficherEtape); + } + + private List collecterCellulesVides() { + List cells = new ArrayList<>(); + // Ajouter d'abord les cases partagées + for (ContrainteCasePartagee contrainte : contraintesPartagees) { + for (Case c : contrainte.getCasesLiees()) { + if (c.getSymbole() == null) { + Sudoku s = trouverSudokuPourCase(c); + cells.add(new GlobalCell(s, c.getLigne(), c.getColonne())); + } + } + } + // Ajouter ensuite les cases non partagées + for (SudokuPlacement sp : placements) { + Sudoku s = sp.getSudoku(); + Grille g = s.getGrille(); + int taille = g.getTaille(); + for (int i = 0; i < taille; i++) { + for (int j = 0; j < taille; j++) { + Case c = g.getCase(i, j); + if (c.getSymbole() == null && !isSharedCase(c)) { + cells.add(new GlobalCell(s, i, j)); + } + } + } + } + return cells; + } + + private boolean resoudreGlobal(List globalCells, int index, boolean afficherEtape) { + if (index == globalCells.size()) { + return true; + } + GlobalCell gc = globalCells.get(index); + Sudoku s = gc.sudoku; + Grille g = s.getGrille(); + Case c = g.getCase(gc.ligne, gc.colonne); + List symboles = g.getSymbolesPossibles(); + + Symbole symboleOriginal = c.getSymbole(); + Map sauvegarde = new HashMap<>(); + sauvegarde.put(c, symboleOriginal); + + for (Symbole symbole : symboles) { + // Affichage AVANT la tentative d'assignation + if (afficherEtape) { + System.out.println("\nTentative symbole " + symbole + " sur " + gc.sudoku.getNom() + " [" + gc.ligne + + "," + gc.colonne + "]"); + System.out.println(this.toStringCombined()); // Afficher l'état actuel + } + + boolean ok; + if (isSharedCase(c)) { + ok = setCaseAvecPropagation(gc.ligne, gc.colonne, symbole, s); + } else { + c.setSymbole(symbole); + ok = s.estValide(c); + if (!ok) { + c.setSymbole(null); + } + } + + if (ok) { + if (afficherEtape) { + System.out.println("-> Affectation réussie pour " + symbole); + System.out.println(this.toStringCombined()); + } + if (globalValide() && resoudreGlobal(globalCells, index + 1, afficherEtape)) { + return true; + } + } + // Backtracking : restauration de l'état sauvegardé + restaurerCases(sauvegarde); + if (afficherEtape) { + System.out.println("Backtracking sur " + gc.sudoku.getNom() + " [" + gc.ligne + "," + gc.colonne + "]"); + System.out.println(this.toStringCombined()); + } + } + return false; + } + + private void restaurerCases(Map sauvegarde) { + for (Map.Entry entry : sauvegarde.entrySet()) { + entry.getKey().setSymbole(entry.getValue()); + } + } + + private boolean setCaseAvecPropagation(int ligne, int colonne, Symbole symbole, Sudoku s) { + Grille g = s.getGrille(); + Case c = g.getCase(ligne, colonne); + Map sauvegarde = new HashMap<>(); + + // Sauvegarde et définition de la case actuelle + sauvegarde.put(c, c.getSymbole()); + c.setSymbole(symbole); + + if (!s.estValide(c)) { + restaurerCases(sauvegarde); + return false; + } + + // Propagation à la case correspondante uniquement + for (ContrainteCasePartagee contrainte : contraintesPartagees) { + if (contrainte.getCasesLiees().contains(c)) { + Case caseCorrespondante = contrainte.getCaseCorrespondante(c); + if (caseCorrespondante != null) { + sauvegarde.put(caseCorrespondante, caseCorrespondante.getSymbole()); + caseCorrespondante.setSymbole(symbole); + + Sudoku autreSudoku = trouverSudokuPourCase(caseCorrespondante); + if (!autreSudoku.estValide(caseCorrespondante)) { + restaurerCases(sauvegarde); + return false; + } + } + } + } + + return true; + } + + private Sudoku trouverSudokuPourCase(Case c) { + for (SudokuPlacement sp : placements) { + if (belongsToSudoku(c, sp.getSudoku())) { + return sp.getSudoku(); + } + } + return null; + } + + private boolean belongsToSudoku(Case c, Sudoku s) { + Grille g = s.getGrille(); + int taille = g.getTaille(); + for (int i = 0; i < taille; i++) { + for (int j = 0; j < taille; j++) { + if (g.getCase(i, j) == c) { + return true; + } + } + } + return false; + } + + private boolean globalValide() { + for (SudokuPlacement sp : placements) { + if (!sp.getSudoku().estValide()) + return false; + } + for (Contrainte c : contraintesPartagees) { + if (!c.estRespectee(null, null)) + return false; + } + return true; + } + + public boolean verifierContraintesPartagees() { + return contraintesPartagees.stream() + .allMatch(c -> c.estRespectee(null, null)); // Adaptation nécessaire selon votre logique + } + + public boolean isSharedCase(Case c) { + for (ContrainteCasePartagee contrainte : contraintesPartagees) { + if (contrainte.getCasesLiees().contains(c)) { + return true; + } + } + return false; + } + + // Classe utilitaire pour représenter une case vide dans un sudoku particulier + public static class GlobalCell { + public Sudoku sudoku; + public int ligne; + public int colonne; + + public GlobalCell(Sudoku sudoku, int ligne, int colonne) { + this.sudoku = sudoku; + this.ligne = ligne; + this.colonne = colonne; + } + } + + // Classe interne pour la contrainte de cases partagées + public class ContrainteCasePartagee implements Contrainte { + private Map casesLiees; // Map associant chaque case à sa correspondante + private Set sudokusLies; + private Multidoku multidoku; + + public ContrainteCasePartagee(List cases, Multidoku multidoku) { + this.casesLiees = new HashMap<>(); + this.sudokusLies = new HashSet<>(); + this.multidoku = multidoku; + + // Les cases sont données dans l'ordre : d'abord celles du premier sudoku, + // puis celles du second dans le même ordre + int size = cases.size() / 2; + for (int i = 0; i < size; i++) { + Case c1 = cases.get(i); + Case c2 = cases.get(i + size); + casesLiees.put(c1, c2); + casesLiees.put(c2, c1); + + Sudoku s1 = multidoku.trouverSudokuPourCase(c1); + Sudoku s2 = multidoku.trouverSudokuPourCase(c2); + if (s1 != null) + sudokusLies.add(s1); + if (s2 != null) + sudokusLies.add(s2); + } + + // Propager les valeurs initiales + propagerValeursInitiales(); + } + + private void propagerValeursInitiales() { + for (Map.Entry entry : casesLiees.entrySet()) { + Case c1 = entry.getKey(); + Case c2 = entry.getValue(); + + if (c1.getSymbole() != null && c2.getSymbole() == null) { + c2.setSymbole(c1.getSymbole()); + } else if (c2.getSymbole() != null && c1.getSymbole() == null) { + c1.setSymbole(c2.getSymbole()); + } + } + } + + @Override + public boolean estRespectee(Grille grille, Case caseActuelle) { + for (Map.Entry entry : casesLiees.entrySet()) { + Case c1 = entry.getKey(); + Case c2 = entry.getValue(); + + if (c1.getSymbole() != null && c2.getSymbole() != null + && !c1.getSymbole().equals(c2.getSymbole())) { + return false; + } + } + return true; + } + + public Case getCaseCorrespondante(Case c) { + return casesLiees.get(c); + } + + public Set getCasesLiees() { + return casesLiees.keySet(); + } } public String toStringCombined() { @@ -92,139 +351,4 @@ public class Multidoku { } return sb.toString(); } - - /** - * Méthode utilitaire qui vérifie si une case est partagée. - * On parcourt la liste des groupes de cases partagées et si la case y figure, - * on renvoie true. - */ - private boolean isSharedCase(Case c) { - for (List groupe : casesPartagees) { - if (groupe.contains(c)) { - return true; - } - } - return false; - } - - public void ajouterCasesPartagees(List cases) { - casesPartagees.add(new ArrayList<>(cases)); - ajouterContraintePartagee(new ContrainteCasePartagee(cases)); - } - - public void ajouterContraintePartagee(Contrainte contrainte) { - contraintesPartagees.add(contrainte); - } - - public boolean verifierContraintesPartagees() { - return contraintesPartagees.stream() - .allMatch(c -> c.estRespectee(null, null)); // Adaptation nécessaire selon votre logique - } - - // Classe interne pour la contrainte de cases partagées - public static class ContrainteCasePartagee implements Contrainte { - private List casesLiees; - - public ContrainteCasePartagee(List cases) { - this.casesLiees = new ArrayList<>(cases); - } - - @Override - public boolean estRespectee(Grille grille, Case caseActuelle) { - Symbole reference = null; - for (Case c : casesLiees) { - if (c.getSymbole() != null) { - if (reference == null) { - reference = c.getSymbole(); - } else if (!c.getSymbole().equals(reference)) { - return false; - } - } - } - return true; - } - } - - public List getSudokus() { - return sudokus; - } - - public List> getCasesPartagees() { - return casesPartagees; - } - - public boolean estValide(Case c) { - for (Contrainte contraintePartagee : contraintesPartagees) { - if (!contraintePartagee.estRespectee(null, c)) { - return false; - } - } - - return true; - } - - public boolean resoudreMultidoku(boolean afficherEtape) { - // Pour chaque sudoku (dans l'ordre des placements) - for (SudokuPlacement sp : placements) { - Sudoku s = sp.getSudoku(); - // Propagation des valeurs partagées avant de résoudre ce sudoku - for (List sharedGroup : casesPartagees) { - // On parcourt le groupe pour trouver, le cas échéant, une valeur de référence - Symbole valeurReference = null; - boolean sudokuContientUneCasePartagee = false; - for (Case c : sharedGroup) { - if (belongsToSudoku(c, s)) { - sudokuContientUneCasePartagee = true; - } - if (c.getSymbole() != null) { - if (valeurReference == null) { - valeurReference = c.getSymbole(); - } else if (!c.getSymbole().equals(valeurReference)) { - System.out.println("Conflit de valeurs dans un groupe partagé avant résolution."); - return false; - } - } - } - // Si le sudoku contient une case partagée et qu'une valeur a été définie, on la - // propage - if (valeurReference != null && sudokuContientUneCasePartagee) { - for (Case c : sharedGroup) { - if (belongsToSudoku(c, s) && c.getSymbole() == null) { - c.setSymbole(valeurReference); - } - } - } - } - // Résolution du sudoku courant par backtracking - ResolveurBacktraceSimple resolver = new ResolveurBacktraceSimple(s); - if (!resolver.resoudre(s, afficherEtape)) { - System.out.println("Échec de la résolution pour un sudoku."); - return false; - } - } - // Vérification finale des contraintes partagées - if (!verifierContraintesPartagees()) { - System.out.println("Les contraintes partagées ne sont pas respectées !"); - return false; - } - return true; - } - - /** - * Méthode utilitaire qui vérifie si une case fait partie du sudoku donné. - * On parcourt la grille du sudoku et on compare les instances. - */ - private boolean belongsToSudoku(Case c, Sudoku s) { - Grille g = s.getGrille(); - int taille = g.getTaille(); - for (int i = 0; i < taille; i++) { - for (int j = 0; j < taille; j++) { - if (g.getCase(i, j) == c) { - return true; - } - } - } - return false; - } - -} +} \ No newline at end of file diff --git a/app/src/main/java/sudoku/Sudoku.java b/app/src/main/java/sudoku/Sudoku.java index 8684fbc..d83b0d3 100644 --- a/app/src/main/java/sudoku/Sudoku.java +++ b/app/src/main/java/sudoku/Sudoku.java @@ -11,12 +11,21 @@ import sudoku.core.Console; public class Sudoku { private final Grille grille; private final List contraintes; + private String nom; public Sudoku(int taille) { this.grille = new Grille(taille, this); this.contraintes = new ArrayList<>(); } + public void setNom(String nom) { + this.nom = nom; + } + + public String getNom() { + return this.nom; + } + public void ajouterContrainte(Contrainte contrainte) { contraintes.add(contrainte); } @@ -34,6 +43,18 @@ public class Sudoku { return true; } + public boolean estValide() { + for (int i = 0; i < this.getGrille().getTaille(); i++) { + for (int j = 0; j < this.getGrille().getTaille(); j++) { + Case c = this.getGrille().getCase(i, j); + if (c.getSymbole() != null && !this.estValide(c)) { + return false; + } + } + } + return true; + } + public boolean verifierToutesContraintes() { return grille.verifierToutesContraintes(contraintes); } diff --git a/app/src/test/java/sudoku/TestMultidokuBloc.java b/app/src/test/java/sudoku/TestMultidokuBloc.java index 549a398..f1d110f 100644 --- a/app/src/test/java/sudoku/TestMultidokuBloc.java +++ b/app/src/test/java/sudoku/TestMultidokuBloc.java @@ -14,6 +14,9 @@ public class TestMultidokuBloc { Sudoku s1 = new Sudoku(9); Sudoku s2 = new Sudoku(9); + s1.setNom("Sudoku 1"); + s2.setNom("Sudoku 2"); + // Placer sudoku1 en haut à gauche (offset (0,0)) multidoku.ajouterSudoku(s1, 0, 0); @@ -21,6 +24,25 @@ public class TestMultidokuBloc { // grille globale multidoku.ajouterSudoku(s2, 6, 6); + ArrayList symboles = new ArrayList<>(); + for (int i = 10; i <= 19; i++) { + symboles.add(Symbole.of(i)); + } + + s1.getGrille().setSymbolesPossibles(symboles); + List sudokus = Arrays.asList(s1, s2); + for (Sudoku sudoku : sudokus) { + sudoku.getGrille().setSymbolesPossibles(symboles); + sudoku.ajouterContrainte(new ContrainteLigne()); + sudoku.ajouterContrainte(new ContrainteColonne()); + sudoku.ajouterContrainte(new ContrainteBloc()); + sudoku.getGrille().creerBlocCarre(); + } + + for (int i = 0; i < s1.getGrille().getTaille(); i++) { + s1.getGrille().setCase(i, i, symboles.get(i)); + } + // Créer un lien entre la case (6,6) de s1 et (0,0) de s2 List casesPartagees = Arrays.asList( s1.getGrille().getCase(6, 6), @@ -44,28 +66,6 @@ public class TestMultidokuBloc { s2.getGrille().getCase(2, 2)); multidoku.ajouterCasesPartagees(casesPartagees); - multidoku.ajouterContraintePartagee(new ContrainteCasePartagee(casesPartagees)); - - ArrayList symboles = new ArrayList<>(); - for (int i = 10; i <= 19; i++) { - symboles.add(Symbole.of(i)); - } - - s1.getGrille().setSymbolesPossibles(symboles); - List sudokus = Arrays.asList(s1, s2); - for (Sudoku sudoku : sudokus) { - sudoku.getGrille().setSymbolesPossibles(symboles); - sudoku.ajouterContrainte(new ContrainteLigne()); - sudoku.ajouterContrainte(new ContrainteColonne()); - sudoku.ajouterContrainte(new ContrainteBloc()); - sudoku.getGrille().creerBlocCarre(); - - } - - for (int i = 0; i < s1.getGrille().getTaille(); i++) { - s1.getGrille().setCase(i, i, symboles.get(i)); - } - System.out.println("Sudoku 1 :"); System.out.println(s1.getGrille().toString()); @@ -88,4 +88,4 @@ public class TestMultidokuBloc { System.out.println("Sudoku 2 résolu :"); System.out.println(s2.getGrille().toString()); } -} +} \ No newline at end of file