Script et programme

Identification

Infoforall

16 - Créer un programme en Python 3


Troisième partie de l'activité "Programme".

Elle permet de découvrir le module pyxel qui permet de créer assez "facilement" des interfaces graphiques ressemblant aux jeux vidéos rétro : des gros pixels, des musiques basiques et du jeu plutôt que du graphisme.

Logo Pyxel
Logo Pyxel (https://github.com/kitao/pyxel#)

Ressource initiale dont cette activité est tirée :

Cette activité est tirée d'un tutoriel créé pour préparer la Nuit du Code : https://nuit-du-code.forge.apps.education.fr

Evaluation : 2 questions

  questions 09

  questions 10

DM 🏠 : Non

Documents de cours PDF : .PDF

Sources latex : .TEX et entete.tex et licence.tex

1 - Gestion des données

Quelques informations générales sur les données du jeu

Nous allons stocker les données du jeu dans plusieurs variables globales qui sont ou contiennent des dictionnaires. Les clés courantes sont :

  • 'x' et 'y' sont les coordonnées de l'objet;
  • 'c' est la couleur que devra avoir l'objet à l'écran. C'est un simple entier sous Pyxel. Chaque nombre entre 0 et 15 correspond à une couleur particulière.
  • 'pv' est le nombre de points de vie de l'objet.

Voici les variables globales :

  • vaisseau : un dictionnaire contenant des informations sur le vaisseau du joueur : ses coordonnées x et y, les points gagnés, sa couleur et son état (True si actif, False si inactif). Exemple :
  • vaisseau = {'x':50, 'y':100, "points":0, 'c':2}

  • ennemis : un tableau de dictionnaires (list[dict]), contenant les ennemis, un dictionnaire par ennemi sous forme d'un dictionnaire par ennemi. Exemple ici avec 3 ennemis :
  • ennemis = [
        {'x':50, 'y':100, 'pv':2, 'c':6},
        {'x':75, 'y':200, 'pv':1, 'c':8},
        {'x':150, 'y':30, 'pv':2, 'c':8}
    ]

  • tirs : un tableau de dictionnaires, contenant les tirs actuellement à l'écran, un dictionnaire par tir. Exemple avec deux tirs :
  • tirs = [
        {'x':50, 'y':100, 'c':10},
        {'x':75, 'y':200, 'c':10},
    ]

  • explosions : un tableau de dictionnaires, contenant les explosions actuellement à l'écran, un dictionnaire par tir. Exemple avec une explosion :
  • explosions = [
        {'x':50, 'y':100, 'c':10, 'rayon':2, 'rayon max':4}
    ]

Commençons donc par quelques rappels sur les dictionnaires.

(Rappel) 1.1 DICTIONNAIRE : accés à une valeur avec [cle]

Accès (avec des crochets)

Pour accéder à la valeur d'une case, il faut connaître sa clé. On accède à une valeur en tapant le nom du dictionnaire suivi de la clé entre crochets.

Attention, on place des crochets pour accéder, les accolades c'est pour la déclaration.

>>> ds = {"Alice": 13, "Bob": 8, "Charlie": 12} >>> ds["Alice"] 13 >>> ds["Charlie"] 12 >>> ds["Bob"] 8 >>> ds["Bob l'éponge"] KeyError: "Bob l'éponge"

Comme vous le voyez, demander un accès avec une clé inconnue provoque une erreur.

Accès (dict) : coût CONSTANT par rapport au nombre n d'éléments.

Résumé
  • Si d est un dictionnaire,
  • Si c est une clé valide dans le dictionnaire,
  • Alors d[c] est le contenu associé à la clé c du dictionnaire d,
(Rappel) 1.2 DICTIONNAIRE : muable en Python (modification possible)

En Python, les dictionnaires sont muables (ou mutables en anglais) : on peut modifier le contenu d'une case après la création du dictionnaire.

Modification d'un couple existant

Imaginons qu'on ai oublié des points à Bob : il n'a pas 8 mais 11 finalement. Voici comment nous pourrions modifier le tableau APRES création

>>> {"Alice": 13, "Bob": 8, "Charlie": 12} >>> notes["Bob"] 8 >>> notes["Bob"] = 11 >>> notes["Bob"] 11 >>> notes {'Alice': 13, 'Bob': 11, 'Charlie': 12}

Notez bien que ce n'est pas une affectation sur le dictionnaire : l'affectation est faite sur l'un des contenus des associations clé-valeur du dictionnaire, pas sur le dictionnaire lui-même.

Accès en modification (dict) : coût CONSTANT par rapport au nombre n d'éléments.

Rajout d'un nouveau couple

On peut rajouter de la même façon un couple qui n'existe pas encore.

Imaginons un nouvel élève nommé David qui a eu 15.

>>> {"Alice": 13, "Bob": 11, "Charlie": 12} >>> notes["David"] = 15 >>> notes {'Alice': 13, 'Bob': 11, 'Charlie': 12, 'David': 15}

1/3 Lire ou modifier les ennemis

⚙ 01° Dans notre programme, un ennemi sera un vaisseau alien ennemi. Nous allons vouloir stocker ses données dans un dictionnaire, le type dict de Python.

  1. La clé 'x' fera référence à son abscisse, sa colonne à l'écran;
  2. La clé 'y' fera référence à son ordonnée, sa ligne à l'écran;
  3. La clé 'pv' fera référence à ses points de vie : 0 pour un ennemi détruit, positif sinon;
  4. La clé 'c' fera référence à son couleur, un nombre compris entre 1 et 15 dans Pyxel.
>>> ennemi = {'x':100, 'y':30, 'pv':2, 'c':6}

Questions

  1. Comment récupérer dans une variable x la valeur associée à la clé 'x' ?
  2. Comment incrémenter la valeur associée à la clé 'x' ?

...CORRECTION...

>>> ennemi = {'x':100, 'y':30, 'c':6} # question A >>> x = ennemi['x'] >>> x 100 # question B >>> ennemi['x'] = ennemi['x'] + 1 >>> ennemi {'x':101, 'y':30, 'c':6}

⚙ 02° Dans notre programme, il y aura plusieurs ennemis. Nous allons les stocker dans un tableau ennemis.

Compléter le programme ci-dessous pour qu'on parvienne à afficher la couleur de tous les ennemis qui ont une ordonnée y supérieure à 85.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ...: # si cet ennemi a une ordonnée y supérieure à 80 print(...) # affiche sa couleur

Sur l'exemple donné, le programme doit donc afficher 8 et 2.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ennemi['y'] > 80: # si cet ennemi a une ordonnée supérieure à 80 print(ennemi['c']) # affiche sa couleur

2/3 Rajouter un ennemi

⚙ 03° Nous allons vouloir faire apparaître régulièrement des ennemis à l'écran. Pour cela, il va falloir les rajouter dans le tableau ennemis (de type list en Python) ennemis avec un s.

Question : quelle solution du QCM permet effectivement de rajouter l'ennemi dans le tableau ennemis ? Critiquer les solutions non retenues.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] nouvel_ennemi = {'x': 40, 'y': 10, 'c': 9, 'pv': 3} ...
  1. ennemis = ennemis + ennemi
  2. ennemis = ennemis + [ennemi]
  3. ennemis.append(ennemi)
  4. ennemi.append(ennemis)

...CORRECTION...

Il s'agit de la solution C.

  1. ennemis = ennemis + ennemi
  2. Erreur d'exécution : on tente de concaténer un type list et un type dict. Cela n'est pas autorisé car ca ne veut rien dire.

  3. ennemis = ennemis + [ennemi]
  4. L'instruction fonctionne mais crée un nouveau tableau plutôt que de modifier le tableau en place. On concaténe un tableau avec un tableau qui ne contient qu'une seule case.

  5. ennemis.append(ennemi)
  6. C'est la bonne solution : on agit sur le tableau ennemis en lui ajoutant une case à la fin.

  7. ennemi.append(ennemis)
  8. C'est faux : ennemi sans s est un dictionnaire.Ici, on demande de modifier le dictionnaire en lui 'ajoutant' le tableau des ennemis. Cela provoque une erreur d'exécution car le type dict ne dipose pas de cette méthode append().

3/3 Supprimer un ennemi

⚙ 04° Reste à voir comment faire disparaître les ennemis qui sortent de l'écran par exemple. Pour cela, il va falloir les supprimer du tableau ennemis à l'aide de la méthode remove(x).

Compléter le programme pour qu'il permette de supprimer du tableau les ennemis dans l'ordonnée y est supérieur à 85 par exemple.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ...: # si cet ennemi a une ordonnée supérieure à 85 ... # Supprime l'ennemi du tableau des ennemis

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ennemi['y'] > 85: # si cet ennemi a une ordonnée supérieure à 85 ennemis.remove(ennemi) # Supprime l'ennemi du tableau des ennemis

✔ 05° Dernier point, plus informatif qu'autre chose : on ne peut pas modifier un tableau qu'on est en train de lire par indices : en supprimant cette case, on réduit de 1 le nombre d'indice disponible et on finira par aller en dehors des indices autorisés. Il faut donc nécessairement utiliser le parcours par valeurs.

Tester le programme ci-dessous pour vérifier qu'il NE fonctionne PAS alors qu'il est plutôt similaire à celui de la question 04 si ce n'est qu'on passe par un parcours par indices.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for i in range(len(ennemis)): # Pour chaque indice du tableau ennemis if ennemis[i]['y'] > 85: # si cet ennemi a une ordonnée supérieure à 85 ennemis.remove(ennemis[i]) # Supprime l'ennemi du tableau des ennemis

2 - Pyxel : le vaisseau

(Rappel) 2.1 Principe d'un programme pyxel

Logo Pyxel
Logo Pyxel

Un programme utilisant le module pyxel est basé sur une boucle infinie décrite ci-dessous.

Principe de fonctionnement

    1 - La fonction init() crée la fenêtre graphique.

    2 - La fonction run() active l'alternance infinie ci dessous :

    3 - TANT QUE le programme est actif :

      On mémorise les actions de l'utilisateur

      SI la dernière mise à jour date de 1/30e de seconde ou plus

        On active la fonction controler() qui mettra les données à jour en fonction des événements détéctés.

        On active la fonction afficher() qui effacera l'écran avec cls() puis générera le nouvel affichage.

      Fin du SI

    Fin TANT QUE

Au vu de la condition sur l'activation des deux fonctions principales (nommées controler et afficher ici), on voit qu'il va y avoir 30 images par seconde.

La structure usuelle du code Python pour ce module
1 2 3 4 5 6 7 8 9 10 11 12 13
import pyxel def controler(): """Récupère l'événement et le gère les données (30 fois par seconde)""" ... # A modifier def afficher(): """création des objets (30 fois par seconde)""" pyxel.cls(0) # vide la fenetre ... # A modifier pyxel.init(128, 128, title="Mon premier jeu") pyxel.run(controler, afficher)
Système d'axes

Le point (0,0) est le bord haut gauche.

L'abscisse va bien vers la gauche.

Par contre, l'ordonnée est orientée vers le bas, attention.

Les axes dans Pyxel

✔ 06° Tester si le module pyxel est importé sur votre ordinateur.

>>> import pyxel

Si cela déclenche une erreur, Outils > Gérer les paquets dans Thonny, chercher et installer pyxel.

Nous allons être ambitieux : le programme final va atteindre environ 200 lignes. Il sera construit progressivement à partir d'un squelette que je fournis. Pas de panique !

✔ 07° Placer ce (long) programme en mémoire. Lancer pour visualiser le résultat. Vous pouvez déplacer l'un des carrés (le "vaisseau") à l'aide des flèches du clavier.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
# 1 - Importations ============================================================ import pyxel import random # 2 - CONSTANTES et variables globales lues directement ========================= vaisseau = {'x': 100, 'y': 100, 'c': 1, 'points': 0} tirs = [] # list de dict {'x':?; 'y':?, 'c':?} ennemis = [] # list de dict {'x':?; 'y':?, 'c':?, 'pv':?} explosions = [] # list de dict {'x':?; 'y':?, 'c':?, "rayon":?} # 3 - FONCTIONS =============================================================== def controleur() -> None: """Fonction qui récupère l'action, modifie les données en conséquence""" if pyxel.btn(pyxel.KEY_LEFT): # Si on maintient appuyé la flèche GAUCHE deplacer_vaisseau(-1, 0) # --> Mettre à jour les coordonnées du vaisseau if pyxel.btn(pyxel.KEY_RIGHT): # Si on maintient appuyé la flèche DROITE deplacer_vaisseau(1, 0) # --> Mettre à jour les coordonnées du vaisseau if pyxel.btnp(pyxel.KEY_SPACE): # Si on a appuyé sur la touche ESPACE x = vaisseau['x'] # --> récupère x y = vaisseau['y'] # --> récupère y creer_tir(x, y) # --> ajoute un tir dans le tableau tirs if pyxel.frame_count % 30 == 0: # S'il s'est écoulé 1 seconde creer_ennemi() # ajoute un ennemi dans le tableau ennemis gerer_explosions() # Mettre les explosions à jour en les amplifiant/supprimant gerer_tirs() # Mettre les tirs à jour (déplacement et collision) gerer_ennemis() # Mettre les ennemis à jour def deplacer_vaisseau(dx:int, dy:int) -> None: """Modifie les données du vaisseau pour intégrer le déplacement dx et dy voulu""" vaisseau['x'] = vaisseau['x'] + dx # Modifie l'abscisse du vaisseau if vaisseau['x'] > 120: # Si on va trop loin à droite vaisseau['x'] = 120 # --> on bloque l'abscisse à 120 if vaisseau['x'] < 0: # Si on va trop loin à gauche vaisseau['x'] = 0 # --> on bloque l'abscisse à 0 def creer_tir(x:int, y:int) -> None: """Ajoute le tir du vaisseau dans le tableau tirs""" pass def gerer_tirs() -> None: """Modifie les coordonnées des tirs, en les supprimant au besoin""" pass def tir_touche(tir:dict, ennemi:dict) -> bool: """Prédicat qui renvoie True si le tir touche l'ennemi""" pass def creer_ennemi() -> None: """Création et ajout d'un ennemi dans le tableau ennemis""" pass def gerer_ennemis() -> None: """Modifie les coordonnées des ennemis en les supprimant au besoin""" pass def creer_explosion(x:int, y:int, rmax:int) -> None: """Ajoute l'explosion dans le tableau explosions""" pass def gerer_explosions() -> None: """Modifie ou supprime les données liées aux explosions""" pass def vue() -> None: """Création de l'affichage (30 fois par seconde)""" pyxel.cls(0) # vide la fenêtre afficher_vaisseau() # vaisseau (carre 8x8) afficher_tirs() # tous les tirs (rectangle 1x4) afficher_ennemis() # tous les ennemis (carré) afficher_explosions() # toutes les explosions (cercle) afficher_informations() # les textes def afficher_vaisseau() -> None: """Dessine le vaisseau""" x = vaisseau['x'] # on récupère l'abscisse y = vaisseau['y'] # on récupère l'ordonnée couleur = vaisseau['c'] # on récupère l'ordonnée pyxel.rect(x, y, 8, 8, couleur) # on trace un carré 8*8 def afficher_tirs() -> None: """Dessine les tirs""" pass def afficher_ennemis() -> None: """Dessine les ennemis""" pass def afficher_explosions() -> None: """Dessine les explosions""" pass def afficher_informations() -> None: """Affiche les informations voulues à l'écran""" texte = str(pyxel.frame_count) # on génère un string à partir du nombre de frames pyxel.text(1, 1, texte, 7) # affiche le texte def lancer_jeu() -> None: """Cette fonction lance la surveillance des événements""" pyxel.init(128, 128, title="Mon premier jeu") # Initialisation de la fenêtre pyxel.run(controleur, vue) # Lancement de l'alternance controleur / vue # 4 - PROGRAMME PRINCIPAL ==================================================== lancer_jeu()

⚙ 08° Répondre aux questions suivantes :

Questions

  1. Quels sont les types respectifs des 4 variables globales destinées à être lues depuis les fonctions : vaisseau ennemis tirs explosions ? S'agit-il de types MUTABLES ou IMMUABLES ?
  2. Le programme a l'air compliqué, le programme principal ne comporte pourtant qu'une ligne : l'appel à lancer_jeu() sur la ligne 136. Suivre le code à partir de cet appel et trouver les noms des deux fonctions qui vont être appelées 30 fois par seconde.
  3. Regarder la fonction controleur() :
    1. Quelles sont les trois interactions via le clavier que peut réaliser le joueur ?
    2. vaisseau est-il un paramètre d'entrée de la fonction ? A-t-il été déclaré dans la fonction ? S'agit-il d'une variable locale ou d'une variable globale ?
    3. Lignes 32-33. pyxel.frame_count permet de récupèrer le nombre de frames/images qui ont été générées depuis le lancement du jeu. Pourquoi la condition ligne 32 permet-elle de réaliser une création d'ennemi par seconde ?
  4. Regarder la fonction deplacer_vaisseau(). Pourquoi le vaisseau ne peut-il jamais quitter l'écran ?
  5. Regarder la fonction afficher_vaisseau(), donner le nom de la fonction qui permet de tracer des rectangles à l'écran.
  6. Regarder la fonction la fonction afficher_informations(). Donner le nom de la fonction qui permet d'afficher un texte à l'écran.

...CORRECTION...

  1. Les 4 variables globales sont des dictionnaires et des tableaux de dictionnaires, et donc des types mutables.
  2. En effectuant ce appel, où voit-on apparaître les noms des deux fonctions qui vont être appelées 30 fois par seconde ?
  3. Ligne 131 : pyxel.run(controleur, vue)

  4. En allant voir controleur()
    1. Les trois interactions que peut réaliser le joueur :
    2. Lignes 21-24-27, on voit qu'on peut appuyer sur certaines touches.

      • L21 : appuyer sur la flèche LEFT semble faire aller le vaisseau à gauche.
      • L24 : appuyer sur la flèche RIGHT semble faire aller le vaisseau à droite.
      • L27 : appuyer sur la barre ESPACE semble faire tirer le vaisseau.
    3. vaisseau est-il un paramètre d'entrée de la fonction controleur() ? A-t-il été déclaré dans la fonction ? En conclure s'il s'agit-il d'une variable locale ou d'une variable globale.
    4. vaisseau n'est pas un paramètre et n'a pas de déclaration DANS la fonction. Il s'agit donc d'un dictionnaire global.

    5. La ligne 32 consiste à réaliser la division euclidienne du nombre de frames par 30. Or, il y a 30 images par seconde. Si le reste vaut bien 0, c'est qu'on vient de finir une seconde de jeu. La condition testée est donc vraie une fois par seconde.
    6. On génère donc un monstre par seconde.

  5. Pourquoi le vaisseau ne peut-il jamais quitter l'écran ?
  6. On voit qu'on ne laisse jamais la valeur associée à la clé 'x' sortir de l'intervalle [0, 120].

  7. Fonction afficher_vaisseau() : on voit L102 qu'il s'agit de rect().

    pyxel.rect(x, y, 8, 8, 1)

  8. Fonction afficher_informations() : on voit L124 qu'il s'agit de text().
  9. pyxel.text(1, 1, texte, 7)

2.4 Configuration initiale

Documentation officielle : https://github.com/kitao/pyxel/blob/main/docs/README.fr.md

Fonction init()

Voici le prototype de cette fonction :

def init(width, height, [title], [fps], [quit_key], [display_scale], [capture_scale], [capture_sec])

Initialise l’application Pyxel avec un écran de taillede largeur width et de hauteur height.

Il est possible de passer comme [options] :

  • le titre de la fenêtre avec title,
  • le nombre d’images par seconde avec fps,
  • la touche pour quitter l’application avec quit_key,
  • l'échelle de l'affichage avec display_scale,
  • l’échelle des captures d’écran avec capture_scale,
  • et le temps maximum d’enregistrement vidéo avec capture_sec.

Deux exemples :

1 2 3
import pyxel pyxel.init(160, 120, title=My Pyxel App")
1 2 3 4 5
import pyxel pyxel.init(160, 120, title=My Pyxel App", fps=60, quit_key=pyxel.KEY_NONE, capture_scale=3, capture_sec=0)

Ici, on modifie à 60 le nombre d'images par seconde.

Fonction run()

Cette fonction permet d'indiquer les noms des deux fonctions qui seront appellées en boucle.

On la trouve souvent sous la forme pyxel.run(update, draw)

Ces deux fonctions seront alors appellées automatiquement plusieurs fois par seconde (le réglage par défaut est 30 fois).

La première fonction update sert habituellement à définir les actions à réaliser après les événements utilisateur. On pourrait également la nommer controle par exemple, pour indiquer qu'elle fait partie du controleur en logique Modèle Vue Controleur.

La seconde fonction draw sert habituellement à définir l'affichage. On pourrait également la nommer vue par exemple, pour indiquer qu'elle fait partie de la Vue en logique Modèle Vue Controleur.

Attention

Il convient de ne pas mettre les parenthèses lors de la transmission des fonctions : uniquement leurs noms, on ne désire pas les activer à ce stade, juste transmettre les adresses.

Compteur des frames frame_count et autres informations

On peut récupérer le nombre de frames depuis le lancement du jeu avec pyxel.frame_count.

Si vous voulez réaliser une action par seconde et qu'il y a 30 frames par seconde, il suffit donc de placer une instruction de ce type :

1 2
if pyxel.frame_count % 30 == 0: # S'il s'est écoulé 30 secondes faire_un_truc()

Sur le même principe, on peut récupérer la taille de l'écran avec pyxel.width et pyxel.height.

Les 16 couleurs disponibles

16 couleurs disponibles sont disponibles par défaut, chacune identifiée par un simple numéro.

L'image ci-dessous fournit les numéros, les codes RGB en hexadécimal précédés d'un # et les valeurs RGB décimales.

Les couleurs dans Pyxel
Les couleurs dans Pyxel
Effacer l'écran avec cls()

Le principe standard de l'affichage d'un jeu est d'effacer l'écran à chaque appel de la vue. Pour cela, on utilise un appel pyxel.cls(col)col est l'une des couleurs du jeu.

Un exemple qui ne sert à rien mais où le fond de l'écran change toutes les secondes.

1 2 3 4 5 6 7 8 9 10 11 12
import pyxel import random def controle(): pass def vue(): if pyxel.frame_count % 30 == 0: pyxel.cls( random.randint(0, 15) ) pyxel.init(128,128) pyxel.run(controle, vue)
2.5 Gestion des touches avec btn()

Fonction btn()

def btn(key:int) -> bool

Fonction-Prédicat qui renvoie True si la touche key (codée par un entier, voir ci-dessous) est appuyée.

Fonction btbp()

def btnp(key:int) -> bool

Fonction-Prédicat qui renvoie True si la touche key est appuyée à cette frame.

def btnp(key:int, [hold:int], [repeat:int]) -> bool

Quand hold et repeat sont spécifiés, True sera renvoyé à l’intervalle de frame repeat quand la touche key est appuyée pendant plus de hold frames.

Encodage des touches

Disponibles ici : https://github.com/kitao/pyxel/blob/main/python/pyxel/__init__.pyi

Chaque touche est encodée à l'interne pour un entier stocké dans une CONSTANTE du module.

Pour les plus utiles :

  • pyxel.KEY_RETURN pour la touche ENTREE.
  • pyxel.KEY_SPACE pour la barre ESPACE.
  • pyxel.KEY_0 à pyxel.KEY_9 pour les touches de chiffres.
  • pyxel.KEY_A à pyxel.KEY_Z pour les touches de lettres.
  • Pour les touches de flèches :
    • pyxel.DOWN
    • pyxel.UP
    • pyxel.LEFT
    • pyxel.RIGHT
2.6 Rectangle avec rect()

Fonction rect()

def rect(x:int, y:int, w:int, h:int, col:int) -> None

Dessine un rectangle de largeur w, de hauteur h et de couleur col à partir de (x, y).

Les coordonnées (x,y) désignent le coin en haut à gauche du rectange.

Exemple

Voici un visuel et le code correspondant.

Exemples de rectangles
Les rectangles dans Pyxel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import pyxel pyxel.init(128,128) # Rectange 20x40 bleu en (x=0, y=0) pyxel.rect(0, 0, 20, 40, 5) # Rectange 20x50 bleu ciel en (x=50, y=0) pyxel.rect(50, 0, 20, 50, 6) # Rectange 20x30 rouge en (x=0, y=50) pyxel.rect(0, 50, 20, 30, 4) # Afficher l'écran sans passer par run() pyxel.show()
Fonction rectb()

def rectb(x:int, y:int, w:int, h:int, col:int) -> None

Comme la précédente mais uniquement les contours.

Exemple

Voici un visuel et le code correspondant.

Exemples de contours de rectangles
Les rectangles dans Pyxel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import pyxel pyxel.init(128,128) # Rectange 20x40 bleu en (x=0, y=0) pyxel.rectb(0, 0, 20, 40, 5) # Rectange 20x50 bleu ciel en (x=50, y=0) pyxel.rectb(50, 0, 20, 50, 6) # Rectange 20x30 rouge en (x=0, y=50) pyxel.rectb(0, 50, 20, 30, 4) # Afficher l'écran sans passer par run() pyxel.show()

Le programme que nous allons concevoir est un peu plus long et complexe que les précédents. Voici comment s'organisent les appels de fonctions. Si pendant ce TP vous ne savez plus qui appelle qui, revenez à ce schéma.

flowchart TB J([lancer_jeu]) --> I([init]) J ---> R([run]) R --> C([controleur]) R --> V([vue]) C --> DV([deplacer_vaisseau]) C --> NT([creer_tir]) C --> NE([creer_ennemi]) C --> GE([gerer_explosions]) C --> DT([gerer_tirs]) --> TT([tir_touche]) DT --> CE([creer_explosions]) C --> DE([gerer_ennemis]) V --> AV([afficher_vaisseau]) V --> AT([afficher_tirs]) V --> AEN([afficher_ennemis]) V --> AEX([afficher_explosions]) V --> AI([afficher_informations])

3 - Pyxel : les tirs

Dans cette partie, vous allez permettre de créer les tirs dans les données lorsqu'on appuie sur espace, les faire appaître à l'écran et enfin les faire évoluer au fil du temps.

On rappelle que tirs est un tableau de dictionnaires, contenant les tirs actuellement à l'écran, un dictionnaire par tir. Exemple avec deux tirs :

tirs = [
    {'x':50, 'y':100, 'c':10},
    {'x':75, 'y':200, 'c':10},
]

⚙ 09° Compléter les fonctions suivantes de façon à gérer les tirs :

creer_tir() : cette fonction devra calculer les coordonnées du coin haut à gauche du tir (en utilisant les coordonnées du coin haut à gauche du vaisseau), créer le dictionnaire modélisant ce tir et l'intégrer au tableau tirs. Vous pouvez prendre la couleur 10, c'est du jaune, voir la fiche des couleurs ci-dessus.

afficher_tir() : cette fonction devra afficher tous les tirs qui sont dans le tableau tirs sous la forme d'un rectangle de 1 pixel de largeur et 4 pixels de hauteur;

...CORRECTION...

1 2 3 4 5
def creer_tir(x:int, y:int) -> None: """Ajoute le tir du vaisseau dans le tableau tirs""" tir = {'x':x+4, 'y':y-2, 'c':10} tirs.append(tir)

Les coordonnées x et y reçues correspondent au coin haut gauche du vaisseau. On rajoute donc 4 à x pour que le tir sorte du milieu du vaisseau (8 de largeur). De la même façon, on enlève 4 au tir pour que le tir apparaisse au dessus du vaisseau.

...CORRECTION...

1 2 3 4 5 6 7 8
def afficher_tirs() -> None: """Dessine les tirs""" for tir in tirs: # pour chaque tir dans tirs x = tir['x'] # on récupère l'abscisse y = tir['y'] # on récupère l'ordonnée couleur = tir['c'] # on récupère l'ordonnée pyxel.rect(x, y, 1, 4, couleur) # on trace un carré 1*4

⚙ 10° Compléter la fonction suivante de façon à gérer les tirs :

gerer_tir() : pour chaque tir, si le tir a une ordonnée négative (il quitte l'écran par le haut), on le supprime du tableau tirs, sinon on diminue son ordonnée (de 1 ou de 2) de façon à le faire monter à l'écran;

...CORRECTION...

1 2 3 4 5 6 7 8
def gerer_tirs() -> None: """Modifie les coordonnées des tirs, en les supprimant au besoin""" for tir in tirs: # pour chaque tir présents dans tirs if tir['y'] < 0: # si le tir quitte l'écran tirs.remove(tir) # -- on supprime ce tir des données else: # sinon tir['y'] = tir['y'] - 2 # -- le tir monte car y est orienté vers le bas

4 - Pyxel : les ennemis

Passons aux ennemis. Nous voulons qu'un ennemi apparaisse aléatoirement en haut de l'écran puis commence sa descente vers le bas de l'écran.

ennemis est un tableau de dictionnaires (list[dict]), contenant les ennemis, un dictionnaire par ennemi sous forme d'un dictionnaire par ennemi. Exemple ici avec 3 ennemis :

ennemis = [
    {'x':50, 'y':100, 'pv':2, 'c':6},
    {'x':75, 'y':200, 'pv':1, 'c':8},
    {'x':150, 'y':30, 'pv':2, 'c':8}
]

⚙ 11° Compléter la fonction creer_ennemi() pour qu'elle réalise ceci :

  1. Tire au hasard une abscisse x de 0 à 120 en utilisant la fonction randint() du module random. Pensez à aller lire les premières du programme comment le module est importé.
  2. Tire au hasard une couleur couleur de 2 à 15 en utilisant la fonction randint() du module random.
  3. Créer une variable ennemi modelisant ce nouvel ennemi sous forme d'un dictionnaire. En plus des deux valeurs précédentes, vous donnerez une valeur associée de 10 à 'y' et une valeur associée de 2 à 'pv'.
  4. Ajouter ennemi au tableau des ennemis.

Puisque la fonction afficher_ennemis() n'est pas encore réalisée vous ne devriez rien voir apparaître à l'écran. Pour l'instant, nous avons juste modifier le modèle du jeu.

...CORRECTION...

1 2 3 4 5 6 7
def creer_ennemi() -> None: """Création et ajout d'un ennemi dans le tableau ennemis""" x = random.randint(0, 120) # Colonne où apparaitra l'ennemi couleur = random.randint(2, 15) # Couleur de cet ennemi ennemi = {'x':x, 'y':10, 'c':couleur, 'pv':2} ennemis.append(ennemi)

⚙ 12° En vous inspirant des fonctions déjà réalisées, réaliser les fonctions afficher_ennemis() et gerer_ennemis(). Les ennemis seront affichés comme des carrés de 8*8 et ils se déplacent vers le bas avant de disparaître. Veillez à supprimer réellement du modèle.

...CORRECTION...

1 2 3 4 5 6 7 8
def afficher_ennemis() -> None: """Dessine les ennemis""" for ennemi in ennemis: # Pour chaque ennemi dans ennemis x = ennemi['x'] # on récupère l'abscisse y = ennemi['y'] # on récupère l'ordonnée couleur = ennemi['c'] # on récupère l'ordonnée pyxel.rect(x, y, 8, 8, couleur) # on trace un carré 1*4

...CORRECTION...

1 2 3 4 5 6 7 8
def gerer_ennemis() -> None: """Modifie les coordonnées des ennemis en les supprimant au besoin""" for ennemi in ennemis: # pour chaque ennemi dans ennemis if ennemi['y'] > 128: ennemis.remove(ennemi) else: ennemi['y'] = ennemi['y'] + 1

⚙ 13° Remplacer la fonction tir_touche() vide par celle qui elle proposée : elle reçoit un tir et un ennemi et renvoie Vrai si le tir est capable de toucher l'ennemi.

Elle agit en se demandant d'abord si le tir est bien sur une colonne capable de toucher l'ennemi.

Elle fait ensuite la même chose avec la ligne du point haut du tir : il doit être sur une ligne touchant l'ennemi.

Lancer pour vérifier que sa mise en mémoire ne provoqie pas d'erreur puis faire un schéma permettant de comprendre comment la fonction calcule s'il y a touche ou pas.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
def tir_touche(tir:dict, ennemi:dict) -> bool: """Prédicat qui renvoie True si le tir touche l'ennemi""" # on considère des ennemis 8*8 et des tirs 1*4 xg = ennemi['x'] # abscisse du point le plus à gauche de l'ennemi xd = xg + 8 # abscisse du point le plus à droite de l'ennemi yh = ennemi['y'] # ordonnée du point le plus haut de l'ennemi yb = yh + 4 # ordonnée du point le plus bas de l'ennemi xt = tir['x'] # abscisse du tir yt = tir['y'] # ordonnée du point haut du tir if xt >= xg and xt <= xd: # le tir est dans une colonne de l'ennemi if yt <= yb and yt >= yh: # le tir est sur une ligne de l'ennemi return True # si on arrive ici, c'est que cet ennemi est touché return False # si on arrive ici, c'est qu'il n'est pas touché par ce tir

...CORRECTION...

⚙ 14° Mettre à jour gerer_tirs() en complétant son code pour qu'elle réalise cela : après avoir déplacé un tir qui est encore à l'écran, on veut réaliser cela :

  • Après avoir déplacé le tir, on vérifie la collision pour tous les ennemis, et si ce tir touche un ennemi (pensez à utiliser la fonction qui va bien) :
    • Diminuer les pv (points de vie) de l'ennemi de 1
    • S'il lui reste encore au moins un pv :
      • Lancer un appel à creer_explosion() en transmettant les coordonnées x et y du tir et en fournissant un rayon maximum de 5.
    • Sinon :
      • Supprimer l'ennemi (il n'a plus de points de vie).
      • Lancer un appel à creer_explosion() en transmettant les coordonnées x et y du tir et en fournissant un rayon maximum de 9.
    • Supprimer le tir
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def gerer_tirs() -> None: """Modifie les coordonnées des tirs, en les supprimant au besoin""" for tir in tirs: # pour chaque tir présents dans tirs if tir['y'] < 0: # si le tir quitte l'écran tirs.remove(tir) # -- on supprime ce tir des données else: # sinon tir['y'] = tir['y'] - 2 # -- le tir monte car y est orienté vers le bas # nouveautés à compléter for ennemi in ...: # pour chaque ennemi if ...: # si le tir touche cet ennemi ennemi['pv'] = ... # Diminue les pv de l'ennemi if...: # S'il a encore au moins 1 pv creer_explosion(..., ..., ...) # Crée l'explosion de rayon max 3 else: creer_explosion(..., ..., ...) # Crée l'explosion de rayon max 9 ... # Supprime l'ennemi ... # Supprime le tir

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def gerer_tirs() -> None: """Modifie les coordonnées des tirs, en les supprimant au besoin""" for tir in tirs: # pour chaque tir présents dans tirs if tir['y'] < 0: # si le tir quitte l'écran tirs.remove(tir) # -- on supprime ce tir des données else: # sinon tir['y'] = tir['y'] - 2 # -- le tir monte car y est orienté vers le bas for ennemi in ennemis: # pour chaque ennemi if tir_touche(tir, ennemi): # si le tir touche cet ennemi ennemi['pv'] = ennemi['pv'] - 1 # Diminue les pv de l'ennemi if ennemi['pv'] > 0: # S'il a encore au moins 1 pv creer_explosion(tir['x'], tir['y'], 3) # Crée l'explosion de rayon max 3 else: creer_explosion(tir['x'], tir['y'], 9) # Crée l'explosion de rayon max 9 ennemis.remove(ennemi) # Supprime l'ennemi tirs.remove(tir) # Supprime le tir

5 - Pyxel : les explosions

Regardons maintenant comment créer et gérer les explosions.

Au niveau de la visualisation, nous allons utiliser la fonction circb du module pyxel qui permet de dessiner des cercles sans fond coloré, là où circ crée un disque plein.

5.1 Cercle avec circ()

Fonction circ()

def circ(x:int, y:int, r:int, col:int) -> None

Dessine un cercle (fortement pixelisé !) de rayon r et de couleur col en (x, y).

Les coordonnées (x,y) désignent le centre du cercle.

Exemple

Voici un visuel et le code correspondant.

Exemples de cercles
Les cercles dans Pyxel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import pyxel pyxel.init(128,128) # Cercle bleu en (x=0, y=0) pyxel.circ(0, 0, 10, 5) # Cercle bleu ciel en (x=50, y=0) pyxel.circ(50, 0, 20, 6) # Cercle rouge en (x=0, y=50) pyxel.circ(0, 50, 30, 4) # Afficher l'écran sans passer par run() pyxel.show()
Fonction circb()

def circb(x:int, y:int, r:int, col:int) -> None

Idem, mais juste la bordure.

Exemple

Voici un visuel et le code correspondant.

Exemples de cercles
Les bordures de cercles dans Pyxel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
import pyxel pyxel.init(128,128) # Bordure de cercle bleu en (x=0, y=0) pyxel.circb(0, 0, 10, 5) # Bordure de cercle bleu ciel en (x=50, y=0) pyxel.circb(50, 0, 20, 6) # Bordure de cercle rouge en (x=0, y=50) pyxel.circb(0, 50, 30, 4) # Afficher l'écran sans passer par run() pyxel.show()

⚙ 15° Regardons comment créer les explosions. Compléter la fonction creer_explosion() qui reçoit trois arguments :

  1. La position de la colonne/abscisse où l'explosion se produit
  2. La position de la ligne/ordonnée où l'explosion se produit
  3. Le rayon maximum que devra avoir l'explosion avant de disparaître.

Votre fonction devra donc créer le dictionnaire correspond à votre explosion en prenant au départ un rayon de -1 pixel pour indiquer que l'explosion vient tout juste de ce produire.

Voici pour rappel la structure du tableau explosions et le prototype de la fonction creer_explosion().

Le tableau de dictionnaire

explosions = [
    {'x':50, 'y':100, 'c':10, 'rayon':2, 'rayon max':4}
]

La fonction

creer_explosion(x:int, y:int, rmax:int) -> None

...CORRECTION...

1 2 3 4 5
def creer_explosion(x:int, y:int, rmax:int) -> None: """Ajoute l'explosion dans le tableau explosions""" explosion = {'x':x, 'y':y, 'c':10, 'rayon':-1, 'rayon max':rmax} explosions.append(explosion)

⚙ 16° Regardons comment faire évoluer les explosions. Compléter la fonction gerer_explosions() : toutes les explosions du tableau explosions doivent avoir un rayon augmenté de 1 pixel. Si le rayon devient strictement plus grand que le rayon maximum, il faudra supprimer cette explosion du tableau.

...CORRECTION...

1 2 3 4 5 6 7
def gerer_explosions() -> None: """Modifie ou supprime les données liées aux explosions""" for explosion in explosions: # Pour chaque explosion explosion['rayon'] = explosion['rayon'] + 1 # On augmente son rayon de 1 if explosion['rayon'] > explosion['rayon max']: explosions.remove(explosion)

⚙ 17° Regardons enfin comment les afficher. Compléter la fonction afficher_explosions() qui doit afficher toutes les explosions modélisées dans le tableau explosions.

Il faudra utiliser la fonction circb() comme indiqué plus haut.

...CORRECTION...

1 2 3 4 5 6 7 8 9
def afficher_explosions() -> None: """Dessine les explosions""" for explosion in explosions: # Pour chaque explosion x = explosion['x'] # on récupère l'abscisse y = explosion['y'] # on récupère l'ordonnée couleur = explosion['c'] # on récupère l'ordonnée rayon = explosion['rayon'] # on récupère son rayon pyxel.circb(x, y, rayon, couleur)

✎ 18° Dans la fonction afficher_informations(), modifier l'affichage pour qu'on affiche plutôt le score stocké dans le dictionnaire points.

A vous de gérer le score comme vous le voulez :

  • Combien de points gagnés pour un ennemi détruit ?
  • Combien de points perdus pour un ennemi qui sort de l'écran ?
  • Combien de points perdus pour un tir perdu ?

Ce bout de cours vous permettra de retrouver facilement le nom des couleurs et fonctions disponibles dans Pyxel. Juste un pense-bête.

5.2 Mémo pyxel

Cet encart condense les informations utiles pour Pyxel.

Les 16 couleurs disponibles
Les couleurs dans Pyxel
Les couleurs dans Pyxel
Les fonctions disponibles
Fiche
Logo Pyxel (https://github.com/kitao/pyxel#)

6 - Facultatif : animation avec Tkinter

6 (Rappel) Principe d'un programme tkinter

Un programme utilisant le module tkinter est basé sur la boucle infinie décrite ci-dessous :

    1 - La fonction Tk() crée la fenêtre graphique.

    2 - La méthode bind() permet de faire le lien entre un événement et une fonction de contrôle controler_...() de l'événement

    3 - La méthode mainloop() lance la surveillance en boucle des événements

    4 - TANT QUE le programme est actif :

      SI l'utilisateur appuie sur une touche, on active la fonction controler_...() correspondante qui va

        D'abord déterminer comment faire appel aux fonctions maj_...() pour modifier les modèles

        Ensuite demander à modifier l'affichage (la vue) en faisant appel à afficher() qui va

        faire appel à delete() pour effacer l'écran

        faire appel à des fonctions afficher_...() pour afficher des formes à l'écran

      Fin du SI

    Fin TANT QUE

Nous allons finir avec une petite application graphique Tkinter qui permet de réaliser une animation : une balle rebondit sur les parois.

S'agissant d'un dessin qu'on modifie rapidement à l'écran pour donner l'illusion d'un déplacement, le programme crée plusieurs éléments :

  • Ligne 78 : la fenêtre de l'application (de type Tk)
  • 78
    fenetre = creation_interface("Balle balle", "#8844BB")
  • Ligne 81 : un nouveau widget (de type Canvas) qui est simplement une zone dans laquelle nous allons pouvoir dessiner des choses
  • 81
    canvas = tk.Canvas(fenetre, bg="dark grey", height=300, width=300, borderwidth=0, highlightthickness=0)
  • Ligne 88 : un dessin rond dont la variable numero_mobile n'est rien d'autres que le numéro du dessin dans le Canvas précédent. Pour le créer, on doit donner les deux coordonnées x1, y1 du point en haut à gauche et les deux coordonnées x2, y2 du point en bas à droite. Comme on décale de 20 pixels à chaque fois, on obtient un rond et pas une forme ovale.
  • 88
    numero_mobile = canvas.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow")

TK 01° Mettre le programme en mémoire pour vérifier qu'il fonctionne à peu près : la balle rebondit bien mais pas sur les parois. Pour activer le déplacement, il faut appuyer sur les flèches gauche ou droite de votre clavier.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
# - 1 - importation des modules nécessaires import tkinter as tk import random as rd # - 2 - variables globales DPCT = 3 # - 3 - Déclaration des fonctions def creation_interface(titre, couleur): """Crée et renvoie la référence d'interface graphique :: param titre(str) :: le titre visible dans la barre de l'application :: param couleur(str) :: une couleur de fond valide pour l'application :: return (tk.Tk) :: la référence Tkinter de l'application """ fe = tk.Tk() fe.geometry("350x350") fe.title(titre) fe.configure(bg=couleur) return fe def activer_gestion_evenements(): """!activation des surveillances des flèches gauche et droite""" fenetre.bind('<Right>', lancement_droite) fenetre.bind('<Left>', lancement_gauche) def stopper_gestion_evenements(): """Annule l'activation des surveillances des flèches gauche et droite""" fenetre.unbind('<Right>') fenetre.unbind('<Left>') def lancement_droite(e): """Fonction évenementielle qui lance juste droite()""" stopper_gestion_evenements() droite() def lancement_gauche(e): """Fonction évenementielle qui lance juste gauche()""" stopper_gestion_evenements() gauche() def droite(): """Fait avancer le mobile de DPCT pixels vers la droite""" canvas.move(numero_mobile, DPCT, 0) canvas.update() if coordonnee_horizontale(numero_mobile, canvas) <= 190: fenetre.after(50, droite) else: fenetre.after(50, gauche) def gauche(): """Fait avancer le mobile de DPCT pixels vers la gauche""" canvas.move(numero_mobile, -DPCT, 0) if coordonnee_horizontale(numero_mobile, canvas) >= 110: fenetre.after(50, gauche) else: fenetre.after(50, droite) def coordonnee_horizontale(n, c): """Renvoie l'abscisse du centre du mobile de numéro n dans le canvas c :: param n(int) :: numéro du dessin à étudier :: param c(Canvas) :: référence du Canvas dans lequel le dessin est présent :: return (int) :: la coordonnée x voulue """ return (c.coords(n)[0] + c.coords(n)[2]) // 2 # - 4 - Programme principal # - 4.1 - Création de la fenêtre du logiciel fenetre = creation_interface("Balle balle", "#8844BB") # - 4.2 - Création du Canvas zone_dessin, la zone de déplacement du mobile canvas = tk.Canvas(fenetre, bg="dark grey", height=300, width=300, borderwidth=0, highlightthickness=0) canvas.place(x=25, y=25) # - 4.3 - Création du mobile qu'on va déplacer sur le Canvas xo = rd.randint(100,200) yo = rd.randint(100,200) numero_mobile = canvas.create_oval(xo, yo, xo+20, yo+20, width=1, fill="yellow") # - 4.4 - Création des événéments activer_gestion_evenements() # - 4.5 - Surveillance de l'application fenetre.mainloop() # Surveillance des événements sur la fenêtre

Questions

  1. Sur quelles lignes sont crées les liaisons entre les événement Flèche Droite et Flèche Gauche aux fonctions évenementielles lancement_gauche() et lancement_droite() ?
  2. A quoi voit-on que les fonctions lancement_gauche() et lancement_droite() sont des fonctions événementielles ?
  3. Que provoque les instructions de lancement_droite() ?

...CORRECTION...

  1. LIGNE 92 : on lance la fonction activer_gestion_evenements() et on arrive LIGNES 28 et 29 :
  2. 26 27 28 29
    def activer_gestion_evenements(): """!activation des surveillances des flèches gauche et droite""" fenetre.bind('<Right>', lancement_droite) fenetre.bind('<Left>', lancement_gauche)
  3. Ces deux fonctions possèdent un paramètre (nommé ici e) qui sera rempli automatiquement avec les informations sur l'événément qui a provoqué son activation.
  4. Il suffit de lire les instructions de la fonction :
    • Ligne 38 : on active la fonction stopper_gestion_evenements() qui désactive la surveillance des touches gauche et droite avec la méthode unbind() (cela évite de pouvoir "démarrer" plusieurs fois l'animation).
    • Ligne 39 : on lance la fonction droite()

Pour comprendre l'animation, il faut étudier les fonctions gauche() et droite() et savoir ce que réalisent les méthodes qu'on y trouve :

  • la méthode move()
  • la méthode update()
  • la méthode after()
46 47 48 49 50 51 52 53
def droite(): """Fait avancer le mobile de DPCT pixels vers la droite""" canvas.move(numero_mobile, DPCT, 0) canvas.update() if coordonnee_horizontale(numero_mobile, canvas) <= 190: fenetre.after(50, droite) else: fenetre.after(50, gauche)

Comment cela fonctionne ?

  • Ligne 48 : la méthode move() permet de déplacer l'un des dessins d'un canvas en x et en y. On doit donner le nom du canvas devant la méthode, puis, entre les parenthèses de la méthode, le numéro du dessin, le décalage en x en pixels et le décalage en y en pixels. On voit ici qu'on va donc déplacer le rond de DPCT pixels horizontalement et 0 pixels verticalement.
  • Ligne 49 : la méthode update() impose au canvas de mettre à jour son apparence. Il le fait régulièrement mais on force ici son changement d'apparence pour être certain que la modification soit prise en compte.
  • Ligne 50 : on teste la coordonnée x centrale du rond à l'aide de la fonction coordonnee_horizontale() dont je n'expliquerai pas le fonctionnement ici. Sachez juste qu'elle renvoie cette coordonnée lorsqu'on lui fournit le numéro d'un dessin et la référence du canvas. Si la coordonnée horizontale est inférieure à 190 pixels (on est pas encore trop à droite), on voit qu'on réactive la fonction droite() quelques millisecondes plus tard. Sinon, on active gauche() pour repartir vers la gauche.
  • Lignes 51 et 53 : la méthode after() demande de lancer une fonction dont on fournit l'adresse après un certain nombdre de millisecondes (50 ms ici). Rappel 50 ms = 50.10-3 s = 0.050 s.

TK 02° Expliquer comment fonctionne la fonction gauche().

Finalement, modifier la fonction de façon à ce que la balle fasse bien des aller-retours lorsqu'elle "percute" le bord du canvas.

Et si on veut faire plus de choses avec Tkinter ?

Il existe beaucoup de documentation sur le Web.

Les informations risquent néanmoins d'être encore difficiles à comprendre pour le moment.

Sinon, vous trouverez ci-dessous un petit tuto pour faire quelques petites manipulations :

FICHE Tkinter

7 - FAQ

C'est quoi la Nuit du Code ?


PRÉSENTATION RAPIDE
La Nuit du Code est un marathon de programmation durant lequel les élèves, par équipes de deux ou trois, ont 6h (ou moins pour le cycle 3) pour coder un jeu avec Scratch ou Python/Pyxel.

Peuvent participer: tous les établissements scolaires français situés en France ou à l’étranger ainsi que tous les établissements scolaires francophones où qu’ils soient. Chaque établissement peut inscrire autant d’élèves qu’il le souhaite. Les inscriptions sont gratuites.

Six catégories :

  • Scratch: CM1-6e, 5e-3e et Lycée
  • Python: NSI Première, NSI Terminale et post-bac

L’an dernier, pour l’édition 2024, 464 établissements se sont inscrits. Soit plus de 11500 élèves de 346 villes et 49 pays.

Si vous n’avez pas participé aux éditions précédentes, voici quelques supports:

  • Diaporama de présentation (pour les élèves , les familles, les collègues, la direction…)
  • Vidéos
  • Photos

Afin de réduire au maximum l’investissement nécessaire pour organiser la Nuit du Code dans les établissements, tous les documents, ressources, tutoriels, supports… sont fournis.

La Nuit du Code peut être un prétexte pour organiser, dans un établissement, un ou plusieurs événements centrés autour de la programmation, du numérique et des nouvelles technologies.


RESSOURCES et COMMUNICATION


ORGANISATION

  • Septembre 2024 - Avril 2025 : Inscriptions
  • Janvier - Mai 2025 : Préparatifs / Entraînements / Sélections
  • Mai 2025 : Chaque établissement organise sa Nuit du Code entre le 28 avril et le 31 mai 2025 selon ses ressources et ses contraintes. Les établissements qui prévoient de ne pas de proposer des jeux pour la sélection internationale peuvent organiser la Nuit du Code jusqu’au 27 juin.
  • Mi-juin 2025 : Annonce des jeux de la sélection « Nuit du Code 2025 »

PRÉSENTATION GÉNÉRALE
Chaque année, plusieurs univers de jeu sont proposés. Ces univers de jeux sont mis à disposition des organisateurs 48h avant la date choisie par l’établissement pour organiser la Nuit du Code. Les élèves les découvrent au début de la Nuit du Code et les équipes doivent en choisir un. Chaque univers de jeu contient tous les éléments, sons et arrière-plans nécessaires pour créer un jeu. Ces univers sont très variés et laissent une grande place à l’imagination et à la construction de scénarios. Pendant l’événement, les élèves peuvent demander de l’aide aux professeurs qui les encadrent, échanger entre équipes, partager leurs connaissances, leurs idées…

Pour préparer les élèves durant l’année, des fiches, des tutoriels, des vidéos et des sujets d’entraînement sont fournis. La préparation peut se faire en présence d’enseignants mais elle peut aussi se faire de façon autonome. Les élèves n’ont pas besoin d’avoir de connaissances particulières en Scratch et Python en début d’année pour participer à la Nuit du Code. Quelques séances de préparation et un peu de travail en autonomie suffisent.

Le budget nécessaire pour organiser la Nuit du Code au sein d’un établissement peut être quasi nul. Il suffit de quelques ordinateurs connectés à internet (un ou plus par équipe), une ou plusieurs salles et des encadrants. La nourriture et les boissons peuvent être pris en charge par les familles. Mais il est aussi possible, si le budget le permet, de prévoir des pizzas, des « goodies » (autocollants et badges par exemple), des récompenses…

La Nuit du Code est avant tout un événement festif et convivial.


DE NUIT OU DE JOUR ?
La Nuit du Code se déroule forcément la nuit ? Non, heureusement. Chaque établissement organise sa Nuit du Code en fonction de ses ressources et de ses contraintes. Le matin, l’après-midi, le soir, en semaine, le week-end, c’est selon.
Alors, pourquoi la Nuit du Code ? Car, lors des premières éditions, les établissements de la zone Asie-Pacifique se retrouvaient à Taipei le temps d’un week-end riche en rencontres et aventures (grâce à Alexis Kauffmann, Jean-Yves Labouche, Jean-Yves Vesseau, Andria Spring et toutes les personnes qui participaient à l’organisation). Et, le marathon de programmation débutait le samedi à 18h pour se finir à minuit. De nuit donc.


L’ÉCOSYSTÈME NUIT DU CODE
Des outils pour préparer les Nuit du Code et enseigner la programmation au collège et au lycée:


Organisateur: Laurent Abbal (laurentabbal.forge.apps.education.fr | x.com/laurentabbal | mastodon.social/@laurentabbal)

Le site de la Nuit du Code et les ressources sont sur LaForgeEdu (Forge des Communs Numériques Éducatifs)

Nous avons maintenant vu tous les outils basiques, à part la boucle non bornée. C'est l'objectif de la dernière activité sur les bases de Python.

Activité publiée le 02 07 2024
Dernière modification : 20 07 2024
Auteur : ows. h.