Aller au contenu

Source : Adaptation du tutoriel de Marine Méra

Objectif⚓︎

Objectifs

  1. Se familiariser avec le module pyxel qui est utilisé dans le concours La Nuit du Code auquel vous pourrez participer un mercredi après-midi en mai.
  2. Programmer une interface graphique simple pour un jeu de Snake :
  3. le serpent se meut automatiquement, on peut le déplacer avec les flèches du clavier.
  4. s'il mange la pomme, il grandit et celle-ci réapparait dans une case vide
  5. s'il quitte l'écran ou se mord, il meurt, et le jeu s'arrête

alt

Principe généraux des jeux vidéos⚓︎

Fonctionnement élémentaire d'un jeu vidéo

Une boucle infinie fait progresser le jeu. À chaque tour :

  1. Étape 1 : On écoute les interactions du joueur
  2. Étape 2 : On met à jour l'état du jeu
  3. Étape 3 : On dessine les éléments à l'écran
  4. Étape 4 : On attend quelques millisecondes

Module pyxel

Dans pyxel, la boucle infinie est implicite, et l'attente des quelques millisecondes déjà prise en charge.

Deux fonctions vont gérer la mise à jour du jeu et le dessin des éléments :

Action Fonction pyxel
Mise à jour du jeu update()
Dessin des éléments draw()

Au début du programme, on importe le module avec import pyxel et on crée la fenêtre du jeu : pyxel.init(400, 320, title="snake"). Les dimensions peuvent être stockées dans des constantes LARGEUR et HAUTEUR.

A la fin du programme, on lance l'exécution du jeu avec pyxel.run(update, draw) qui fait appel aux deux fonctions prédéfinies, qui seront appelées 20 fois par seconde.

Exercice 1

Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale puis exécuter.

🐍 Script Python
# import du module
import pyxel
# constantes (à compléter)
LARGEUR = 400
HAUTEUR = 320
# initialisation de la fenêtre
pyxel.init(LARGEUR, HAUTEUR, title="snake")
# fonction de dessin
def draw():
    # à compléter plus tard
    ...
# fonction de mise à jour
def update():
    # à compléter plus tard
    ...
# programme à compléter
# on lance l'exécution du jeu
pyxel.run(update, draw)

Dessiner le serpent et le score⚓︎

Module pyxel

La fenêtre est un ensemble de pixels qui sont repérés par leurs coordonnées dans le repère lié à la fenêtre :

  • l'origine est le coin supérieur gauche
  • les abscisses sont les colonnes indexées de 0 à LARGEUR - 1 et l'axe des abscisses est le bord supérieur orienté de gauche à droite
  • les ordonnées sont les lignes indexées de 0 à HAUTEUR - 1 et l'axe des ordonnées est le bord gauche orienté de haut en bas

alt

On se limitera à deux fonctions de dessin :

Action Fonction pyxel
dessiner un rectangle de coordonnées (x, y), de largeur L, de hauteur H et de couleur c pyxel.rect(x, y, L, H, c)
Colorier tout l'écran en noir pyxel.cls(0)

Le module pyxel propose une palette de 16 couleurs indexées de 0 à 15.

alt

Exercice 2

On a créé dans l'exercice 1 une fenêtre de largeur 400 pixels et de hauteur 320 pixels. On choisit de grossir les pixels et de ne manipuler que des carrés de 20\(\times\)20 pixels. Chaque carré est repéré par un couple de coordonnées en cases, en découpant la fenêtre en une grille de \((400/20)\times (320/20)=20 \times 16\) pixels.

Coordonnées en cases d'un carré 20\(\times\)20 Coordonnées en pixels de son coin supérieur gauche
(x, y) (x * CASE, y * CASE)

On représentera le serpent comme une liste de carrés en distinguant le carré de tête en orange, des carrés du corps en vert.

Warning

Par la suite, lorsqu'on désignera les coordonnées du serpent, il s'agira des coordonnées en cases.

  1. Quelles sont les coordonnées en case de la case en bas à gauche de la fenêtre ? et en bas à droite ? Répondre par un commentaire dans l'activité pyxel sur Capytale.
  2. Pour le système de coordonnées en case définir l'abscisse maximale XMAX et l'ordonnée maximale YMAX dans les constantes du programme.
  3. On représente le serpent par une liste de listes : snake = [[3, 3], [2, 3], [1, 3]][3,3] sont les coordonnées de la tête et [2, 3] et [1, 3] les coordonnées des anneaux du corps. a. Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonction draw pour qu'elle dessine le carré de tête en orange et les carrés du corps en vert puis exécuter.

    🐍 Script Python
    # import du module
    import pyxel
    # constantes (à compléter)
    LARGEUR = 400
    HAUTEUR = 320
    CASE = 20
    ORANGE = 9
    VERT = 11
    BLANC = 7
    NOIR = 0
    # initialisation de la fenêtre
    pyxel.init(LARGEUR, HAUTEUR, title="snake")
    # variables globales
    snake = [[3, 3], [2, 3], [1, 3]]
    
    # fonction de dessin
    def draw():
        pyxel.cls(NOIR)
        # dessiner le serpent
        # dessiner le corps en vert
        for anneau in snake[1:]:
            # à compléter
            ...
        # dessiner la tête en orange
        # à compléter
        ...
        
    # fonction de mise à jour
    def update():
        # à compléter plus tard
        ...
    # programme à compléter
    # on lance l'exécution du jeu
    pyxel.run(update, draw)
    
  4. On veut aussi afficher un score en haut à gauche. Ajouter dans le code une nouvelle variable globale score initialisée à 0 puis une instruction permettant de dessiner le score dans la fonction draw.

Action Fonction pyxel
Dessiner la chaîne de caractères s en (x, y) (coordonnées en pixels) avec la couleur c pyxel.text(x, y, s, col)

Animer le serpent⚓︎

Dans cette partie, on va animer le serpent en déplaçant la tête selon un certain vecteur deplacement.

Exercice 3

Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.

  1. Considérons le serpent initial snake = [[3, 3], [2, 3], [1, 3]]. Que devient snake après un déplacement de vecteur [0, -1] ?
  2. Comment accède-t-on à la tête du serpent snake ? et à sa queue ?
  3. Ajouter deplacement = [1, 0] comme variable globale en dessous de snake et score.

    🐍 Script Python
    # variables globales
    snake = [[3, 3], [2, 3], [1, 3]]
    score = 0
    deplacement = [1, 0]
    
  4. Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonction update pour qu'elle mette à jour les coordonnées des parties du serpent stockées dans la variable globale snake après le mouvement de vecteur deplacement de la tête. Exécuter, que se passe-t-il ?

    🐍 Script Python
    # mise à jour des positions des objets
    def update():
        # mise à jour du serpent qui avance selon le vecteur deplacement
        # ancienne  tête
        head = snake[0]
        # nouvelle  tête à compléter
        head = ...
        # insertion de la nouvelle tête au début du serpent, à compléter
        snake.insert(0, head)
        # on supprime de snake les anciennes coordonnées de la queue, à compléter
        ...
    
  5. 30 images par secondes (ou Frames Per Second FPS), ça donne une bonne fluidité d'affichage, mais c'est trop rapide pour le mouvement du serpent. Pour ralentir, on va utiliser le compteur de frames pyxel.frame_count intégré à Pyxel, en effectuant le mouvement par exemple uniquement tous les 15 frames.

    Ajouter la constante FRAME_REFRESH = 15 au début avec les constantes, puis dans la fonction update effectuer la mise à jour des positions uniquement toutes les 15 Frames en testant la condition pyxel.frame_count % FRAME_REFRESH == 0.

    Exécuter et vérifier le mouvement est plus lent.

Exercice 4

Pour que le joueur puisse contrôler le mouvement du serpent en modifiant le vecteur deplacement, il faut réaliser l'Étape 1 d'un jeu vidéo : écouter les interactions du joueur.

On choisit de diriger le serpent avec les quatre flèches du pavé directionnel et on écoute l'événement appui sur la touche. On surveille en permanence dans la boucle implicite un événement avec un écouteur et si l'événement est capturé on déclenche une action :

🐍 Script Python
if ecouteur(evenement):
    action

Par exemple si on détecte un appui sur la touche avec flèche vers le haut, on modifie deplacement en [0, -1] :

🐍 Script Python
if pyxel.btn(pyxel.KEY_UP):
    direction = [0, -1]

Warning

Pour modifier la variable globale direction depuis l'intérieur de la fonction update, il faut la déclarer au début de la fonction avec le mot clef global. C'est nécessaire pour les variables qu'on modifie par affectation mais pas pour celles comme snake qu'on peut modifier par effet de bord.

🐍 Script Python
def update():
    global deplacement, score, snake  # variables globales modifées dans update
    # snake peut être modifiée par effet de bord
    ...

On donne les quatre événements correspondants aux appuis sur les touches du pavé directionnel :

Syntaxe Événement Valeur de deplacement
pyxel.KEY_RIGHT Appui sur Flèche ➡️ [1, 0]
pyxel.KEY_LEFT Appui sur Flèche ⬅️ ...
pyxel.KEY_UP Appui sur Flèche ⬆️ [0, -1]
pyxel.KEY_DOWN Appui sur Flèche ⬇️ ...

Copier/coller le code ci-dessous dans l'éditeur de l'activité pyxel sur Capytale, compléter la fonction update avec tous les tests d'écouteurs d'événements qui vont permettre de diriger le serpent au clavier.

🐍 Script Python
# mise à jour des positions des objets
def update():
    if pyxel.frame_count % FRAME_REFRESH == 0:
        # mise à jour du serpent qui avance selon le vecteur deplacement
        # ancienne  tête
        head = snake[0]
        # nouvelle  tête à compléter
        head = ...
        # insertion de la nouvelle tête au début du serpent, à compléter
        snake.insert(0, head)
        # on supprime de snake les anciennes coordonnées de la queue, à compléter
        ...
    if pyxel.btn(pyxel.KEY_UP):
        direction = [0, -1]
    # compléter avec les tests pour les  trois autres écouteurs d'événements

Faire mourir le serpent⚓︎

Exercice 5

Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.

Dans notre version du jeu : le serpent meurt lorsqu'il se mord la queue, ou lorsqu'il quitte l'écran. Dans ce cas, le jeu s'arrête, et on quitte la fenêtre avec pyxel.quit().

  1. On suppose qu'on a récupéré dans une variable head les coordonnées de la tête du serpent :
    • (C1) quelle expression permet de tester si ces coordonnées apparaissent aussi dans le reste du corps du serpent ?
    • (C2) quelle expression permet de tester si l'abscisse de la tête n'est pas dans la fenêtre ?
    • (C3) quelle expression permet de tester si l'ordonnée de la tête n'est pas dans la fenêtre ?
  2. Compléter la fonction update avec un test qui déclenche une fermeture de la fenêtre si l'une des conditions précédentes est vérifiée.

Manger des pommes⚓︎

Exercice 6

Pour les questions qui ne sont pas du code à compléter, répondez par un commentaire dans l'activité pyxel sur Capytale en indiquant l'exercice et le numéro de la question.

On place une pomme, matérialisée par une case magenta (couleur 8), au hasard dans la fenêtre. Lorsque le serpent mange la pomme, il grandit d'un anneau (sa queue n'est pas effacée), et le score augmente de 1.
On importera le module random au début du programme avec import random.

Si le serpent mange la pomme, pour en placer une nouvelle, on utilisera le code suivant :

🐍 Script Python
x_pomme, y_pomme = pomme
while [x_pomme, y_pomme] in snake:
            x_pomme = random.randint(0, XMAX)
            y_pomme = random.randint(0, YMAX)
pomme = [x_pomme, y_pomme]
  1. Expliquer le code précédent.
  2. On définit une nouvelle variable globale pomme avec les coordonnées de la pomme :

    🐍 Script Python
    # variables globales
    snake = [[3, 3], [2, 3], [1, 3]]
    score = 0
    deplacement = [1, 0]
    pomme = [7, 5]
    
    a. Compléter la fonction draw pour dessiner la pomme.

    b. Compléter la fonction update : si la tête du serpent se trouve sur la pomme alors il grandit d'un anneau et le score augmente de 1 (déclarer score avec global dans update), de plus il faut créer une nouvelle pomme.