Derniere version du jeu

This commit is contained in:
lecointre
2026-04-12 16:40:07 +02:00
parent 43cc3c1145
commit 86ff392e75
7 changed files with 734 additions and 244 deletions
+69 -5
View File
@@ -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;
}
}
+111 -42
View File
@@ -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) {}
}
+56 -12
View File
@@ -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 !");
}
}
}
+121 -36
View File
@@ -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 !");
}
}
}
+172 -62
View File
@@ -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);
}
}
}
+107 -40
View File
@@ -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);
+98 -47
View File
@@ -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);
}