Rex de Ladybug

Dans sa quête pour approfondir ses compétences en Java, Frédéric (consultant et formateur chez OXiane Institut) a créé le jeu Ladybug, une cousine éloignée de Pac-Man. Ce projet lui a permis d’explorer et d’appliquer des concepts comme l’architecture, les design patterns (MVC, Stratégie, …), Spring, des pratiques comme le Clean Code et les principes SOLID.

Je vous propose cet article sur mon projet Ladybug, intitulé « Créer une application par l’émotion, pour monter en compétence », développé en langage Java.
Dans cet article, je vais vous raconter l’histoire de cette petite coccinelle, expliquer pourquoi je l’ai créée, et partager avec vous les bénéfices qu’elle m’a apportés en mettant en lumière les principaux points marquants. 

Avant de commencer, je tiens à remercier Alex, Christophe, Kylian, Cyril, Jim, ainsi que tous les autres qui, par leur savoir et leur bienveillance, m’ont aidé à progresser et à faire de ce projet ce qu’il est aujourd’hui, et sans doute ce qu’il deviendra demain. 

Naissance de l’idée

Il était une fois un expert en XML (moi, en l’occurrence) qui décida d’élargir ses connaissances professionnelles vers un langage orienté objet. Le choix se porta sur le langage Java. Par où commencer ? Je disposais déjà de quelques notions, acquises au fil des différentes missions réalisées dans ma carrière professionnelle. Je sentais que j’avais encore du potentiel à exploiter. Et j’ai la chance de faire partie d’OXiane Institut i, un centre offrant un large éventail de supports de formation axés sur le développement informatique : Java, Spring, Hibernate, Git, Maven, et bien d’autres encore. J’avais donc l’embarras du choix. J’ai alors débuté par les supports et les travaux pratiques (TP), en autonomie. 

J’ai commencé par les bases : Java de base, Java avancé, et les différentes versions de Java, puis, avec le temps, Spring ii. Je m’intéressais aux diverses formations, faisais les exercices des TP, et prenais des notes. Puis, un matin, une idée m’est venu pour mieux intégrer toutes ces nouvelles connaissances : et si je les réunissais dans une petite application fun, utile pour tous, où je pourrais expérimenter et mettre en œuvre des stratégies et des design patterns rencontrés dans les supports et sur le web ?

C’est ainsi qu’est née la fameuse Ladybug, une cousine éloignée du célèbre Pacman, avec ses fantômes, ses fruits et ses gommes.

Pour ceux qui souhaitent découvrir les règles du jeu, je vous invite à consulter le README du projet : https://github.com/FredericLanic/ladybug

Dans un premier temps, j’ai choisi de créer un nouveau projet Maven, hébergé sur GitHub. Pour la gestion des branches, je me suis inspiré de l’environnement de travail que j’avais connu. La branche master est destinée à contenir les versions stables, tandis que la branche develop hébergera la version en cours de développement. D’autres branches pourront être créées ultérieurement pour des tests ou des versions spécifiques. Pour l’instant, le projet est encore vide. 

C’est parti ! Je commence par explorer le web pour voir ce qui a déjà été réalisé sur le sujet. En bon développeur, je cherche de l’inspiration : je consulte divers sites, scrute GitHub, et découvre des projets déjà bien avancés, mais dont certaines logiques ne me convainquent pas totalement. Par hasard, je tombe sur des images, des musiques et des polices liées au jeu iii. Je les garde de côté, elles pourraient me servir plus tard.

Et là, je tombe sur un projet de Pacman réduit à deux classes iv : un petit projet très basique, très condensé, et loin d’être évolutif. Intrigué, je l’examine, le teste, et le modifie. Bref, je suis séduit. J’ai trouvé mon point de départ et mon inspiration.

Le développement

Dans un premier temps, j’analyse ce petit programme et j’identifie trois entités principales : l’affichage du jeu à l’écran, les interactions via les touches du joueur, et les calculs nécessaires au déroulement du jeu. Ces trois éléments évoquent immédiatement le triplet Modèle-Vue-Contrôleur (MVCv) : le cœur du jeu correspond au modèle, l’affichage à la vue, et les actions de l’utilisateur au contrôleur.

Cela me semble pertinent. Certes, le design MVC n’est pas très classique pour un jeu vi, mais je suis convaincu que cette approche pourra m’être utile un jour dans mon métier.

Une fois le choix du modèle MVC fait, je me suis attelé à créer les trois entités :

  • GameModel : cette classe est dédiée au calcul de l’état du jeu et des éléments. J’y ai intégré un petit timer de 40 ms, correspondant à la fréquence du jeu. Voilà, mon projet a désormais son petit cœur qui bat.
  • GameView : cette classe sert à l’affichage, dans un premier temps d’un simple point, en utilisant AWT vii.
  • GameControl : cette classe gère les interactions utilisateur, notamment la gestion des touches appuyées.

Pour que tout fonctionne harmonieusement, j’ai adopté le design Observable viii. Ce modèle permet au contrôleur d’envoyer ses actions au modèle, qui calcule les nouvelles coordonnées du point avant de notifier la vue pour un affichage à jour.

Nickel ! Je peux déjà voir un petit point jaune se déplacer sur mon écran au gré de mes envies. Le design MVC est posé !

Affichage du jeu : le labyrinthe

Ensuite, j’ai choisi d’afficher le jeu : le fameux labyrinthe où Ladybug et les fantômes pourront se déplacer librement, accompagné de ses différents éléments. Pour cela, j’ai créé une instance ScreenData, destinée à gérer dynamiquement les données du jeu. Cette classe contient la carte ainsi que les emplacements des gums, des super gums, des fruits, des points de téléportation et le point de régénération des fantômes.

Chaque niveau implémente l’interface LevelStructure. Lors du chargement d’un niveau, le tableau de la carte est converti en un tableau de ScreenBlock, ce qui permet de définir les bordures de chaque case ainsi que les coordonnées des éléments qu’elle contient (gum, super gum, fruits, etc.).

Note : Un grand merci à Cyril pour sa fonctionnalité brillante permettant d’ajouter facilement une nouvelle carte via un simple tableau d’enums LevelConstruct. Un véritable coup de génie !

Avec ces premières données en place, j’ai pu les afficher à l’écran. La vue lit l’instance ScreenData, récupère les tableaux de ScreenBlock, et les affiche à l’écran via la classe ScreenBlockView. Petit à petit, les bordures, les gums et les super gums apparaissent à l’écran. L’esprit du jeu commence à prendre vie.

Un affichage dynamique

À présent, j’ai un affichage dynamique qui se met à jour toutes les 40 ms. C’est un bon début.

Gestion des personnages : Ladybug et les fantômes

Il était temps d’introduire les personnages : Ladybug et les fantômes. Tous partagent des caractéristiques communes :

  • Ils se déplacent dans le labyrinthe (ScreenData)
  • Ils ne peuvent pas traverser les murs (même les fantômes)
  • Ils ont une vitesse
  • Ils peuvent effectuer des actions

J’ai donc conçu une classe abstraite, Body, dont héritent Ladybug et les fantômes.

Les fantômes, à savoir Blinky, Clyde, Inky et Pinky, héritent d’une autre classe abstraite, Ghost, qui stocke leur comportement (défini par l’enum GhostBehaviour : smart, aggressive, ou bydefault), leur état (défini par GhostStatus : dying, flash, scared, normal ou regenerated), et leur déplacement. La gestion de leur vitesse est spécifique à chacun et dépend de leur comportement, grâce au polymorphisme ix.

Interactions entre les personnages

Selon leurs positions, états et comportements, les personnages interagissent entre eux. Ces interactions sont gérées par le modèle :

  • Si Ladybug entre en contact avec un fantôme, elle peut soit le manger (si elle est en super mode), soit perdre une vie. En cas de vie perdue, la partie recommence ou le jeu se termine.
  • Les fantômes, en fonction de leur état, peuvent soit poursuivre Ladybug, soit fuir (s’ils ont peur), soit revenir à leur point de régénération.

Tous ces calculs sont effectués par le modèle à chaque battement du timer. Les directions de Ladybug sont pilotées par les informations envoyées par le contrôleur. Le modèle effectue ensuite les calculs nécessaires, met à jour les états des personnages et s’envoie à la vue.

Rôle de la vue et du contrôleur

La vue récupère le modèle et réalise l’affichage en convertissant chaque élément du modèle en un objet propre à l’affichage. Elle se concentre uniquement sur l’affichage des données, sans logique métier.

Le contrôleur, lui, permet d’envoyer des informations au modèle, notamment pour modifier les directions de Ladybug. Initialement, j’avais développé cette fonctionnalité pour les touches du clavier. Puis, j’ai cherché un moyen plus amusant de jouer. Mon fils ayant des manettes Xbox inutilisées, cela m’a inspiré. Grâce à une dépendance trouvée sur GitHub pour un driver Xbox, j’ai pu intégrer les manettes au jeu. Merci, William !

Un jeu jouable

À ce stade, le jeu est pleinement jouable : un premier niveau, des fantômes qui se déplacent, des gums dévorées par Ladybug… Le projet prend forme !

La vie du programme – les nouvelles fonctionnalités

À ce moment-là, j’avais encore plein d’idées à réaliser. Petit à petit, ces idées se sont concrétisées.

Au début, lors du lancement du jeu, Ladybug était directement placée sur une carte pour manger des gums, tandis que les fantômes actifs tentaient de l’en empêcher. Ayant joué de nombreuses fois sur des arcades étant jeune, je me suis vite rendu compte qu’il manquait un workflow dans le jeu, avec différents états : une présentation dynamique, un début de jeu avec le titre, un niveau gagné, une vie perdue… Je me demandais comment ajouter ce workflow sans avoir à modifier radicalement le programme.

C’est à ce moment-là que j’ai eu la chance de suivre la formation Software Craftsmanship x, qui met en avant la méthode de travail en TDD xi (Test-Driven Development), et surtout qui m’a permis de mettre en application le design pattern Stratégie xii.
J’ai donc associé une nouvelle classe au modèle pour chaque état du jeu (dans le package model.strategy). Le jeu contient maintenant une page de présentation, des affichages pour les niveaux… Comme un bon vieux jeu d’arcade. Cool ! En plus, le code est devenu plus robuste et maintenable, et la classe GameModel est désormais plus légère. À voir maintenant si le design pattern État xiii conviendrait mieux.

L’animation de Ladybug

Comme son cousin Pacman, Ladybug doit se déplacer en mangeant les gums. Sa mâchoire doit donc s’activer en permanence. Pour cela, dans la classe LadybugView, l’affichage de la coccinelle est géré par un thread qui traite une liste chaînée circulaire xiv d’BodyImg. Chaque BodyImg contient l’image à afficher et un lien vers la suivante.
Ainsi, Ladybug a la même manie que Pacman, et montre bien qu’elle a toujours faim en ouvrant et fermant la bouche de manière infinie.

Les fantômes et leur intelligence

Le fantôme est parfois agressif et recherche activement le meilleur chemin pour attraper Ladybug.
Pour cela, le modèle calcule au besoin le plus court chemin d’un fantôme vers Ladybug en utilisant l’algorithme de Dijkstra xv. Comme le nombre de cases est limité, le calcul se fait rapidement et convient très bien au jeu. Son choix s’est avéré assez évident, son utilisation étant plus simple que d’autres algorithmes comme Bellman-Ford xvi ou Floyd-Warshall xvii, et son implémentation est intuitive.

Améliorations sonores

J’ai également enrichi l’architecture MVC en ajoutant une nouvelle vue pour le son : la classe GameSound. En fonction des actions des personnages et de l’état du jeu, cette classe récupère le modèle et décide d’émettre un ou plusieurs sons particuliers. Les sons rendent le jeu plus fun et améliorent l’immersion dans l’univers du jeu.
Cependant, j’ai rencontré quelques surprises concernant l’émission de sons qui se chevauchaient parfois. Pour remédier à cela, j’ai fait en sorte que GameSound ne puisse pas émettre simultanément le même son, en utilisant des threads indépendants.

La vie du programme – point de vue technique

Au cours du développement, j’ai exploré différentes techniques pour améliorer la lisibilité du code et faciliter sa lecture.

Lors de mes premières lectures sur la qualité du code, j’ai notamment trouvé les articles d’un passionné xviii, François-Xavier Robin, qui m’a bien inspiré pour débuter le clean code et rendre mes classes plus lisibles. Je me suis aussi rendu compte que limiter le nombre de lignes par fonction et par classe permet de trouver des stratégies élégantes de programmation.
J’ai également commencé à lire le livre Clean Code xix de Bob Martin. Dès le début de la lecture, j’ai modifié et normalisé les noms des variables, des fonctions et des classes pour rendre le parcours du projet plus attrayant et fluide.
Je me suis également intéressé à la notion de SOLID xx, en me concentrant sur le principe Single Responsibility Principle (SRP) : une classe, une fonction doit avoir une seule raison d’être modifiée. Le code devient alors plus clair. Et, en discutant avec les experts ici, j’ai réalisé que j’avais déjà intégré les quatre autres principes dans mon projet.

Singletons et erreurs de design

Un moment donné, je me suis rendu compte que je n’utilisais qu’une seule instance pour de nombreuses classes : GameModel, Ladybug, Blinky, etc. J’ai alors décidé de travailler avec des singletons. Grave erreur ! Je me suis perdu dans les classes. Les accès illimités entre elles ont rendu le programme inexploitable.
Puis est arrivée la nouvelle saison : Spring xxi ! Son intégration dans le projet m’a permis de rendre le code plus maintenable et lisible, de limiter les dépendances entre les classes, et de clarifier mes idées. Bref, Spring a été un véritable boost pour repartir sur de bonnes bases.

Cerise sur le gâteau, Spring m’a permis d’ajouter un fichier application-context.xml qui regroupe en XML☺ les éléments essentiels du jeu : les caractéristiques de Ladybug et des fantômes. Les autres beans techniques du programme sont directement annotés dans les classes.
En modifiant ce fichier, je peux changer les caractéristiques du jeu sans recompiler. Nickel ! Je peux même définir un nombre infini de fantômes.

L’architecture et l’interface de vue

Petit à petit, au fur et à mesure du développement, l’harmonie entre le modèle, la vue et le contrôleur s’est quelque peu détériorée. Je me suis rendu notamment compte qu’à certains endroits, la vue modifiait le modèle, alors que son rôle se limite à en faire une lecture pour l’affichage.
L’architecture Observable mise en place permet d’envoyer l’instance de GameModel aux vues à une fréquence donnée. Afin de limiter l’accès au modèle, chaque vue reçoit une version limitée de celui-ci, filtrée par une interface dédiée. Par exemple, la vue voit le modèle à travers l’interface GameModelForViews, qui restreint l’accès aux getters uniquement.
Ce mécanisme m’a permis de détecter des incohérences dans les actions des vues et m’a poussé à réajuster mon code pour le rendre plus robuste.

Et pour conclure…

Développer Ladybug m’a apporté de nombreux bénéfices, tant sur le plan professionnel que personnel. J’ai pris beaucoup de plaisir à maintenir ce projet, à y ajouter de nouvelles fonctionnalités, et à appliquer le clean code.
L’objectif principal est atteint : monter en compétences sur l’écosystème Java, les patterns, les frameworks, et la littérature, tout en visant le clean code.
Un second point, inattendu, a été le plaisir de discuter du projet avec mes collègues, mon fils (mon autre source d’inspiration), et de leur donner envie de réaliser à leur tour un jeu, comme Rémi en Python, voire même créer un autre article sur les Github actions xxii.
Enfin, un dernier point, mais non des moindres : ce projet m’a permis de me plonger pleinement dans Java et son écosystème, au point de pouvoir donner des formations sur les différentes versions de Java à des professionnels.

Sachez que ce projet est toujours en développement. Je continue à m’investir de manière épisodique pour analyser et rendre réalisables de nouvelles idées. La dernière en date ? Faire en sorte que Ladybug délivre son cousin Pacman, emprisonné dans la prison du méga méchant. Sera-t-elle capable de le libérer ?

par Frédéric Lanic
Consultant- Formateur chez OXiane Institut

Notes

i OXiane Institut (https://oxiane-institut.com/) est un organisme de formation continue spécialisé dans les technologies numériques, proposant plus de 500 formations couvrant divers domaines du digital. Rattaché auGroupe Clever Age, il dispose de plusieurs agences en France et à l’international, offrant des formations adaptées aux professionnels du secteur.

ii Les formations Oxiane Institut sont accessibles à https://oxiane-institut.com/formations/

iii Ressources trouvées sur le web pour le projet :

iv Pacman a deux classes trouvé sur github : https://github.com/janbodnar/Java-Pacman-Game

v Modèle MVC : https://fr.wikipedia.org/wiki/Mod%C3%A8le-vue-contr%C3%B4leur

vi Modèle MVC du jeu : https://github.com/FredericLanic/ladybug/blob/master/readme/architecture/mvc.jpg

vii Awt, bibliothèque graphique de java : https://fr.wikipedia.org/wiki/Abstract_Window_Toolkit

viii Design pattern Observable : https://fr.wikipedia.org/wiki/Observateur_(patron_de_conception)

ix Polymorphisme en informatique : https://fr.wikipedia.org/wiki/Polymorphisme_(informatique)

x Formation SoftWare Craftsmanshipt : https://oxiane-institut.com/formations/software-craftsmanship/

xi TDD : https://fr.wikipedia.org/wiki/Test_driven_development

xii Design pattern Strategy : https://fr.wikipedia.org/wiki/Strat%C3%A9gie_(patron_de_conception)

xiii Design pattern Etat : https://fr.wikipedia.org/wiki/%C3%89tat_(patron_de_conception)

xiv Liste chainée circulaire : https://www.geeksforgeeks.org/circular-linked-list/

xv Algorithme de Dijkstra : https://fr.wikipedia.org/wiki/Algorithme_de_Dijkstra

xvi Algorithme de Bellman Ford : https://fr.wikipedia.org/wiki/Algorithme_de_Bellman-Ford

xvii Algorithme de Floyd-Warshall : https://fr.wikipedia.org/wiki/Algorithme_de_Floyd-Warshall

xviii Article de fxrobin : https://fxrobin.developpez.com/tutoriels/java/coder-jeuvideo-libgdx/

xix Extrait du livre « Clean Code » de Bob Martin : https://www.google.fr/books/edition/Coder_proprement/5uq8PDtq1IwC? hl=fr&gbpv=1&pg=PA2&printsec=frontcover

xx Principe de conception SOLID : https://fr.wikipedia.org/wiki/SOLID_(informatique)

xxi Spring https://fr.wikipedia.org/wiki/Spring_(framework)

xxii Article sur les Github actions de Jim https://www.oxiane.com/github-en-action/