Copy of https://perso.isima.fr/loic/java/exo_jse_base_demineur_javafx.php
tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
Institut d'informatique ISIMA
  • twitter
  • linkedin
  • viadeo

[JavaSE] Démineur FX

 Cette page commence à dater. Son contenu n'est peut-être plus à jour. Contactez-moi si c'est le cas!

Date de première publication : 2019/02/27

Notions : Java FX, patron MVC, patron observateur

Nous voulons réaliser un démineur en Java avec Java FX (Avec la bibliothèque Swing, c'est ici !

Le principe du jeu est très simple : on propose à une personne de découvrir petit à petit une zone "minée" en signalant à chaque case, le nombre de mines adjacentes. Il faut découvrir toute la zone sans tomber sur une mine. Pour une plus grande sécurité, on peut en général marquer la zone où l'on est persuadé qu'il y a une mine pour ne pas la découvrir.

Nous voulons programmer ce petit jeu en utilisant le patron de conception Modèle-Vue-Contrôleur, même si en JavaFX on fusionne assez facilement les parties Vue et Contrôleur. Nous montrerons comment des objets peuvent communiquer entre eux : par appel de méthodes bien entendu mais surtout en utilisant le patron de conception Observateur/Observable qui permet à un objet d'être prévenu d'un changement. Le patron Observateur/Observable permet de mettre en place un couplage "lâche" entre deux classes dans le sens où l'objet observé n'a pas d'information sur l'observateur.

Le modèle

Une pratique usuelle de développement consiste à mettre toutes les classes d'un modèle dans un même package

Nous allons tout d'abord nous intéresser à la zone de jeu, le Terrain : celui-ci comporte une grille de cellules (voir plus bas) d'une certaine dimension (nous l'avons choisie carrée :-)) et un nombre de mines à découvrir. Nous le dotons également du nombre de cases qui n'ont pas encore été ou découvertes ou marquées ainsi que d'un état : partie en cours, partie gagnée, partie perdue.

Pour l'état de la partie, vous pouvez utiliser une énumération : dans son expression la plus simple, elle s'écrit comme en C++ même si en java la notion d'énumération est beaucoup plus puissante (c'est une classe avec constructeur et méthodes)

enum Enumeration { VALEUR1, VALEUR2, VALEUR3 }

Si vous devez utiliser une énumération dans un switch, le type de l'énumération est déduit au niveau du case : seule la valeur suffit.

Cellule

Le terrain est un tableau de cases, que l'on appelera "cellules" (vous avez déjà essayé d'appeler une variable case) pour lequelles on a les informations suivantes :

Écrire un constructeur qui permet d'initialiser les attributs x, y et valeur (visible et selected sont "faut par défaux" ;-))

Doter tous les attributs de méthodes get (cela peut se faire automatiquement avec un EDI)

Dois-je vous rappeler qu'avec les conventions d'écriture l'accesseur pour selected se nomme isSelected() ?

Doter la classe d'autres méthodes : uncover() qui permet de rendre visible la cellule si elle n'est pas marquée, et toggleSelected() qui permet de changer la valeur du marquage.

Il faut encore les méthodes set pour les attributs selected et visible mais elles nécessitent un peu de travail. Elles ne devront pas être accessibles de l'extérieur alors je propose de les mettre privées et ces méthodes auront la "lourde" tâche de prévenir d'éventuels observateurs que l'état de la classe a changé ! Pour réaliser cela, la classe devra être une instance de java.util.Observable et toute classe qui veut être notifiée des changements devra implémenter java.util.Observer.

setChanged();
notifyObservers();

L'observateur devra implémenter une méthode update() que je vous laisse découvrir dans la documentation.

Les classes Observer et Observable sont antérieures à Swing et encore plus à JavaFX (qui implémentent toutes deux ce patron avec des Property. Je garde ce patron car cela permet de s'abstraire de la couche graphique !!!

Si vous ne voulez pas utiliser le patron observateur, il reste quand meme des solutions :

Terrain

Écrire le constructeur de la classe qui initialisera la grille (taille et nombre de mines donnés en paramètres).

Proposer une méthode create() qui s'assure que la grille est vierge de toute information, qui génère le nombre fixé de mines et qui calcule la matrice d'adjacence. Il est alors possible de commencer à jouer ... Cette méthode est appelable plusieurs fois lors d'une même exécution du programme

Le terrain sera un observateur pour chacune des cellules de la grille et cela permettra de prendre en compte les situations suivantes :

  1. Si une cellule est changée, l'état de la partie de jeu peut être changé.
  2. On peut aussi programmer la découverte automatique des cellules.

La vue

La vue en Java FX est relativement simple : une Application et une fenêtre principale à base de Canvas. Pour avoir un canvas redimensionnable, n'hésitez pas à (re)lire le TP Picasso.

fenetre principale du demineur

La case graphique

Le canvas permet d'afficher les cellules qui ont différentes représentations en fonction des choix du joueur :

Voici un morceau de code pour afficher du texte avec un Canvas

 
gc.setFont(Font.font("Courier New", FontWeight.BOLD, taille));
gc.setTextAlign(TextAlignment.CENTER);
gc.setTextBaseline(VPos.CENTER);
fillText(texte, x, y);

Il faudra implémenter une gestion d'événement souris (MouseEvent) pour le clic l'interface. Le paramètre de la méthode setOnMousePressed() contient l'information du bouton appuyé.

Mesurer le temps qui s'écoule...

Je vous propose d'afficher le temps qui s'écoule dans une barre des tâches constituée d'un Labelspécialisé.

Timer timer = new Timer();
timer.schedule(tt, 1000, 1000);

Un Timer est annulable (méthode cancel()). TimerTask est une simple interface qui définit une méthode run().

Platform.runLater() peut vous sauver la mise.

Fin du jeu

Il reste à signaler la fin de la partie au joueur : gagné ou perdu ! Je vais vous proposer trois solutions, elles considèrent que la vue principale observe le terrain afin de connaître le moindre changement de statut dans la partie du jeu.

glasspane de fin de jeu

Aller plus loin ...

Affichage de la case

La case, quand elle est marquée  , n'est pas très jolie. Si vous trouvez une belle image, vous pouvez l'intégrer comme suit.

Image bombe = new Image("file:gnome-mines.desktop.png");
gc.fillImage(bombe, x, y, w, h);

Ce qui est important dans le cas présent, c'est de savoir où placer le fichier de ressource pour qu'il soit lu par le programme Java. Si vous développez avec un éditeur simple, le répertoire par défaut où cherche la machine virtuelle est le répertoire à partir duquel la machine virtuelle est lancée. Maintenant, si vous êtes sur Eclipse, vous pouvez placer l'image dans le répertoire principal du projet. À l'exécution tout se passe comme si l'image était au bon endroit. Il faut rafraîchir (touche F5) le projet pour voir le fichier dans l'explorateur d'Eclipse

Vous auriez pu placer l'image dans le répertoire src, mais pas dans le répertoire bin qui est "reconstruit" régulièrement à partir des sources (il est d'abord effacé)

J'ai aussi affiné la couleur de la case en fonction du nombre de mines adjacentes :

Différer la création de la grille

La génération de la grille peut être frustrante parfois : une découverte de bombe dès le premier clic. Vous pouvez différer la dispersion des bombes au premier clic si vous en avez envie.

Modèle objet explosif

Cela peut vous frustrer que l'on exploite pas complètement le modèle objet pour les cases / cellules. On pourrait tout à fait créer une classe Bombe qui spécialise Case/Cellule. Si vous voulez savoir de quel type est la case/cellule, on peut utiliser un opérateur comme instanceof

if (case instanceof Bombe)
  System.out.println("je suis une bombe");