Aller au contenu principal

🧪 TP4 – Démineur


🎯 Objectif du TP4

Réaliser un jeu de Démineur en C# Windows Forms.

Ce travail te fera passer de la console aux interfaces graphiques (GUI) tout en investissant les notions de tableaux 2D, de boucles, de conditions et de lecture/écriture de fichiers.

Fonctionnalités attendues

  • Menu avec raccourcis clavier :
    • Cliquer sur Nouvelle partie démarre une nouvelle partie.
    • Cliquer sur Quitter ferme l'application.
  • 🧩 Grille de jeu composée de boutons (une case = un bouton)
  • 🎚️ Niveau de difficulté : 9 x 9 avec 10 mines
  • 📋 Barre de menu
  • 📐 Redimensionnement automatique de la fenêtre selon la taille de la grille

🧑‍💻 Création du projet Windows Forms

Note

Créer un projet Application Windows Forms (.NET).

  • Langage : C#
  • Nom du projet : par exemple TP4_Demineur

Étape 1 - Préparer la structure du projet

🎯 Objectif

Mettre en place les variables globales et les premiers éléments nécessaires au Démineur :

  • Les tableaux 2D pour représenter le jeu
  • Les constantes liées aux niveaux de difficulté
  • Une fonction Tests() pour vérifier progressivement le comportement du programme (comme aux TP2 et TP3).

1️⃣ Variables globales et constantes

Ajouter dans ton fichier Form1.cs (à l’intérieur de la classe Form1, mais en dehors des méthodes) :

// Taille de la grille et nombre de mines
const int COLONNES_GRILLE = 9;
const int LIGNES_GRILLE = 9;
const int NOMBRE_MINES = 10;

// Valeurs pour le contenu des cases
const int CASE_VIDE = 0; // aucune mine autour
const int CASE_MINE = -1; // case contenant une mine

// Grilles de jeu
int[,] grilleMines; // contient mines / nombres
bool[,] grilleRevelee; // indique si une case est révélée
Button[,] grilleBoutons; // boutons affichés à l'écran

2️⃣ Méthode Tests()

Ajoute une méthode simple pour tester progressivement le contenu des tableaux.

private void Tests()
{
// Exemple : afficher quelques infos dans la console de sortie
Console.WriteLine($"Taille : {colonnesGrille} x {lignesGrille}");
Console.WriteLine($"Nombre de mines : {nombreMines}");
}

Appelle Tests() depuis le constructeur de Form1 après InitializeComponent() :

public Form1()
{
InitializeComponent();
Tests();
}
Où voir Console.WriteLine ?

Dans un projet Windows Forms, tu peux ouvrir la fenêtre de sortie de Visual Studio pour voir le texte envoyé par Console.WriteLine.

✅ Résultat attendu

  • Le projet compile sans erreur.
  • Au démarrage, la fenêtre s’ouvre et des informations de test s’affichent dans la sortie.

Étape 2 - Créer la barre de menu

🎯 Objectif

Ajouter une barre de menus qui donnera accès aux principales fonctionnalités du jeu.


1️⃣ Ajouter un MenuStrip

Dans le concepteur Windows Forms :

  1. Glisser un contrôle MenuStrip sur le formulaire.
  2. Renommer-le (propriété Name) en menuPrincipal.
  3. Ajouter les menus suivants :
    • Jeu
      • Nouvelle partie (raccourci : Ctrl+N)
      • Quitter (raccourci : Ctrl+Q)

Pour chaque élément, tu peux double-cliquer pour générer un gestionnaire d'évènement Click.


2️⃣ Code minimal pour les menus

Exemple de pseudo-code pour les gestionnaires d'événements dans Form1 :

Quand on clique sur "Nouvelle partie" :
→ lancer l'initialisation de la grille et commencer une partie

Quand on clique sur "Quitter" :
→ fermer la fenêtre principale de l'application

✅ Résultat attendu

  • Cliquer sur Nouvelle partie affiche pour l'instant un comportement simple (par exemple un message) ou ne fait rien si la logique n'est pas encore codée.
  • Cliquer sur Quitter ferme l'application.

Étape 3 - Générer dynamiquement la grille de boutons

🎯 Objectif

Créer une grille de boutons représentant le plateau du Démineur.

Chaque case du tableau correspond à un bouton positionné dynamiquement sur le formulaire.


📚 Notions importantes : Ajouter un contrôle par le code

Dans Windows Forms, on peut créer et ajouter des contrôles (boutons, labels, etc.) dynamiquement depuis le code plutôt que depuis le concepteur visuel.

Créer et ajouter un bouton au formulaire

int tailleBouton = 30;
int margeX = 10;
int margeY = 25;
// 1. Créer un nouveau bouton
Button monBouton = new Button();

// 2. Configurer ses propriétés
monBouton.Text = "";
monBouton.Width = tailleBouton;
monBouton.Height = tailleBouton;
monBouton.Location = new Point(tailleBouton*colonne+margeX, tailleBouton*ligne+margeY); // Emplacement du bouton dans le formulaire

// 3. Brancher un événement au clic et au clic droit
monBouton.Click += MonBouton_Click;
monBouton.MouseUp += MonBouton_RightClick;

// 4. Ajouter le bouton au formulaire
this.Controls.Add(monBouton);

Utiliser la propriété Tag pour stocker des données

La propriété Tag permet d'associer n'importe quelle donnée à un contrôle. C'est très utile pour retrouver des informations lors d'un événement (par exemple, savoir quelle case a été cliquée).

// Stocker une position (x, y) dans le Tag
// ou position (ligne, colonne) du bouton
// le but étant de retrouver cette case dans la grille lors d'un clic
monBouton.Tag = new Point(x, y);

Récupérer le Tag lors d'un clic

private void MonBouton_Click(object sender, EventArgs e)
{
// Récupérer le bouton qui a déclenché l'événement
Button btn = (Button)sender;

// Récupérer la position stockée dans le Tag
Point position = (Point)btn.Tag;

int x = position.X;
int y = position.Y;

MessageBox.Show($"Case cliquée : ({x}, {y})");
}
Pourquoi utiliser Tag ?

Quand on génère plusieurs boutons dans une boucle, ils partagent tous le même gestionnaire d'événement. Le Tag permet de savoir quel bouton précis a été cliqué et d'agir en conséquence.


1️⃣ Préparer une fonction InitialiserGrille()

Dans Form1.cs :

Fonction InitialiserGrille :
→ Initialiser grilleMines à la bonne taille
→ Initialiser grilleRevelee à la bonne taille
→ Initialiser grilleBoutons à la bonne taille

→ Effacer tous les contrôles du formulaire
→ Réajouter la barre de menu en haut

→ Définir la taille d'une case en pixels (par ex. 30)
→ Définir une marge autour de la grille (par ex. 10)

Pour chaque ligne de 0 à nombre de lignes - 1 :
Pour chaque colonne de 0 à nombre de colonnes - 1 :
→ Créer un nouveau bouton
→ Fixer sa largeur et sa hauteur à la taille d'une case

→ Calculer sa position en X en fonction de la colonne, de la taille de la case et de la marge
→ Calculer sa position en Y en fonction de la ligne, de la taille de la case, de la marge et de la hauteur du menu

→ Associer au bouton sa position (ligne, colonne) dans le Tag pour la retrouver lors du clic
→ Brancher le gestionnaire d'événement de clic (Bouton_Click)

→ Ajouter le bouton sur le formulaire
→ Ajouter le bouton à grilleBoutons

→ Ajuster la largeur de la fenêtre en fonction du nombre de colonnes et de la taille des cases
→ Ajuster la hauteur de la fenêtre en fonction du nombre de lignes, de la taille des cases et de la hauteur du menu

Ajoute ensuite une méthode pour gérer le clic sur une case (en pseudo‑code) :

Quand on clique sur un bouton de la grille (Bouton_Click) :
→ Récupérer la case cliquée (le bouton qui a envoyé l'événement)
→ Lire, dans le Tag du bouton, la position (ligne, colonne)
→ Pour l’instant : afficher la position de la case cliquée sous la forme "(ligne, colonne)"
→ (à la prochaine étape) appeler une fonction qui révélera la case dans la grille du démineur

✅ Résultat attendu

  • Cliquer sur Nouvelle partie → la fenêtre affiche une grille de boutons 9x9.
  • Cliquer sur une case montre un message avec (ligne, colonne).

Étape 4 - Placer les mines et calculer les nombres

🎯 Objectif

  • Placer aléatoirement les mines dans grilleMines.
  • Calculer, pour chaque case non-mine, le nombre de mines adjacentes.

1️⃣ Fonction PlacerMines()

Fonction PlacerMines :
→ Initialiser un compteur « minesPlacees » à 0

Tant que « minesPlacees » est strictement inférieur à « NOMBRE_MINES » :
→ Choisir au hasard une ligne entre 0 et LIGNES_GRILLE - 1
→ Choisir au hasard une colonne entre 0 et COLONNES_GRILLE - 1

→ Si la case (ligne, colonne) ne contient pas déjà une mine dans le tableau « grilleMines » :
→ Placer une mine dans cette case (mettre CASE_MINE dans grilleMines[ligne, colonne])
→ Incrémenter « minesPlacees » de 1

2️⃣ Fonction CalculerNombreMines()

Pour chaque case, on regarde les 8 voisins autour d’elle pour trouver le nombre de mines qui lui touche :

dc = -1dc = 0dc = +1
dl = -1(-1,-1)(-1,0)(-1,1)
dl = 0(0,-1)(0,1)
dl = +1(1,-1)(1,0)(1,1)
  • dl → Décalage de ligne
  • dc → Décalage de colonne
  • nl = ligne + dl → ligne voisine
  • nc = colonne + dc → colonne voisine

Exemple pour une case à la position (4,2) on veut valider si le coin en haut à gauche contient une mine

ligne = 4;
colonne = 2;
dl = -1;
dc = -1;
nl = ligne + dl = 4 - 1 = 3;
nc = colonne + dc = 2 - 1 = 1;

On valider donc si (nl,nc) → (3,1) contient une mine

On ne compte la case voisine que si (nl, nc) reste dans les bornes du tableau.

Fonction CalculerNombreMines :

Pour chaque ligne de 0 à LIGNES_GRILLE - 1 :
Pour chaque colonne de 0 à COLONNES_GRILLE - 1 :

→ Si la case (ligne, colonne) contient une mine dans grilleMines :
passer à la case suivante (ne rien calculer pour cette case)

→ Sinon :
initialiser un compteur « nbMines » à 0

Pour chaque décalage de ligne dl allant de -1 à +1 :
Pour chaque décalage de colonne dc allant de -1 à +1 :

→ si (dl, dc) vaut (0, 0), ignorer (c’est la case elle‑même)

→ calculer nl = ligne + dl
→ calculer nc = colonne + dc

→ si (nl, nc) est à l’intérieur de la grille:
→ si la case (nl, nc) contient une mine dans grilleMines :
augmenter « nbMines » de 1

→ après avoir regardé toutes les cases voisines,
enregistrer « nbMines » dans grilleMines[ligne, colonne]

3️⃣ Mettre à jour InitialiserGrille()

Ajouter les appels à nos 2 fonctions.

private void InitialiserGrille()
{
//...

PlacerMines();
CalculerNombreMines();
//...
}

✅ Résultat attendu

Ajoute ceci dans Tests() temporairement :

InitialiserGrille();

string lignes = "";

for (int i = 0; i < LIGNES_GRILLE; i++)
{

for (int j = 0; j < COLONNES_GRILLE; j++)
{
lignes += grilleMines[i, j].ToString().PadLeft(3);
}
lignes += "\r\n";
}

MessageBox.Show(lignes);
  • Tu dois voir dans la fenêtre modal une grille de nombres :
    • -1 pour les mines
    • 0,1,2,... pour les nombres de mines adjacentes.

Étape 5 - Révéler une case

🎯 Objectif

  • Quand on clique sur un bouton, révéler la valeur de la case (mine, nombre, vide).
  • Marquer la case comme révélée dans grilleRevelee.

1️⃣ Fonction RevelerCase(int ligne, int colonne)

Fonction RevelerCase(ligne, colonne) :
→ Si la ligne ou la colonne est en dehors des limites de la grille :
arrêter la fonction (return)

→ Si la case (ligne, colonne) est déjà révélée dans grilleRevelee :
arrêter la fonction (return)

→ Marquer la case comme révélée dans grilleRevelee[ligne, colonne]

→ Récupérer le bouton qui vient d'être cliqué dans grilleBoutons

→ Si il y a une mine sur la case :
afficher "💣" sur le bouton
mettre le fond en rouge
(la gestion de la défaite sera ajoutée à l'étape suivante)

→ Sinon, si la case est vide :
ne rien afficher sur le bouton (Text = "")
mettre le fond en gris clair

→ Sinon (c'est un nombre) :
afficher le nombre sur le bouton
mettre le fond en blanc

2️⃣ Utiliser RevelerCase dans Bouton_Click

private void Bouton_Click(object sender, EventArgs e)
{
Button btn = (Button)sender;
Point position = (Point)btn.Tag;

RevelerCase(position.X, position.Y);
}
Fonction Bouton_RightClick(object sender, MouseEventArgs e) :

→ Si le bouton de la souris n’est PAS le clic droit (e.Button != MouseButtons.Right) :
arrêter la fonction

→ Récupérer le bouton cliqué
→ Récupérer la position (ligne, colonne)

→ Si le texte du bouton est déjà "🚩" :
→ enlever le drapeau
btn.Text = ""
remettre la couleur de fond par défaut (SystemColors.Control)

→ Sinon (il n’y a pas encore de drapeau) :
→ ajouter un drapeau
btn.Text = "🚩"
changer la couleur de fond (par ex. Color.LightSteelBlue)

✅ Résultat attendu

  • Cliquer sur une case affiche :
    • 💣 sur un fond rouge (ou autre couleur selon vos goût) si c’est une mine
    • un chiffre sur fond gris si c’est une case numérotée
    • rien, "", (sur fond gris) si c’est vide.
  • Le clic de droite sur une case affiche :
    • 🚩 sur un fond de couleur s'il n'y avait pas déjà un 🚩
    • Rien ("") et rétablit la couleur pas défaut du bouton (SystemColors.Control)

Étape 6 - Gérer la fin de partie (victoire / défaite)

🎯 Objectif

  • Perdre quand on clique sur une mine.
  • Gagner quand toutes les cases non-mines sont révélées.

1️⃣ Défaite : clic sur une mine

  • Modifie RevelerCase pour gérer la fin de partie si c’est une mine :

    • Afficher un message de défaite tel que "💣⛏️ Oups c'était une mine ! 💥 "
    • Appeler RevelerTout() et terminer la partie
  • Crée une fonction RevelerTout qui affichera toutes les cases de la grille lorsque l'on clique sur une mine :

Fonction RevelerTout() :

→ Pour chaque ligne de 0 à LIGNES_GRILLE - 1 :
Pour chaque colonne de 0 à COLONNES_GRILLE - 1 :

→ Récupérer le bouton correspondant dans grilleBoutons
→ Désactiver le bouton

→ Si la case contient une mine :
afficher "💣" sur le bouton
mettre le fond en rouge (ou une autre couleur pour les mines non cliquées)

→ Sinon, si la case est vide :
ne rien afficher (texte vide)
mettre le fond en gris clair

→ Sinon (c'est un nombre) :
afficher le nombre sur le bouton
mettre le fond gris

→ Marquer la case comme révélée dans grilleRevelee[ligne, colonne]

2️⃣ Victoire : toutes les cases non-mines révélées

  • Ajouter une fonction VerifierVictoire() :
Fonction VerifierVictoire :

Pour chaque ligne de 0 à LIGNES_GRILLE - 1 :
Pour chaque colonne de 0 à COLONNES_GRILLE - 1 :

→ Si la case (ligne, colonne) ne contient PAS une mine
ET que la case n'est PAS révélée dans grilleRevelee :

→ retourner faux (il reste encore au moins une case sûre à révéler)
→ Si la case (ligne, colonne) contient une mine
ET que la case EST révélée dans grilleRevelee :

→ retourner faux (Le joueur a révélé une mine, il a perdu)

→ Si on a terminé toutes les cases sans trouver de case sûre non révélée :
retourner vrai (toutes les cases sans mine sont révélées)
  • Ajouter à la fin de RevelerCase la vérification de la victoire:
  • Afficher un message à l'utilisateur et Initialiser une nouvelle partie
→ Valider si c'est une victoire ou non avec `VerifierVictoire()`
→ Si c'est une victoire :
→ Afficher un MessageBox.Show("🎉 Victoire !");
→ Démarrer une nouvelle partie

✅ Résultat attendu

  • Clic sur une mine → message de défaite + plus aucune réaction aux clics.
  • Révéler toutes les cases sans mine → message de victoire.

Bonus - Ouverture en cascade des cases vides

🎯 Objectif

Quand on clique sur une case vide (0 mines autour), on veut révéler automatiquement toutes les cases vides voisines, ainsi que les nombres autour de cette zone (comme dans le vrai Démineur).


1️⃣ Fonction récursive RevelerCase(int ligne, int colonne)

  • Modifier RevelerCase pour pouvoir révéler une zone vide
  • C'est sur le clic d'une case vide que nous pourrons rechercher les autres cases adjacentes vides
  • Utiliser la récursivité (une fonction qui s'appelle elle-même) pour afficher les cases vides voisines
→ Sinon (la case est vide, CASE_VIDE) :
→ Pour chaque voisin (dl de -1 à +1, dc de -1 à +1) :

→ calculer nl = ligne + dl
→ calculer nc = colonne + dc

→ Appeler récursivement RevelerCase(nl, nc)

Important :

  • La condition d’arrêt empêche la récursion infinie :
    • hors de la grille → on s’arrête
    • case déjà révélée → on s’arrête
    • case non vide → on s’arrête après l’avoir révélée
  • On ne propage la récursion que depuis les cases vides.

✅ Résultat attendu (bonus récursif)

  • Clic sur une case vide → toutes les cases vides connectées sont révélées.
  • Les nombres autour de la zone vide sont révélés.
  • L’algorithme utilise la récursivité pour parcourir la zone

🧾 Grille de correction – TP4 : Démineur

⚠️☢️🔥 ATTENTION!!!

⚠️ Compilation obligatoire :

  • ✅ Le code compile : correction complète selon les critères ci‑dessous.
  • ❌ Le code ne compile pas : note maximale 60%.

🔍 Évaluation détaillée

🎨 Qualité de l'interface graphique (Windows Forms) – /40 points
  • Utilisation appropriée des contrôles Windows Forms (MenuStrip, boutons…).
  • Grille de boutons générée dynamiquement.
  • Redimensionnement de la fenêtre en fonction de la taille de la grille.
  • Interface claire, lisible et utilisable.
🧠 Fonctionnement du jeu (logique, tableaux 2D) – /60 points
  • Représentation correcte du plateau avec des tableaux 2D.
  • Placement correct des mines.
  • Calcul correct du nombre de mines adjacentes.
  • Révélation des cases cohérente (mine, nombre, case vide).
  • Détection correcte de la fin de partie (victoire et défaite).

⭐ Bonus – /10 points
  • Implémentation d'une ouverture de zone vide.

💯 Total : 100 points