Skip to article frontmatterSkip to article content

Licence CC BY-NC-ND, Thierry Parmentelat & Aurélien Noce

Le but de ce TP est de réaliser un petit jeu en Python. L’objectif est de vous apprendre à concevoir et réaliser un programme complet, et non de réaliser le nouveau best-seller.

Gardez en tête que votre objectif est de réaliser un programme qui marche, et pas un programme parfait.

Objectifs et démarche

on va commencer par se créer un dossier vierge

$ mkdir mysnake
$ cd mysnake

Mode d’emploi

Votre travail se passe exclusivement dans un seul fichier snake.py, que vous allez d’abord créer avec vs-code - à partir du code de démarrage - puis modifier (et le moins possible d’ailleurs) à chaque étape

Et ensuite, on va bien faire attention de committer chaque fois qu’on aura une version qui marche
c’est-à-dire dans ce TP très guidé, un commit par étape en gros !

Et comme ça quand on aura un bug on pourra se concentrer sur ce qui a changé depuis la version qui marchait

Enfin si vous créez votre dépôt à l’intérieur d’un autre dépôt (de cours par exemple), reportez-vous à la toute dernière section pour comprendre comment ça fonctionne.

Mais avant de pouvoir commencer, un peu de préparation...

On s’installe (optionnel)

Ce qui suit suppose que vous avez installé Python avec conda et que vous avez un terminal bash fonctionnel sur votre ordinateur.

Commencez par créer et activer un environnement dédié au TP:

# on commence par créer un environnement "snake"
(base) $ conda create -n snake python=3.12

# puis on l'active
(base) $ conda activate snake

# votre terminal doit indiquer le nom d'environnement:
(snake) $

Prérequis

Installez ensuite la dernière version du module pygame avec pip:

(snake) $ pip install pygame

Pour tester votre installation, vous pouvez lancer le programme d’exemple comme suit:

(snake) $ python -m pygame.examples.aliens

soyez patient lors du premier lancement, la librairie initialise des tas de choses...

Sachez aussi que vous pouvez aussi voir la version installée d’une librairie avec

(snake) pip show pygame

v01: Code de démarrage

Un premier code très simple est le suivant, écrivez-le dans un fichier snake.py

snake-01.py
"""
le snake - v01
on repeint l'écran à une période de 1 seconde
et on a du mal à sortir du programme
"""

# les imports standard en premier
from random import randint

import pygame as pg

# on initialise pygame et on crée une fenêtre de 400x300 pixels
pg.init()
screen = pg.display.set_mode((400, 300))

# on crée aussi un objet "horloge"
clock = pg.time.Clock()

# enfin on boucle à l'infini pour faire le rendu de chaque image
while True:
    # l'objet "clock" permet de limiter le nombre d'images par secondes
    # ici pour cette démo on demande 1 image par seconde
    clock.tick(1)

    # il faut traiter les événements a minima
    # pour que la fenêtre s'affiche
    for event in pg.event.get():
        pass

    # on génère une couleur (Rouge, Vert, Bleu) au hasard
    random_color = (randint(0, 255), randint(0, 255), randint(0, 255))
    # et on colorie l'écran avec cette couleur
    screen.fill(random_color)

    # enfin on met à jour la fenêtre avec tous les changements
    pg.display.update()

et lancez-le avec la commande python :

(snake) $ python snake.py

Nous avons une version qui marchouille; du coup on en fait quoi ?

un commit bien sûr

Astuces vs-code

Astuce #1 : il est fortement recommandé d’installer l’extension de vs-code pour Python

Astuce #2 : on a créé un environnement virtuel; du coup il est opportun d’indiquer à vs-code qu’il faut utiliser l’environnement conda snake - plutôt que base pour cela cliquer dans la bannière du bas la zone qui indique le Python courant

Astuce #3 : une fois que c’est fait, pour lancer le programme directement depuis vs-code :

Astuce #4 : si vous voulez avoir en permanence une indication sur la qualité de votre code, regardez la zone en bas à gauche

Un petit détail: update()

Il faut savoir que c’est l’appel à pg.display.update() qui produit réellement l’affichage.

En fait, tous les autres calculs se produisent en mémoire (c’est très rapide), mais à un moment il faut bien parler à la carte vidéo pour l’affichage, et ça c’est beaucoup plus lent (+ieurs centaines de fois plus lent).

Du coup, même si ce display.update() reste dans l’ordre de grandeur de la milliseconde, il faut s’efforcer, pour une bonne fluidité du jeu, de n’appeler update() que le minimum, pour nous ici une fois par itération de la boucle (une fois par frame, quoi)

v02: Continuons

Afin d’avoir un comportement plus “normal”, nous devons instruire Pygame en lui disant comment réagir aux clicks sur le clavier ou sur la fenêtre:

snake-02.py
"""
v02 : pareil mais au moins on peut sortir du programme
avec la touche 'q', ou avec la souris en fermant la fenêtre
"""

from random import randint
import pygame as pg

pg.init()
screen = pg.display.set_mode((400, 300))
clock = pg.time.Clock()

# on rajoute une condition à la boucle: si on la passe à False le programme s'arrête
running = True
while running:

    clock.tick(1)

    # on itère sur tous les évênements qui ont eu lieu depuis le précédent appel
    # ici donc tous les évènements survenus durant la seconde précédente
    for event in pg.event.get():
        # chaque évênement à un type qui décrit la nature de l'évênement
        # un type de pg.QUIT signifie que l'on a cliqué sur la "croix" de la fenêtre
        if event.type == pg.QUIT:
            running = False
        # un type de pg.KEYDOWN signifie que l'on a appuyé une touche du clavier
        elif event.type == pg.KEYDOWN:
            # si la touche est "Q" on veut quitter le programme
            if event.key == pg.K_q:
                running = False

    # xxx ici c'est discutable, car si on tape 'q'
    # on va quand même changer de couleur avant de sortir...

    random_color = (randint(0, 255), randint(0, 255), randint(0, 255))
    screen.fill(random_color)
    pg.display.update()


# Enfin on rajoute un appel à pg.quit()
# Cet appel va permettre à Pygame de "bien s'éteindre" et éviter des bugs sous Windows
pg.quit()

et on n’oublie pas de faire un commit...

v03: Le damier

Nous allons commencer par construire notre plateau de jeu ainsi:

pour la v3 vous devez remplacer dans la v2 le code qui affiche (la couleur random) pour obtenir le damier ci-dessous (vous pouvez bien sûr choisir d’autres couleurs):

pour cela, vous pouvez utiliser la méthode pg.draw.rect() qui dessine un rectangle:

# une recette pour dessiner un rectangle:

# les coordonnées de rectangle que l'on dessine
x, y = 100, 200            # les coordonnées du coin du rectangle (en pixels)

# la taille du rectangle
width, height = 20, 20     # largeur et hauteur du rectangle, toujours en pixels

# on crée un objet 'Rect'
rect = pg.Rect(x, y, width, height)

# la couleur de remplissage
color = (255, 0, 0)        # couleur rouge

# et on le dessine comme ceci dans l'écran virtuel
pg.draw.rect(screen, color, rect)

une fois que ça marche, vous faites quoi ?

v04: Un serpent fixe

À partir de maintenant, on va garder le damier comme fond d’écran (même si les illustrations ne le montrent pas)

L’étape suivante est de dessiner le serpent. Le serpent est simplement une suite de blocs de couleurs. On veut dessiner le serpent aux coordonnées suivantes:

# les coordonnées du corps du serpent
snake = [
    (10, 15),
    (11, 15),
    (12, 15),
]

pour obtenir un schéma comme suit; disons pour fixer les idées que dans ce cas de figure (10,15) est la queue, et (12, 15) est la tête (mais c’est totalement arbitraire et pas du tout imposé) :

v05: Un serpent qui bouge

Ensuite, nous allons faire bouger le serpent. C’est en fait très simple:

dans la v5 donc, le serpent avance vers la droite à chaque itération, mais on ne peut pas encore le controler

n’oubliez pas de commit

v06: On peut contrôler la direction

Une fois que le serpent bouge, ajouter les commandes pour se déplacer dans les 4 directions, en cliquant sur les flèches (par exemple le code renvoyé par la flêche vers le haut est pg.K_UP)

Aussi on peut commencer à envisager d’accélérer un peu le jeu à ce stade...

BONUS faites en sorte que le serpent ne puisse pas faire “demi tour”

v07: Le fruit

Il faut maintenant faire manger notre serpent. On va procéder comme suit:

v08: Épilogue

Il nous reste deux petits changements pour avoir un serpent complètement fonctionnel:


Fin de la partie obligatoire


Options

Pour les rapides, je vous invite à aborder les sujets suivants (dans l’ordre qui vous inspire le plus):

Variables globales

De manière générale, les variables globales sont considérées comme néfastes à la réutilisabilité du code; retouchez votre code pour minimiser le nombre de variables globales.

Ligne de commande

On aimerait pouvoir passer sur la ligne de commande les paramètres du jeu; par exemple, le nombre de cases du tableau en hauteur et largeur, la taille d’une case en pixels, ...

Indice: cherchez le module argparse dans la documentation Python.

Vitesse de réaction

Ralentissez le jeu à 4 images/secondes; êtes-vous satisfait de la vitesse de réaction ? dit autrement, est-ce qu’il arrive que le serpent tourne trop tard ? si oui modifiez votre code pour une bonne synchronisation

De la même façon, si vous revenez artificiellement à une image par seconde ou moins, et que vous quittez le jeu avec ‘q’, est-ce que ça fonctionne immédiatement ? si non, comment améliorer le code pour que ce soit plus réactif ?

Toujours à cette vitesse lente, que constatez-vous au tout début du jeu ? est-ce que c’est grave ? si on voulait vraiment le corriger (pas forcément utile en pratique hein), comment on pourrait faire ?

Asynchronisme

À ce stade nous avons un jeu à une seule vitesse; la boucle principale est entièrement cadencée par le clock.tick(n), et la vitesse du serpent est entièrement fixée par ce moyen-là.

Mais en fait on triche complètement; que se passerait-il si on avait par exemple deux objets à animer à des vitesses différentes ?

Modifiez votre code pour pouvoir paramétrer deux fréquences séparément :

Notes à propos des environnements virtuels

Voici un très rapide résumé des commandes pour gérer ses environnements virtuels

Note à propos des dépôts git imbriqués

Si vous avez reçu ce TP depuis un dépôt git (celui de votre cours d’info), ce qu’on vous invite à faire c’est finalement de créer un dépôt git ... à l’intérieur d’un autre dépôt git.

Sachez que ça marche sans aucun souci (et en pratique on finit par avoir ce genre de tricotage avec une profondeur non triviale, 3 voire même parfois 4 dépôts les uns dans les autres)

La seule chose à savoir c’est que, lorsque vous tapez une commande git, pour trouver le “bon” dépôt, on utilise assez naturellement l’algo suivant:

on regarde si le dossier courant est un dépôt git, si oui on a trouvé, sinon on regarde dans le dossier parent, et ainsi de suite

Donc c’est assez simple, mais faites juste attention à ne pas ajouter vos fichiers dans le mauvais dépôt

Dernière astuce pour les geeks: si vous voulez savoir où se trouve la racine de votre dépôt courant:

git config --global alias.root "rev-parse --show-toplevel"

après quoi vous pourrez taper n’importe où git root pour voir s’afficher le (chemin complet du) dossier racine de votre dépôt.