diff --git a/FenetreJeu.java b/FenetreJeu.java index bc36998..0dfd38d 100644 --- a/FenetreJeu.java +++ b/FenetreJeu.java @@ -1,15 +1,18 @@ import javax.swing.*; import java.awt.*; - +/** + * Fenêtre principale pendant une partie de Démineur. + */ public class FenetreJeu extends JFrame { private Grille grille; + private JButton[][] boutons; - public FenetreJeu(Grille g) { super("Démineur - Partie en cours"); this.grille = g; + this.boutons = new JButton[g.getLignes()][g.getColonnes()]; this.setSize(600, 600); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); @@ -18,18 +21,67 @@ public class FenetreJeu extends JFrame { JPanel panneauGrille = new JPanel(); panneauGrille.setLayout(new GridLayout(g.getLignes(), g.getColonnes())); + ControleurGrille controleurSouris = new ControleurGrille(this, this.grille); + for (int i = 0; i < g.getLignes(); i++) { for (int j = 0; j < g.getColonnes(); j++) { - JButton boutonCase = new JButton("?"); + JButton boutonCase = new JButton(""); + this.boutons[i][j] = boutonCase; panneauGrille.add(boutonCase); + + boutonCase.addMouseListener(controleurSouris); } } this.add(panneauGrille, BorderLayout.CENTER); JButton btnSauverQuitter = new JButton("Sauver et Quitter"); this.add(btnSauverQuitter, BorderLayout.SOUTH); - - ControleurJeu controleur = new ControleurJeu(this); - btnSauverQuitter.addActionListener(controleur); + ControleurJeu controleurQuitter = new ControleurJeu(this); + btnSauverQuitter.addActionListener(controleurQuitter); } -} + + public JButton getBouton(int l, int c) { + return this.boutons[l][c]; + } + + /** + * Met à jour le visuel d'un bouton en fonction de l'état de sa Case logique. + */ + public void rafraichirCase(int l, int c) { + Case caseLogique = this.grille.getCase(l, c); + JButton btn = this.boutons[l][c]; + + 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(""); + } + } else { + if (caseLogique.getMarqueur() == 1) { + btn.setText("★"); + } else if (caseLogique.getMarqueur() == 2) { + btn.setText("?"); + } else { + btn.setText(""); + } + } + } + + /** + * Rafraîchit l'affichage de tous les boutons de la grille d'un seul coup. + * Indispensable après une réaction en chaîne. + */ + public void rafraichirTouteLaGrille() { + for (int i = 0; i < grille.getLignes(); i++) { + for (int j = 0; j < grille.getColonnes(); j++) { + rafraichirCase(i, j); + } + } + } +} // <--- L'accolade finale est bien ici maintenant ! diff --git a/Grille.java b/Grille.java index f8999e9..04d421d 100644 --- a/Grille.java +++ b/Grille.java @@ -1,16 +1,18 @@ import java.io.Serializable; import java.util.Random; +/** + * Représente le plateau de jeu contenant les cases. + */ public class Grille implements Serializable { private Case[][] plateau; private int lignes; private int colonnes; private int mines; - public int getLignes() { return this.lignes; } - public int getColonnes() { return this.colonnes; } - - + /** + * Initialise une nouvelle grille et place les mines. + */ public Grille(int l, int c, int m) { this.lignes = l; this.colonnes = c; @@ -18,11 +20,141 @@ public class Grille implements Serializable { this.plateau = new Case[this.lignes][this.colonnes]; + // 1. Initialisation : on remplit avec des cases vides for (int i = 0; i < this.lignes; i++) { for (int j = 0; j < this.colonnes; j++) { this.plateau[i][j] = new Case(); } } - + + // 2. On appelle nos nouvelles méthodes + placerMines(); + calculerVoisins(); } -} + + // --- Les Getters --- + public int getLignes() { return this.lignes; } + public int getColonnes() { return this.colonnes; } + + /** + * Permet d'accéder à une case spécifique de la grille. + */ + public Case getCase(int l, int c) { + return this.plateau[l][c]; + } + + // --- Les algorithmes du jeu --- + + /** + * Place les mines aléatoirement dans le tableau. + */ + private void placerMines() { + Random rand = new Random(); + int minesPlacees = 0; + + // Tant qu'on n'a pas placé toutes les mines demandées... + while (minesPlacees < this.mines) { + int l = rand.nextInt(this.lignes); + int c = rand.nextInt(this.colonnes); + + // Si la case n'est pas déjà minée, on y met une mine + if (!this.plateau[l][c].isMinee()) { + this.plateau[l][c].setMinee(true); + minesPlacees++; + } + } + } + + /** + * Calcule le nombre de mines autour de chaque case. + */ + private void calculerVoisins() { + for (int i = 0; i < this.lignes; i++) { + for (int j = 0; j < this.colonnes; j++) { + + // Si la case elle-même n'est pas une mine + if (!this.plateau[i][j].isMinee()) { + int compteMines = 0; + + // On parcourt les 8 cases autour (de -1 à +1 en ligne et colonne) + for (int di = -1; di <= 1; di++) { + for (int dj = -1; dj <= 1; dj++) { + int voisinI = i + di; + int voisinJ = j + dj; + + // On vérifie qu'on ne sort pas du tableau ! + // (Pour éviter l'ArrayIndexOutOfBoundsException vu dans ton cours) + if (voisinI >= 0 && voisinI < this.lignes && voisinJ >= 0 && voisinJ < this.colonnes) { + if (this.plateau[voisinI][voisinJ].isMinee()) { + compteMines++; + } + } + } + } + // On enregistre le chiffre dans la case + this.plateau[i][j].setNbMinesVoisines(compteMines); + } + } + } + } + + /** + * Révèle une case et déclenche une réaction en chaîne si elle vaut 0. + * C'est l'algorithme récursif principal du jeu ! + * @return true si on a cliqué sur une mine (défaite), false sinon. + */ + public boolean revelerCase(int l, int c) { + Case caseCliquee = this.plateau[l][c]; + + // Conditions d'arrêt : on s'arrête si la case est déjà révélée ou a une étoile + if (caseCliquee.isRevelee() || caseCliquee.getMarqueur() == 1) { + return false; + } + + // On révèle la case + caseCliquee.setRevelee(true); + + // Si c'est une mine, c'est perdu ! + if (caseCliquee.isMinee()) { + return true; + } + + // RÉACTION EN CHAÎNE : Si c'est un 0, on révèle les voisins + 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; + + // On vérifie qu'on ne sort pas des bords du tableau + if (voisinL >= 0 && voisinL < this.lignes && voisinC >= 0 && voisinC < this.colonnes) { + // LA RÉCURSIVITÉ EST ICI : la fonction s'appelle elle-même + revelerCase(voisinL, voisinC); + } + } + } + } + + return false; // Pas de mine explosée + } + /** + * Vérifie si le joueur a gagné la partie. + * @return true si toutes les cases sans mine sont révélées, false sinon. + */ + public boolean estGagnee() { + int casesRevelees = 0; + + // On compte combien de cases le joueur a réussi à révéler + for (int i = 0; i < this.lignes; i++) { + for (int j = 0; j < this.colonnes; j++) { + if (this.plateau[i][j].isRevelee()) { + casesRevelees++; + } + } + } + + // C'est gagné si le nombre de cases révélées correspond au total des cases vides + int casesVidesTotales = (this.lignes * this.colonnes) - this.mines; + return casesRevelees == casesVidesTotales; + } +} diff --git a/Makefile b/Makefile index f551980..18b7c76 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ JC = javac JFLAGS = -implicit:none -all: MenuPrincipal.class Grille.class +all: MenuPrincipal.class Case.class: Case.java $(JC) $(JFLAGS) Case.java @@ -9,8 +9,9 @@ Case.class: Case.java Grille.class: Grille.java Case.class $(JC) $(JFLAGS) Grille.java -FenetreJeu.class: FenetreJeu.java Grille.class ControleurJeu.java - $(JC) $(JFLAGS) FenetreJeu.java ControleurJeu.java + +FenetreJeu.class: FenetreJeu.java ControleurJeu.java ControleurGrille.java Grille.class + $(JC) $(JFLAGS) FenetreJeu.java ControleurJeu.java ControleurGrille.java MenuPrincipal.class: MenuPrincipal.java ControleurMenu.java FenetreJeu.class $(JC) $(JFLAGS) MenuPrincipal.java ControleurMenu.java