Based on Lunar Lander from 1979 by Atari.
By Adame NAJI & Simon SASSI
-
Random terrain generation
-
AI playing the game.
-
Fullscreen mode
-
Beautiful demo mode
https://gitlab.univ-nantes.fr/E183694S/planet-lander-python
Requirements:
pygame >= 1.8.1
python3
Install:
git clone https://github.com/maxerbox/Planet-Lander-with-pygame --branch 1.2-final
cd planet-Lander-with-pygame
pip3 install pygame
To launch the game, run
python3 run_game.py
Go to game_config.py and set ENABLE_FULLSCREEN
to True
You can also edit WINDOW_W_WINDOWED/WINDOW_H_WINDOWED
variables to change the window size in windowed mode.
Key | Description |
---|---|
Arrow left (in game) | Rotate aircraft by +2° (to the right) |
Arrow right (in game) | Rotate aircraft by -2° (to the left) |
Arrow Up/ Space Bar (in game) | Aircraft boost |
Key D (before game) | Enable map debug |
Key P (before game) | Enable trajectory prediction |
Key I (before game) | Start game with AI |
Key U/J (before game) | Increase/Decrease number of plateforms |
Arrow Up/Down (before game) | Increase/Decrease fuel quantity |
Key A (before game) | Enable demo mode |
Key ENTER | Launch a new game |
Key Q | Quit the game |
-
Implement drag like the original game
-
Random velocity of aircraft when it spawn (fix to 50 vx)
-
Enable map wrapping (player is telepported at the opposite edge when he reach a edge)
-
AI: Estimate the best plateform to land to maximize point (predict fuel use)
-
AI: Calculate the best min height to allow the aircraft to rotate before he stabilize on the plateform
Code structure is inspired from the game Cabbages and king of Mekire. We are using the same state machine.
Copyright 2018, Sean J. McKiernan
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Only in french. Report given to the teacher.
Dans le cadre du module de modĂ©lisation mathĂ©matique, nous avons dĂ©veloppĂ© un jeu vidĂ©o et une intelligence artificielle capable de jouer Ă ce jeu. Nous avons choisi de reprendre le jeu âhitâ des annĂ©es 80 âLunar Landerâ sur borne arcade. Ce jeu est une âsimulationâ d'atterrissage sur la lune avec un seul but: poser un module lunaire.
Afin de gagner, il pose le module lunaire sur une des plusieurs plateformes dissĂ©minĂ©es dans la zone de jeu. Pour lancer une partie, il suffit d'appuyer sur la touche âentrĂ©eâ du jeu, ou sinon laisser jouer lâintelligence artificielle avec la touche I. Les seules actions possibles en jeu par le joueur sont dâagir sur l'orientation du vaisseau ou dâactiver le booster principal.
-
Il y a une quantitĂ© dâessence limitĂ©e: Ă chaque fois que le joueur appuie sur la touche âBoosterâ, une quantitĂ© dâessence est consommĂ©e.
-
Il nây a pas de possibilitĂ© de gagner de lâessence : le nombre dâunitĂ©s dâessence est fixĂ©e au dĂ©but de la partie, et il nâest pas possible dâen obtenir plus ! Sâil nây a plus dâessence, les moteurs sâĂ©teignent et le module lunaire ne peut plus utiliser son booster. Ce comportement est basĂ© sur le jeu originel, ou seul le niveau dâessence pouvait augmenter en mettant des piĂšces dans la borne d'arcade.
-
Il ne faut pas arriver trop vite sur les plateformes: une vitesse horizontale supérieure à 15 ou une vitesse verticale supérieure à 20 fera exploser le module !
-
Il ne faut pas arriver nâimporte comment sur les plateformes: un module lunaire doit atterrir perpendiculairement Ă la plateforme, un angle +- 10° est tolĂ©rĂ©.
-
Si le joueur ne respecte pas une des deux rĂšgles dĂ©crites ci-dessus, son vaisseau explose Ă l'atterrissage et il perd alors 200 unitĂ©s dâessences. La partie est perdue si lâessence atteint le niveau 0.
-
Le niveau est régénéré aléatoirement si le joueur arrive à bien se poser.
-
Le module lunaire a une vitesse horizontale initiale de 50 et apparaĂźt dans le coin supĂ©rieur gauche de lâĂ©cran en dĂ©but de partie.
-
Il nây a pas de frottements (il y en a dans le jeu originel) et toucher le bord gauche ou droit de lâĂ©cran fait exploser le module.
Chaque fois que le joueur se pose, son score augmente. Une plateforme peut multiplier le score obtenu. La détermination de ce multiplicateur est expliquée plus bas.
Le score est calculé de cette façon:
La rĂ©alisation du projet est dĂ©coupĂ©e en trois Ă©tapes. PremiĂšrement la crĂ©ation du sol lunaire avec des plateformes d'atterrissage et une gĂ©ographie alĂ©atoire. En parallĂšle, le dĂ©veloppement du comportement du module lunaire et de ses trajectoires. Enfin le dĂ©veloppement de lâIA capable de jouer au jeu.
Le sol de la Lune est gĂ©nĂ©rĂ© alĂ©atoirement Ă chaque dĂ©but de partie ou bon atterrissage. Il est composĂ© dâun ensemble de plateformes et de points (qui forment le dĂ©cor).
Une plateforme a une taille variable et une position variable. Le nombre de plateforme est dĂ©terminĂ© manuellement avant le dĂ©but de la partie. Les plateformes sont rĂ©parties dans des âzonesâ: pour N plateformes, lâĂ©cran est divisĂ© en N partit. Une plateforme est ensuite positionnĂ©e alĂ©atoirement dans sa partie dâĂ©cran, tout en respectant une contrainte de hauteur maximale.
La taille dâune plateforme est mesurĂ©e en segment de 16 px. Une plateforme fait une taille alĂ©atoire qui peut varier de 2 Ă 6 segments. Son multiplicateur de point est dĂ©terminĂ© Ă partir de sa taille (un tableau liste les diffĂ©rents multiplicateurs possibles, le premier Ă©lĂ©ment du tableau est le plus haut multiplicateur, le dernier le plus bas, câest-Ă -dire âx1â, on accĂšde au tableau Ă partir du pourcentage de la taille maximale que la plateforme reprĂ©sente).
Le dĂ©cor est ensuite gĂ©nĂ©rĂ© entre les plateformes. Lâalgorithme utilisĂ© est âlâalgorithme itĂ©ratif de dĂ©placement de point centralâ.
Cela va permettre, Ă partir de deux points qui forment un segment (le point de dĂ©but et le point de fin, start, end), dâune duretĂ© (roughness), dâune limite de dĂ©placement (limite), dâun dĂ©placement vertical (vertical_displacement) et dâun nombre dâitĂ©rations (number_of_iterations), de gĂ©nĂ©rer un ensemble de points.
Il fonctionne de la maniĂšre suivante:
-
On prend le milieu dâun segment. On dĂ©coupe ce segment en deux sous-segments (cela crĂ©e un nouveau point ayant pour coordonnĂ©es du milieu du segment).
-
On dĂ©cale ensuite ce point dâun dĂ©placement vertical alĂ©atoire (compris entre -vertical_displacement et +vertical_displacement. Ce dĂ©placement vertical est limitĂ© par limit, un tuple contenant la hauteur minimale et maximale que peut prendre le point. Ce paramĂštre limit est utilisĂ© pour Ă©viter que le dĂ©cor dĂ©borde en dehors de lâĂ©cran.
-
On multiplie ensuite le déplacement vertical par:
Cela permet de âlisserâ la courbe que vont former les segments (seulement si la duretĂ© est supĂ©rieure Ă 1). -
On rĂ©pĂšte lâopĂ©ration N fois (number_of_iterations)
Cela va donc créer (2^{nombre\ d'itérations})segments.
Le nombre dâitĂ©rations Ă effectuer pour obtenir les points entre deux plateformes est dĂ©terminĂ© Ă partir du calcul suivant:
Cela permet de garder une longueur de segment en x homogĂšne entre les plateformes. Le dĂ©placement vertical passĂ© Ă lâalgorithme est dĂ©terminĂ© selon cette formule:
Si lâespacement en X entre les deux plateformes est infĂ©rieur Ă 100, alors le dĂ©placement vertical est Ă©gal Ă la distance du milieu des deux plateformes et des plateformes (cela permet dâĂ©viter dâavoir des pics entre les plateformes proches les unes des autres)
Sinon, le déplacement vertical est égal au minimum entre:
-
la distance entre la plateforme de droite et les bords horizontaux de lâĂ©cran
-
lâordonnĂ©e du point qui est le milieu des deux plateformes
Le module lunaire, dĂ©finie par la classe Lander, est caractĂ©risĂ© par sa position sur lâaxe des abscisses((x) et des ordonnĂ©es (y) , il nous faut Ă©galement sa vitesse horizontale (vx) et la vitesse verticale (vy), pour finir on enregistre sa masse (m) et son orientation.
Pour faire avancer le vaisseau, on crĂ©e un vecteur de force basĂ© sur lâangle du vaisseau (orientation) et une accĂ©lĂ©ration (engine_power) dĂ©finie (de maniĂšre Ă rendre le jeu rĂ©aliste).
Le vecteur de force appliquĂ© sur le vaisseau est orientĂ© Ă partir de lâorientation de la façon suivante:
Ce vecteur de force nâest appliquĂ© sur le module que lorsque le joueur active les boosters. Il est donc Ă©gal Ă 0 lorsque le joueur nâappuie pas sur la touche.
Le vecteur de gravitĂ© est lui appliquer constamment, câest une accĂ©lĂ©ration verticale:
LâaccĂ©lĂ©ration du module en x et en y est obtenue Ă partir de la somme de toutes les forces que subit le module:
On stocke les coordonnées x et y de ce vecteur (fx et fy). On utilise ces coordonnées pour définir une accélération qui sera décrite horizontalement (ax) et verticalement (ay).
On la calcule ainsi :
Comme (\widehat{F} = \ m*\widehat{a}\ )alors (\widehat{a} = \frac{\widehat{F}}{m}\ ) (\ `$
Une fois lâaccĂ©lĂ©ration obtenue, on ajoute cette derniĂšre Ă la vitesse courante via les formules suivantes : (v_{x} = v_{\text{x\ }} + \widehat{a_{x}}*dt) et (v_{y} = v_{\text{y\ }} + \widehat{a_{y}}*dt`$
$`\text{dt}) correspond au temps écoulé (ce temps est fixé dans le jeu).
La derniÚre étape consiste à mettre à jour les coordonnées du vaisseau y ajoutant la vitesse par rapport à un temps (\text{dt}) prédéfini : (x = x + v_{x}*dt) et (y = y + v_{y}*dt`$
La rotation du module utilise la fonction pygame.transform.rotate qui effectue la rotation dâune surface (image) Ă partir dâun point.
Cette fonction s'appuie sur une matrice de rotation:
ou (\theta)est lâangle en radian.
Ramené à la distance entre un centre et un point:
Chacun des points de lâimage vont ĂȘtre tournĂ©es tour Ă tour.
La âflammeâ du module lunaire est un triangle dont la taille grandit quand le joueur appuie sur la touche pour booster et diminue lorsque le joueur n'appuie plus. La flamme fluctue au fur et Ă mesure du temps. Les points du triangle, qui sont relatifs au centre du vaisseau, utilisent aussi une matrice de rotation. Afin que la transition de disparition de la flamme soit plus rĂ©elle, la hauteur du triangle diminue en fonction de la racine carrĂ©e du temps:
La module lunaire prĂ©dit Ă chaque fois la trajectoire quâil va effectuer (Ă des fins visuelles et utilisĂ© par lâintelligence artificielle).
Cette trajectoire est composĂ©e dâun ensemble de points par lesquels le centre du module va passer. Elle est dĂ©terminĂ©e Ă partir de la vĂ©locitĂ© initiale et de la gravitĂ©. La trajectoire est recalculĂ© lorsque le joueur modifie les forces du module (lorsquâil utilise le booster). Les points de trajectoire sont obtenues en augmentant le temps passĂ© (\text{dt}) Ă chaque fois:
La formule (\frac{1}{2}*gravitĂ©\ *\ (dtÂČ)) est une simple intĂ©gration de lâaccĂ©lĂ©ration.
Par la mĂȘme occasion, un masque qui suit les segments formĂ©s par ces points est crĂ©Ă© (cela est utilisĂ© par lâintelligence artificielle afin de dĂ©terminer sâil va y avoir une collision avec une montagne du terrain avant lâatteinte dâune plateforme, voir le texte ci-dessous sur la dĂ©tection des collisions).
Toutes les collisions sont rĂ©alisĂ©es Ă partir des âmasquesâ intĂ©grĂ©s Ă pygame. Ces masques, qui font la taille dâune surface (une surface est une image sur laquelle on va dessiner, par exemple lâĂ©cran en est une), sont composĂ©s dâune matrice de boolĂ©en. Cette matrice de boolĂ©en est dĂ©terminĂ© Ă partir des pixels de la surface qui sont opaques (si leur opacitĂ© est au-dessus dâun certain seuil). Ces masques permettent de dĂ©tecter des collisions aux pixels prĂȘts. Un masque est appliquĂ© sur le module lunaire, le sol de la lune et les plateformes. Les masques sont comparĂ©s Ă chaque mise Ă jour du jeu (Ă chaque tour de boucle) bit Ă bit de façon diagonale[1] afin de dĂ©terminer sâil y a collision ou non.
Lâintelligence artificiel nâenvoie que des entrĂ©es claviers. Elle nâagit pas sur les Ă©lĂ©ments du jeu. Elle a accĂšs Ă la configuration du sol de la lune, lâĂ©tat du module, notamment sa vitesse, sa trajectoire, son orientation etc.
La premiĂšre IA fonctionne en apprenant de ses erreurs. Elle dĂ©coupe la fenĂȘtre de jeu en 3 parties. Lorsque le rover est dans la partie rouge Ă gauche, il est maintenu Ă une hauteur minimale de 550. Dans le cas oĂč le vaisseau tombe en dessous de cette altitude, il doit sâorienter vers le haut et effectuer une poussĂ© jusqu'Ă avoir une altitude supĂ©rieure Ă 550 et une vitesse vy de -10 (donc +10 vers le haut). Lorsque le rover est dans la partie rouge Ă droite, il devient impossible pour lui dâatteindre la plateforme et va donc se laisser tomber sur la surface de la Lune. Enfin lorsque le vaisseau se trouve dans la zone jaune, il est en phase de dĂ©cĂ©lĂ©ration. Cette zone est dĂ©limitĂ©e par une variable distance et câest cette variable que lâIA modifie pour finir une partie.
Lorsque que le vaisseau est en phase de dĂ©cĂ©lĂ©ration, lâIA oriente le moteur dans la direction opposĂ© Ă la vitesse horizontale et accĂ©lĂšre jusquâĂ avoir une vitesse vx de 0. Le vaisseau va alors se laisser tomber vers le bas en maintenant une vitesse infĂ©rieure Ă la vitesse maximale autorisĂ©e pour atterrir. LâIA ne sait pas si le vaisseau est bien au-dessus de la plateforme, mais elle va quand mĂȘme tenter de poser le vaisseau.
LâIA va ensuite entrer dans une phase de recherche de la distance idĂ©ale pour dĂ©cĂ©lĂ©rer via un algorithme de recherche dichotomique. Si le vaisseau atterrit Ă droite de la plateforme, cela signifie que la distance Ă©tait insuffisante pour dĂ©cĂ©lĂ©rer donc on la multiplie par 1.5. Dans le cas oĂč le vaisseau est Ă gauche, cela signifie que la distance Ă©tait trop grande et on la divise par deux. Au fur et Ă mesure des Ă©checs on augmente la prĂ©cision de lâIA.
La seconde IA est plus complĂšte que la premiĂšre avec un fonctionnement mathĂ©matique totalement diffĂ©rent, en plus des exigences de la premiĂšre IA, on rajoute le fait dâĂ©conomiser le carburant. On effectue les poussĂ©es strictement nĂ©cessaire pour mettre le module lunaire au-dessus de la plateforme ayant le plus haut multiplicateur de score. LâIA va donc faire en sorte dâavoir une vĂ©locitĂ© horizontale de 0 au-dessus de la plateforme, et dĂ©terminer la distance minimale Ă partir de laquelle il faut activer la pousse inverse. Elle va ensuite se laisser tomber, puis effectuer la mĂȘme chose verticalement: elle va dĂ©terminer Ă partir de quand il va falloir activer le booster pour rĂ©aliser un atterrissage parfait. Elle va aussi effectuer une poussĂ©e verticale pour Ă©viter les montagnes qui se situeraient devant la plateforme visĂ©e.
La distance minimale Ă partir de laquelle activer la âpoussĂ©e inverseâ (le module est orientĂ© Ă -180° afin dâannuler sa vĂ©locitĂ© horizontale) est dĂ©terminĂ© Ă partir de la formule suivante:
ou (v_{x})est la vitesse horizontale du module et (a)est lâaccĂ©lĂ©ration du module (Ă©gale Ă engine_power)
Lorsque le module se trouve au-dessus de la plateforme, il sâoriente Ă 90° afin dâĂȘtre dans son axe. La distance minimum Ă partir de laquelle activer le booster est calculĂ©e en âprĂ©disant le futurâ:
En augmentant (\text{dt})avec un petit pas Ă chaque fois, on obtient la vitesse verticale et la distance verticale parcourue Ă chaque instant.
A chaque futur instant, on peut ainsi calculer la distance minimale nĂ©cessaire Ă partir de la vitesse Ă lâinstant:
Si cette distance minimale est trop grande (quâelle dĂ©passe la plateforme, câest Ă cet instant que lâon doit activer le booster).
LâIA Ă©vite les montagnes en effectuant une poussĂ©e au dĂ©but de la partie, tant que la trajectoire du module est en collision avec la montagne (en utilisant le masque de la trajectoire).
- Source: bitmap.c du dépÎt officiel de pygame