« Pygame/Concevoir des jeux avec Pygame » : différence entre les versions

Contenu supprimé Contenu ajouté
m →‎Une remarque sur les styles d'écriture : remplacement: quelque soit → quel que soit avec AWB
m Formatage, ajout de code
Ligne 21 :
* '''La boucle principale''' : C'est dans cette boucle que vous placerez la gestion des entrées (c'est à dire l'acquisition des évènements utilisateurs que sont les frappes de clavier/bouton de souris), le code de mise à jour des objets du jeu, et finalement la mise à jour de l'écran.
 
Tous les jeux que vous ferez auront certaines, voire la totalité de ces sections, et probablement d'autres de votre propre cru. Dans le cadre de ce tutoriel, je parlerai de la façon dont Tom's Pong est agencé, et de la façon d'appliquer cette organisation à chaque projet de jeu que vous pourriez avoir. Je supposerai également que vous voudriez garder tout le code dans un seul fichier, mais si vous faites un jeu plutôt conséquent en taille de code, c'est souvent une bonne idée de séparer le jeu en plusieurs modules. Mettre les classes des objets du jeu dans un fichier <ttcode>objects.py</ttcode>, par exemple, peut vous aider à séparer la logique du jeu de ses objets. Si vous avez énormément de code pour la manipulation des ressources, il peut également être pratique de le mettre dans un module <ttcode>ressources.py</ttcode>. Vous pourrez alors écrire <ttcode>from objects, resources import *</ttcode> pour importer toutes les classes et les fonctions.
 
=== Une remarque sur les styles d'écriture ===
Ligne 87 :
=== Objets Pygame de base ===
 
Comme vous pouvez le constater, le code se divise en trois catégories principales : la fenêtre d'affichage (<ttcode>screen</ttcode>), l'arrière-plan (<ttcode>background</ttcode>) et le texte (<ttcode>text</ttcode>). Chacun de ces objets a pu être créé grâce à l'appel en premier lieu de la méthode <ttcode>pygame.init()</ttcode>, que nous avons modifiée ensuite pour qu'elle convienne à nos besoins. La fenêtre d'affichage est un cas un peu spécial, car elle modifie l'affichage à travers les appels <ttcode>pygame</ttcode>, plutôt que d'appeler les méthodes appartenant aux objets de l'écran. Mais pour tous les autres objets Pygame, nous créons d'abord l'objet comme une copie d'un objet Pygame, en lui affectant certains attributs, et développons les objets de notre jeu à partir de celui-ci.
 
Pour l'arrière-plan, nous créons d'abord un objet Surface et lui donnons la taille de la fenêtre. Nous utilisons ensuite la méthode <ttcode>convert()</ttcode> pour convertir la Surface en un unique [[w:Espace colorimétrique|espace colorimétrique]]. C'est particulièrement recommandé lorsque nous manipulons plusieurs images et surfaces, toutes dans un espace colorimétrique différent, sinon cela ralentirait de beaucoup le rendu. En convertissant toutes les surfaces, nous pouvons accélérer drastiquement les temps de rendu. Enfin nous remplissons la surface d'arrière-plan en blanc (255, 255, 255). Ces valeurs sont en RGB et nous pouvons les retrouver à partir de n'importe quel bon programme de dessin.
 
En ce qui concerne le texte, nous avons besoin de plus d'un objet. D'abord nous créons un objet <ttcode>font</ttcode>, qui définira quelle police nous utiliserons, ainsi que sa taille. Ensuite nous créons un objet <ttcode>text</ttcode>, en utilisant la méthode de rendu de notre objet <ttcode>font</ttcode> et en lui fournissant trois arguments : le texte à faire le rendu, qui sera ou non anti-crénelé (1=oui, 0= non), ainsi que la couleur du texte (toujours dans un format RGB). Ensuite nous créons un troisième objet texte qui fournira le rectangle du texte. La manière la plus simple à comprendre est de s'imaginer en train de dessiner un rectangle qui englobera tout le texte. Vous pourrez alors utiliser ce rectangle afin d'obtenir ou de définir la position du texte sur la fenêtre d'affichage. Ainsi dans cet exemple nous avons le rectangle, et définissons ses attributs <ttcode>centerx</ttcode> et <ttcode>centery</ttcode> pour correspondre aux <ttcode>centerx</ttcode> et <ttcode>centery</ttcode> de l'arrière-plan, alors le texte aura le même centre que l'arrière-plan. Dans cet exemple, le texte sera centré sur les axes <ttcode>x</ttcode> et <ttcode>y</ttcode> de la fenêtre d'affichage.
 
=== Blitting ===
 
Maintenant que nous avons créé les objets de notre jeu, nous avons besoin d'en faire le rendu. Si nous ne le faisons pas et que nous exécutons le programme, nous ne verrons qu'une fenêtre blanche, et nos objets resteront invisibles. Le terme employé pour faire un rendu des objets est le ''blitting'' (que nous franciserons par ''blit''), qui correspond à la copie de pixels d'un objet source vers un objet de destination. Ainsi pour faire une rendu de l'objet <ttcode>background</ttcode>, vous le blitez<!-- aah !--> sur l'objet <ttcode>screen</ttcode>. Dans cet exemple, pour faire les choses simples, nous blitons le texte sur l'arrière-plan (donc l'arrière-plan possède une copie du texte sur lui), et ensuite nous blitons l'arrière-plan sur l'écran.
 
Le blit est une des opérations les plus lentes dans un jeu, vous devez donc faire attention à ne pas trop faire de blit sur l'écran pour chaque image. Par exemple, si vous avez une image d'arrière-plan, et une balle se déplaçant à travers l'écran, alors vous pouvez bliter l'arrière-plan en entier et ensuite la balle, tout ceci à chaque image, ce qui recouvrira la position précédente de la balle et fera un rendu de la nouvelle balle, mais ce sera plutôt lent. Une meilleure solution consiste à bliter une partie de l'arrière-plan sur la zone occupée par la balle à l'image précédente, qui peut être trouvée grâce au <ttcode>rect</ttcode> de la balle précédente, et ensuite bliter la nouvelle balle, ce qui aura pour effet de bliter seulement deux petites zones.
 
=== La boucle d'évènement ===
 
Une fois que vous avez défini le jeu, vous avez besoin de le mettre dans une boucle qui s'exécutera en continu jusqu'à ce que l'utilisateur signale qu'il veuille quitter. Vous démarrerez donc une boucle ouverte, et à chaque itération de la boucle, qui sera chaque image du jeu, vous actualiserez le jeu. La première chose à contrôler pour chaque évènement, est de savoir si l'utilisateur à enfoncé une touche du clavier, cliqué un bouton de la souris, déplacé le joystick, redimensionné la fenêtre, ou tenté de la fermer. Dans ce cas, nous voudrons simplement examiner si l'utilisateur a essayé de fermer la fenêtre, auquel cas le jeu engendrera un <ttcode>return</ttcode>, ce qui terminera la boucle <ttcode>while</ttcode>. Alors nous aurons simplement besoin de re-bliter l'arrière-plan, et faire un ''flip'' (actualisation de l'affichage) de l'écran pour que chaque chose soit redessinée. D'accord, étant donné que rien ne se passe ou se déplace dans cet exemple, nous n'avons aucunement besoin de re-bliter l'arrière-plan à chaque itération, mais je le met parce que si certaines choses se déplacent à travers l'écran, vous aurez besoin de faire tous vos blits ici.
 
=== Ta-da! ===
Ligne 109 :
== Coup d'envoi ==
 
Les premières sections du code sont relativement simples, et une fois écrites peuvent souvent être réutilisées dans d'autres jeux que vous programmerez. Elles s'occuperont de toutes les tâches fastidieuses et génériques comme : charger des modules, charger des images, ouvrir des connections réseau, jouer de la musique, etc. Elles incluront également de simples mais efficaces gestionnaire d'erreurs, et les quelques personnalisations que vous souhaiterez effectuer par dessus les fonctions fournies par des modules comme <ttcode>sys</ttcode> et <ttcode>pygame</ttcode>.
 
=== Les premières lignes et le chargement de modules ===
Ligne 163 :
</source>
 
Ici nous avons créé une fonction de chargement d'image plus sophistiquée que celle fournie par Pygame : <ttcode>image.load()</ttcode>. À noter que la première ligne de la fonction débute par un ''docstring'' (chaine de caractère de documentation) qui décrit ce que fait la fonction et quel objet elle retourne. La fonction suppose que toutes vos images soient dans un répertoire appelé <ttcode>data<ttcode>, et donc utilisera le nom de fichier et créera le chemin complet (par exemple <ttcode>data/ball.png</ttcode>), en utilisant le module <ttcode>os</ttcode> pour s'assurer de la compatibilité entre plateforme différente (Linux, MacOS, Windows, ...). Ensuite elle essaye de charger l'image, et de convertir les régions alpha (ce qui vous permettra d'utiliser la transparence), et le cas échéant retourne une erreur ''lisible par un être humain'' si elle rencontre un problème. Finalement elle retourne un objet image, ainsi que son <ttcode>rect</ttcode>.
 
Vous pouvez créer des fonctions similaires pour le chargement de n'importe quelle autre ressource, tel que le chargement des sons. Vous pouvez aussi créer des classes de gestion de ressources, pour vous donner plus de flexibilité avec des ressources plus complexes. Par exemple, vous pouvez créer une classe <ttcode>Music</ttcode>, avec une fonction <ttcode>__init__()</ttcode> qui charge le son (peut-être en empruntant la fonction <ttcode>load_sound()</ttcode>), une méthode pour mettre en pause la musique, une méthode pour la redémarrer. Une autre classe de gestion de ressources utile peut être créée pour les connexions réseau. Des fonctions pour ouvrir des [[Apprendre à programmer avec Python/Communications à travers un réseau|sockets]], passer des données avec une sécurité appropriée et muni d'un contrôle d'erreur, fermer des sockets, [http://jargonf.org/wiki/finger finger] des adresses, ainsi que d'autres tâches concernant le réseau, pourront rendre l'écriture d'un jeu avec des capacités réseau moins pénible.
 
Souvenez-vous que la tâche première de ces fonctions/classes est de s'assurer qu'avec le temps, l'écriture des classes d'objet, et de la boucle principale, il n'y ait presque plus rien à faire. L'héritage de classes peut rendre ces classes de bases utiles. Ne vous emballez pas, des fonctions qui ne seront utilisées que par une classe devront être écrites dans cette classe, et non pas dans une fonction globale.
Ligne 199 :
</source>
 
Ce n'est bien sûr qu'un exemple très simple, et vous aurez besoin d'y insérer tout le code nécessaire, en lieu et place des commentaires entre crochets. Mais vous devez connaître l'idée de base. Vous créez une classe, dans laquelle vous insérez toutes les fonctions d'une balle, en y incluant <ttcode>__init__()</ttcode>, qui créera tous les attributs d'une balle, et <ttcode>update()</ttcode>, qui déplacera la balle dans sa nouvelle position, avant de la bliter à l'écran dans cette position.
 
Vous avez la possibilité de créer d'autres classes pour tous les autres objets de jeu, et vous pourrez ensuite créer des instances pour chaque, et ainsi les gérer facilement à partir de la fonction <ttcode>main</ttcode> et/ou de la boucle du programme principal. Contrastez ceci avec le fait d'initialiser la balle dans la fonction <ttcode>main</ttcode>, et alors vous obtiendrez quantité de fonctions sans classes pour manipuler cet objet balle, et vous comprendrez heureusement pourquoi l'utilisation des classes est un avantage : cela vous permet de mettre tout le code de chaque objet à un seule endroit. Ceci rend l'utilisation des objets plus simple, et l'ajout de nouveaux objets et leur manipulation plus flexible. Au lieu d'ajouter plus de code pour chaque nouvel objet balle, vous pouvez simplement créer de nouvelles instances pour chaque nouvel objet balle. Magique!
 
=== Une simple classe balle ===
 
Voici une classe simple incluant les fonctions nécessaires pour la création d'un objet balle qui se déplacera sur l'écran si la fonction <ttcode>update()</ttcode> est appelée :
 
<source lang="python">
Ligne 231 :
</source>
 
Ici nous avons la classe <ttcode>Ball</ttcode>, avec une méthode <ttcode>__init__()</ttcode>, qui paramètre la balle, et une méthode <ttcode>update()</ttcode> qui change le rectangle de la balle pour une nouvelle position, et une méthode <ttcode>calcNewPos()</ttcode> pour calculer la nouvelle position de la balle basée sur sa position courante, et le vecteur par lequel elle se déplace. J'expliquerai la gestion de la physique dans un moment. La seule autre chose à noter est le ''docstring'', qui est un peu plus long cette fois, et explique les bases de la classe. Ces chaînes de caractères sont utiles non seulement pour vous-même et les autres programmeurs qui lisent votre code, mais aussi pour les outils qui [[w:Parseur|parsent]] votre code et le documentent. Elle ne feront pas la différence dans de petits programmes, mais dans les gros elles sont inestimables, c'est donc une bonne habitude à prendre.
 
==== Diversion 1 : Sprites ====
 
L'autre raison à la création d'une classe pour chaque objet est les sprites. Chaque image dont vous ferez le rendu dans votre jeu sera un objet sprite, et pour commencer : la classe de chaque objet devra hériter de la classe <ttcode>Sprite</ttcode>. L'héritage de classe est une fonctionnalité géniale de Python. A partir de maintenant la classe <ttcode>Ball</ttcode> possède toutes les méthodes de la classe <ttcode>Sprite</ttcode>, n'importe quelle instance d'objet de la classe <ttcode>Sprite</ttcode> sera enregistrée comme étant un sprite par Pygame. Tant que le texte et l'arrière-plan ne se déplacent pas, ca reste correcte de bliter l'objet sur l'arrière-plan, Pygame manipule les objets sprite d'une manière différente que vous verrez lorsque nous examinerons le code du programme en entier.
 
En résumé, vous créez un objet <ttcode>Ball</ttcode> et un objet <ttcode>Sprite</ttcode> pour cette balle, et ensuite vous appelez la méthode <ttcode>update()</ttcode>sur l'objet <ttcode>Sprite</ttcode>, ce qui actualisera le sprite. Les sprites vous fournissent une manière sophistiquée de déterminer si deux objets sont en collision. Normalement vous pouvez simplement contrôler dans la boucle principale si leur rectangle se chevauchent, mais ca implique beaucoup de code qui sera inutile puisque la classe <ttcode>Sprite</ttcode> vous fournit spécialement les deux méthodes <ttcode>spritecollide()</ttcode> et <ttcode>groupcollide()</ttcode>.
 
==== Diversion 2 : Physique des vecteurs ====
 
Les autres choses à connaître à propos de ce code, en dehors de la structure de la classe <ttcode>Ball</ttcode>, ce sont les physiques de vecteur, utilisées pour calculer le mouvement de la balle. Dans n'importe quel jeu impliquant un mouvement angulaire, vous devez être à l'aise en trigonométrie, je ferais juste une petite introduction sur ce que vous devez savoir, pour comprendre la méthode <ttcode>calcNewPos()</ttcode>.
 
Pour commencer, vous avez remarqué que la balle possède un attribut <ttcode>vector</ttcode>, qui est construit à partir de <ttcode>angle</ttcode> et de <ttcode>z</ttcode>. L'angle est mesuré en [[w:Radian|radians]] et vous donne la direction dans laquelle se dirige la balle. <ttcode>z</ttcode> correspond à la vitesse à laquelle la balle se déplace. Ainsi en utilisant ce vecteur, nous pouvons déterminer la direction et la vitesse de la balle, et donc de combien elle doit se déplacer sur les axes X et Y.
 
Dans les bases mathématiques derrière les vecteurs, la partie gauche montre le mouvement projeté de la balle, représenté par la ligne bleue. La longueur de cette ligne (z) représente sa vitesse et l'angle est la direction dans elle se déplace. L'angle 0 pour le mouvement de la balle sera toujours pris dans le sens positif de l'axe des X (vers la droite), et sera mesuré dans le sens des aiguilles d'une montre comme vu sur le diagramme.
Ligne 259 :
=== Une simple classe Bat ===
 
Le principe derrière la classe Bat est similaire à la classe Ball. Vous avez besoin d'une méthode <ttcode>__init__()</ttcode> pour initialiser la raquette (vous pourrez donc créer des instances d'objet pour chaque raquette), d'une méthode <ttcode>update()</ttcode> pour appliquer les changements sur la raquette avant de la blitter à l'écran, et diverses autres méthodes qui définiront ce que fait cette classe. Voici un échantillon du code :
 
<source lang="python">
Ligne 301 :
</source>
 
Comme vous pouvez le voir, cette classe est très similaire à la classe Ball dans sa structure. Mais les différences se situent dans ce que fait chaque fonction. Tout d'abord, il y a une méthode <ttcode>reinit()</ttcode> qui est utilisée lorsqu'un round est terminé : la raquette retourne dans sa position de départ, et chacun de ses attributs à ses valeurs d'origine. Ensuite, la manière dont la raquette bouge est un peu plus complexe que la balle, étant donné que ses mouvements sont simples (haut/bas) mais dépendent de ce que désire l'utilisateur, tandis que la balle conservera son mouvement à chaque image. Pour mettre en évidence la façon dont la raquette bouge, il est pratique d'examiner ce petit diagramme pour voir la séquence des évènements :
 
{| border = "0"
Ligne 313 :
|}
 
C'est ce qui se passe ici si la personne qui contrôle la raquette enfonce la touche qui fait se déplacer la raquette vers le haut. A chaque itération de la boucle principale du jeu (à chaque image), si la touche est maintenue enfoncée, alors l'attribut <ttcode>state</ttcode> de cet objet raquette sera paramétré à "moving", et la méthode <ttcode>moveup()</ttcode> sera appelée, causant la réduction de la position Y de la raquette d'une valeur correspondant à l'attribut <ttcode>speed</ttcode> (dans cet exemple 10). En d'autre mots, tant que la touche reste enfoncée, la raquette se déplacera à l'écran de 10 pixels par image. L'attribut <ttcode>state</ttcode> n'est pas utilisé ici, mais c'est très utile de le connaître si vous désirez appliquer des effets à la balle, ou si vous utilisez une sortie pour le débugage.
 
==== Diversion 3 : évènements Pygame ====
Ligne 334 :
</source>
 
Ici nous supposons que vous avez déjà créé une instance de <ttcode>Bat</ttcode>, et appelé l'objet <ttcode>player</ttcode>. Vous pouvez observer la couche familière de la structure <ttcode>for</ttcode>, qui produit une itération à chaque évènement trouvé dans la file d'évènement de Pygame, eux-même retrouvés grâce à la fonction <ttcode>event.get()</ttcode>. L'utilisateur enfonce une touche, appuie sur le bouton de la souris, ou bouge le joystick, toutes ces actions seront placées dans la file d'évènement de Pygame, et conservées jusqu'à leur utilisation. Donc à chaque itération de la boucle de jeu principale, vous irez faire un tour dans ces évènements vérifier s'il y en a quelques uns que vous pouvez utiliser. La fonction <ttcode>event.pump()</ttcode> qui était dans la méthode <ttcode>Bat.update()</ttcode> est appelée à chaque itération pour ''pomper'' les vieux évènements et garder la file à jour.
 
D'abord nous vérifions si l'utilisateur veut quitter le programme, et si oui on quitte le programme. Ensuite nous vérifions si une touche est enfoncée, et si oui, nous vérifions si elle correspond à une des touches affectée au déplacement de la raquette, si oui alors nous appelons la méthode de déplacement appropriée, et définissons l'état du joueur. A travers les états "moveup" et "movedown", modifiés par les méthodes <ttcode>moveup()</ttcode> et <ttcode>movedown()</ttcode>, nous produisons un code plus soigné et nous ne cassons pas l'''encapsulation'', ce qui signifie que vous assignez des attributs aux objets eux-mêmes, sans se référer au nom de l'instance de cet objet. Remarquez que nous avons 3 états : "still", "moveup" et "mouvedown". Encore une fois, ils deviennent utiles si vous voulez débugguer ou calculer un effet sur la balle. Nous vérifions aussi si une touche est "partie" (si elle n'est plus enfoncée), et alors si c'est la bonne touche, nous stoppons le déplacement de la raquette.
 
== Assembler le tout ==
Ligne 344 :
=== Faire rebondir la balle sur les bords de l'écran ===
 
La principe de base de ce type de rebond est simple à comprendre. Vous prenez les coordonnées des 4 coins de la balle, et vous vérifiez s'ils correspondent avec les coordonnées X et Y des bords de l'écran. Donc si les coins haut-gauche et haut-droit ont leur coordonnée Y à 0, vous savez que la balle est actuellement contre le bord haut de l'écran. Nous ferons tout cela dans la fonction <ttcode>update()</ttcode>, après que nous ayons défini la nouvelle position de la balle.
 
<source lang="python">
Ligne 362 :
</source>
 
Ici nous contrôlons que la variable <ttcode>area</ttcode> contient la nouvelle position de la balle. Elle devrait toujours être vraie, nous n'aurons donc pas besoin de la clause <ttcode>else</ttcode>, bien que dans d'autres circonstances vous devriez considérer ce cas de figure. Nous contrôlons alors si les coordonnées des quatre coins entrent en collision avec les bords de l'écran, et créons des objets pour chaque résultat. Si c'est vérifié, les objets auront leur valeur à 1, ou <ttcode>true</ttcode>. Sinon, la valeur sera <ttcode>None</ttcode>, ou <ttcode>false</ttcode>. Nous verrons alors si elle touche le dessus ou le dessous, et si oui nous changerons la direction de la balle. En pratique, grâce à l'utilisation des radians, nous pourrons le faire facilement, juste en inversant sa valeur (positif/négatif). Nous contrôlons aussi que la balle ne traverse pas les bords, ou alors nous appellerons la fonction <ttcode>offcourt()</ttcode>. Ceci dans mon jeu, replace la balle, ajoute 1 point au score du joueur spécifié lors de l'appel de la fonction, et affiche le nouveau score.
 
Enfin, nous réaffectons le vecteur basé sur le nouvel angle. Et voilà, la balle rebondiras gaiement sur les murs et sortira du court avec un peu de chance.
Ligne 393 :
</source>
 
Nous débutons cette section à partir de la condition <ttcode>else</ttcode>, à cause de la partie précédente du code qui vérifie si la balle frappe les bords. Cela prend tout son sens si elle ne frappe pas les bords, elle pourrait frapper une raquette, donc nous poursuivons la condition précédente. Le premier problème est de réduire le rectangle des joueurs de 3 pixels dans les deux dimensions pour empêcher la raquette de frapper la balle lorsqu'elle est derrière elle. Imaginez que vous venez juste de bouger la raquette et que la balle se trouvait derrière elle, les rectangles se chevauchent, et la balle sera considérée comme "frappée", c'est pour prévenir ce petit bug.
 
Ensuite nous vérifions si les rectangles entrent en collision, avec une correction de bug de plus. Remarquez que j'ai commenté toute cette partie de code, c'est toujours préférable d'expliquer une partie du code qui parait obscure, que ce soit pour les autres qui regardent votre code, ou pour vous lorsque vous y reviendrez plus tard. Sans la correction du bug, la balle pourra heurter un coin de la raquette, changer de direction, et l'image d'après, trouver qu'elle est toujours à l'intérieur de la raquette. Alors le programme pensera que la balle est frappée une nouvelle fois et rechangera de direction. Cela peut survenir plusieurs fois, ce qui rendrait le comportement de la balle complètement irréaliste. Donc nous avons une variable, <ttcode>self.hit</ttcode> qui sera définie à <ttcode>true</ttcode> quand elle sera frappée, et à <ttcode>false</ttcode> une image plus tard. Quand nous vérifions si les rectangles sont entrés en collision, nous contrôlons si <ttcode>self.hit</ttcode> est à <ttcode>true</ttcode> ou <ttcode>false</ttcode> pour stopper les rebonds internes.
 
L'imposant code ici est facile à comprendre. Tous les rectangle possèdent une fonction <ttcode>colliderect()</ttcode>, dans laquelle vous envoyez le rectangle d'un autre objet, qui retournera <ttcode>1</ttcode> (<ttcode>true</ttcode>) si les rectangles se chevauchent, et <ttcode>0</ttcode> (<ttcode>false</ttcode>) dans le cas contraire. En cas de réponse positive, nous pouvons changer la direction en soustrayant l'angle actuel par <math> \boldsymbol{\pi}</math> (encore un petit truc que vous pouvez faire avec les radians, qui ajustera l'angle de 90 degrés et renverra la bonne direction. Vous pourrez trouver qu'à ce stade la compréhension approfondie des radians soit un exigeance!). Pour terminer le contrôle du bug, nous repassons <ttcode>self.hit</ttcode> à <ttcode>false</ttcode> à partir de l'image qui suit la frappe de la balle.
 
Nous réaffectons alors le vecteur. Vous aimeriez bien sur enlever la même ligne dans la précédente partie de code, mais vous ne pourrez le faire qu'après le test conditionnel <ttcode>if-else</ttcode>. Et ce sera tout. Le code assemblé permettra maintenant à la balle de frapper les côtés et les raquettes.
 
=== Le produit final ===