Laboratoire 4

Les nouvelles notions abordées dans ce laboratoire sont les suivantes:

  • Physique:
    • Les deux premières lois de Newton.
    • La gravitation universelle.
  • Programmation Python:
    • L’écriture d’un programme complet.

Programme 7: Le vaisseau spatial

A ce stade, vous en connaissez assez pour écrire votre propre programme en Python. Ce laboratoire ne consistera donc pas à modifier quelques lignes d’un programme déjà existant, mais à développer votre propre réalisation en partant d’une feuille (presque) blanche.

L’objectif du programme que vous allez créer est de simuler le mouvement d’un vaisseau spatial dans un espace à deux dimensions. Le travail à effectuer se décompose en plusieurs étapes qui sont détaillées ci-dessous.

Création de la fenêtre

Pour commencer, le plus simple est de partir du fragment de code suivant:

import math
import pygame
import sys

# Constantes

NOIR = (0, 0, 0)

# Paramètres

dimensions_fenetre = (800, 600)  # en pixels
images_par_seconde = 25

# Initialisation

pygame.init()

fenetre = pygame.display.set_mode(dimensions_fenetre)
pygame.display.set_caption("Programme 7")

horloge = pygame.time.Clock()
couleur_fond = NOIR

while True:
   for evenement in pygame.event.get():
      if evenement.type == pygame.QUIT:
          pygame.quit()
          sys.exit()

   fenetre.fill(couleur_fond)
   pygame.display.flip()
   horloge.tick(images_par_seconde)

Ce programme initialise le module Pygame, et crée ensuite une fenêtre de 800 pixels de large et de 600 pixels de haut. (Ces valeurs sont gardées dans un paramètre afin de pouvoir facilement les modifier si nécessaire.)

L’instruction horloge = pygame.time.Clock() crée un temporisateur qui permettra de spécifier la fréquence à laquelle le contenu de la fenêtre doit être rafraîchi. La dernière instruction horloge.tick(images_par_seconde) du programme utilise ce temporisateur afin d’attendre le laps de temps approprié avant de passer à l’itération suivante de la boucle principale.

Cette boucle principale se contente actuellement d’effectuer les opérations suivantes:

  • récupérer les actions de l’utilisateur depuis l’itération précédente: pygame.event.get(). En cas de fermeture de la fenêtre (signalée par un évènement de type pygame.QUIT), le programme termine son exécution: pygame.quit() et sys.exit().
  • remplir la fenêtre avec la couleur de fond: fenetre.fill(couleur_fond).
  • rendre les dernières modifications apportées à la fenêtre visibles à l’écran: pygame.display.flip().
  • attendre le moment approprié pour passer à l’itération suivante.

Vous pouvez recopier ce programme dans un nouveau fichier appelé prog-7.py, et vérifier qu’il fonctionne correctement.

Affichage du vaisseau

Nous allons maintenant ajouter à ce programme les mécanismes permettant de simuler le mouvement d’un vaisseau spatial. La première étape consiste à gérer la position et l’orientation de celui-ci. Les modifications à effectuer sont les suivantes:

  1. Créer une variable position_vaisseau contenant les coordonnées courantes du vaisseau, exprimées dans le système d’axes de la fenêtre (en deux dimensions). Attention, comme nous souhaiterons bien sûr modifier ces coordonnées au cours de l’exécution du programme (pour simuler les mouvements du vaisseau), ces coordonnées doivent être représentées par une liste ([ ..., ... ]) et non par un tuple (( ..., ... )). (Rappel: En Python, les éléments d’un tuple ne sont pas modifiables.)

    Vous pouvez initialiser cette variable à l’aide de la position de votre choix dans la fenêtre, par exemple au centre de celle-ci, ou bien près d’un coin.

  2. De la même façon, créer une variable orientation_vaisseau contenant l’orientation courante du vaisseau, exprimée en radians. (Pour interpréter les angles, nous utiliserons les mêmes conventions que dans le laboratoire 2.)

  3. Définir une fonction afficher_vaisseau() chargée d’afficher le vaisseau dans la fenêtre, à la position donnée par position_vaisseau et avec l’orientation correspondant à orientation_vaisseau. Cette opération peut s’effectuer de la façon suivante:

    • Le corps du vaisseau est un disque rouge de 15 pixels de rayon, que l’on affiche grâce à la fonction pygame.draw.circle().

    • Pour que l’orientation du vaisseau soit visible, on va dessiner la tuyère de celui-ci, sous la forme d’un triangle orange. Afin de simplifier cette opération, il est utile de définir une fonction auxilaire dessiner_triangle(couleur, p, r, a, b) chargée de dessiner le motif ci-dessous:

      Triangle à dessiner.

      Triangle à dessiner.

      Sur cette figure, le paramètre p donne les coordonnées du centre du cercle, et r le rayon de celui-ci. Les paramètres a et b sont des angles, exprimés en radians, qui déterminent respectivement l’orientation globale du triangle et l’ouverture de celui-ci.

      Pour implémenter cette fonction, vous devez d’abord calculer les coordonnées p1 et p2 des deux autres sommets du triangle, et puis utiliser pygame.draw.polygon() pour tracer ce triangle.

    • La tuyère peut maintenant être dessinée en invoquant dessiner_triangle() avec une valeur de p correspondant à la position du vaisseau, r égal à 23 pixels, a égal à l’orientation du vaisseau plus \(\pi\) (car la tuyère est située à l’arrière), et b égal à \(\frac{\pi}{7}\).

      Une bonne technique consiste à dessiner le corps du vaisseau après la tuyère, de façon à ce que seulement l’extrémité de cette dernière reste visible.

  4. Ne pas oublier d’appeler afficher_vaisseau() dans la boucle principale, avant pygame.display.flip().

  5. Vérifier que cet affichage fonctionne correctement. Le résultat devrait être similaire à celui-ci (pour une orientation de 0 radian):

    Affichage du vaisseau.

    Affichage du vaisseau.

Orientation du vaisseau

L’étape suivante consiste à permettre à l’utilisateur de modifier l’orientation du vaisseau en agissant sur les touches « flèche gauche » et « flèche droite » du clavier. Les modifications à effectuer sont les suivantes:

  1. Insérer l’instruction pygame.key.set_repeat(10, 10) avant le début de la boucle principale. Cette instruction configure le clavier de façon à envoyer rapidement des évènements multiples chaque fois qu’une touche reste enfoncée.
  2. Au début de la boucle principale, modifier le code responsable de la gestion des évènements de façon à également traiter ceux de type pygame.KEYDOWN. Chaque fois qu’un tel évènement est reçu, invoquer une fonction gerer_touche().
  3. Dans cette nouvelle fonction gerer_touche(), déterminer sur quelle touche du clavier on a appuyé en consultant le champ key de l’évènement reçu. Celui-ci est égal respectivement à pygame.K_LEFT et pygame.K_RIGHT pour les flèches gauche et droite du clavier.
  4. Lorsque des appuis sur les flèches sont détectés, modifier l’orientation du vaisseau, par incréments de \(\frac{\pi}{20}\) radian.
  5. Vérifier que le programme fonctionne correctement.

Propulsion du vaisseau

Nous allons à présent essayer de faire bouger le vaisseau, en allumant son moteur. Dans un premier temps, nous allons mettre en place le mécanisme permettant de réaliser cet allumage. Nous aborderons ensuire la question de simuler le mouvement qui résulte de la poussée fournie par le moteur.

Le principe consiste à détecter les appuis sur une autre touche du clavier, par exemple la flèche haute, et à allumer le moteur pendant un court instant chaque fois que cela se produit. Voici les modifications à apporter à votre programme:

  1. Créer une variable compteur_propulseur initialisée à 0. Chaque fois que cette variable contiendra une valeur non nulle, cela signifiera que le moteur est allumé. Lorsque l’on détectera un appui sur la flèche haute, on attribuera une valeur donnée à cette variable, par exemple 3. Cette valeur sera ensuite décrémentée à chaque rafraîchissement de la fenêtre, jusqu’à atteindre 0. En d’autres termes, un court appui sur la flèche haute allumera le moteur pendant 3 itérations de la boucle principale.

  2. Modifier la fonction gerer_touche() de façon à également détecter les appuis sur la flèche haute (correspondant à pygame.K_UP) et, le cas échéant, mettre à jour compteur_propulseur.

  3. A la fin de la boucle principale du programme, ne pas oublier de décrémenter compteur_propulseur si sa valeur n’est pas nulle.

  4. Modifier la fonction afficher_vaisseau() afin que l’état du moteur devienne visible. On peut par exemple afficher des flammes jaunes sortant de la tuyère lorsque le moteur est allumé. Cela peut facilement se faire en réutilisant la fonction dessiner_triangle(). On peut par exemple l’invoquer avec les arguments suivants:

    • une première fois avec p égal à la position du vaisseau, r égal à 38 pixels, a égal à l’orientation du vaisseau plus \(\frac{21\pi}{20}\) et b égal à \(\frac{\pi}{30}\).
    • une seconde fois avec p égal à la position du vaisseau, r égal à 38 pixels, a égal à l’orientation du vaisseau plus \(\frac{19\pi}{20}\) et b égal à \(\frac{\pi}{30}\).

    (Une bonne idée est d’afficher les flammes avant de dessiner la tuyère.)

  5. Tester votre programme. L’affichage du moteur en fonctionnement devrait ressembler à celui-ci:

    Moteur en fonctionnement.

    Moteur en fonctionnement.

Calcul du mouvement

Nous sommes maintenant prêts à mettre le vaisseau en mouvement. Pour calculer ce mouvement, nous allons appliquer les deux premières lois de Newton, en considérant que lorsque le moteur est allumé, une force de poussée constante est appliquée au vaisseau. La direction de cette force correspond à l’orientation courante du vaisseau.

Procédure à suivre:

  1. Insérer dans la boucle principal un appel à une nouvelle fonction mettre_a_jour_position(), prenant les arguments suivants:

    • La position courante du vaisseau. Le rôle de celle fonction est de mettre à jour cette position à chaque itération de la boucle principale.

    • La valeur courante de l’horloge (appelée temps_maintenant dans les programmes précédents). Celle-ci correspond à la valeur retournée par pygame.time.get_ticks() et est exprimée en millisecondes.

    • La masse du vaisseau, exprimée en tonnes. Pour cet exercice, on peut fixer cette masse à 1 T.

    • La force de poussée exercée par le moteur sur le vaisseau. Pour rester cohérent avec les autres unités utilisés dans ce projet et simplifier les calculs, cette force ne sera pas exprimée en Newtons mais en tonnes fois pixels par milliseconde au carré. (Rappel: 1 Newton = 1 kg.m par seconde au carré.)

      Pour cet exercice, on peut considérer que la force de poussée vaut 0.0003 tonne fois pixel par milliseconde au carré lorsque le moteur est allumé, et est nulle sinon.

    • L’orientation courante du vaisseau, qui détermine donc la direction de la force de poussée.

  2. Implémenter la fonction mettre_a_jour_position(). La clé de cette opération est de déterminer combien de temps s’est écoulé depuis l’appel précédent, et d’appliquer les lois de Newton de façon à mettre à jour le vecteur vitesse et ensuite la position du vaisseau.

    Vous pouvez bien sûr utiliser les mêmes mécanismes que dans les programmes précédents, en particulier, le programme gérant la vitesse et l’accélération verticales d’un avion dans le laboratoire 1 et celui affichant les vecteurs vitesse et accélération dans le laboratoire 2. En particulier, il est nécessaire que la fonction mettre_a_jour_position() retienne l’instant de son invocation précédente, ainsi que la dernière valeur du vecteur vitesse.

    Remarque: Si vous souhaitez que votre simulation soit précise, il faut veiller à ce que le vecteur vitesse du vaisseau et la position de celui-ci soient représentés à l’aide de nombres en virgule flottante et non d’entiers. L’arrondi peut se faire au moment de l’affichage.

  3. En testant votre programme, vous allez rapidement être confronté à une situation problématique: Lorsque le vaisseau sort de la zone affichée, il est extrêmement difficile de l’y ramener sans disposer d’informations sur sa localisation exacte.

    Une façon simple d’éviter ce problème est de considérer que la simulation se déroule dans un espace fini: Si le vaisseau sort par le haut de la fenêtre, il y rentrera immédiatement par le bas, s’il sort par la gauche, il rentrera par la droite, et ainsi de suite. (Beaucoup de jeux vidéo fonctionnent ainsi.)

    Pour implémenter cela, il est utile de définir une marge, par exemple de 100 pixels. Chaque fois qu’une coordonnée du vaisseau dépassera le bord de la fenêtre d’une distance supérieure à cette marge, elle sera ramenée au côté opposé de la fenêtre, à une distance égale à la marge.

    Par exemple pour une fenêtre de 800 par 600 pixels et une marge de 100 pixels, lorsque la coordonnée \(X\) du vaisseau devient plus petite que -100 (le bord gauche de la fenêtre en tenant compte de la marge), on lui donne la valeur 900 (le bord droit avec la marge). Les autres cas se traitent d’une façon similaire.

  4. Reporter sur votre feuille de laboratoire les formules que vous avez implémentées au point 2 pour mettre à jour la position du vaisseau (Code P7-1).

Ajout d’une planète

L’étape suivante consiste à ajouter la possibilité de créer une planète, qui va exercer une influence gravitationnelle sur le vaisseau.

  1. Créer une variable position_planete contenant les coordonnées courantes de la planète, et une autre variable planete_est_presente contenant une valeur booléenne destinée à indiquer si la planete est présente ou non. Initialement, la planète n’est pas présente.

  2. Au début de la boucle principale, dans le code gérant les évènements, ajouter une condition pour les évènements de type pygame.MOUSEBUTTONDOWN, correspondant aux clics de la souris. Ces clics peuvent être traités par un appel à une fonction gerer_bouton().

  3. Dans cette nouvelle fonction, déterminer s’il s’agit d’un clic gauche ou droit en consultant le champ button de l’évènement.

    S’il s’agit d’un clic gauche, on déplace la planète à l’endroit désigné par le clic (fourni par le champ pos de l’évènement), et celle-ci devient présente si elle ne l’était pas déjà. Dans le cas d’un clic droit, la planète doit disparaître.

  4. Créer une fonction afficher_planete() chargée d’afficher la planète à sa position courante, à la condition que planete_est_presente soit vraie. La planète peut être dessinée comme un disque de 40 pixels de rayon. Ne pas oublier d’appeler cette fonction dans la boucle principale.

  5. A chaque itération de la boucle principale, vérifier que le vaisseau n’est pas entré en collision avec la planète. Si cela se produit, vous devez interrompre l’exécution de la simulation, par exemple en appelant les mêmes fonctions que lorsque l’évènement pygame.QUIT est détecté.

  6. Testez soigneusement que le mécanisme de création et de destruction de la planète fonctionne correctement. Voici un exemple du résultat attendu:

    Affichage de la planète.

    Affichage de la planète.

Attraction gravitationnelle

La dernière étape de ce laboratoire consiste à simuler l’effet de l’attraction gravitationnelle exercée par la planète sur le vaisseau. Nous supposerons que que la planète est beaucoup plus massive que le vaisseau, et nous négligerons donc les mouvements de la planète résultant de cette attraction gravitationnelle. (En d’autres termes, nous supposerons que la planète reste fixe.)

Procédure à suivre:

  1. Ajouter deux nouveaux paramètres à la fonction mettre_a_jour_position():

    • la masse de la planète, et
    • les coordonnées de celle-ci.

    Lorsque cette fonction est appelée dans le programme principal, vous pouvez utiliser une masse de 1600 T dans le cas où la planète est présente, et de 0 T si elle est absente (ce procédé permet de gérer de la même façon ces deux situations).

  2. Dans la fonction mettre_a_jour_position(), calculer la force d’attraction exercée par la planète sur le vaisseau, et tenir compte de cette force dans le calcul du mouvement du vaisseau. Pour cette simulation, la constante gravitationnelle doit être égale à 0.001. Dans notre système d’unités exotique, cette constante s’exprime en pixels au cube par tonne fois milliseconde au carré.

  3. Votre programme fonctionne-t-il? Arrivez-vous à piloter le vaisseau de façon à éviter une collision avec la planète? Et seriez-vous capable de placer le vaisseau en orbite autour de celle-ci?

    Ne vous privez pas d’expérimenter! Essayez par exemple de doubler la masse du vaisseau sans modifier les autres données du problème. Que se passe-t-il alors? L’aviez-vous prédit?

  4. Reporter l’expression de la force d’attraction obtenue au point 2 sur votre feuille de laboratoire (Code P7-2).

  5. Déposer votre programme dans le répertoire centralisé des laboratoires:

    • Placez-vous dans le répertoire /home/boigelot/lab-mp-1/labo-4.
    • Recopiez-y votre programme dans un fichier appelé nom1-nom2-nom3--prog-7.py, où le préfixe contient les noms des membres de votre groupe. (Vous pouvez bien sûr l’adapter à la taille de votre groupe. En cas d’homonymie, veillez à indiquer également votre prénom.)
    • Protégez ce fichier contre la lecture grâce à la commande chmod 600 nom-du-fichier.