TD 8 : Exemple en AWT : Le démineur



Dans ce TD, nous allons programmer un logiciel de démineur. Pour rappel, le démineur est un jeu qui se joue à un seul joueur. Des bombes sont disséminées sur une grille rectangulaire et le but du jeu est de trouver leurs positions. Au début du jeu toutes les cases sont cachées, puis on les découvre petit à petit en cliquant sur celles-ci. Lorsque l'on clique sur une case avec le bouton gauche de la souris il y a deux possibilités: Si le clic a été effectué à l'aide du bouton droit alors: Par exemple dans le dessin ci-contre:
On remarque différents types de case:

1. Représentation du tableau de jeu.



Téléchargez tout d'abord les fichiers Demineur.java et Case.java . Ces fichiers contiennent respectivement l'interface graphique proprement dite pour le premier et les données concernant la représentation du jeu pour le second. L'interface graphique est concue de la manière suivante: On représentera un jeu à l'aide d'un tableau de type Case de taille (largeur+2)*(hauteur+2): Une Case est un objet qui peut revêtir trois formes selon les trois différents types de cases du jeu:
  1. Une case du bord (CaseBord). Pour simplifier certaines méthodes, au lieu de créer un tableau de taille de taille largeur*hauteur on rajoute une bande d'une case tout autour.
  2. Une case vide (CaseVide).
  3. Une case contenant une bombe (CaseBombe)
Chaque case devra aussi stocker son statut à savoir: Pour représenter naturellement ces trois types de case nous allons créer une hiérarchie de classe de la manière suivante: Remarquez que la classe Case est abstraite car le comportement d'une case pour une certaine action dépend de son type (vide, bombe ou bord)
  1. Compléter la méthode public static void initJeu(int l,int h) de la classe Case de manière à réaliser l'initialisation du tableau de jeu. Le nombre de bombes sera égal à ratio*l*h, ratio étant une constante prédéfinie dans la classe Case. Rappelons que l'appel à Math.random() renvoie un nombre de type double compris entre 0 inclus et 1 exclus. On pourra dans un premier temps construire la grille de jeu en mettant les cases du bord et des cases vides au milieu. Ensuite il suffit de tirer aléatoirement des coordonnées et de placer une bombe à cette place s'il n'y en a pas déjà. Exécutez votre programme par java Demineur
  2. Le dessin des cases est réalisé par la méthode statique paintAll(Graphics g). Cette méthode appelle la méthode d'objet paint(Graphics g) pour chaque case. Cette même méthode affiche au centre de la case la chaine renvoyée par la méthode d'objet public String toString(). Redéfinissez cette méthode String toString dans la case CaseBord de manière à afficher un symbole différent (un espace par exemple) pour les cases du Bord. Exécutez votre programme. Modifiez de même cette méthode pour les autres types de base de manière à visualiser la position des bombes. (On marquera les bombes par le symbole 'X')
  3. Compléter les méthodes boolean existeBombe() dans les différentes classes Case pour qu'elles renvoient true dans le cas d'une CaseBombe et false sinon.
  4. Écrire une méthode int nombreBombes() qui renvoie -1 si la case contient une bombe et le nombre de bombes (0 à 8) aux alentours sinon. Vous créerez pour celà une méthode générique pour la classe Case que vous redéfinierez pour les classes qui en héritent. Modifiez votre programme pour afficher le nombre de bombes voisins d'une case dans les cases vides. Vous devez obtenir le shéma suivant:
  5. Compléter la méthode public void paint(Graphics g) de la classe Case de manière à afficher en plus du symbole un quadrillage représentant les différentes cases. L'appel à g.drawLine(x0,y0,x1,y1) dessine une ligne entre les points (x0,y0) et (x1,y1).
  6. Enlevez le quadrillage pour les cases du Bord.
Corrigé à télécharger ici

2. Où on ajoute un peu d'interactivité.



Dans cette partie nous allons gérer l'appui sur le bouton gauche de la souris de manière à découvrir une case. Pour cela, vous aurez besoin de retoucher le programme donné dans le fichier Demineur.java. Ce fichier contient plusieurs classes:
  1. Écrivez les différentes méthodes public boolean decouvreCase() dans la classe Case (ou CaseVide,CaseBombe ...) pour qu'elles découvrent la case et renvoient true si cette case contenait une bombe. Modifiez la méthode paint pour n'afficher la valeur de toString que si la case a été découverte.
  2. Creéer une classe GestionSouris héritant de la classe MouseAdapter dans le fichier Demineur.java. Un appel à addMouseListener(new GestionSouris()) dans le constructeur de la classe Dessin permet de relier toute action de la souris à l'intérieur du panneau de dessin à l'appel à une méthode de la classe GestionSouris. Par exemple, l'appui sur un bouton provoquera l'exécution de la méthode public void mousePressed(MouseEvent e). Le clic sur un bouton de la souris (on appuie et on relache) appellera la méthode public void mouseClicked(MouseEvent e). Toutes ces méthodes sont déjà définies dans la classe MouseAdapter de Java avec un comportement nul. Redéfinissez la méthode mouseClicked pour appeler la méthode decouvreCase() de la case sélectionnée lors de l'appui sur un bouton. Si une bombe est découverte alors vous pouvez découvrir toutes les cases du tableau et afficher perdu. Pour forcer le réaffichage à l'écran il faut appeler la méthode repaint() de la classe Canvas. Remarquez que pour faire appel à cette méthode vous devez avoir une référence au Canvas. Pour obtenir les coordonnées de la souris les méthodes getX() et getY() de la classe MouseEvent renvoie respectivement l'abscisse et l'ordonnée de la souris au moment ou l'évènement s'est produit.
  3. Dans le jeu du démineur losque l'on clique sur une case pour la découvrir et qu'elle ne contient aucune bombe, alors automatiquement ses 8 cases alentours sont découvertes. Cette découverte est récursive. Modifiez donc votre méthode decouvreCase() des classes Cases... pour réaliser cette opération récursive.
(Corrigé de cette partie) Case.java Demineur.java

3. Et quand est-ce qu'on gagne ?



On va maintenant gérer le marquage des bombes c'est à dire l'appui sur le bouton droit de la souris.
  1. Modifier une dernière fois les méthodes toString pour marquer d'un symbole spécial les cases marqués, les cases découvertes et les autres.
  2. Complétez la méthode marqueCase dans la classe Case pour marquer la case (x,y) et verifie si la partie est gagnee.
  3. Pour détecter quel bouton de la souris a déclenché l'évènement dans la méthode public void mouseClicked(MouseEvent e) on récupère son numéro grâce à l'appel à e.getButton();. Le bouton gauche est appuyé si l'entier renvoyé est égal à MouseEvent.BUTTON1 et au bouton de droite si la valeur est MouseEvent.BUTTON3. Modifiez ainsi la méthode pour qu'un clic droit déclenche l'appel à marqueCase alors que le clic gauche déclenche l'appel à decouvreCase.
(Corrigé de cette partie) Case.java Demineur.java

4. Pour aller plus loin