Retour au blog

Comment j’ai entraîné un algorithme génétique à jouer à Geometry Dash en JavaScript

En regardant une vidéo YouTube, je suis tombé sur un projet où une IA apprend à jouer à Geometry Dash grâce à un algorithme génétique. Intrigué, j’ai décidé de recréer l’expérience moi-même en JavaScript : clone du jeu, éditeur de niveaux et système d’entraînement pour faire évoluer 1000 joueurs virtuels génération après génération. Voici comment j’ai construit ce projet… et pourquoi la solution la plus simple s’est finalement révélée la plus efficace.

Il y a quelques semaines, en regardant des vidéos sur YouTube, je suis tombé sur une vidéo du YouTuber CODEBH qui m’a particulièrement intrigué :

https://www.youtube.com/watch?v=MTcXW94V838

Dans cette vidéo, il explique comment il a utilisé un algorithme génétique pour entraîner une IA à jouer au jeu Geometry Dash.

Le principe est assez fascinant : plutôt que de programmer explicitement une stratégie pour éviter les obstacles, on crée une population d’agents qui apprennent progressivement en évoluant génération après génération.

Malheureusement, la vidéo ne proposait ni dépôt GitHub ni explication détaillée de l’algorithme utilisé. J’ai donc décidé de recréer le projet moi-même en me basant uniquement sur les informations données dans la vidéo et j’ai décidé de le faire dans mon langage de prédilection : JavaScript.


Recréer le jeu

La première étape était évidemment de recréer le jeu lui-même.

Mon objectif n’était pas seulement d’entraîner une IA. Je voulais aussi construire un petit environnement complet permettant :

  • de jouer manuellement au jeu
  • de créer des niveaux
  • d’entraîner l’IA
  • et de rejouer les meilleurs génomes trouvés par l’algorithme

J’ai donc réfléchi à l’architecture globale de l’application.

Le projet devait inclure :

  • un moteur de jeu
  • un éditeur de niveaux
  • un dashboard d’entraînement
  • un mode simulation pour l’IA
  • un mode joueur pour tester la physique du jeu

Comme recréer un clone complet de Geometry Dash pouvait prendre beaucoup de temps, j’ai décidé d’accélérer cette première étape.

J’ai demandé de l’aide à une IA pour générer un clone du jeu.

Et honnêtement, le résultat était assez impressionnant : l’IA a réussi à recréer un jeu fonctionnel assez rapidement.

Bien sûr, tout n’était pas parfait. J’ai dû corriger plusieurs bugs, notamment sur la physique du joueur et la détection de collision, mais la base était déjà là.

Ensuite, toujours avec l’aide d’une IA, j’ai généré un éditeur de niveaux. Après quelques ajustements, j’avais un outil utilisable.

Au final, en environ une heure, j’avais déjà :

  • un clone jouable du jeu
  • un éditeur de niveaux
  • une base solide pour commencer à entraîner l’IA


Architecture du projet

Une fois le jeu en place, la partie intéressante pouvait commencer : l’algorithme génétique.

J’ai structuré mon projet autour de plusieurs classes principales :

  • GA Manager
    orchestre les générations et la population

  • GA Worker
    exécute les simulations dans un Web Worker

  • Simulator
    crée un environnement virtuel pour tester un joueur

  • AI Controller
    pilote le joueur virtuel

  • Genome
    représente le “cerveau” de l’IA

  • Genetic Algorithm
    gère sélection, reproduction et mutation

L’objectif était de pouvoir simuler beaucoup de joueurs simultanément.

Pour entraîner efficacement l’IA, je voulais pouvoir tester 1000 joueurs par génération. Pour éviter de bloquer le thread principal du navigateur, j’ai donc exécuté les simulations dans un Web Worker.

Chaque joueur virtuel joue une partie complète et obtient un score basé sur la distance parcourue.


Comment fonctionne le “cerveau” de l’IA

Plutôt que d’utiliser un réseau de neurones classique, j’ai utilisé un système beaucoup plus simple basé sur des capteurs (triggers).

Chaque génome est composé de plusieurs réseaux de capteurs.

Dans mon implémentation :

  • un génome contient plusieurs réseaux
  • chaque réseau contient plusieurs capteurs

La logique est la suivante :

  • tous les capteurs d’un réseau doivent être actifs (AND)
  • si au moins un réseau est actif (OR)
    → le joueur saute.

Chaque capteur possède :

  • une position relative autour du joueur
  • un type de détection

Les capteurs peuvent détecter :

  • la présence d’air
  • la présence d’un bloc
  • la présence d’un pic

Et aussi l’absence de ces éléments.

Au total, cela donne 6 types de capteurs différents.

Lorsqu’un objet entre dans la zone du capteur, celui-ci s’active.

Si toutes les conditions d’un réseau sont validées, l’IA déclenche un saut.


L’algorithme génétique

Pour l’évolution, j’ai utilisé un algorithme génétique assez simple.

Chaque génération commence avec 1000 joueurs.

Chaque joueur possède un génome généré aléatoirement.

Une simulation est ensuite exécutée pour chaque joueur afin d’obtenir son fitness, qui correspond simplement à la distance parcourue dans le niveau.

Une fois tous les joueurs évalués :

  1. la population est triée par score
  2. les 50 meilleurs joueurs sont conservés (les élites)
  3. chaque élite produit plusieurs enfants mutants

Le calcul est simple :

(1000 - 50) / 50 = 19

Chaque élite produit donc 19 enfants mutants afin de recréer une population totale de 1000 joueurs.



Les mutations

Chaque enfant reçoit une seule mutation.

Les probabilités sont les suivantes :

  • 50 % → modifier un capteur existant
  • 25 % → ajouter un capteur
  • 25 % → supprimer un capteur

Lorsqu’un capteur est modifié, plusieurs mutations sont possibles :

  • changer son type (air, bloc, pic…)
  • modifier sa position
  • le déplacer vers un autre réseau

Ces petites modifications permettent d’explorer progressivement l’espace des solutions.


Quand la simplicité gagne

Au départ, j’ai essayé de faire quelque chose de plus sophistiqué.

J’ai testé plusieurs techniques classiques utilisées dans les algorithmes génétiques :

  • croisement de génomes
  • pénalité pour les comportements inutiles
  • pénalité pour les génomes trop complexes
  • pénalité pour les élites trop similaires
  • taux de mutation variable

Par exemple, j’avais essayé de pénaliser les joueurs qui sautaient trop souvent afin d’éviter les comportements absurdes.

Le problème, c’est que ces modifications rendaient la fonction de fitness beaucoup trop complexe.

Résultat : l’IA ne terminait jamais le niveau.

Après plusieurs essais infructueux, j’ai pris une décision simple : tout supprimer et revenir à l’approche la plus basique possible.

Et c’est là que ça a marché.

Dès la première tentative avec une fitness simplifiée, l’IA a réussi à terminer le niveau.

la solution la plus simple était la meilleure.


Le comportement de l’entraînement

L’évolution de la fitness suit une courbe assez classique.

Au début, les progrès sont rapides.

Ensuite, l’algorithme atteint parfois des plateaux.

Cela arrive souvent lorsqu’un nouveau type d’obstacle apparaît dans le niveau. L’algorithme doit alors découvrir une nouvelle combinaison de capteurs permettant de passer cet obstacle.

Après quelques générations supplémentaires, une nouvelle solution apparaît et la progression reprend.



Limites de l’approche

Même si l’expérience fonctionne bien, cette approche a plusieurs limites.

Contrairement à un réseau de neurones entraîné par rétro-propagation, un algorithme génétique n’apprend pas directement de ses erreurs.

Chaque génération repose en grande partie sur le hasard des mutations.

Dans ce projet, cela fonctionne plutôt bien car :

  • le nombre d’actions possibles est très limité (sauter ou attendre)
  • l’espace des solutions est relativement restreint
  • les capteurs sont confinés dans une zone autour du joueur

Mais sur des problèmes plus complexes, cette approche peut devenir inefficace.

Il existe d’ailleurs de nombreuses techniques avancées pour améliorer les algorithmes génétiques :

  • NEAT
  • speciation
  • mutation adaptative
  • co-évolution

Mais leur implémentation devient rapidement beaucoup plus complexe.


Axes d’amélioration

Même si l’IA arrive à terminer le niveau, plusieurs points pourraient être améliorés.

Par exemple, les génomes que j’obtiens sont souvent assez complexes avec de nombreux capteurs, alors que dans la vidéo originale ils semblent beaucoup plus propres.

Plusieurs pistes pourraient être explorées :

  • introduire une pénalité douce pour les génomes trop complexes
  • permettre à un capteur d’appartenir à plusieurs réseaux
  • améliorer la sélection des élites
  • ajouter un mécanisme de speciation pour préserver la diversité
  • limiter la zone de recherche des capteurs

Il serait aussi intéressant d’expérimenter avec d’autres types de capteurs ou d’actions.


Conclusion

Ce projet était avant tout une expérience technique.

Au final, j’ai réussi à :

  • recréer un clone jouable de Geometry Dash
  • construire un éditeur de niveaux
  • implémenter un algorithme génétique complet
  • entraîner une IA capable de terminer un niveau

Et surtout, j’ai appris une chose importante :

dans ce type de système, la simplicité est souvent la clé.

Si le projet vous intéresse, voici le code GitHub afin que vous puissiez tester ou expérimenter vous-même :

https://github.com/alexisgrau/gd-genetic-algo