package sudoku; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Scanner; import sudoku.core.Console; import sudoku.core.GenerateurCouleur; /** * Classe Grille permettant de créer une grille de sudoku. */ public class Grille { private final int taille; private final Case[][] cases; private final ArrayList blocs; private ArrayList symbolesPossibles; private final Sudoku sudoku; // Référence à Sudoku private Multidoku multidoku; // Référence à Multidoku private List generatedColors; /** * Constructeur permettant d'initialiser une grille grace aux paramètres * suivants : * * @param taille : taille de la grille * @param sudoku : sudoku */ public Grille(int taille, Sudoku sudoku) { this.taille = taille; this.cases = new Case[taille][taille]; this.blocs = new ArrayList<>(); this.symbolesPossibles = new ArrayList<>(); this.sudoku = sudoku; initColors(); // Initialiser les cases for (int i = 0; i < taille; i++) { for (int j = 0; j < taille; j++) { cases[i][j] = new Case(i, j, null); } } } /** * Méthode permettant de mettre à jour la référence au multidoku * * @param multidoku : multidoku * @see Multidoku */ 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, * on génère donc 'taille' couleurs. */ private void initColors() { // nombre de couleurs = nombre de blocs (pour sudoku classique) List colors = GenerateurCouleur.paletteCouleurEtendue(taille); generatedColors = new ArrayList<>(); for (GenerateurCouleur.Couleur color : colors) { generatedColors.add(convertToAnsi(color)); } } /** * Convertit une couleur en format ANSI pour l'affichage en console. * * @param color : couleur à convertir * @return couleur convertie */ private String convertToAnsi(GenerateurCouleur.Couleur color) { int r = Math.round(color.r * 255); int g = Math.round(color.g * 255); int b = Math.round(color.b * 255); return String.format("\u001B[38;2;%d;%d;%dm", r, g, b); } /** * Méthode permettant de définir un symbole dans une case en fonction de sa * ligne, de sa colonne et du symbole * * @param ligne : coordonnée de la ligne * @param colonne : coordonnée de la colonne * @param symbole : symbole à placer */ public void setCase(int ligne, int colonne, Symbole symbole) { try { if (symbole != null && !symbolesPossibles.contains(symbole)) { throw new IllegalArgumentException("Symbole non autorisé : " + symbole); } Case currentCase = cases[ligne][colonne]; Symbole ancienSymbole = currentCase.getSymbole(); currentCase.setSymbole(symbole); if (!sudoku.verifierToutesContraintes() || (multidoku != null && !multidoku.verifierContraintesPartagees())) { currentCase.setSymbole(ancienSymbole); throw new IllegalArgumentException( "Les contraintes ne sont pas respectées pour la case (" + ligne + ", " + colonne + ")"); } } catch (Exception e) { Console.errorln(e.getMessage()); } } /** * Méthode permettant de retourner une case en fonction de sa ligne et de sa * colonne * * @param ligne : coordonnée de la ligne * @param colonne : coordonnée de la colonne * @return Case */ public Case getCase(int ligne, int colonne) { return cases[ligne][colonne]; } /** * Méthode permettant de créer un bloc personnalisé en fonction des positions * passées en paramètre
*
* * Exemple de positions pour un sudoku 9x9 :
* sudoku.getGrille().creerBlocPersonnalise(Arrays.asList(
* new int[] { 0, 0 },
* new int[] { 0, 1 },
* new int[] { 0, 2 },
* new int[] { 1, 0 },
* new int[] { 1, 1 },
* new int[] { 1, 2 },
* new int[] { 2, 0 },
* new int[] { 2, 1 },
* new int[] { 2, 2 }
* ); * * @param positions : liste de positions pour le bloc personnalisé
*/ public void creerBlocPersonnalise(List positions) { try { // Validation des positions for (int[] pos : positions) { if (pos[0] < 0 || pos[0] >= taille || pos[1] < 0 || pos[1] >= taille) { throw new IllegalArgumentException("Position invalide : (" + pos[0] + ", " + pos[1] + ")"); } } // Choisir la couleur suivante en fonction du nombre de blocs déjà créés setCouleurBloc(positions); } catch (IllegalArgumentException e) { Console.errorln(e.getMessage()); } } private void setCouleurBloc(List positions) { int couleurIndex = blocs.size() % generatedColors.size(); Bloc bloc = new Bloc(generatedColors.get(couleurIndex), couleurIndex); for (int[] pos : positions) { bloc.ajouterCase(cases[pos[0]][pos[1]]); } blocs.add(bloc); } /** * Crée des blocs carrés (par exemple pour un sudoku classique). */ public void creerBlocCarre() { int blocSize = (int) Math.sqrt(taille); if (blocSize * blocSize != taille) { throw new IllegalArgumentException("La taille de la grille doit être un carré parfait."); } // Création des blocs en motif (par exemple 3x3 pour un sudoku 9x9) for (int blocRow = 0; blocRow < blocSize; blocRow++) { for (int blocCol = 0; blocCol < blocSize; blocCol++) { List positions = new ArrayList<>(); // Ajouter toutes les positions pour le bloc courant for (int i = 0; i < blocSize; i++) { for (int j = 0; j < blocSize; j++) { positions.add(new int[] { blocRow * blocSize + i, blocCol * blocSize + j }); } } setCouleurBloc(positions); } } } /** * Crée des blocs rectangulaires. * * @param blocHeight : hauteur du bloc * @param blocWidth : largeur du bloc */ public void creerBlocRectangulaire(int blocHeight, int blocWidth) { if (taille % blocHeight != 0 || taille % blocWidth != 0) { throw new IllegalArgumentException( "La taille de la grille doit être divisible par les dimensions des blocs."); } int blocsParLigne = taille / blocWidth; int blocsParColonne = taille / blocHeight; // Création des blocs en motif rectangulaire for (int blocRow = 0; blocRow < blocsParColonne; blocRow++) { for (int blocCol = 0; blocCol < blocsParLigne; blocCol++) { List positions = new ArrayList<>(); // Ajouter toutes les positions pour le bloc courant for (int i = 0; i < blocHeight; i++) { for (int j = 0; j < blocWidth; j++) { positions.add(new int[] { blocRow * blocHeight + i, blocCol * blocWidth + j }); } } setCouleurBloc(positions); } } } /** * Méthode permettant d'afficher les blocs de la grille à l'aide de la méthode * toString de la classe Bloc. */ public void printBlocs() { for (Bloc bloc : blocs) { System.out.println(bloc.toString()); } } /** * Méthode utilisée pour le menu princial. * Méthode permettant de choisir le type de symbole à utiliser pour la grille * * @param scanner : scanner pour lire l'entrée utilisateur * @return int : choix de l'utilisateur */ public static int choisirTypeSymbole(Scanner scanner) { while (true) { System.out.println("Choisissez le type de symbole :"); System.out.println("1 : Entiers"); System.out.println("2 : Lettres"); System.out.println("3 : Chaînes de caractères / Emoji"); String input = scanner.nextLine(); try { int choix = Integer.parseInt(input); if (choix >= 1 && choix <= 3) { return choix; } else { System.out.println("Choix invalide. Veuillez entrer 1, 2 ou 3."); } } catch (NumberFormatException e) { System.out.println("Entrée invalide. Veuillez entrer un nombre."); } } } /** * Méthode utilisée pour le menu principal. * Méthode permettant de demander à l'utilisateur de saisir les symboles * possibles pour la grille. * * @param choix : choix de l'utilisateur * @param scanner : scanner pour lire l'entrée utilisateur */ public void askSetSymbolesPossibles(int choix, Scanner scanner) { try { for (int i = 0; i < taille; i++) { System.out.println("Entrez le symbole " + (i + 1) + "/" + taille + " :"); String input = scanner.nextLine(); switch (choix) { case 1: // Nombres try { Symbole intTemp = Symbole.of(Integer.parseInt(input)); if (symbolesPossibles.contains(intTemp)) { Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole"); i--; } else { symbolesPossibles.add(intTemp); } } catch (NumberFormatException e) { Console.errorln("Veuillez entrer un nombre valide"); i--; } break; case 2: // Lettres if (input.length() == 1 && Symbole.of(input.charAt(0)).isLetter()) { Symbole charTemp = Symbole.of(input.charAt(0)); if (symbolesPossibles.contains(charTemp)) { Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole"); i--; } else { symbolesPossibles.add(charTemp); } } else { Console.errorln("Veuillez entrer une lettre valide"); i--; } break; case 3: // Texte/Emoji Symbole stringTemp = Symbole.of(input); if (symbolesPossibles.contains(stringTemp)) { Console.errorln("Ce symbole existe déjà, veuillez entrer un autre symbole"); i--; } else if (Objects.equals(input, "ESC")) { Console.errorln("Ce symbole est interdit, veuillez entrer un autre symbole"); i--; } else { symbolesPossibles.add(stringTemp); } break; default: Console.errorln("Type non supporté"); return; } } } catch (Exception e) { Console.errorln("Une erreur est survenue : " + e.getMessage()); Console.errorln("Une erreur est survenue : " + e.getMessage()); } } /** * Méthode permettant de retourner la liste des symboles possible. * Cela dépend de ce que saisit l'utilisateur dans la méthode * askSetSymbolesPossibles * * @return List la liste des symboles possibles * @see Symbole */ public List getSymbolesPossibles() { return symbolesPossibles; } /** * Méthode permettant d'ajouter un symbole à la liste des symboles possibles * * @param symbolesPossibles : liste des symboles possibles */ public void setSymbolesPossibles(ArrayList symbolesPossibles) { this.symbolesPossibles = symbolesPossibles; } /** * Méthode permettant d'afficher la liste des symboles possibles */ public void printSymbolesPossibles() { StringBuilder sb = new StringBuilder(); for (Symbole symbole : symbolesPossibles) { sb.append(symbole.toString()).append(" "); } System.out.println(sb); } /** * Méthode permettant de retourner la taille de la grille * * @return taille */ public int getTaille() { return taille; } Bloc findBlocForCase(Case target) { for (Bloc bloc : blocs) { if (bloc.getCases().contains(target)) { return bloc; } } return null; // Ne dois jamais arriver si la grille est bien construite } /** * Vérifie si toutes les contraintes sont respectées. * S'arrête dès qu'une contrainte n'est pas respectée. * * @param contraintes : liste des contraintes à vérifier * @return true si toutes les contraintes sont respectées, false sinon */ public boolean verifierToutesContraintes(List contraintes) { // Vérifier chaque case de la grille for (int i = 0; i < taille; i++) { for (int j = 0; j < taille; j++) { Case currentCase = cases[i][j]; // Ne vérifier que les cases qui ont un symbole if (currentCase.getSymbole() != null) { // Vérifier toutes les contraintes pour cette case for (Contrainte contrainte : contraintes) { if (!contrainte.estRespectee(this, currentCase)) { Console.errorln( "GRILLE: Contrainte non respectée à la position : ligne=" + i + ", colonne=" + j); return false; } } } } } return true; } /** * Méthode permettant de retourner la longueur du symbole le plus long pour * dimensionner correctement les cases et la grille en console * * @return int */ public int getLongueurSymboleLePlusLong() { int max = 0; for (Symbole symbole : symbolesPossibles) { if (symbole.toString().length() > max) { max = symbole.toString().length(); } } return max; } /** * Méthode permettant d'afficher la grille * * @return String : représentation de la grille */ @Override public String toString() { StringBuilder sb = new StringBuilder(); int maxLen = getLongueurSymboleLePlusLong(); // longueur maximale des symboles for (int i = 0; i < taille; i++) { for (int j = 0; j < taille; j++) { Case currentCase = cases[i][j]; String cellStr = currentCase.toString(); // Calculer le nombre d'espaces à ajouter pour atteindre maxLen int pad = maxLen - cellStr.length(); String padding = " ".repeat(pad); Bloc bloc = findBlocForCase(currentCase); if (bloc != null) { sb.append(bloc.getCouleur()) // couleur du bloc .append(cellStr) // symbole .append(padding) // padding pour aligner les symboles .append("\u001B[0m ") // réinitialiser la couleur .append(" "); } else { // pas de bloc, afficher normalement sb.append(cellStr) .append(padding) .append(" "); } } sb.append("\n"); // nouvelle ligne } return sb.toString(); } }