🧪 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
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();
}
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 :
- Glisser un contrôle
MenuStripsur le formulaire. - Renommer-le (propriété
Name) enmenuPrincipal. - Ajouter les menus suivants :
JeuNouvelle 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})");
}
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 = -1 | dc = 0 | dc = +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 lignedc→ Décalage de colonnenl = ligne + dl→ ligne voisinenc = 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 :
-1pour les mines0,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
RevelerCasepour 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
RevelerToutqui 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
RevelerCasela 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
RevelerCasepour 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
⚠️ 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
- 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.
- 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).
- Implémentation d'une ouverture de zone vide.