27 - 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.

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

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...
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 vaut0
. - 2e tour de boucle : Case numéro 1 - ligne vaut
0
et colonne vaut1
. - 3e tour de boucle : Case numéro 2 - ligne vaut
0
et colonne vaut2
. - 4e tour de boucle : Case numéro 3 - ligne vaut
1
et colonne vaut0
. - 5e tour de boucle : Case numéro 4 - ligne vaut
1
et colonne vaut1
. - ...
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...
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 Labelfg
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.
fenetre
pourihm
codes
pourcodes_data
refs
pourrefs_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 :
- codes : les parties du programme servant à gérer, calculer et stocker des informations sur l'état du jeu (ici à droite)
- 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.
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 :
- Activité sur l'ancien site : Interface Tk - GESTION DES EVENEMENTS
- Directement sur Effbot.org : Vers Effbot.org : les événements
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
- c'est le gestionnaire d'événements qui va la lancer lorsqu'il détecte que les conditions sont réunies.
- 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
- On replace la couleur de base lorsqu'on sort du widget
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')
|
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.
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 :
- 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
- 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)
- En groupe : Réaliser les prototypages
- Individuel : Travailler sur sa fonction, créer sa documentation et des tests si possible.
Un exemple - Parvenir à déplacer l'aventurier :
- On crée une fonction-événement evt_clic_cases (à faire) liée aux clics sur les cases
- Dans cette fonction evt_clic_cases :
- on accepte d'aller lire directement certaines variables globales car on a pas le choix.
- on récupère la référence du widget, sa ligne et sa colonne
- 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).
- 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)
- Dans cette fonction deplacement_aventurier_data :
- on va voir dans la matrice des données s'il s'agit bien d'une case vide (codée par 0)
- 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
- on modifie le contenu des données depuis la partie Données (on a donc le droit) : on inverse le 8 et le 0
- on renvoit True pour dire qu'il y a eu modification, ou False sinon.

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.
Activité publiée le 29 11 2019
Dernière modification : 13 12 2020
Auteur : ows. h.