Aller au contenu

TP pile d'annulation

  • Télécharger le code, le mettre dans un dossier pour ce TP et ouvrir ce dossier avec VSCodium.

    Ouvrez VSCodium, menu File->Open Folder et chercher le dossier du TP, acceptez si on vous demande si on fait confiance au dossier, puis dans l'onglet des extensions à gauche cherchez et installez l'extension ms-python

  • L'exécuter pour tester le fonctionnement avec la commande pgzrun dessin.py, à lancer dans le terminal en bas.

Objectif

Le but du TP est de permettre de revenir en arrière autant qu'on veut dans le dessin en appuyant sur Ctrl+Z.

Cela fonctionne bien comme une pile :

  • Avant chaque nouveau trait, on sauvegarde l'image en haut de la pile.
  • À chaque retour en arrière, on dépile la dernière image et on la restaure.

Le code de ce TP utilise la bibliothèque Pygame Zero, un enrobage un peu simplifié autour de Pygame, une bibliothèque python pour faire des petits logiciels / jeux.

1) Ajouter une pile en attribut de l'objet Dessin

  • Copiez-collez le code de la pile avec liste chaînée qu'on avait fait ci-dessous et mettez-les dans un autre fichier dans le même dossier, qu'on pourra appeler liste_chainee.py
Code de la pile avec liste chaînée
class Maillon:
    def __init__(self, valeur, suivant):
        self.valeur = valeur
        self.suivant = suivant
        
class Pile:
    def __init__(self):
        """Crée une pile vide"""
        self.contenu = None
    def empile(self, valeur):
        """ajoute valeur en haut de la pile"""
        self.contenu = Maillon(valeur, self.contenu)
    def est_vide(self):
        return self.contenu == None
    def depile(self):
        """renvoie la valeur dépilée"""
        valeur_enlevee = self.contenu.valeur
        self.contenu = self.contenu.suivant
        return valeur_enlevee

Info

La structure de donnée de pile étant indépendante du code du dessin, il est mieux de ne pas tout mettre dans le même fichier.

C'est aussi une bonne pratique de modularité pour vos projets : mettre les classes/fonctions dans des fichiers différents, surtout s'il y a beaucoup de code.

  • Importer la classe Pile dans dessin.py (from liste_chainee import Pile)

  • Ajouter un attribut pile_annulation à la classe Dessin, en l'initialisant avec une Pile vide.

2) Sauvegarder en haut de la pile à chaque trait

  • Repérer dans le code de la classe l'endroit qui dessine un nouveau trait de manière définitive (cette méthode est appelée quand on relache la souris).
  • Juste avant, empiler une copie de l'image actuelle dans la pile d'annulation.

Warning

Si vous empilez directement self.image, on aura plusieurs variables (celles dans la pile, et self.image elle-même) qui pointeront vers le même objet (de type pygame.Surface) en mémoire. Donc les sauvegardes seront modifiées quand on modifiera self.image, ce qu'on veut éviter !

Une solution est de demander explicitement une copie de l'objet avec self.image.copy(). De cette façon les sauvegardes seront des objets différents en mémoire, qui ne seront pas modifiés quand on continue le dessin.

Info

Beaucoup de classes en python fournissent cette méthode de copie, les listes python aussi par exemple.

L1 = [1,2,3]
L2 = L1
L3 = L1.copy()
L1[0] = 42 # modifie L1 et L2 mais pas L3

  • Vérifier que le code fonctionne sans erreurs

3) Détecter le Ctrl+Z

Avec pygame zero, il y a deux manière de gérer le clavier :

  • On peut utiliser des attributs booléens de l'objet prédéfini keyboard qui indiquent si chaque touche est actuellement pressée (voir ici pour des détails, et ici pour la liste des noms de touche). Si on utilise ça dans la fonction update, on risque de détecter plusieurs (ou aucune) fois le même appui de touche car celui-ci dure un certain temps, et la fonction update est elle-même appelée de nombreuses fois par seconde.
  • On peut créer une fonction on_key_down(key) qui sera appelée par pgzero à chaque fois qu'une touche est appuyée avec en paramètre le code de la touche (voir ici). Ces codes sont des entiers mais on peut comparer leur valeur avec les codes stockés dans l'objet keys, la liste est dans le lien précédent. Cela permet de détecter exactement une fois chaque appui.

Ici, on veut détecter Ctrl+Z donc on peut :

  • utiliser on_key_down en testant que le code de la touche pressée est keys.Z
  • vérifier à ce moment que la touche Ctrl est bien enfoncée en regardant la valeur booléenne keyboard.lctrl (lctrl = left control).

Ajouter cela à votre code avec pour l'instant juste un print("ctrl+z !") si le test passe et vérifier que le message est bien affiché dans le terminal quand vous exécutez le programme et appuyez sur Ctrl+Z.

4) Restaurer l'état précédent

  • Ajouter une méthode annule à la classe Dessin qui :

    • n'a pas besoin de paramètre autre que self
    • récupère l'image en haut de la pile
    • la remet dans self.image
  • Appeler cette méthode dans la fonction on_key_down à la place du print.

  • Vérifier que ça fonctionne

  • Que se passe-t-il si vous faites une fois de trop Ctrl+Z ? Modifier le code pour avoir le comportement souhaité.

5) Extension : Ctrl+R

Parfois, on appuie une fois de trop sur Ctrl+Z en revenant en arrière.

Certains logiciels ont un autre raccourci Ctrl+R pour retourner en avant après avoir utilisé Ctrl+Z.

  • Réfléchir à comment doit se comporter cette fonctionnalité
  • L'ajouter au programme comme on a fait précédemment.