Lemming, écrire un jeu avec Laravel

Il y a un jeu de société avec lequel je joue souvent avec mes enfants, il s’agit de Lemming; pas le jeu vidéo, mais celui avec une course sur un plateau rempli d’hexagones. Pour ceux qui ne connaissent pas, je vous le recommande. Je vais vous présenter comment je l’ai construit en mode web, et quelques bonnes pratiques à respecter. Et pour ceux qui veulent essayer, voici le site démo: https://lemming.gameandme.fr

Les règles du jeu:

Les sources sont disponibles ici: https://github.com/ynizon/lemming

Le javascript:

1er difficultée: le jeu est construit sur une carte à base d’hexagones. J’ai essayé de recréer des hexagones avec des <divs> mais le fait de les survoler ne permettait pas de bien délimiter les limites de l’hexagone. Au cours de mes recherches, je suis tombé sur la librairie JS: Honeycomb. Cette librairie est exactement ce qu’il me fallait, et de plus, elle permet de faire des calculs de cases dans certaines directions. La map est construite en SVG, et chaque case possède des attributs paramétrables tels que type de terrain, classe css, est ce une case de fin…

J’ai essayé autant que possible d’utiliser ES 6, et donc d’utiliser webpack afin de regrouper mes javascripts et mes css dans un seul fichier (app.js / app.css) pour profiter de la minification. J’ai donc ajouté plusieurs dépendances que vous pourrez voir dans le fichier package.json. Les dépendances s’installent avec un « npm install nom du js ». Ensuite, il suffit de les importer via des imports et des requires.

Le contrôleur et la gestion logique:

Le contrôleur est là pour récupérer les requêtes et appeler les fonctions de validation. Toutes les fonctions logiques ont donc étés déportées dans GameManager.php. Le contrôleur est là juste pour renvoyer où il faut.

La qualité:

Les normes PHP évoluent et deviennent de plus en plus strictes (regardez le type hinting en PHP 8). Je ne suis pas passé sur Laravel 9 / PHP 8 car OVH ne le gère pas encore, et je voulais pouvoir tester en live mon site. J’ai donc seulement mis en place PHPMD, Php Code Sniffer, et Larastan (PHPStan) pour lever toutes les erreurs basiques.

Coder proprement est un ouvrage dans je vous avais parlé ici. J’ai essayé de m’en inspirer au mieux, et je me retrouve donc avec des passages que je trouve vraiment ultra propre. Admirez:

public function update(Request $request)
    {
        $game = $this->game;
        $cardId = (int) $request->input('card_id');
        $cards = unserialize($game->cards);
        $this->moveLemming($request);
        $this->playACard($cards, $cardId);
        $this->updateMap($request);
        $this->hasWinner(unserialize($this->game->lemmings_positions));

        $game->cards = serialize($cards);
        $game->save();

        return $game;
    }

L’interface web:

L’interface utilisateur est essentielle pour un jeu vidéo. Elle doit être plaisante, et permettre de facilement comprendre les commandes. J’ai utilisé bootstrap pour que les boutons soient agréables à regarder, ainsi que font-awesome pour les icones. Pour la carte, c’était assez bizarre. J’ai constaté que le jeu de plateau démarre en haut à droite, puis il faut effectuer un tour dans le sens des aiguilles d’une montre. Ca n’est pas facilement compréhensible surtout si on ne voit pas le plateau dans son ensemble. J’ai commencé par le tourner à 90 degrés, mais il nécessitait de scroller continuellement vers le bas. Finalement, j’ai fait en sorte d’avoir une symétrie verticale afin de replacer les tuiles aux mêmes endroits mais en commençant en haut à gauche.

La carte d’origine commence en haut à droite, pas simple de comprendre comment démarrer…
Regardez bien; la carte est différente, mais pourtant le parcourt est identique 🙂

Ensuite, plutôt que de dessiner une ligne pour le départ et l’arrivée, j’ai décidé de rajouter des emojis: 🚦et 🏁. C’est plus simple, et plus visuel.

Enfin, j’ai regroupé les 2 paquets de cartes pour qu’on puisse facilement visualiser laquelle il faut jouer, et j’ai rajouté des informations sur les joueurs en haut à gauche.

Tout tient sur un écran (ok, un peu grand), mais si on scroll on peut facilement tout lire.

Les cartes:

Comme nous venons de le voir, la carte est un élément important du jeu. Elle doit permettre des couloirs, des moments de blocage… J’ai pris la carte existante du jeu original, mais dans l’absolu, je me suis dit qu’on pouvait en créer d’autres. J’ai donc créer une table pour y ajouter des cartes, si un jour quelqu’un souhaitait aller dans cette direction.

Les icônes:

Il me fallait des pions. Au départ, j’avais des numéros pour chacun, et je changeais la couleur. Mais je me suis dit qu’aujourd’hui, nous avions aussi des emojis qui pouvaient faire l’affaire. J’ai donc été cherché des petits animaux: 🦊 🦝 🦁 🐇 🐋 🦖sur le site https://emojicombos.com/animal

L’avantage c’est qu’on peut facilement les intégrer comme du texte, mais le désavantage c’est que dans le code, il devient difficile de faire des « if lion »… et qu’il faut utiliser un éditeur compatible. L’autre problème survient aussi sur les anciens systèmes qui ne possèdent pas la font adéquat. (il suffit de la télécharger, j’ai mis le lien dans le readme).
En testant sur des tablettes, je me suis rendu compte également que les fonts d’emojis avaient pas mal évolués, et que si certains comme le lapin fonctionnent un peu partout, d’autres comme le renard sont plus récents et ne fonctionnent pas sur des vieilles tablettes. J’ai donc finalement tout remplacé par des émojis certes moins jolis, mais plus fonctionnels: 🐶🐨🐗🐇🐳🐢.
De plus le placement des émojis sur les cases m’a aussi posé problème. Il y a une différence dans le placement des icones en SVG lorsqu’on les place en fonction de la hauteur des cases: le calcul basé sur les offsets n’est pas le même entre Firefox et Chrome.

Comment prévenir l’adversaire de jouer:

J’ai utilisé Pusher JS, et le système de Broadcast de Laravel. Cela permet que dès que vous avez joué, la page de l’adversaire se recharge automatiquement. Au cas ou vous ne souhaitez pas utiliser Pusher, vous pouvez utiliser d’autres librairies JS, mais il vous faudra un serveur node. Il y aussi un système de refresh automatique de 30 secondes si jamais Pusher venait à tomber. J’aurais aimé pouvoir mettre un son lorsque c’est à votre tour de jouer, mais il n’est pas possible de jouer un signal avant que l’utilisateur n’ai cliqué sur quoi que ce soit (pour éviter la pollution sonore).

Docker:

J’ai mis en place Laravel Sail. Celui-ci m’a automatiquement créé la configuration docker (docker-composer.yml), et un simple « sail up -d » et le site fonctionne directement 🙂

Les images et Dall E:

Pour ceux qui ne connaissent pas Dall E, il s’agit d’un projet mené par Google pour créer des images à partir de descriptions, et quand je vois le résultat, je suis époustouflé ! J’ai demandé de me créer un logo pour un jeu de courses entre un lapin, un renard, un lion, et un raton laveur sur un fond d’hexagones dans le style de Pokémon, et regardez le résultat :

Image générée automatiquement par Dall E 2

Oui le lion est vert, le texte est foireux, et ca bave un peu, mais vu que mon logo doit faire 100 pixels sur 100, franchement, moi je trouve ca bluffant.

Une baleine franchissant la lignée d’arrivée d’une course dans le style Pokémon.

La sécurité:

Le chat du jeu est protégé contre les failles XSS, il y a du CSRF dans les formulaires, les mots de passe sont chiffrés… mais il reste encore surement des éléments à sécuriser. Lorsque vous vous déplacer, les emplacements des pions sont envoyés. Il y a certains contrôles qui vous empêchent de vous retrouver sur une case sur laquelle vous n’avez pas cliqué (je saute sur la case fin par ex). Mais il est fort probable qu’il existe encore des moyens pour gagner la course avant les autres via des modifications de code. Mon jeu n’étant que très basique, je ne perdrais pas mon temps à les développer.

Gestion de l’imprévu:

J’avais anticipé certains imprévus comme le fait de passer sur les cases vides pour se retrouver rapidement sur la ligne d’arrivée, ou encore le fait de pousser un concurrent hors carte, mais je n’avais pas anticipé l’abandon soudain d’un concurrent. Que faire dans ce cas là ? Mon jeu était tout simplement bloqué, et il fallait recommencer une course. Suite à ce cas de figure, j’ai donc rajouté un système qui permet aux autres joueurs de l’éjecter si celui ci n’a pas jouer au bout de 90 secondes (je compare le champ updated_at de la partie à l’instant présent). Et pour le joueur qui prend son temps, il reçoit un message d’alerte toutes les 60 secondes, ce qui lui permet de continuer de garder la main et de continuer à réfléchir si il répond « je suis là » (on modifie alors simplement le champ updated_at avec l’heure actuelle).

Conclusion:

Voila, ca m’a pris environ 7 jours pour tout faire. Mais je suis content, j’ai compris comment intégrer les javascripts d’une meilleure façon dans mon framework PHP préféré. C’est encore complexe pour moi, mais je sens que je suis sur la bonne voie, et j’ai enfin pu mettre à profit les différentes choses que j’avais apprises en ce début d’année. Et en plus, je peux jouer avec mes enfants aussi en vacances maintenant 🙂

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.