python projet 2

Identification

Infoforall

16 - Projet avec tkinter


Cette activité vous montre comment travailler en groupe sur un travail de type Interface Homme Machine. Vous en savez maintenant beaucoup plus que la première fois où nous avons travaillé avec Tkinter.

Prérequis : l'activité Données sur les matrices

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

1 - Création de l'application graphique

RAPPEL

Contrairement au système d'axes Oxy classique en mathématiques, y est orienté vers le bas et le point d'origine (0,0) correspond au point en haut à gauche. C'est le standard en informatique.

systeme de coorcodes

Nous allons voir comment réaliser une fenêtre graphique dans laquelle nous allons placer nos éléments graphiques. Par exemple ceci :

Une application graphique avec des cases colorées

Nous prendrons comme support de travail le plateau de jeu que nous avions modélisé dans l'activité Matrice de la partie Données.

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
# - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # - - - - - - - - # 3 - Fonctions # - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") # - - - - - - - - - - - - - # 4- Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

Que fait le programme ?

Importation du module sous l'alias tk

5
import tkinter as tk

Créations de 2 variables globales

On commence avec les données stockant l'état du jeu, indépendamment de l'interface utilisée. On les nommera _data pour clairement les distinguer des données liées à l'interface homme machine (_ihm.)

12 13 14 15 16 17 18 19 20 21 22 23
# informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]
  • infos_data est un tableau n'ayant qu'un seul index, 0. Il nous permettra de profiter de la nature mutable des tableaux : on pourra modifier infos_data[0] depuis une fonction. -1 si aucun joueur en cours, 1 ou 2 sinon. Vous pourrez bien entendu rajouter des index encodant d'autres informations.
  • codes_data est une matrice, un tableau de tableaux, qui va contenir les données liées aux cases du jeu. Dans la version présentée ici, on y stocke juste une valeur par case (par exemple 0 pour non sélectionnée ou pour dire mur, 1 pour sélectionnée par le joueur 1 ou pour dire couloir...), mais on pourrait y mettre un tuple, un string, un autre tableau... On accède au contenu d'une case précise à l'aide de sa ligne et sa colonne :
    codes_data[ligne][colonne].

01° Que renvoie codes_data[2][4] ?

...CORRECTION...

On se souviendra que l'index commence à 0.

Le contenu de la case de ligne d'index 2.

15 16 17 18 19 20 21 22 23
# codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Il ne reste qu'à chercher la colonne d'index 4 : elle fait référence à la valeur 3.

15 16 17 18 19 20 21 22 23
# codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Passons rapidement sur la partie fonction. Nous verrons ce qu'elles font lorsque nous aurons besoin de le savoir.

Action du programme principal, notamment Activation des doctests :

45 46 47 48 49
if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod()

Puis Création de la fenêtre graphique.

51 52 53
# B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm)

Ligne 52 : Création d'un objet de classe Tk et stockage de sa référence dans ihm.

Ligne 53 : Activation de la procédure configurer_fenetre_ihm qui reçoit ici l'argument ihm et le place dans le paramètre fenetre.

30 31 32 33 34 35 36 37 38 39
def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black")
  • Ligne 37 : geometry permet de fournir les dimensions en pixels (largeur puis hauteur) sous forme d'un string.
  • Ligne 38 : title permet de faire de même pour le titre de la fenêtre
  • Ligne 39 : configure permet de modifier certains attributs de l'objet. Ici, on modifie la couleur de fond (background, encodé par le paramètre nommé bg) à qui on affecte la couleur "black"

02° Lancer l'interface pour visualiser ce que cela donne. Modifier quelques valeurs de la fonction pour voir le résultat.

On rappelle que les couleurs d'une image sont définies en R-V-B (ou RGB en anglais). On peut donc soit donner le nom d'une couleur prédéfinie ou fournir les valeurs RGB en hexadécimal. Chaque composante peut prendre une valeur allant de 00 à FF.

Exemples :

"#000000" équivalent à "black"

"#FF0000" équivalent à "red"

"#00FF00" équivalent à "green"

"#FFFF00" équivalent à "yellow"

A partir de là, on peut lancer l'interface graphique avec cette dernière partie :

Activation de la surveillance en boucle sur l'interface graphique jusqu'à fermeture

55 56
# E - Activation de la surveillance sur l'application graphique ihm.mainloop()

2 - Les carrés colorés

Voyons maintenant comment créer des carrés colorés sur cette interface.

03° Lancer le code ci-dessous pour voir la nouveauté qu'il apporte. Lire ensuite la suite des explications.

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
# - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # - - - - - - - - # 3 - Fonctions # - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000') # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case # - - - - - - - - - - - - - # 4- Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

Les deux nouveautés apparaissent ligne 105 et 106. Elles consistent en la création des widgets colorés après la création de la fenêtre de l'application elle-même.

104 105 106
# C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets

Création de la matrice de stockage des références

Ligne 105, on commence par créer une nouvelle matrice nommée refs_ihm qui va être destinée à contenir les références des différents widgets colorés que nous allons créés sur l'interface. Cette création utilise la fonction creer_matrice_ihm. Voyons comment elle fonctionne.

L'appel à la fonction se fait ainsi :

105
refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage

Le code de la fonction est :

41 42 43 44 45 46 47 48 49 50
def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ]

Ligne 105 + Ligne 41 : le paramètre codes reçoit donc la réference de la matrice des données codes_data.

Ligne 48 : on parvient à trouver le nombre de lignes en cherchant la longueur de codes, ce qui revient à chercher la longueur de codes_data. Si vous regarder ci-dessous, vous pourrez constatez qu'on dénombre 6 éléments.

16 17 18 19 20 21 22 23
codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Ligne 49 : pour trouver le nombre de colonnes, on détermine la longueur du premier élément de codes, donc de codes_data. Si vous comptez, vous trouverez 6 également.

16 17 18 19 20 21 22 23
codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ]

Ligne 50 : la dernière ligne permet de voir qu'on renvoie bien un tableau de tableau, une matrice.

50
return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ]

04° QCM : Cette matrice retournée est construite par :

  • A : Compréhension
  • B : Déception
  • C : Incompréhension
  • D : Extension

...CORRECTION...

  • A : Compréhension
  • On voit d'ailleurs qu'elle est contruite à partir de deux constructions imbriquées : on commence par rajouter un élément pour chaque ligne

    50
    return [ ELEMENT for ligne in range(lignes) ]

    Et cet ELEMENT est lui-même en réalité un tableau construit par compréhension.

    50
    return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ]

    On va donc créer un tableau de 6 éléments et chaque élement va contenir ici 6 None. Nous sommes parvenus à obtenir une nouvelle matrice qui a les mêmes dimensions que l'ancienne.

    La fonction renvoie sa référence qui va donc être stockée dans refs_ihm (voir ligne 105).

    104 105 106
    # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets

    Création des cases et remplissage de refs_ihm

    Nous avons donc une belle matrice vide nommée refs_ihm.

    Voyons comment on parvient à la remplir avec la fonction creer_cases_ihm alors qu'elle ne renvoie rien...

    106
    creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets

    On transmet donc deux arguments à cette fonction.

    • ihm : la référence de la fenêtre tkinter de l'interface homme machine
    • refs_ihm : la matrice ayant les bonnes dimensions pour associer une référence Tkinter à une case du jeu

    Il faut donc regarder cette fonction creer_cases_ihm pour comprendre ce qu'elle fait.

    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
    def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000') # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case

    Lignes 52-59 : le prototypage de la fonction et sa documentation nous permettent de voir qu'on attend une référence de fenêtre graphique et une référence de matrice.

    52 53 54 55 56 57 58 59
    def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python '''

    On va ensuite récupérer le nombre de lignes et de colonnes (avec un s attention) et nous allons effectuer une double boucle FOR pour obtenir toutes les combinaisons possibles :

    60 61 62 63 64
    lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) :

    On va donc boucler plusieurs fois et obtenir la suite de valeurs suivantes pour ligne et colonne (sans s !) :

    Si on prend l'exemple d'une petite matrice 3x3 avec des index valant donc 0, 1 ou 2 :

    • 1er tour de boucle : Case numéro 0 - ligne vaut 0 et colonne vaut 0.
    • 2e tour de boucle : Case numéro 1 - ligne vaut 0 et colonne vaut 1.
    • 3e tour de boucle : Case numéro 2 - ligne vaut 0 et colonne vaut 2.
    • 4e tour de boucle : Case numéro 3 - ligne vaut 1 et colonne vaut 0.
    • 5e tour de boucle : Case numéro 4 - ligne vaut 1 et colonne vaut 1.
    • ...

    05° QCM : Cela revient donc à parcourir les cases du plateau :

    • A : En commençant en haut à gauche et en partant d'abord vers la droite
    • B : En commençant en haut à gauche et en partant d'abord vers le bas
    • C : En commençant en bas à gauche et en partant d'abord à droite
    • D : En commençant en bas à gauce et en partant d'abord en haut

    ...CORRECTION...

  • A : En commençant en haut à gauche et en partant d'abord vers la droite
  • Bon, en même temps, la question n'était pas difficile puisque les cases sont numérotées de base dans l'ordre de création !

    Voyons maintenant comment on parvient à créer ces widgets affichés sous forme d'un carré coloré.

    On commence par calculer le numéro qu'on veut attribuer à ce widget ainsi que ses coordonnées en pixels.

    63 64 65 66 67 68 69
    for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels

    Si les écartements entre cases ne vous conviennent pas, il suffit de modifier les valeurs.

    Vient ensuite la véritable création du widget :

    71 72 73 74 75
    # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne
    • Ligne 72 : Création d'un objet widget de class Label en utilisant le constructeur Label. On lui transmet différents paramètres nommés, dont on peut trouver la description dans la documentation de Tkinter.
      • Un paramètre obligatoire qui est la fenêtre à qui on veut le rattacher (la réference fenetre ici)
      • text pour text, le texte qu'on veut voir s'afficher dans le widget Label
      • fg pour foreground, fond du premier plan, le texte.
      • bg pour background, fond coloré.
      • width pour width, la largeur (en largeur de caractères puisqu'il s'agit d'un texte)
      • height pour height, la hauteur (en hauteur de caractères puisqu'il s'agit d'un texte).
    • Lignes 73 à 75 : on rajoute quelques attributs à notre objet Label de façon à faciliter l'élaboration du jeu. Il s'agit d'un avantage important de la création des objets en Python : on peut rajouter ce qu'on veut à nos objets. Je ne rentre pas dans le détail, cet aspect ne sera approndi qu'en Terminale. La seule chose à comprendre ce qu'à partir de maintenant, on pourra récupérer la colonne d'un widget à l'aide de sa référence : il suffira de demander reference_de_la_case.colonne. Pratique, non ?

    06° Répondre aux deux questions en analysant la ligne 72.

    Quel est le texte qu'on veut voir s'afficher dans notre carré ?

    Quelle devrait être la couleur de fond de tous nos carrés ?

    ...CORRECTION...

    On veut afficher le numéro de la case.

    Toutes les cases devraient être grises !

    On pourrait penser que le widget s'affiche alors automatiquement. Mais non. Il est juste créé. Il faut maintenant demander à l'interface de l'afficher en lui donnant les coordonnées que nous avions calculées en pixels et stockées dans px et py.

    77 78
    # Affichage et placement du widget w_case.place(x=px, y=py)

    Comme nous l'avons vu, les carrés devraient être gris. Or, ils ne sont pas tous gris. Pourquoi ?

    80 81 82 83 84
    # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000')
    • Ligne 81 : on teste si notre case se situe dans la colonne 1
    • Ligne 82 : si c'est le cas, on utilise la méthode configure qui modifie le réglage d'un paramètre après création. Ici, fond coloré (bg) en rouge vif (red).
    • Ligne 83 : sinon, on teste sur notre case se situe sur la ligne 2
    • Ligne 84 : si c'est le cas, on utilise la méthode configure pour modifier le fond coloré en rouge foncé.

    07° Modifier le script pour afficher le texte 'C1' dans les carrés de la colonne 1.

    ...CORRECTION...

    Il suffit de placer ceci au bon endroit :

    w_case.configure(text='C1')

    08° Pourquoi la case de colonne 1 et de ligne 2 a-t-elle la couleur imposée par sa colonne alors que le test de la ligne se situe après ?

    80 81 82 83 84
    # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000')

    ...CORRECTION...

    Justement parce que le test de la ligne se fait après celui de la colonne.

    Il faut vous souvenir que dès qu'on valide l'un des blocs d'une séquence if-elif-else, on sort de la structure sans tester les blocs suivants. Ici, si la colonne vaut bien 1, il effectue les modifications et on part ensuite directement en lignes 77/78.

    Dernière étape importante si on veut pouvoir agir par la suite sur nos carrés colorés : on mémorise leurs références.

    86 87
    # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case

    09° Comment se fait-il qu'on parvienne à modifier la matrice globale refs_ihm alors qu'on est à l'intérieur d'une fonction et qu'on agit sur une variable nommée refs ?

    ...CORRECTION...

    Cet effet se nomme l'effet de bord.

    Lors de l'appel de la fonction, on a transmis l'argument refs_ihm qu'on stocke dans le paramètre refs. Les deux variables font donc référence à la même zone mémoire.

    Ensuite, on ne réalise pas d'affectation sur la matrice (on ne note pas refs = quelque chose) : on modifier le contenu d'un des index. Et comme le tableau est mutable, c'est permis.

    Agir sur le contenu de refs revient donc à agir sur le contenu de refs_ihm.

    En conclusion, voici la documentation complète qu'il aurait fallu fournir pour cette fonction :

    52 53 54 55 56 57 58 59 60
    def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python .. Effet de bord :: Modifie refs '''
    Faciliter la lecture du code

    Les variables globales n'ont pas vocation à être directement lues ou modifiées depuis une fonction si on peut faire autrement.

    Les transmettre en arguments et les placer dans des paramètres est la meilleur possibilité. Par contre, changer le nom des paramètres à chaque fois est ennuyeux pour la compréhension du code.

    Vous remarquerez que j'ai fait le choix que garder constamment les mêmes noms pour les paramètres contenant des références vers des variables globales.

    1. fenetre pour ihm
    2. codes pour codes_data
    3. refs pour refs_ihm

    On pourrait leur donner exactement le même nom, mais dans ce cas, on risque de ne plus rien comprendre si on a moment on ne transfère par la variable globale mais un contenu alternatif par exemple.

    De la même façon, remarquez bien qu'à chaque fois que je pars chercher l'une des références de widgets contenues dans la matrice refs_ihm, je la place dans une variable nommée w_case.

    Rien n'oblige à faire cela, si ce n'est rendre le code plus facile à lire.

    3 - Compartimenter données et interface

    Avez-vous remarqué que pour l'instant l'affichage n'a aucun rapport avec ce qu'on a placé dans la matrice codes_data ?

    Dans ce genre d'application graphique, il faut toujours veiller à séparer :

    1. codes : les parties du programme servant à gérer, calculer et stocker des informations sur l'état du jeu (ici à droite)
    2. IHM : les parties liées purement à l'interface graphique (ici au milieu)

    Pourquoi ?

    Pour faciliter la maintenance et la conversion du système si on décide de changer de type d'interface.

    Comme vous pouvez le voir

    • les variables et fonctions qui sont propres à l'interface (IHM)
      • refs_ihm, ihm, creer_cases_ihm, configurer_fenetre_ihm
      • creer_matrice_ihm : appartient à la partie IHM mais travaille à partir de données fournies par la partie codes
    • les variables et fonctions qui sont propres aux données
      • codes_data, infos_data

    Pour créer une fonction affichant le contenu de la matrice codes_data, il faut transmettre en tant qu'argument de fonction cette variable codes_data.

    Nommons cette fonction actualiser_ihm.

    10° Prototyper cette fonction : quels paramètres faut-il lui transmettre pour qu'elle puisse modifier les couleurs (ou le texte) des cases affichées en fonction des données stockées sous forme de matrice ?

    ...CORRECTION...

    On a besoin de lui transmettre au minimum deux arguments : la matrice des données (codes_data dans le programme principal) et la matrice des références des widgets (refs_ihm dans le programme principal).

    Pour garder une cohérence avec les autres fonctions, je décide de nommer les paramètres qui vont les recevoir codes et refs.

    def actualiser_ihm(codes, refs):

    11° Fournir maintenant la documentation de cette fonction, pas son code. Vous en indiquerez le type des paramètres et leurs preconditions sur ces paramètres.

    ...CORRECTION...

    1 2 3 4 5 6 7 8
    def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python '''

    12° Rajouter l'appel à cette fonction dans la partie C, juste après avoir créé les widgets : on affichera une première fois notre matrice.

    * * * *
    # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran

    13° Fournir le code permettant de transformer :

    • Une valeur 0 en case blanche
    • Une valeur 1 en case noire
    • Une valeur 3 en case bleu clair avec un A dessus, comme Aventurier.
    • Une valeur 8 en case rouge avec un D dessus, comme Dragon.
    • Résultat attendu de ce type :

    ...CORRECTION...

    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
    def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg='white') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg='black') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 3 : w_case.configure(bg='#8888FF') w_case.configure(text='A') elif codes[ligne][colonne] == 8 : w_case.configure(bg='#FF5555') w_case.configure(text='D')

    14° Zut, c'est l'inverse. Le 8 pour l'Aventurier et le 3 pour le Dragon. Modifier. Avez-vous dû modifier uniquement la partie IHM ou également la partie Données ?

    Résultat attendu :

    ...CORRECTION...

    Puisqu'il n'y a que l'affichage à modifier : on ne touche pas aux données. Il suffit d'intervertir le 8 et le 3 dans les tests de la fonction.

    Vous savez maintenant créer une interface (vous avez au moins un exemple)

    Et vous savez maintenant comment afficher les données sur l'IHM de façon à informer l'utilisateur. En gros, vous savez aller de la droite vers la gauche :

    Prochaine partie : aller de la gauche vers la droite : récupérer les choix de l'utilisateur et les transmettre à l'IHM. Et faire transmettre ces choix à la partie Gestion de données par l'IHM.

    4 - Evénements

    Tout d'abord : qu'est-ce qu'un événement pour l'interface ? Il s'agit d'une modification de l'environnement extérieur : une touche de clavier sur laquelle on appuie, le déplacement de la souris, l'appui sur une touche de la souris...

    La traduction d'événement est event.

    Lorsqu'on constate un événement, encore faut-il le gérer.

    C'est le rôle du gestionnaire d'événement, qu'on traduit par event handler. C'est ce gestionnaire qui va donner l'ordre à la fonction désignée de s'activer si tel ou tel événement survient.

    Pour créer une liaison entre l'événement et la fonction à réaliser, il faut utiliser la méthode bind, appliquée au widget qu'on souhaite surveiller.

    Exemple  Clic-gauche sur le label

    Le clic-gauche est identifié par '<Button-1>'. Si veut associer ce clic sur un widget de référence ref_widget de façon à ce qu'il lance une fonction agir, il faut mettre ceci dans le code :

    ref_widget.bind('<Button-1>', agir)

    On notera qu'on ne place par les parenthèses de la fonction : on ne donne que son adresse interne. Sous cette forme, on ne peut donc pas fournir d'arguments.

    Le transfert d'arguments est possible mais à travers quelques notions que nous n'aborderons pas ici. Voir FAQ si vous voulez plus de détails.

    Liste d'événements

    Pas de blabla. Juste la description de l'événement et la façon de le gérer. Vous testerez pendant votre projet.

    Evénements liés à la souris

    1 2 3 4 5 6 7 8 9 10 11
    ref_widget.bind('<Button-1>', agir1) # Appui sur clic-gauche sur ce widget ref_widget.bind('<Button-2>', agir2) # Appui sur clic-molette sur ce widget ref_widget.bind('<Button-3>', agir3) # Appui sur clic-droit sur ce widget ref_widget.bind('<ButtonRelease-1>', agir4) # Relachement après clic-gauche ref_widget.bind('<ButtonRelease-2>', agir5) # Relachement après clic-molette ref_widget.bind('<ButtonRelease-3>', agir6) # Relachement après clic-droit ref_widget.bind('<Enter>', agir7) # Pointeur souris rentre sur ce widget ref_widget.bind('<Leave>', agir8) # Pointeur souris sort de ce widget ref_widget.bind('<Motion>', agir9) # Pointeur souris sort de ce widget

    Si vous voulez que la détection soit appliquée sur toute votre fenêtre d'application, il faut lier l'événement à votre fenêtre principale (ihm dans le code de l'activité présentée ici).

    Evénements liés aux touches du clavier

    On notera qu'il faut cette fois lié l'événement à un widget-fenêtre. C'est par exemple le widget ihm de notre activité.

    Voici pour les flèches.

    1 2 3 4 5 6 7 8 9
    ref_fenetre.bind('<KeyPress-Left>', agir1) # Appui sur la flèche Gauche ref_fenetre.bind('<KeyPress-Right>', agir2) # Appui sur la flèche Droit ref_fenetre.bind('<KeyPress-Up>', agir3) # Appui sur la flèche Haut ref_fenetre.bind('<KeyPress-Down>', agir4) # Appui sur la flèche Bas ref_fenetre.bind('<KeyRelease-Left>', agir5) # Relachement de la flèche Gauche ref_fenetre.bind('<KeyRelease-Left>', agir6) # Relachement de flèche Droit ref_fenetre.bind('<KeyRelease-Up>', agir7) # Relachement de flèche Haut ref_fenetre.bind('<KeyRelesase-Down>', agir8) # Relachement de la flèche Bas

    On peut aussi demander à agir si n'importe quelle touche est activée :

    1
    ref_fenetre.bind('<Any-KeyPress>', agir9) # Appui sur n'importe quelle touche

    De façon générale pour les autres touches du clavier, cette utilisation basique demande de faire attention à l'utilisation conjointe de majuscule ou pas :

    1 2 3 4 5 6 7 8 9
    ref_fenetre.bind('<KeyPress-a>', agir10) # Appui sur la touche A sans majuscule activée ref_fenetre.bind('<KeyPress-b>', agir11) # Appui sur la touche B sans majuscule activée ref_fenetre.bind('<KeyPress-c>', agir12) # Appui sur la touche C sans majuscule activée ref_fenetre.bind('<KeyPress-d>', agir13) # Appui sur la touche D sans majuscule activée ref_fenetre.bind('<KeyPress-A>', agir14) # Appui sur la touche A avec majuscule activée ref_fenetre.bind('<KeyPress-B>', agir15) # Appui sur la touche B avec majuscule activée ref_fenetre.bind('<KeyPress-C>', agir16) # Appui sur la touche C avec majuscule activée ref_fenetre.bind('<KeyPress-D>', agir17) # Appui sur la touche D avec majuscule activée

    Si vous voulez d'autres explications :

    Et pourquoi on ne place pas _ihm à la fin des noms des fonctions ?

    On peut : elles appartiennent clairement à la partie IHM .

    Par contre, ça va alourdir le nom, surtout que vous allez voir que je les fais toutes commencer par evt_ comme événementielle.

    Mais on pourrait les nommer evt_click_case_ihm. C'est un peu lourd à lire, mais c'est explicite.

    Passons à la pratique : nous allons rajouter une partie D à notre programme : après la création de widgets, il est temps de surveiller quelques événements sur ceux-ci.

    15° Utiliser le code ci-dessous qui découpe maintenant les fonctions en deux catégories :

    • Les fonction événementielles
    • Les autres fonctions de l'IHM

    Les rajouts sont surlignés.

    Les surveillances sont déclarées ici :

    • Ligne 150 : on lance une surveillance sur l'activation de la flèche gauche
    • Ligne 151 : on lance une surveillance sur l'activation de la flèche droite

    Tester l'interface pour vérifier que les événements sont bien gérés.

    Question : étudier les deux prototypes des fonctions événementielles, notamment les paramètres. Regarder les endroits où on a créé la surveillance des événements. Quelle est la particulartié des fonctions événementielles ?

    ...CORRECTION...

    Les deux fonctions possèdent un paramètre qu'on ne fournit pas. C'est Tkinter qui va automatiquement le remplir. Il se nomme ici event.

    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de gauche") fenetre.configure(bg='#440000') def evt_fd(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de droite") fenetre.configure(bg='#000044') # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000') # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg='white') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg='black') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg='#8888FF') w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg='#FF5555') w_case.configure(text='D') # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    Regardons d'un peu plus près comme cela fonctionne :

    Fonction événementielle

    Il s'agit d'une fonction normale si ce n'est que

    1. c'est le gestionnaire d'événements qui va la lancer lorsqu'il détecte que les conditions sont réunies.
    2. elle doit avoir un paramètre permettant d'accueillir un objet contenant des informations sur l'événement.
    149 150 151
    # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite

    Ligne 150 : on demande au gestionnaire d'événements de lier la fonction événementielle evt_fg à l'appui sur la touche Flèche Gauche lorque la fenêtre graphique ihm a le focus.

    Ligne 151 : idem avec la fonction événementielle evt_fg et la touche Flèche Droite lorsque la fenêtre graphique ihm a le focus.

    Que font ces fonctions ?

    30 31 32 33 34 35 36 37 38
    def evt_fg(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de gauche") fenetre.configure(bg='#440000') def evt_fd(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de droite") fenetre.configure(bg='#000044')

    Rien d'original, à part le paramètre event qui est un objet qui regroupe tout un tas d'informations sur l'événement. event.widget renvoie la référence du widget qui a provoqué cet appel. Pratique pour savoir sur quoi agir avec configure par exemple.

    Ici, chacune des deux fonctions récupére la référence du widget et le place dans fenetre, affiche un petit message dans la console et modifie la couleur du fond de l'application puisque c'est la référence de ihm qui a été placée dans event.

    Quelques autres informations contenues dans cet objet event :

    • event.x : renvoie l'abscisse en pixels du pointeur de la souris sur le widget lors de l'activation de l'événement.
    • event.y : renvoie l'ordonnée en pixels du pointeur de la souris sur le widget lors de l'activation de l'événement.
    • event.type : renvoie le type de l'événement (click, leave, keypress...).
    • ou d'autres choses encore (voir sur la documentation sur Effbot.org)

    16° Observer le code ci-dessous.

    Sur quelles lignes lance-t-on la surveillance sur les cases ?

    Compléter le code des fonctions 

    • evt_rentre_case pour qu'un survol sur une case change le fond en gris clair.
    • evt_sort_case pour qu'en sortant de la case sa couleur devienne gris foncé.
    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de gauche") fenetre.configure(bg='#440000') def evt_fd(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de droite") fenetre.configure(bg='#000044') def evt_rentre_case(event) : # A vous de travailler pass def evt_sort_case(event) : # A vous de travailler pass # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000') # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg='white') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg='black') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg='#8888FF') w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg='#FF5555') w_case.configure(text='D') # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite for ligne in range(len(refs_ihm)) : for colonne in range(len(refs_ihm[0])) : refs_ihm[ligne][colonne].bind("<Enter>", evt_rentre_case) refs_ihm[ligne][colonne].bind("<Leave>", evt_sort_case) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    ...CORRECTION...

    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de gauche") fenetre.configure(bg='#440000') def evt_fd(event) : fenetre = event.widget print(f"Vous venez d'appuyer sur la flèche de droite") fenetre.configure(bg='#000044') def evt_rentre_case(event) : w_case = event.widget w_case.configure(bg="#BBBBBB") def evt_sort_case(event) : w_case = event.widget w_case.configure(bg="#666666") # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # Modification du widget (pour montrer qu'on peut modifier après création) if colonne == 1 : w_case.configure(bg='red') elif ligne == 2 : w_case.configure(bg='#AA0000') # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg='white') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg='black') w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg='#8888FF') w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg='#FF5555') w_case.configure(text='D') # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite for ligne in range(len(refs_ihm)) : for colonne in range(len(refs_ihm[0])) : refs_ihm[ligne][colonne].bind("<Enter>", evt_rentre_case) refs_ihm[ligne][colonne].bind("<Leave>", evt_sort_case) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    Par contre, ce qui serait vraiment bien, c'est de pouvoir juste éclaircir la case lorsqu'on passe dessus. Pour cela, il suffirait de lire la couleur de la case actuelle. On va alors éclaircir la case, juste le temps du survol. On ne modifie pas la valeur contenue dans les données et encodant vide, monstre, couloir ou aventurier. C'est juste de l'affichage. Du coup, on peut tout gérer de l'IHM sans passer par les données.

    Méthode cget

    C'est une méthode propre aux widgets de Tkinter. Elle n'est donc pas à connaître. Elle permet de récupérer l'une des valeurs graphiques d'un widget : couleur du fond, couleur du texte, texte...

    Un exemple : on récupère la couleur du fond du widget dont la référence est stockée dans w_get.

    1
    couleur = w_case.cget('bg')

    Du coup, on peut l'utiliser pour modifier la couleur d'une des cases lorsqu'on la survole :

    • On modifie la couleur lorsqu'on entre sur le widget avec la souris
    • 1 2 3 4 5 6 7 8 9 10
      def evt_rentre_case(event) : w_case = event.widget if w_case.cget('bg') == 'white' : # Couleur du couloir w_case.configure(bg="#DDDDDD") elif w_case.cget('bg') == 'black' : # Couleur du mur w_case.configure(bg="#222222") elif w_case.cget('bg') == '#8888FF' : # Couleur bleutée / aventurier w_case.configure(bg='#AAAAFF') elif w_case.cget('bg') == '#FF5555' : # Couleur rougeatre / dragon w_case.configure(bg='#FF7777')
    • On replace la couleur de base lorsqu'on sort du widget
    1 2 3 4 5 6 7 8 9 10
    def evt_sort_case(event) : w_case = event.widget if w_case.cget('bg') == '#DDDDDD' : # Couleur du couloir w_case.configure(bg="white") elif w_case.cget('bg') == '#222222' : # Couleur du mur w_case.configure(bg="black") elif w_case.cget('bg') == '#AAAAFF' : # Couleur bleutée / aventurier w_case.configure(bg='#8888FF') elif w_case.cget('bg') == '#FF7777' : # Couleur rougeatre / dragon w_case.configure(bg='#FF5555')

    17° Utiliser le code ci-dessous qui permet de gérer les survols. On remarquera qu'on doit utiliser strictement les mêmes couleurs à plusieurs endroits du code. Le mieux est alors de définir des variables CONSTANTES pour ces couleurs, des variables qui ne sont absolument pas destinées à être modifiées après création. On les différencie des autres variables en les notant en MAJUSCULES.

    25 26 27 28 29 30 31 32 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
    COULEUR_DRA = '#FF5555' COULEUR_DRA_SURVOL = '#FF7777' COULEUR_AVE = '#8888FF' COULEUR_AVE_SURVOL = '#AAAAFF' COULEUR_COU = 'white' COULEUR_COU_SURVOL = '#DDDDDD' COULEUR_MUR = 'black' COULEUR_MUR_SURVOL = '#222222' def evt_rentre_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU : # Couleur du couloir w_case.configure(bg=COULEUR_COU_SURVOL) elif w_case.cget('bg') == COULEUR_MUR : # Couleur du mur w_case.configure(bg=COULEUR_MUR_SURVOL) elif w_case.cget('bg') == COULEUR_AVE : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE_SURVOL) elif w_case.cget('bg') == COULEUR_DRA : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA_SURVOL) def evt_sort_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU_SURVOL : # Couleur du couloir w_case.configure(bg=COULEUR_COU) elif w_case.cget('bg') == COULEUR_MUR_SURVOL : # Couleur du mur w_case.configure(bg=COULEUR_MUR) elif w_case.cget('bg') == COULEUR_AVE_SURVOL : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE) elif w_case.cget('bg') == COULEUR_DRA_SURVOL : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA)

    Questions : ces constantes (qui permettent de définir les couleurs d'affichage) sont-elles des variables qu'on doit placer dans la partie IHM ou dans la partie codes ?

    ...CORRECTION...

    Au côté IHM puisque ce sont des valeurs qui n'interviennent que dans l'IHM. Imaginons qu'on décide de prendre la console comme IHM. Du texte pur. Plus de couleurs à gérer.

    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] COULEUR_DRA = '#FF5555' COULEUR_DRA_SURVOL = '#FF7777' COULEUR_AVE = '#8888FF' COULEUR_AVE_SURVOL = '#AAAAFF' COULEUR_COU = 'white' COULEUR_COU_SURVOL = '#DDDDDD' COULEUR_MUR = 'black' COULEUR_MUR_SURVOL = '#222222' # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget fenetre.configure(bg='#BBBBBB') def evt_fd(event) : fenetre = event.widget fenetre.configure(bg='#666666') def evt_rentre_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU : # Couleur du couloir w_case.configure(bg=COULEUR_COU_SURVOL) elif w_case.cget('bg') == COULEUR_MUR : # Couleur du mur w_case.configure(bg=COULEUR_MUR_SURVOL) elif w_case.cget('bg') == COULEUR_AVE : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE_SURVOL) elif w_case.cget('bg') == COULEUR_DRA : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA_SURVOL) def evt_sort_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU_SURVOL : # Couleur du couloir w_case.configure(bg=COULEUR_COU) elif w_case.cget('bg') == COULEUR_MUR_SURVOL : # Couleur du mur w_case.configure(bg=COULEUR_MUR) elif w_case.cget('bg') == COULEUR_AVE_SURVOL : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE) elif w_case.cget('bg') == COULEUR_DRA_SURVOL : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA) # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg=COULEUR_COU) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg=COULEUR_MUR) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg=COULEUR_AVE) w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg=COULEUR_DRA) w_case.configure(text='D') # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite for ligne in range(len(refs_ihm)) : for colonne in range(len(refs_ihm[0])) : refs_ihm[ligne][colonne].bind("<Enter>", evt_rentre_case) refs_ihm[ligne][colonne].bind("<Leave>", evt_sort_case) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    Et peut-on connaitre facilement la colonne et la ligne de notre widget ?

    Oui et c'est même plus simple car ce sont des attributs que nous avons créés nous-même (contrairement à bg, text...). Nous avions rajouté trois attributs : un pour la ligne, un autre pour la colonne et un troisième pour le numero.

    Avec ces attributs maison, il suffit de taper w_case.colonne pour récupérer la valeur de la colonne. Pas la peine de passer par cget.

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
    def evt_rentre_case(event) : w_case = event.widget colonne = w_case.colonne ligne = w_case.ligne numero = w_case.numero print(f"Vous venez de rentrer dans la case ligne {ligne} - colonne {colonne}") if w_case.cget('bg') == COULEUR_COU : # Couleur du couloir w_case.configure(bg=COULEUR_COU_SURVOL) elif w_case.cget('bg') == COULEUR_MUR : # Couleur du mur w_case.configure(bg=COULEUR_MUR_SURVOL) elif w_case.cget('bg') == COULEUR_AVE : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE_SURVOL) elif w_case.cget('bg') == COULEUR_DRA : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA_SURVOL)

    18° Modifier la fonction comme ci-dessus pour visualiser dans la console qu'on parvient bien à récupérer la ligne et la colonne lorsqu'on rentre sur une case avec la souris.

    19° Expliquer si on peut accéder au contenu de la case (0 pour couloir, 1 pour couloir...) directement depuis cette fonction en faisant ceci :

    1 2 3 4 5 6 7
    def evt_rentre_case(event) : w_case = event.widget colonne = w_case.colonne ligne = w_case.ligne numero = w_case.numero code = codes_data[ligne][colonne] # Lecture des données print(f"Le contenu de la case est codé par {code}")

    ...CORRECTION...

    On peut le faire sans problème puisqu'on va lire la variable globale codes_data, ce qui est possible avec Python.

    Il n'est pas recommandé de le faire, mais puisqu'on n'a pas trop le choix (on ne peut pas transmettre d'arguments facilement aux fonctions événementielles), vous ne connaissez que cette solution.

    Voir la partie FAQ si vous voulez savoir comment faire autrement, mais c'est hors programme.

    20° Expliquer si on a le droit de faire ceci dans le cadre général de Python ? Expliquer si on a le droit de faire ceci dans le cadre de notre projet de jeu ?

    1 2 3 4 5 6
    def evt_rentre_case(event) : w_case = event.widget colonne = w_case.colonne ligne = w_case.ligne numero = w_case.numero codes_data[ligne][colonne] = 0 # Modification des données

    ...CORRECTION...

    Techniquement oui puisqu'on récupère en lecture la référence de la matrice codes_data. Ensuite, on parvient à modifier le contenu de la matrice puisque les tableaux sont mutables en Python.

    Par contre, dans le cadre du jeu, c'est très mal : on modifie le contenu d'une variable de la partie codes à partir d'une fonction de la partie IHM.

    On perd donc toute la partie gestion précise de ce qu'est l'IHM et de ce qu'est la pure gestion des données.

    Bien. Vous avez maintenant vu comment gérer les événements : vous savez donc comment transmettre l'information de l'utilisateur vers l'IHM.

    Mais comment garder en mémoire (proprement) nos clics alors puisqu'on a pas le "droit" de modifer la matrice des données depuis la fonction événementielle ?

    Par exemple, comment réussir à déplacer l'aventurier ?

    5 - Mini projet décrit : IHM vers DONNEES

    Avant de commencer, réfléchissons à la manière de procéder. Vous allez avoir besoin d'appliquer cette méthode de réflexion pendant votre projet.

    Un bon moyen de faire un projet en équipe est de :

    1. En groupe : réflechir avec un texte écrit en français qui décrit le déroulé des actions. On y trouve le nom des fonctions et ce qu'elles doivent faire
    2. En groupe : créer le schéma du projet pour vérifier qu'il est bien conçu (la partie ihm n'agit que sur l'ihm et la partie données n'agit que sur les données)
    3. En groupe : Réaliser les prototypages
    4. Individuel : Travailler sur sa fonction, créer sa documentation et des tests si possible.

    Un exemple - Parvenir à déplacer l'aventurier :

    1. On crée une fonction-événement evt_clic_cases (à faire) liée aux clics sur les cases
    2. Dans cette fonction evt_clic_cases :
      1. on accepte d'aller lire directement certaines variables globales car on a pas le choix.
      2. on récupère la référence du widget, sa ligne et sa colonne
      3. on envoie la ligne et la colonne du clic à une fonction deplacement_aventurier_data (à faire) appartenant à la partie Données (et on la laisse gérer les modifications).
      4. si la fonction deplacement_aventurier_data répond par True, on met à jour l'affichage de l'interface avec la fonction actualiser_ihm (déja codée)
    3. Dans cette fonction deplacement_aventurier_data :
      1. on va voir dans la matrice des données s'il s'agit bien d'une case vide (codée par 0)
      2. si c'est le cas, on utilise une fonction position_aventurier_data (à faire) qui va chercher la position de l'aventurier en cherchant où se trouve le code 8 dans la matrice et renvoie les coordonnées sous forme d'un tuple
      3. on modifie le contenu des données depuis la partie Données (on a donc le droit) : on inverse le 8 et le 0
      4. on renvoit True pour dire qu'il y a eu modification, ou False sinon.
    Description du projet Aventurier

    Pour travailler en équipe, vous allez donc devoir avoir au moins le prototype des fonctions (et même la documentation avec une description précises des préconditions et postconditions si nous avions un peu plus de temps).

    Trois fonctions à réaliser, trois personnes qui peuvent travailler en même temps, pourvu qu'elles sachent ce que doit recevoir leurs fonctions et ce qu'elles doivent retourner.

    Voici le code avant réalisation des fonctions.

    ...ETAT INITIAL DU PROJET...

    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] COULEUR_DRA = '#FF5555' COULEUR_DRA_SURVOL = '#FF7777' COULEUR_AVE = '#8888FF' COULEUR_AVE_SURVOL = '#AAAAFF' COULEUR_COU = 'white' COULEUR_COU_SURVOL = '#DDDDDD' COULEUR_MUR = 'black' COULEUR_MUR_SURVOL = '#222222' # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget fenetre.configure(bg='#BBBBBB') def evt_fd(event) : fenetre = event.widget fenetre.configure(bg='#666666') def evt_rentre_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU : # Couleur du couloir w_case.configure(bg=COULEUR_COU_SURVOL) elif w_case.cget('bg') == COULEUR_MUR : # Couleur du mur w_case.configure(bg=COULEUR_MUR_SURVOL) elif w_case.cget('bg') == COULEUR_AVE : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE_SURVOL) elif w_case.cget('bg') == COULEUR_DRA : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA_SURVOL) def evt_sort_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU_SURVOL : # Couleur du couloir w_case.configure(bg=COULEUR_COU) elif w_case.cget('bg') == COULEUR_MUR_SURVOL : # Couleur du mur w_case.configure(bg=COULEUR_MUR) elif w_case.cget('bg') == COULEUR_AVE_SURVOL : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE) elif w_case.cget('bg') == COULEUR_DRA_SURVOL : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA) def evt_clic_case(event) : '''Récupère ligne et colonne du wdiget et lance l'appel à déplacement_aventurier''' pass # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg=COULEUR_COU) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg=COULEUR_MUR) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg=COULEUR_AVE) w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg=COULEUR_DRA) w_case.configure(text='D') # - - - - - - - - - - - - - - - - - - - - - - - - # 3 C - Fonctions qui agissent sur les données - # - - - - - - - - - - - - - - - - - - - - - - - - def deplacement_aventurier_data(ligne, colonne, codes) : '''Renvoie True si elle a placé l'aventurier dans une nouvelle case :: param ligne(int) :: ligne où on veut placer l'aventurier :: param colonne(col) :: colonne où on veut placer l'aventurier :: param codes(list(list)) :: matrice des données :: return (bool) :: True si l'aventurier bouge de case. ''' return True def position_aventurier_data(codes) : '''Renvoie un tuple (ligne,colonne) contenant la position actuelle de l'aventurier :: param codes(list(list)) :: matrice des données ne contenant qu'un seul 8 ! :: return (tuple) :: les coordonnées (ligne, colonne) de l'aventurier (code 8) ''' return (0,0) # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite for ligne in range(len(refs_ihm)) : for colonne in range(len(refs_ihm[0])) : refs_ihm[ligne][colonne].bind("<Enter>", evt_rentre_case) refs_ihm[ligne][colonne].bind("<Leave>", evt_sort_case) refs_ihm[ligne][colonne].bind("<Button-1>", evt_clic_case) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    21° Quel est l'intérêt de placer de fausses réponses dans les fonctions plutôt qu'un simple pass ?

    68 69 70 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
    def evt_clic_case(event) : '''Récupère ligne et colonne du wdiget et lance l'appel à déplacement_aventurier''' pass def deplacement_aventurier_data(ligne, colonne, codes) : '''Renvoie True si elle a placé l'aventurier dans une nouvelle case :: param ligne(int) :: ligne où on veut placer l'aventurier :: param colonne(col) :: colonne où on veut placer l'aventurier :: param codes(list(list)) :: matrice des données :: return (bool) :: True si l'aventurier bouge de case. ''' return True def position_aventurier_data(codes) : '''Renvoie un tuple (ligne,colonne) contenant la position actuelle de l'aventurier :: param codes(list(list)) :: matrice des données ne contenant qu'un seul 8 ! :: return (tuple) :: les coordonnées (ligne, colonne) de l'aventurier (code 8) ''' return (0,0)

    ...CORRECTION...

    Cela permet simplement de commencer à travailler même si on a besoin d'une réponse de l'une de ces fonctions. On sait que la réponse est fausse mais on n'a pas à entendre la version finale pour avancer dans une autre direction.

    Correction de la fonction deplacement_aventurier_data

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    def deplacement_aventurier_data(ligne, colonne, codes) : '''Renvoie True si elle a placé l'aventurier dans une nouvelle case :: param ligne(int) :: ligne où on veut placer l'aventurier :: param colonne(col) :: colonne où on veut placer l'aventurier :: param codes(list(list)) :: matrice des données :: return (bool) :: True si l'aventurier bouge de case. ''' if codes[ligne][colonne] == 0 : # on vérifie que la destination est un couloir (ligne_avant, colonne_avant) = position_aventurier_data(codes) codes[ligne][colonne] = 8 # on place l'aventurier dans la nouvelle case codes[ligne_avant][colonne_avant] = 0 # on place couloir dans l'ancienne case return True # True car le déplacement a eu lieu return False # pas la peine de mettre else car le cas précédent finit pas un return

    On notera que la personne qui a codé cette fonction ne sait pas si la fonction position_aventurier_data fonctionne ou comment elle fonctionne. Elle a juste besoin de savoir ce qu'on doit lui envoyer et ce qu'elle retourne. Au pire, la fonction position_aventurier_data renvoie (0,0) dans la version de base.

    22° Pourquoi cette fonction deplacement_aventurier_data ne retourne-t-elle pas systématiquement False alors qu'il s'agit de la dernière ligne du code ?

    ...CORRECTION...

    C'est noté dans les commentaires !

    Il est impératif de se souvenir qu'on sort immédiatement d'une fonction dès qu'on lui demande de renvoyer un résultat. Or, justement on trouve une demande de ce type sur la ligne juste au dessus.

    La seule possibilité pour atteindre la dernière ligne est donc que le test n'est pas répondu positivement.

    Correction de la fonction position_aventurier_data

    1 2 3 4 5 6 7 8 9 10 11 12 13
    def position_aventurier_data(codes) : '''Renvoie un tuple (ligne,colonne) contenant la position actuelle de l'aventurier :: param codes(list(list)) :: matrice valide des données ne contenant qu'un seul 8 ! :: return (tuple) :: les coordonnées (ligne, colonne) de l'aventurier (code 8) .. post condition : on renvoie toujours un tuple, au pire (None, None) ''' for ligne in range(len(codes)) : # on parcourt les lignes une à une for colonne in range(len(codes[0])) : # on parcout les colonnes une à une if codes[ligne][colonne] == 8 : return (ligne, colonne) return (None, None)

    23° Comment se nomme la structure de données utilisée pour fournir à la fois la ligne et la colonne ?

    ...CORRECTION...

    Un tuple.

    L'élément caractéristique du tuple est la virgule. Souvenez-vous qu'une fonction ne peut renvoyer qu'un seul élément. Ici, on place donc nos deux réponses dans une structure de données qui comporte deux éléments.

    Les parenthèses ne sont pas obligatoires mais permettent de beaucoup mieux comprendre ce qu'on fait.

    Et voici le code de la fonction événementielle qui permet d'envoyer justement la demande de l'utilisateur vers les deux fonctions de la partie gestion des données :

    1 2 3 4 5 6 7 8 9
    def evt_clic_case(event) : '''Récupère ligne et colonne du wdiget et lance l'appel à déplacement_aventurier''' w_case = event.widget ligne = w_case.ligne colonne = w_case.colonne codes = codes_data # Lecture d'une variable globale. Acceptable avec un événement. if deplacement_aventurier_data(ligne, colonne, codes) : # Si les données ont été modifiées refs = refs_ihm # Lecture d'une variable globale. Acceptable avec un événement. actualiser_ihm(codes, refs) # On actualise la vue

    24° Expliquer en quelques mots comment fonctionne cette fonction ?

    Pourquoi la personne qui a tapé ce code précise-t-elle qu'on lit ici une variable globale (plutôt que de transmettre cette variable via un paramètre) ?

    ...CORRECTION...

    C'est juste une traduction des phrases que nous avions noté en début de projet.

    Pour les variables globales, il faut se souvenir dans vos projets qu'il faut éviter de le lire directement depuis une fonction, même si c'est possible, nous sommes d'accord.

    Par contre, c'est compliqué de s'en passer depuis une fonction événementielle puisqu'on ne peut pas facilement lui transmettre d'arguments.

    Voici le code complet après réalisation des fonctions.

    ...ETAT FINAL DU PROJET...

    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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    # - - - - - - - - - - # 1 - Importations - # - - - - - - - - - - import tkinter as tk # - - - - - - - - - - - - - - - - - - - - - - - - # 2 - Les variables globales et les constantes - # - - - - - - - - - - - - - - - - - - - - - - - - # informations : infos_data = [-1] # Index 0 contiendra le joueur actif : -1 (aucun), 1 , 2 ... # codes_data : matrice encodant le contenu des cases, "0" par défault codes_data = [ [1, 0, 0, 1, 1, 0], [1, 0, 3, 1, 1, 0], [1, 0, 0, 0, 3, 0], [8, 0, 0, 0, 1, 1], [1, 0, 1, 0, 1, 0], [1, 0, 1, 0, 0, 0] ] COULEUR_DRA = '#FF5555' COULEUR_DRA_SURVOL = '#FF7777' COULEUR_AVE = '#8888FF' COULEUR_AVE_SURVOL = '#AAAAFF' COULEUR_COU = 'white' COULEUR_COU_SURVOL = '#DDDDDD' COULEUR_MUR = 'black' COULEUR_MUR_SURVOL = '#222222' # - - - - - - - - - - - - - - - - - # 3 A - Fonctions événementielles - # - - - - - - - - - - - - - - - - - def evt_fg(event) : fenetre = event.widget fenetre.configure(bg='#BBBBBB') def evt_fd(event) : fenetre = event.widget fenetre.configure(bg='#666666') def evt_rentre_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU : # Couleur du couloir w_case.configure(bg=COULEUR_COU_SURVOL) elif w_case.cget('bg') == COULEUR_MUR : # Couleur du mur w_case.configure(bg=COULEUR_MUR_SURVOL) elif w_case.cget('bg') == COULEUR_AVE : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE_SURVOL) elif w_case.cget('bg') == COULEUR_DRA : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA_SURVOL) def evt_sort_case(event) : w_case = event.widget if w_case.cget('bg') == COULEUR_COU_SURVOL : # Couleur du couloir w_case.configure(bg=COULEUR_COU) elif w_case.cget('bg') == COULEUR_MUR_SURVOL : # Couleur du mur w_case.configure(bg=COULEUR_MUR) elif w_case.cget('bg') == COULEUR_AVE_SURVOL : # Couleur bleutée / aventurier w_case.configure(bg=COULEUR_AVE) elif w_case.cget('bg') == COULEUR_DRA_SURVOL : # Couleur rougeatre / dragon w_case.configure(bg=COULEUR_DRA) def evt_clic_case(event) : '''Récupère ligne et colonne du wdiget et lance l'appel à déplacement_aventurier''' w_case = event.widget ligne = w_case.ligne colonne = w_case.colonne codes = codes_data # Lecture d'une variable globale. Acceptable avec un événement. if deplacement_aventurier_data(ligne, colonne, codes) : # Si les données ont été modifiées refs = refs_ihm # Lecture d'une variable globale. Acceptable avec un événement. actualiser_ihm(codes, refs) # On actualise la vue # - - - - - - - - - - - - - - - - - - - - - # 3 B - Fonctions qui agissent sur l'IHM - # - - - - - - - - - - - - - - - - - - - - - def configurer_fenetre_ihm(fenetre) : '''Procédure gérant les paramètres de la fenêtre reçue en paramètre :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: return (None) :: "procédure" Python ''' fenetre.geometry("1200x600") fenetre.title("Mon super jeu") fenetre.configure(bg="black") def creer_matrice_ihm(codes) : '''Renvoie une matrice ayant les mêmes dimensions que codes mais contenant None :: param codes(list(list)) :: la matrice dont on veut copier les dimensions :: return (list(list)) :: matrice ayant les dimensions d'entrée, contenant None ''' lignes = len(codes) colonnes = len(codes[0]) return [ [None for colonne in range(colonnes)] for ligne in range(lignes) ] def creer_cases_ihm(fenetre, refs): '''Procédure qui crée les cases graphiques et place leurs références dans la matrice fournie :: param fenetre(tkinter.Tk) :: la référence d'une fenêtre de classe Tk :: param refs(list(list)) :: la référence d'une matrice destinée à recevoir les réferences :: return (None) :: "procédure" Python :: Effet de bord :: modifie refs ''' lignes = len(refs) # Nombre de lignes dans la matrice fournie colonnes = len(refs[0]) # Nombre colonnes dans la matrice fournie for ligne in range(lignes) : for colonne in range(colonnes) : # Calcul du numéro et des positions à appliquer numero = colonne + ligne * colonnes px = 20 + colonne * 90 # position x en pixels py = 20 + ligne * 85 # position y en pixels # Création du widget (et rajout d'attributs utiles pour notre jeu) w_case = tk.Label(fenetre, text=numero, fg="white", bg='grey', width=10, height=5) w_case.numero = numero w_case.colonne = colonne w_case.ligne = ligne # Affichage et placement du widget w_case.place(x=px, y=py) # On mémorise la référence du widget dans la matrice refs[ligne][colonne] = w_case def actualiser_ihm(codes, refs) : '''Modifie les widgets pour refléter les données du plateau de jeu :: param codes (list(list)) :: matrice contenant les données des cases :: param refs (list(list)) :: matrice contenant les références des widgets :: return (None) :: "procédure" Python ''' lignes = len(codes) colonnes = len(codes[0]) for ligne in range(lignes) : for colonne in range(colonnes) : w_case = refs[ligne][colonne] # On stocke car on va l'utiliser plusieurs fois if codes[ligne][colonne] == 0 : w_case.configure(bg=COULEUR_COU) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 1 : w_case.configure(bg=COULEUR_MUR) w_case.configure(text=w_case.numero) elif codes[ligne][colonne] == 8 : w_case.configure(bg=COULEUR_AVE) w_case.configure(text='A') elif codes[ligne][colonne] == 3 : w_case.configure(bg=COULEUR_DRA) w_case.configure(text='D') # - - - - - - - - - - - - - - - - - - - - - - - - # 3 C - Fonctions qui agissent sur les données - # - - - - - - - - - - - - - - - - - - - - - - - - def deplacement_aventurier_data(ligne, colonne, codes) : '''Renvoie True si elle a placé l'aventurier dans une nouvelle case :: param ligne(int) :: ligne où on veut placer l'aventurier :: param colonne(col) :: colonne où on veut placer l'aventurier :: param codes(list(list)) :: matrice des données :: return (bool) :: True si l'aventurier bouge de case. ''' if codes[ligne][colonne] == 0 : # on vérifie que la destination est un couloir (ligne_avant, colonne_avant) = position_aventurier_data(codes) codes[ligne][colonne] = 8 # on place l'aventurier dans la nouvelle case codes[ligne_avant][colonne_avant] = 0 # on place couloir dans l'ancienne case return True # True car le déplacement a eu lieu return False # pas la peine de mettre else car le cas précédent finit pas un return def position_aventurier_data(codes) : '''Renvoie un tuple (ligne,colonne) contenant la position actuelle de l'aventurier :: param codes(list(list)) :: matrice valide des données ne contenant qu'un seul 8 ! :: return (tuple) :: les coordonnées (ligne, colonne) de l'aventurier (code 8) .. post condition : on renvoie toujours un tuple, au pire (None, None) ''' for ligne in range(len(codes)) : # on parcourt les lignes une à une for colonne in range(len(codes[0])) : # on parcout les colonnes une à une if codes[ligne][colonne] == 8 : return (ligne, colonne) return (None, None) # - - - - - - - - - - - - - # 4 - Programme principal - # - - - - - - - - - - - - - if __name__ == '__main__' : # A - Activation des tests sur les fonctions du module import doctest doctest.testmod() # B - Création d'une fenêtre graphique maitre qu'on nomme ihm ihm = tk.Tk() configurer_fenetre_ihm(ihm) # C - Création de widgets refs_ihm = creer_matrice_ihm(codes_data) # Création de la variable de stockage creer_cases_ihm(ihm, refs_ihm) # Création effective et stockage des widgets actualiser_ihm(codes_data, refs_ihm) # Représente les données stockées à l'écran # D - Création des surveillance par le gestionnaire d'événements ihm.bind("<KeyPress-Left>", evt_fg) # Lorsqu'on appuie sur la flèche Gauche ihm.bind("<KeyPress-Right>", evt_fd) # Lorsqu'on appuie sur la flèche Droite for ligne in range(len(refs_ihm)) : for colonne in range(len(refs_ihm[0])) : refs_ihm[ligne][colonne].bind("<Enter>", evt_rentre_case) refs_ihm[ligne][colonne].bind("<Leave>", evt_sort_case) refs_ihm[ligne][colonne].bind("<Button-1>", evt_clic_case) # E - Activation de la surveillance sur l'application graphique ihm.mainloop()

    25° Utiliser ce code final pour voir comment il fonctionne.

    Après cette activité très linéaire qui vous montre comment utiliser une bibliothèque logicielle que vous connaissez pas plus que cela, vous allez pouvoir vous regrouper par deux ou trois et choisir l'un des sujets proposés dans l'activité suivante.

    Comme il s'agit de votre premier projet, une grosse partie du travail est déjà avancé de façon à ce que vous puissiez vous concentrer sur l'essentiel.

    Si vous voulez plus d'informations, vous pouvez faire des recherches sur le Web.

    Vous trouverez également quelques fiches sur ce site, comme celle-ci par exemple.

    FICHE Tkinter

    Si vous voulez plus d'informations, vous pouvez faire des recherches sur le Web.

    Vous trouverez également quelques fiches sur ce site, comme celle-ci par exemple.

    FICHE Tkinter

    Activité publiée le 29 11 2019
    Dernière modification : 07 12 2019
    Auteur : ows. h.