From 86ff392e7582c40b632b773472b8e3a68b615c8a Mon Sep 17 00:00:00 2001 From: lecointre Date: Sun, 12 Apr 2026 16:40:07 +0200 Subject: [PATCH] Derniere version du jeu --- Case.java | 74 ++++++++++++- ControleurGrille.java | 153 +++++++++++++++++++-------- ControleurJeu.java | 68 +++++++++--- ControleurMenu.java | 157 +++++++++++++++++++++------- FenetreJeu.java | 234 +++++++++++++++++++++++++++++++----------- Grille.java | 147 ++++++++++++++++++-------- MenuPrincipal.java | 145 +++++++++++++++++--------- 7 files changed, 734 insertions(+), 244 deletions(-) diff --git a/Case.java b/Case.java index 38602ff..b358fe4 100644 --- a/Case.java +++ b/Case.java @@ -1,28 +1,92 @@ import java.io.Serializable; +/** + * Représente une case individuelle sur le plateau du Démineur. + * Elle contient son état (minée, révélée) et son marqueur (étoile, interrogation). + */ public class Case implements Serializable { + + /** Constante indiquant que la case n'a aucun marqueur. */ + public static final int MARQUEUR_VIDE = 0; + + /** Constante indiquant que la case est marquée d'une étoile (mine supposée). */ + public static final int MARQUEUR_ETOILE = 1; + + /** Constante indiquant que la case est marquée d'un point d'interrogation (doute). */ + public static final int MARQUEUR_INTERROGATION = 2; + + /** Constante définissant le nombre total d'états de marqueurs possibles. */ + private static final int NOMBRE_ETATS_MARQUEUR = 3; + + /** Indique si la case contient une mine (true) ou non (false). */ private boolean minee; + + /** Indique si la case a été cliquée et révélée au joueur. */ private boolean revelee; + + /** Stocke l'état actuel du marqueur posé par le joueur (clic droit). */ private int marqueur; + + /** Stocke le nombre de mines présentes dans les 8 cases adjacentes. */ private int nbMinesVoisines; + /** + * Constructeur par défaut d'une Case. + * Initialise une case vide, cachée et sans marqueur. + */ public Case() { this.minee = false; this.revelee = false; - this.marqueur = 0; + this.marqueur = MARQUEUR_VIDE; this.nbMinesVoisines = 0; } + /** + * Vérifie si la case contient une mine. + * @return true si minée, false sinon. + */ public boolean isMinee() { return this.minee; } + + /** + * Vérifie si la case est révélée. + * @return true si révélée, false sinon. + */ public boolean isRevelee() { return this.revelee; } + + /** + * Récupère le marqueur actuel de la case. + * @return l'entier correspondant au type de marqueur. + */ public int getMarqueur() { return this.marqueur; } + + /** + * Récupère le nombre de mines autour de cette case. + * @return le nombre de mines voisines. + */ public int getNbMinesVoisines() { return this.nbMinesVoisines; } - public void setMinee(boolean m) { this.minee = m; } - public void setRevelee(boolean r) { this.revelee = r; } - public void setNbMinesVoisines(int n) { this.nbMinesVoisines = n; } + /** + * Définit si la case doit contenir une mine ou non. + * @param estMinee true pour placer une mine, false pour l'enlever. + */ + public void setMinee(boolean estMinee) { this.minee = estMinee; } + + /** + * Modifie l'état de révélation de la case. + * @param estRevelee true pour révéler la case, false pour la cacher. + */ + public void setRevelee(boolean estRevelee) { this.revelee = estRevelee; } + + /** + * Enregistre le nombre de mines calculé autour de cette case. + * @param nombre le nombre de mines voisines (de 0 à 8). + */ + public void setNbMinesVoisines(int nombre) { this.nbMinesVoisines = nombre; } + /** + * Alterne le marqueur de la case dans l'ordre : Vide -> Étoile -> Interrogation -> Vide. + */ public void changerMarqueur() { - this.marqueur = (this.marqueur + 1) % 3; + this.marqueur = (this.marqueur + 1) % NOMBRE_ETATS_MARQUEUR; } } diff --git a/ControleurGrille.java b/ControleurGrille.java index 23e4147..d818431 100644 --- a/ControleurGrille.java +++ b/ControleurGrille.java @@ -2,61 +2,130 @@ import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.JOptionPane; +/** + * Observateur gérant les interactions de la souris sur la grille de jeu. + */ public class ControleurGrille implements MouseListener { + /** Constante pour le bouton gauche de la souris (Capture 26). */ + private static final int BOUTON_GAUCHE = MouseEvent.BUTTON1; + + /** Constante pour le bouton droit de la souris (Capture 26). */ + private static final int BOUTON_DROIT = MouseEvent.BUTTON3; + + /** Message affiché en cas de défaite. */ + private static final String MESSAGE_DEFAITE = "BOUM ! Vous avez sauté sur une mine !"; + + /** Message affiché en cas de victoire. */ + private static final String MESSAGE_VICTOIRE = "Félicitations ! Vous avez trouvé toutes les mines !"; + + /** Référence à la fenêtre affichant la grille. */ private FenetreJeu fenetre; + + /** Référence au modèle contenant la logique du plateau. */ private Grille grille; - public ControleurGrille(FenetreJeu f, Grille g) { - this.fenetre = f; - this.grille = g; + /** + * Constructeur de l'écouteur de la grille. + * @param fenetreJeu la fenêtre contenant les boutons. + * @param grilleJeu la grille contenant les données. + */ + public ControleurGrille(FenetreJeu fenetreJeu, Grille grilleJeu) { + this.fenetre = fenetreJeu; + this.grille = grilleJeu; } + /** + * Méthode appelée lors d'un clic sur n'importe quel bouton de la grille. + * @param evenement les informations du clic souris. + */ @Override - public void mouseClicked(MouseEvent e) { - if (this.grille.isPartieTerminee()) return; + public void mouseClicked(MouseEvent evenement) { + if (this.grille.isPartieTerminee()) { + return; + } - for (int i = 0; i < grille.getLignes(); i++) { - for (int j = 0; j < grille.getColonnes(); j++) { - - if (e.getSource() == fenetre.getBouton(i, j)) { - Case caseCliquee = grille.getCase(i, j); - - if (caseCliquee.isRevelee()) return; - - if (e.getButton() == MouseEvent.BUTTON1) { - if (caseCliquee.getMarqueur() != 1) { - boolean aExplose = grille.revelerCase(i, j); - if (aExplose) { - grille.setPartieTerminee(true); - fenetre.afficherDefaite(i, j); - JOptionPane.showMessageDialog(fenetre, "BOUM ! Vous avez sauté sur une mine !"); - // NOUVEAU : On ne quitte plus de force, on change juste le bouton - fenetre.changerBoutonEnRetourMenu(); - } else { - fenetre.rafraichirTouteLaGrille(); - if (grille.estGagnee()) { - grille.setPartieTerminee(true); - JOptionPane.showMessageDialog(fenetre, "Félicitations ! Vous avez trouvé toutes les mines !"); - // NOUVEAU : Idem pour la victoire - fenetre.changerBoutonEnRetourMenu(); - } - } - } - } - else if (e.getButton() == MouseEvent.BUTTON3) { - caseCliquee.changerMarqueur(); - fenetre.rafraichirCase(i, j); - fenetre.mettreAJourCompteur(); - } + for (int ligne = 0; ligne < grille.getLignes(); ligne++) { + for (int colonne = 0; colonne < grille.getColonnes(); colonne++) { + if (evenement.getSource() == fenetre.getBouton(ligne, colonne)) { + traiterClicSurCase(evenement, ligne, colonne); return; } } } } - public void mousePressed(MouseEvent e) {} - public void mouseReleased(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} + /** + * Identifie le type de clic (gauche ou droit) et appelle la méthode correspondante. + * @param evenement l'action de la souris. + * @param ligne la ligne de la case cliquée. + * @param colonne la colonne de la case cliquée. + */ + private void traiterClicSurCase(MouseEvent evenement, int ligne, int colonne) { + Case caseCliquee = grille.getCase(ligne, colonne); + if (caseCliquee.isRevelee()) { + return; + } + + if (evenement.getButton() == BOUTON_GAUCHE) { + gererClicGauche(caseCliquee, ligne, colonne); + } else if (evenement.getButton() == BOUTON_DROIT) { + gererClicDroit(caseCliquee, ligne, colonne); + } + } + + /** + * Tente de révéler une case et gère l'éventuelle explosion. + * @param caseCliquee la case logique ciblée. + * @param ligne index de ligne. + * @param colonne index de colonne. + */ + private void gererClicGauche(Case caseCliquee, int ligne, int colonne) { + if (caseCliquee.getMarqueur() == Case.MARQUEUR_ETOILE) { + return; + } + + boolean aExplose = grille.revelerCase(ligne, colonne); + if (aExplose) { + grille.setPartieTerminee(true); + fenetre.afficherDefaite(ligne, colonne); + JOptionPane.showMessageDialog(fenetre, MESSAGE_DEFAITE); + fenetre.changerBoutonEnRetourMenu(); + } else { + fenetre.rafraichirTouteLaGrille(); + verifierVictoire(); + } + } + + /** + * Teste si toutes les cases saines ont été révélées et annonce la victoire. + */ + private void verifierVictoire() { + if (grille.estGagnee()) { + grille.setPartieTerminee(true); + JOptionPane.showMessageDialog(fenetre, MESSAGE_VICTOIRE); + fenetre.changerBoutonEnRetourMenu(); + } + } + + /** + * Pose, change ou retire un marqueur sur la case ciblée. + * @param caseCliquee la case logique ciblée. + * @param ligne index de ligne. + * @param colonne index de colonne. + */ + private void gererClicDroit(Case caseCliquee, int ligne, int colonne) { + caseCliquee.changerMarqueur(); + fenetre.rafraichirCase(ligne, colonne); + fenetre.mettreAJourCompteur(); + } + + /** @param evenement l'événement souris. */ + public void mousePressed(MouseEvent evenement) {} + /** @param evenement l'événement souris. */ + public void mouseReleased(MouseEvent evenement) {} + /** @param evenement l'événement souris. */ + public void mouseEntered(MouseEvent evenement) {} + /** @param evenement l'événement souris. */ + public void mouseExited(MouseEvent evenement) {} } diff --git a/ControleurJeu.java b/ControleurJeu.java index 85b2070..60e7be9 100644 --- a/ControleurJeu.java +++ b/ControleurJeu.java @@ -1,26 +1,70 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.io.FileOutputStream; +import java.io.ObjectOutputStream; +import java.io.IOException; +import javax.swing.JOptionPane; +/** + * Observateur gérant le bouton situé en bas de la fenêtre de jeu. + */ public class ControleurJeu implements ActionListener { + /** Constante définissant le nom du fichier de sauvegarde. */ + private static final String NOM_FICHIER_SAUVEGARDE = "sauvegarde.dat"; + + /** Constante définissant le code de sortie normal de l'application. */ + private static final int CODE_SORTIE_NORMALE = 0; + + /** Référence à la fenêtre de jeu actuellement ouverte. */ private FenetreJeu fenetre; - public ControleurJeu(FenetreJeu f) { - this.fenetre = f; + /** + * Constructeur de l'écouteur du bouton de jeu. + * @param fenetreJeu la fenêtre à observer. + */ + public ControleurJeu(FenetreJeu fenetreJeu) { + this.fenetre = fenetreJeu; } + /** + * Agit selon l'état de la partie : retourne au menu ou sauvegarde la progression. + * @param evenement le clic sur le bouton. + */ @Override - public void actionPerformed(ActionEvent e) { - // Si la partie est finie, le bouton sert à retourner au menu + public void actionPerformed(ActionEvent evenement) { if (fenetre.getGrille().isPartieTerminee()) { - this.fenetre.dispose(); - MenuPrincipal nouveauMenu = new MenuPrincipal(); - nouveauMenu.setVisible(true); - } - // Sinon (partie en cours), c'est bien le bouton "Sauver et Quitter" - else { - System.out.println("Sauvegarde de la partie en cours... (Bientôt implémenté)"); - System.exit(0); + retournerAuMenu(); + } else { + sauvegarderEtQuitter(); + } + } + + /** + * Ferme la partie et rouvre une nouvelle instance du menu principal. + */ + private void retournerAuMenu() { + this.fenetre.dispose(); + MenuPrincipal nouveauMenu = new MenuPrincipal(); + nouveauMenu.setVisible(true); + } + + /** + * Grave la grille en mémoire dans un fichier et ferme l'application. + */ + private void sauvegarderEtQuitter() { + try { + FileOutputStream fluxSortie = new FileOutputStream(NOM_FICHIER_SAUVEGARDE); + ObjectOutputStream graveurObjet = new ObjectOutputStream(fluxSortie); + + graveurObjet.writeObject(fenetre.getGrille()); + + graveurObjet.close(); + fluxSortie.close(); + System.exit(CODE_SORTIE_NORMALE); + + } catch (IOException exception) { + JOptionPane.showMessageDialog(fenetre, "Erreur lors de la sauvegarde !"); } } } diff --git a/ControleurMenu.java b/ControleurMenu.java index 676d17d..6f64b67 100644 --- a/ControleurMenu.java +++ b/ControleurMenu.java @@ -1,50 +1,135 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JOptionPane; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.IOException; +/** + * Observateur gérant les interactions avec les boutons du menu principal. + */ public class ControleurMenu implements ActionListener { - + + /** Constante définissant la valeur de retour en cas d'annulation ou d'erreur de saisie. */ + private static final int ERREUR_SAISIE = -1; + + /** Constante définissant le nombre minimum de lignes ou colonnes. */ + private static final int DIMENSION_MIN = 4; + + /** Constante définissant le nombre maximum de lignes ou colonnes. */ + private static final int DIMENSION_MAX = 30; + + /** Référence vers la fenêtre du menu. */ private MenuPrincipal menu; - - public ControleurMenu(MenuPrincipal m) { - this.menu = m; + /** + * Constructeur de l'écouteur du menu. + * @param menuPrincipal la fenêtre du menu principal. + */ + public ControleurMenu(MenuPrincipal menuPrincipal) { + this.menu = menuPrincipal; } - @Override + /** + * Déclenchée lors d'un clic sur l'un des boutons du menu. + * @param evenement l'action générée par le clic. + */ + @Override public void actionPerformed(ActionEvent evenement) { - if (evenement.getSource() == this.menu.getBtnNouvellePartie()) { - try { - String strL = JOptionPane.showInputDialog(menu, "Nombre de lignes (entre 4 et 30) :"); - if (strL == null) return; - int l = Integer.parseInt(strL); - - String strC = JOptionPane.showInputDialog(menu, "Nombre de colonnes (entre 4 et 30) :"); - if (strC == null) return; - int c = Integer.parseInt(strC); - - String strM = JOptionPane.showInputDialog(menu, "Nombre total de mines :"); - if (strM == null) return; - int m = Integer.parseInt(strM); - - if (l < 4 || l > 30 || c < 4 || c > 30 || m >= (l * c)) { - JOptionPane.showMessageDialog(menu, "Valeurs incorrectes ! Les dimensions doivent être entre 4 et 30, et les mines inférieures au nombre de cases."); - return; - } - - Grille nouvelleGrille = new Grille(l, c, m); - FenetreJeu fenetre = new FenetreJeu(nouvelleGrille); - fenetre.setVisible(true); - this.menu.dispose(); - - } catch (NumberFormatException ex) { - JOptionPane.showMessageDialog(menu, "Erreur : Vous devez entrer des nombres entiers !"); - } - - } else if (evenement.getSource() == this.menu.getBtnReprendre()) { - System.out.println("Chargement de la sauvegarde..."); - } else if (evenement.getSource() == this.menu.getBtnQuitter()) { + if (evenement.getSource() == this.menu.getBoutonNouvellePartie()) { + lancerNouvellePartie(); + } else if (evenement.getSource() == this.menu.getBoutonReprendre()) { + chargerPartieSauvegardee(); + } else if (evenement.getSource() == this.menu.getBoutonQuitter()) { System.exit(0); } } + + /** + * Demande les dimensions et génère une nouvelle partie si les saisies sont valides. + */ + private void lancerNouvellePartie() { + try { + int nbLignes = demanderValeur("Nombre de lignes (entre 4 et 30) :"); + if (nbLignes == ERREUR_SAISIE) return; + + int nbColonnes = demanderValeur("Nombre de colonnes (entre 4 et 30) :"); + if (nbColonnes == ERREUR_SAISIE) return; + + int nbMines = demanderValeur("Nombre total de mines :"); + if (nbMines == ERREUR_SAISIE) return; + + if (!sontDimensionsValides(nbLignes, nbColonnes, nbMines)) { + JOptionPane.showMessageDialog(menu, "Valeurs incorrectes !"); + return; + } + + demarrerJeu(nbLignes, nbColonnes, nbMines); + + } catch (NumberFormatException exception) { + JOptionPane.showMessageDialog(menu, "Erreur : Vous devez entrer des nombres entiers !"); + } + } + + /** + * Vérifie si les dimensions entrées par l'utilisateur respectent les limites. + * @param lignes le nombre de lignes choisi. + * @param colonnes le nombre de colonnes choisi. + * @param mines le nombre de mines choisi. + * @return true si tout est correct, false sinon. + */ + private boolean sontDimensionsValides(int lignes, int colonnes, int mines) { + boolean lignesInvalides = lignes < DIMENSION_MIN || lignes > DIMENSION_MAX; + boolean colonnesInvalides = colonnes < DIMENSION_MIN || colonnes > DIMENSION_MAX; + boolean minesInvalides = mines >= (lignes * colonnes) || mines <= 0; + + return !lignesInvalides && !colonnesInvalides && !minesInvalides; + } + + /** + * Initialise la fenêtre de jeu avec les paramètres validés et ferme le menu. + * @param lignes le nombre de lignes. + * @param colonnes le nombre de colonnes. + * @param mines le nombre de mines. + */ + private void demarrerJeu(int lignes, int colonnes, int mines) { + Grille nouvelleGrille = new Grille(lignes, colonnes, mines); + FenetreJeu fenetre = new FenetreJeu(nouvelleGrille); + fenetre.setVisible(true); + this.menu.dispose(); + } + + /** + * Ouvre une boîte de dialogue pour demander un entier à l'utilisateur. + * @param message le texte à afficher. + * @return le nombre entier saisi, ou ERREUR_SAISIE en cas d'annulation. + */ + private int demanderValeur(String message) { + String saisie = JOptionPane.showInputDialog(menu, message); + if (saisie == null || saisie.trim().isEmpty()) return ERREUR_SAISIE; + return Integer.parseInt(saisie); + } + + /** + * Tente de lire le fichier de sauvegarde et restaure la partie si possible. + */ + private void chargerPartieSauvegardee() { + try { + FileInputStream fluxEntree = new FileInputStream("sauvegarde.dat"); + ObjectInputStream lecteurObjet = new ObjectInputStream(fluxEntree); + + Grille grilleChargee = (Grille) lecteurObjet.readObject(); + + lecteurObjet.close(); + fluxEntree.close(); + + FenetreJeu fenetre = new FenetreJeu(grilleChargee); + fenetre.rafraichirTouteLaGrille(); + fenetre.setVisible(true); + this.menu.dispose(); + + } catch (IOException | ClassNotFoundException exception) { + JOptionPane.showMessageDialog(menu, "Aucune sauvegarde trouvée ou fichier corrompu !"); + } + } } diff --git a/FenetreJeu.java b/FenetreJeu.java index 2276b32..38ab3f8 100644 --- a/FenetreJeu.java +++ b/FenetreJeu.java @@ -1,112 +1,222 @@ import javax.swing.*; import java.awt.*; +/** + * Fenêtre affichant la partie en cours. + * Gère l'affichage de la grille, du compteur de mines et du bouton de sauvegarde. + */ public class FenetreJeu extends JFrame { - + + /** Position X initiale de la fenêtre de jeu sur l'écran. */ + private static final int POSITION_X = 100; + + /** Position Y initiale de la fenêtre de jeu sur l'écran. */ + private static final int POSITION_Y = 100; + + /** Estimation de la taille en pixels d'une case pour la largeur de la fenêtre. */ + private static final int LARGEUR_CASE_PIXELS = 50; + + /** Estimation de la taille en pixels d'une case pour la hauteur de la fenêtre. */ + private static final int HAUTEUR_CASE_PIXELS = 50; + + /** Espace supplémentaire en hauteur pour afficher l'en-tête et le pied de page. */ + private static final int MARGE_HAUTEUR_PIXELS = 100; + + /** Référence vers le modèle logique du jeu. */ private Grille grille; + + /** Tableau à deux dimensions stockant les composants graphiques des cases. */ private JButton[][] boutons; + + /** Étiquette textuelle affichant le nombre de mines restantes à trouver. */ private JLabel labelCompteur; - private JButton btnActionBas; // NOUVEAU : On garde le bouton en mémoire + + /** Bouton dynamique en bas de la fenêtre (Sauvegarder ou Retour Menu). */ + private JButton boutonActionBas; - public FenetreJeu(Grille g) { + /** + * Constructeur de la fenêtre de jeu. + * @param grilleJeu la grille logique initialisée à afficher. + */ + public FenetreJeu(Grille grilleJeu) { super("Démineur - Partie en cours"); - this.grille = g; - this.boutons = new JButton[g.getLignes()][g.getColonnes()]; + this.grille = grilleJeu; + this.boutons = new JButton[grilleJeu.getLignes()][grilleJeu.getColonnes()]; - this.setSize(600, 600); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - this.setLayout(new BorderLayout()); + + int largeurFenetre = grilleJeu.getColonnes() * LARGEUR_CASE_PIXELS; + int hauteurFenetre = (grilleJeu.getLignes() * HAUTEUR_CASE_PIXELS) + MARGE_HAUTEUR_PIXELS; + + this.setSize(largeurFenetre, hauteurFenetre); + this.setLocation(POSITION_X, POSITION_Y); - this.labelCompteur = new JLabel("Mines restantes : " + g.getMinesRestantes()); - this.labelCompteur.setHorizontalAlignment(SwingConstants.CENTER); - this.labelCompteur.setFont(new Font("Arial", Font.BOLD, 16)); + initialiserEntete(); + initialiserPanneauGrille(); + initialiserBoutonBas(); + } + + /** + * Prépare et ajoute le compteur de mines en haut de la fenêtre. + */ + private void initialiserEntete() { + this.labelCompteur = new JLabel("Mines restantes : " + this.grille.getMinesRestantes()); + this.labelCompteur.setHorizontalAlignment(JLabel.CENTER); this.add(this.labelCompteur, BorderLayout.NORTH); + } + /** + * Prépare et ajoute la zone centrale contenant tous les boutons cliquables. + */ + private void initialiserPanneauGrille() { JPanel panneauGrille = new JPanel(); - panneauGrille.setLayout(new GridLayout(g.getLignes(), g.getColonnes())); - + panneauGrille.setLayout(new GridLayout(this.grille.getLignes(), this.grille.getColonnes())); ControleurGrille controleurSouris = new ControleurGrille(this, this.grille); - for (int i = 0; i < g.getLignes(); i++) { - for (int j = 0; j < g.getColonnes(); j++) { + for (int ligne = 0; ligne < this.grille.getLignes(); ligne++) { + for (int colonne = 0; colonne < this.grille.getColonnes(); colonne++) { JButton boutonCase = new JButton(""); - this.boutons[i][j] = boutonCase; + + this.boutons[ligne][colonne] = boutonCase; panneauGrille.add(boutonCase); boutonCase.addMouseListener(controleurSouris); } } this.add(panneauGrille, BorderLayout.CENTER); + } - // --- MODIFICATION DU BOUTON DU BAS --- - this.btnActionBas = new JButton("Sauver et Quitter"); - this.add(this.btnActionBas, BorderLayout.SOUTH); + /** + * Prépare et ajoute le bouton de contrôle général en bas de la fenêtre. + */ + private void initialiserBoutonBas() { + this.boutonActionBas = new JButton("Sauver et Quitter"); + this.add(this.boutonActionBas, BorderLayout.SOUTH); ControleurJeu controleurQuitter = new ControleurJeu(this); - this.btnActionBas.addActionListener(controleurQuitter); + this.boutonActionBas.addActionListener(controleurQuitter); } - // --- NOUVEAUX OUTILS --- - public JButton getBouton(int l, int c) { return this.boutons[l][c]; } - public Grille getGrille() { return this.grille; } // Permet au contrôleur de lire la grille + /** + * Récupère un bouton graphique spécifique de la grille. + * @param indexLigne la ligne du bouton. + * @param indexColonne la colonne du bouton. + * @return le bouton correspondant. + */ + public JButton getBouton(int indexLigne, int indexColonne) { return this.boutons[indexLigne][indexColonne]; } + /** + * Accède à l'instance de la grille logique. + * @return la grille du jeu. + */ + public Grille getGrille() { return this.grille; } + + /** + * Transforme le bouton de sauvegarde en bouton de retour au menu. + */ public void changerBoutonEnRetourMenu() { - this.btnActionBas.setText("Retour au Menu"); + this.boutonActionBas.setText("Retour au Menu"); } + /** + * Interroge la grille et rafraîchit le texte du compteur de mines. + */ public void mettreAJourCompteur() { this.labelCompteur.setText("Mines restantes : " + this.grille.getMinesRestantes()); } - public void rafraichirCase(int l, int c) { - Case caseLogique = this.grille.getCase(l, c); - JButton btn = this.boutons[l][c]; + /** + * Met à jour l'apparence visuelle d'un bouton selon son état logique. + * @param ligne la ligne du bouton à rafraîchir. + * @param colonne la colonne du bouton à rafraîchir. + */ + public void rafraichirCase(int ligne, int colonne) { + Case caseLogique = this.grille.getCase(ligne, colonne); + JButton bouton = this.boutons[ligne][colonne]; if (caseLogique.isRevelee()) { - btn.setEnabled(false); - if (caseLogique.isMinee()) { - btn.setText("M"); - btn.setBackground(Color.RED); - } else if (caseLogique.getNbMinesVoisines() > 0) { - btn.setText("" + caseLogique.getNbMinesVoisines()); - } else { - btn.setText(""); - } + appliquerStyleCaseRevelee(caseLogique, bouton); } else { - if (caseLogique.getMarqueur() == 1) { - btn.setText("★"); - } else if (caseLogique.getMarqueur() == 2) { - btn.setText("?"); - } else { - btn.setText(""); - } + appliquerStyleCaseCachee(caseLogique, bouton); } } + /** + * Applique les couleurs et le texte d'une case qui vient d'être révélée. + * @param caseLogique la donnée de la case. + * @param bouton le composant graphique associé. + */ + private void appliquerStyleCaseRevelee(Case caseLogique, JButton bouton) { + if (caseLogique.isMinee()) { + bouton.setText("M"); + bouton.setBackground(Color.RED); + } else if (caseLogique.getNbMinesVoisines() > 0) { + bouton.setText("" + caseLogique.getNbMinesVoisines()); + bouton.setBackground(Color.WHITE); + } else { + bouton.setText(""); + bouton.setBackground(Color.LIGHT_GRAY); + } + } + + /** + * Applique les symboles d'une case qui est encore masquée (marqueurs). + * @param caseLogique la donnée de la case. + * @param bouton le composant graphique associé. + */ + private void appliquerStyleCaseCachee(Case caseLogique, JButton bouton) { + if (caseLogique.getMarqueur() == Case.MARQUEUR_ETOILE) { + bouton.setText("*"); + } else if (caseLogique.getMarqueur() == Case.MARQUEUR_INTERROGATION) { + bouton.setText("?"); + } else { + bouton.setText(""); + } + } + + /** + * Force la mise à jour visuelle de toutes les cases de la grille. + */ public void rafraichirTouteLaGrille() { - for (int i = 0; i < grille.getLignes(); i++) { - for (int j = 0; j < grille.getColonnes(); j++) { - rafraichirCase(i, j); + for (int ligne = 0; ligne < grille.getLignes(); ligne++) { + for (int colonne = 0; colonne < grille.getColonnes(); colonne++) { + rafraichirCase(ligne, colonne); } } } - public void afficherDefaite(int lExplose, int cExplose) { - for (int i = 0; i < grille.getLignes(); i++) { - for (int j = 0; j < grille.getColonnes(); j++) { - Case c = grille.getCase(i, j); - JButton btn = boutons[i][j]; - btn.setEnabled(false); - - if (i == lExplose && j == cExplose) { - btn.setText("BOUM"); - btn.setBackground(Color.RED); - } else if (c.isMinee() && c.getMarqueur() != 1) { - btn.setText("M"); - btn.setBackground(Color.ORANGE); - } else if (!c.isMinee() && c.getMarqueur() == 1) { - btn.setText("X"); - btn.setBackground(Color.YELLOW); - } + /** + * Affiche l'état de défaite en révélant les erreurs et les mines manquantes. + * @param ligneExplosion la ligne de la mine déclenchée. + * @param colonneExplosion la colonne de la mine déclenchée. + */ + public void afficherDefaite(int ligneExplosion, int colonneExplosion) { + for (int ligne = 0; ligne < grille.getLignes(); ligne++) { + for (int colonne = 0; colonne < grille.getColonnes(); colonne++) { + Case caseActuelle = grille.getCase(ligne, colonne); + JButton bouton = boutons[ligne][colonne]; + appliquerStyleDefaite(caseActuelle, bouton, ligne, colonne, ligneExplosion, colonneExplosion); } } } + + /** + * Détermine la couleur spécifique d'une case en fin de partie (erreur, oubli, ou explosion). + * @param caseActuelle la donnée de la case. + * @param bouton le composant graphique. + * @param ligne la ligne en cours de parcours. + * @param colonne la colonne en cours de parcours. + * @param ligneExplosion la ligne de la mine fatale. + * @param colonneExplosion la colonne de la mine fatale. + */ + private void appliquerStyleDefaite(Case caseActuelle, JButton bouton, int ligne, int colonne, int ligneExplosion, int colonneExplosion) { + if (ligne == ligneExplosion && colonne == colonneExplosion) { + bouton.setText("BOUM"); + bouton.setBackground(Color.RED); + } else if (caseActuelle.isMinee() && caseActuelle.getMarqueur() != Case.MARQUEUR_ETOILE) { + bouton.setText("M"); + bouton.setBackground(Color.ORANGE); + } else if (!caseActuelle.isMinee() && caseActuelle.getMarqueur() == Case.MARQUEUR_ETOILE) { + bouton.setText("X"); + bouton.setBackground(Color.YELLOW); + } + } } diff --git a/Grille.java b/Grille.java index 37b9afa..022fa38 100644 --- a/Grille.java +++ b/Grille.java @@ -1,24 +1,44 @@ import java.io.Serializable; -import java.util.Random; +/** + * Gère la logique principale du Démineur (le plateau de jeu). + * Initialise les cases, place les mines et contient l'algorithme de révélation. + */ public class Grille implements Serializable { + + /** Tableau à deux dimensions contenant toutes les cases du jeu. */ private Case[][] plateau; + + /** Nombre total de lignes de la grille. */ private int lignes; + + /** Nombre total de colonnes de la grille. */ private int colonnes; + + /** Nombre total de mines cachées dans la grille. */ private int mines; + + /** Indique si la partie est bloquée (gagnée ou perdue). */ private boolean partieTerminee; - public Grille(int l, int c, int m) { - this.lignes = l; - this.colonnes = c; - this.mines = m; + /** + * Constructeur de la grille. + * Crée le plateau, place les mines aléatoirement et calcule les voisins. + * @param nbLignes le nombre de lignes souhaité. + * @param nbColonnes le nombre de colonnes souhaité. + * @param nbMines le nombre de mines à placer. + */ + public Grille(int nbLignes, int nbColonnes, int nbMines) { + this.lignes = nbLignes; + this.colonnes = nbColonnes; + this.mines = nbMines; this.partieTerminee = false; this.plateau = new Case[this.lignes][this.colonnes]; - for (int i = 0; i < this.lignes; i++) { - for (int j = 0; j < this.colonnes; j++) { - this.plateau[i][j] = new Case(); + for (int ligne = 0; ligne < this.lignes; ligne++) { + for (int colonne = 0; colonne < this.colonnes; colonne++) { + this.plateau[ligne][colonne] = new Case(); } } @@ -26,18 +46,47 @@ public class Grille implements Serializable { calculerVoisins(); } + /** + * Récupère le nombre de lignes de la grille. + * @return le nombre de lignes. + */ public int getLignes() { return this.lignes; } + + /** + * Récupère le nombre de colonnes de la grille. + * @return le nombre de colonnes. + */ public int getColonnes() { return this.colonnes; } - public Case getCase(int l, int c) { return this.plateau[l][c]; } + + /** + * Accède à une case spécifique du plateau. + * @param indexLigne la ligne de la case cible. + * @param indexColonne la colonne de la case cible. + * @return l'objet Case correspondant. + */ + public Case getCase(int indexLigne, int indexColonne) { return this.plateau[indexLigne][indexColonne]; } + + /** + * Vérifie si la partie est verrouillée en état de fin. + * @return true si terminée, false si en cours. + */ public boolean isPartieTerminee() { return this.partieTerminee; } + + /** + * Modifie l'état de fin de partie (verrouillage de la grille). + * @param terminee true pour bloquer le jeu, false pour le débloquer. + */ public void setPartieTerminee(boolean terminee) { this.partieTerminee = terminee; } - // --- NOUVELLE MÉTHODE : Calcul des mines restantes --- + /** + * Calcule le nombre de mines restantes à trouver selon les drapeaux posés. + * @return la soustraction des mines totales par le nombre d'étoiles posées. + */ public int getMinesRestantes() { int drapeaux = 0; - for (int i = 0; i < this.lignes; i++) { - for (int j = 0; j < this.colonnes; j++) { - if (this.plateau[i][j].getMarqueur() == 1) { // 1 = étoile + for (int ligne = 0; ligne < this.lignes; ligne++) { + for (int colonne = 0; colonne < this.colonnes; colonne++) { + if (this.plateau[ligne][colonne].getMarqueur() == Case.MARQUEUR_ETOILE) { drapeaux++; } } @@ -45,54 +94,68 @@ public class Grille implements Serializable { return this.mines - drapeaux; } + /** + * Place aléatoirement le nombre défini de mines sur le plateau vide. + */ private void placerMines() { - Random rand = new Random(); int minesPlacees = 0; while (minesPlacees < this.mines) { - int l = rand.nextInt(this.lignes); - int c = rand.nextInt(this.colonnes); - if (!this.plateau[l][c].isMinee()) { - this.plateau[l][c].setMinee(true); + int ligneAleatoire = (int) (Math.random() * this.lignes); + int colonneAleatoire = (int) (Math.random() * this.colonnes); + + if (!this.plateau[ligneAleatoire][colonneAleatoire].isMinee()) { + this.plateau[ligneAleatoire][colonneAleatoire].setMinee(true); minesPlacees++; } } } + /** + * Parcourt la grille pour calculer le nombre de mines autour de chaque case non-minée. + */ private void calculerVoisins() { - for (int i = 0; i < this.lignes; i++) { - for (int j = 0; j < this.colonnes; j++) { - if (!this.plateau[i][j].isMinee()) { + for (int ligne = 0; ligne < this.lignes; ligne++) { + for (int colonne = 0; colonne < this.colonnes; colonne++) { + if (!this.plateau[ligne][colonne].isMinee()) { int compteMines = 0; - for (int di = -1; di <= 1; di++) { - for (int dj = -1; dj <= 1; dj++) { - int voisinI = i + di; - int voisinJ = j + dj; - if (voisinI >= 0 && voisinI < this.lignes && voisinJ >= 0 && voisinJ < this.colonnes) { - if (this.plateau[voisinI][voisinJ].isMinee()) { + for (int decalageLigne = -1; decalageLigne <= 1; decalageLigne++) { + for (int decalageColonne = -1; decalageColonne <= 1; decalageColonne++) { + int voisinLigne = ligne + decalageLigne; + int voisinColonne = colonne + decalageColonne; + if (voisinLigne >= 0 && voisinLigne < this.lignes && voisinColonne >= 0 && voisinColonne < this.colonnes) { + if (this.plateau[voisinLigne][voisinColonne].isMinee()) { compteMines++; } } } } - this.plateau[i][j].setNbMinesVoisines(compteMines); + this.plateau[ligne][colonne].setNbMinesVoisines(compteMines); } } } } - public boolean revelerCase(int l, int c) { - Case caseCliquee = this.plateau[l][c]; - if (caseCliquee.isRevelee() || caseCliquee.getMarqueur() == 1) return false; + /** + * Révèle une case et déclenche une réaction en chaîne récursive si elle vaut 0. + * @param cibleLigne la ligne de la case à révéler. + * @param cibleColonne la colonne de la case à révéler. + * @return true si une mine a explosé, false sinon. + */ + public boolean revelerCase(int cibleLigne, int cibleColonne) { + Case caseCliquee = this.plateau[cibleLigne][cibleColonne]; + + if (caseCliquee.isRevelee() || caseCliquee.getMarqueur() == Case.MARQUEUR_ETOILE) return false; + caseCliquee.setRevelee(true); if (caseCliquee.isMinee()) return true; if (caseCliquee.getNbMinesVoisines() == 0) { - for (int di = -1; di <= 1; di++) { - for (int dj = -1; dj <= 1; dj++) { - int voisinL = l + di; - int voisinC = c + dj; - if (voisinL >= 0 && voisinL < this.lignes && voisinC >= 0 && voisinC < this.colonnes) { - revelerCase(voisinL, voisinC); + for (int decalageLigne = -1; decalageLigne <= 1; decalageLigne++) { + for (int decalageColonne = -1; decalageColonne <= 1; decalageColonne++) { + int voisinLigne = cibleLigne + decalageLigne; + int voisinColonne = cibleColonne + decalageColonne; + if (voisinLigne >= 0 && voisinLigne < this.lignes && voisinColonne >= 0 && voisinColonne < this.colonnes) { + revelerCase(voisinLigne, voisinColonne); } } } @@ -100,11 +163,15 @@ public class Grille implements Serializable { return false; } + /** + * Vérifie si le joueur a révélé toutes les cases non-minées du plateau. + * @return true si la partie est gagnée, false si elle est toujours en cours. + */ public boolean estGagnee() { int casesRevelees = 0; - for (int i = 0; i < this.lignes; i++) { - for (int j = 0; j < this.colonnes; j++) { - if (this.plateau[i][j].isRevelee()) casesRevelees++; + for (int ligne = 0; ligne < this.lignes; ligne++) { + for (int colonne = 0; colonne < this.colonnes; colonne++) { + if (this.plateau[ligne][colonne].isRevelee()) casesRevelees++; } } return casesRevelees == ((this.lignes * this.colonnes) - this.mines); diff --git a/MenuPrincipal.java b/MenuPrincipal.java index d09754c..12f271d 100644 --- a/MenuPrincipal.java +++ b/MenuPrincipal.java @@ -1,69 +1,120 @@ import javax.swing.*; import java.awt.*; +import java.io.FileInputStream; /** - * Fenêtre principale affichant le menu du Démineur. + * Fenêtre du menu principal s'affichant au lancement de l'application. + * Permet de démarrer, reprendre ou quitter une partie. */ public class MenuPrincipal extends JFrame { - - private JButton btnNouvellePartie; - private JButton btnReprendre; - private JButton btnQuitter; + + /** Largeur de la fenêtre en pixels. */ + private static final int LARGEUR_FENETRE = 400; + + /** Hauteur de la fenêtre en pixels. */ + private static final int HAUTEUR_FENETRE = 300; + + /** Position X initiale de la fenêtre sur l'écran. */ + private static final int POSITION_X = 100; + + /** Position Y initiale de la fenêtre sur l'écran. */ + private static final int POSITION_Y = 100; + + /** Nombre de lignes pour la disposition des boutons. */ + private static final int LIGNES_GRILLE = 3; + + /** Nombre de colonnes pour la disposition des boutons. */ + private static final int COLONNES_GRILLE = 1; + + /** Bouton permettant de lancer une nouvelle partie personnalisée. */ + private JButton boutonNouvellePartie; + + /** Bouton permettant de recharger la dernière sauvegarde trouvée. */ + private JButton boutonReprendre; + + /** Bouton permettant de fermer complètement l'application. */ + private JButton boutonQuitter; /** * Constructeur du menu principal. + * Initialise l'interface graphique en respectant les méthodes du cours. */ public MenuPrincipal() { - // Configuration de la fenêtre (héritée de JFrame) super("Démineur - Menu Principal"); - this.setSize(300, 250); + this.setSize(LARGEUR_FENETRE, HAUTEUR_FENETRE); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - - // Mise en page principale - this.setLayout(new BorderLayout()); + this.setLocation(POSITION_X, POSITION_Y); - // Titre en haut - JLabel etiquetteTitre = new JLabel("DÉMINEUR"); - etiquetteTitre.setHorizontalAlignment(JLabel.CENTER); - this.add(etiquetteTitre, BorderLayout.NORTH); - - // Panneau pour les boutons au centre - JPanel panneauBoutons = new JPanel(); - panneauBoutons.setLayout(new GridLayout(3, 1)); // 3 lignes, 1 colonne - - // Création des boutons - this.btnNouvellePartie = new JButton("Commencer une nouvelle partie"); - this.btnReprendre = new JButton("Reprendre la partie précédente"); - this.btnQuitter = new JButton("Quitter"); - - // Désactivation du bouton "Reprendre" par défaut (comme demandé dans le sujet) - this.btnReprendre.setEnabled(false); - - // Ajout des boutons au panneau - panneauBoutons.add(this.btnNouvellePartie); - panneauBoutons.add(this.btnReprendre); - panneauBoutons.add(this.btnQuitter); - - // Ajout du panneau au centre de la fenêtre - this.add(panneauBoutons, BorderLayout.CENTER); - - // Connexion avec l'observateur (le contrôleur) - ControleurMenu observateur = new ControleurMenu(this); - this.btnNouvellePartie.addActionListener(observateur); - this.btnReprendre.addActionListener(observateur); - this.btnQuitter.addActionListener(observateur); + initialiserPanneauBoutons(); } - // --- Accesseurs pour que l'observateur puisse identifier les boutons --- - public JButton getBtnNouvellePartie() { return this.btnNouvellePartie; } - public JButton getBtnReprendre() { return this.btnReprendre; } - public JButton getBtnQuitter() { return this.btnQuitter; } + /** + * Crée le panneau, instancie les boutons et les ajoute à l'interface. + */ + private void initialiserPanneauBoutons() { + JPanel panneauBoutons = new JPanel(); + panneauBoutons.setLayout(new GridLayout(LIGNES_GRILLE, COLONNES_GRILLE)); + + this.boutonNouvellePartie = new JButton("Nouvelle Partie"); + this.boutonReprendre = new JButton("Reprendre une partie"); + this.boutonQuitter = new JButton("Quitter"); + + panneauBoutons.add(this.boutonNouvellePartie); + ajouterBoutonReprendreSiSauvegarde(panneauBoutons); + panneauBoutons.add(this.boutonQuitter); + + this.add(panneauBoutons, BorderLayout.CENTER); + + connecterControleur(); + } /** - * Méthode principale pour lancer le programme. - * @param args arguments de la ligne de commande (inutilisés) + * Tente de lire le fichier de sauvegarde. Si le fichier existe, + * le bouton "Reprendre" est ajouté au panneau. Sinon, l'erreur est ignorée. + * @param panneau le panneau graphique qui contiendra le bouton. */ - public static void main(String[] args) { + private void ajouterBoutonReprendreSiSauvegarde(JPanel panneau) { + try { + FileInputStream testLecture = new FileInputStream("sauvegarde.dat"); + testLecture.close(); + panneau.add(this.boutonReprendre); + } catch (Exception exception) { + } + } + + /** + * Instancie le contrôleur et l'attache aux différents boutons. + */ + private void connecterControleur() { + ControleurMenu controleur = new ControleurMenu(this); + this.boutonNouvellePartie.addActionListener(controleur); + this.boutonReprendre.addActionListener(controleur); + this.boutonQuitter.addActionListener(controleur); + } + + /** + * Accède au bouton de nouvelle partie. + * @return le bouton "Nouvelle Partie". + */ + public JButton getBoutonNouvellePartie() { return this.boutonNouvellePartie; } + + /** + * Accède au bouton de reprise de partie. + * @return le bouton "Reprendre une partie". + */ + public JButton getBoutonReprendre() { return this.boutonReprendre; } + + /** + * Accède au bouton pour quitter le jeu. + * @return le bouton "Quitter". + */ + public JButton getBoutonQuitter() { return this.boutonQuitter; } + + /** + * Point d'entrée principal du programme. + * @param arguments les arguments passés en ligne de commande. + */ + public static void main(String[] arguments) { MenuPrincipal menu = new MenuPrincipal(); menu.setVisible(true); }