Ressources⚓︎
Tutoriels divers sur Tkinter
Deux tutoriels de présentation du module d'interface graphique Tkinter :
Sitographie de sites ressources sur Tkinter :
Introduction⚓︎
Ancienne version⚓︎
À partir de l'année scolaire 2026/2027, l'ancienne version de l'introduction aux projets Tkinter est remplacé par un nouveau TP. Cependant, l'ancienne version reste intéressante en complément.
Introduction ancienne version
Téléchargement des scripts à compléter des exercices 1 à 4
Exercice 1
-
Télécharger le script hello_world.py et l'exécuter dans l'IDE Spyder.
code source
🐍 Script Pythonfrom tkinter import * # import du module fen = Tk() # création de la fenêtre racine msg = Label(fen, text="Hello World") # création d'un widget Label dans la fenêtre racine msg.pack() # positionnement géométrique du widget avec la méthode `pack` fen.mainloop() # boucle infinie avec réceptionnaire d'événementsÀ retenir
- tkinter est un module de la bibliothèque standard qui permet de réaliser des interfaces graphiques.
- Chaque objet graphique, appelé widget, est créé avec un constructeur, par exemple Label dont le premier argument est l'objet parent dans lequel il est placé.
- Le widget créé avec le constructeur
Tkest l'ancêtre de tous les autres, il n'a pas de parent, c'est la fenêtre racine. De plus il n'est pas nécessaire de positionner géométriquement cet objet. - Pour que le widget construit s'affiche il faut deux instructions supplémentaires :
- le positionnement géométrique du widget dans son parent : dans l'exemple ci-dessus avec la méthode
pack. On utilise la notation pointéeobjet.methode() - en dernière instruction du programme, il faut lancer la méthode
mainloopsur la fenêtre racine : c'est une boucle infinie avec un réceptionnaire d'événements qui va capturer tous les événements (appui sur touche, mouvement de la souris ...). En effet une interface graphique fonctionne avec une programmation par événements.
- le positionnement géométrique du widget dans son parent : dans l'exemple ci-dessus avec la méthode
-
Remplacer
msg = Label(fen, text="Hello World")parmsg = Label(fen, text="Hello World", font=("Arial", 20)). Exécuter, observer. -
La fenêtre est-elle redimensionnable ? Insérer les instructions suivantes, juste après la définition de la fenêtre racine. Exécuter, observer.
🐍 Script Pythonfen.geometry("300x50") fen.resizable(width=False, height=False)
Exercice 2
-
Télécharger le script controle_barre.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * # import du module tkinter ### Constantes (valeurs non modifiables à l'exécution) LARG_BARRE = 60 # largeur de la barre en pixels HAUT_BARRE = 20 # hauteur de la barre en pixels DX = 10 # déplacement horizontal en pixels pour un appui sur touche DY = 10 # déplacement vertical en pixels pour un appui sur touche HAUT_FENETRE = 400 LARG_FENETRE = 400 ### Variables globales (valeurs modifiables à l'exécution) x_barre = 160 # abscisse de la barre y_barre = 180 # ordonnée de la barre ### Modèle def deplacement(event): """Fonction gestionnaire de l'événement appui sur une touche""" global x_barre, y_barre # variables globales if event.keysym == 'Up': y_barre = y_barre - DY elif event.keysym == 'Down': y_barre = y_barre + DY # à compléter avec les déplacements droite / gauche # on modifie les coordonnées de la barre dans le widget canevas can.coords(barre, x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE) # on met à jour l'affichage de la fenêtre fen.update() ### Vue : interface graphique # Fenêtre racine fen = Tk() fen.title("Controle Barre") # Canevas d'affichage de la barre can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE) can.pack() # Barre rectangulaire dessinée dans le canevas barre = can.create_rectangle(x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE, fill="white") ### Controleur # Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement fen.bind("<KeyPress>", deplacement) ### Boucle infinie , réceptionnaire d'événement fen.mainloop()À retenir
Dans cet exemple, on organise le programme selon l'architecture Modèle - Vue - Contrôleur :

Source : Wikipedia
Partie Vue
C'est la mise en place de tous les widgets de l'interface graphique. Ici la fenêtre racine contient un widget Canvas qui est une zone de dessin. Dans ce canevas, on dessine un rectangle. Ce n'est pas un widget, c'est un item du canevas : la méthode de positionnement géométrique
packs'applique uniquement au canevas pour le positonner dans son parent la fenêtre racine.Partie Modèle
La fonction
deplacementva être appelée par un contrôleur pour mettre à jour un objet graphique, ici l'item barre dessiné dans le canevas. Elle prend en paramètre l'événementeventqui lui a été transmis et selon sa valeur (le symbole de la touche appuyée) modifie l'abscisse ou l'ordonnée avec la méthodecoordsdu canevas. Après avoir modifié l'objet graphique, il faut mettre à jour l'affichage de toute la fenêtre racine avecfen.update().deplacementest un gestionnaire d'événement.Repérage dans le canevas
Traditionnellement en informatique, l'origine du repère est en haut à gauche de la zone de dessin, l'axe des abscisses est orienté vers la droite et la 'axe des ordonnées vers le bas.

Partie Contrôleur
Le réceptionnaire d'événements de la boucle
mainloop()capture tous les événements (appui touche, mouvement de la souris). L'instructionfen.bind("<KeyPress>", deplacement)met en place un contrôleur qui va appeler l'exécution de la fonctiondeplacement(partie Modèle) si l'événement Appui sur une touche est capturé. -
Pourquoi faut-il retrancher
DYsi on veut déplacer verticalement la barre vers le haut ? - Dans le programme les noms des constantes sont en majuscules et les noms des variables en minuscules. Quelle est la différence entre constantes et variables ?
- Dans le programme on utilise deux variables globales
x_barreety_barre. Quelle est la différence avec la variable localevar_locale? A quoi sert le mot clefglobaldans une fonction ? - Modifier la fonction
deplacementpour qu'elle gère aussi les déplacements horizontaux, lorsqu'on appuie sur la flèche correspondante du pavé directionnel. -
Modifier la fonction
deplacementpour que la barre soit bloquée lorsqu'un de ses bords touche un bord du canevas. La barre ne doit pas pouvoir sortir du canevas. On pourra utiliser les fonctionsminetmax. Par exemple pour que la barre ne puisse pas sortir par le bas du canevas, on peut écrire :🐍 Script Pythony_barre = max(0, y_barre - DY)
Exercice 3
-
Télécharger le script clic_ballon.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * import random ### Constantes (valeurs non modifiables à l'execution) HAUT_FENETRE = 400 LARG_FENETRE = 400 ### Fonctions : boite à outils def from_rgb(r, g, b): """ Description : Renvoie le codage RGB d'une couleur en notation HTML au format #FF00AA à partir des composantes rouge r, vert v et bleue b Paramètre : r : entier entre 0 et 255 g : entier entre 0 et 255 b : entier entre 0 et 255 Renvoie : une chaien de caractères de type str """ # Préconditions sur les paramètres assert isinstance(r, int) and 0 <= r <= 255, "r doit être un entier entre 0 et 255" assert isinstance(g, int) and 0 <= g <= 255, "g doit être un entier entre 0 et 255" assert isinstance(b, int) and 0 <= g <= 255, "b doit être un entier entre 0 et 255" # valeur renvoyée return f'#{r:02x}{g:02x}{b:02x}' ### Modèle def ballon(event): """Fonction gestionnaire de l'événement clic sur bouton gauche de souris""" rouge = 255 vert = 0 bleu = 0 can.create_oval(event.x - 10, event.y - 10, event.x + 10, event.y + 10, fill=from_rgb(rouge, vert, bleu)) fen.update() ### Vue : interface graphique # Fenetre racine fen = Tk() fen.title("Clic ballon") # Canevas d'affichage de la barre can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE) can.pack() ### Controleur # Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement fen.bind("<ButtonPress-1>", ballon) ### Boucle infinie , réceptionnaire d'événement fen.mainloop() -
Quel est le type de la valeur renvoyée par la fonction
from_rgb? - Insérer
print(help(from_rgb))avantfen.mainloop(). Que s'affiche-t-il dans la console interactive ? - Dans la fonction
from_rgba quoi servent les instructionsassert? - Dans la partie Contrôleur, ajouter une instruction pour mettre en place un contrôleur qui appelle la fonction
ballonsi l'événement"<ButtonPress-1>"(clic sur le bouton gauche de la souris) est capturé. Exécuter de nouveau et décrire le fonctionnement de ce programme. - Qu'est-ce que le codage (Rouge, Vert, Bleu) d'une couleur ?
- Dans la console interactive afficher l'aide de la fonction
randintdu modulerandom(importé au début du programme) avechelp(random.randint). Modifier alors la fonctionballonpour que lorsqu'on clique, un ballon de couleur aléatoire soit créé. tkinter utilise-t-il la notation décimale pour représenter les valeurs des composantes (Rouge, Vert, Bleu) d'un pixel ?
Exercice 4
-
Télécharger le script mention.py et l'exécuter dans l'IDE Spyder.
Code source
🐍 Script Pythonfrom tkinter import * ### Modèle def calcul_mention(): """Calcul de la mention Modifie la variable de contrôle mention""" m = float(moyenne.get()) if 0<= m <=20: if m < 8: mention.set("Refusé") elif m < 10: mention.set("Passe second groupe") elif m < 12: mention.set("Passable") elif m < 14: mention.set("Mention Assez Bien") # à compléter avec tous les autres cas de mention else: mention.set("Valeur non conforme") ### Vue : interface graphique # Fenêtre racine fen = Tk() fen.title("Compte à rebours") fen.geometry("600x300") fen.resizable(width=False, height=False) # Variables de contrôles (variables globales) moyenne = StringVar() # variable de contrôle du widget saisie_moyenne mention = StringVar() # variable de contrôle du widget etiq_mention # Etiquette du champ de saisie etiq_moy = Label(fen, text="Moyenne : ", font=("Arial", 20)) etiq_moy.pack() # Champ de saisie de la moyenne saisie_moyenne = Entry(fen, textvariable=moyenne, font=("Arial", 20), fg='blue') saisie_moyenne.pack() # Bouton de commande bouton = Button(fen, text="Calcul mention", command=calcul_mention, font=("Arial", 20)) bouton.pack() # Etiquette d'affichage de la mention etiq_mention = Label(fen, textvariable=mention, font=("Arial", 20), fg='red') etiq_mention.pack() ### Boucle infinie , réceptionnaire d'événement fen.mainloop()À retenir
Dans le programme clic_ballon.py on utilise deux nouveaux types de widgets :
- Entry qui est un champe de saisie permettant de récupérer une valeur saisie par l'utilisateur.
- Button qui permet de lancer l'exécution d'une fonction lorsqu'on clique dessus. En fait il s'agit d'un contrôleur visible destiné à capturer le clic sur un emplacement précis de la fenêtre (le bouton en l'occurrence). C'est pourquoi on a un paramètre
commandqui prend pour valeur la fonction appelée lorsqu'on clique sur le bouton, c'est le même principe que pour une fonction appelée par un contrôleur d'événement comme dansfen.bind("<ButtonPress-1>", ballon).
Où est stockée la valeur de la saisie de l'utilisateur dans le widget Entry ?
-
Dans la variable de contrôle qui lui est passée en valeur du paramètre
textvariable. Ici on a :🐍 Script Pythonsaisie_moyenne = Entry(fen, textvariable=moyenne, font=("Arial", 20), fg='blue')La variable de contrôle est donc
moyenne. Elle doit avoir été définie avant par :🐍 Script Pythonmoyenne = StringVar()Notez qu'il s'agit d'un type de variable spécifique à tkinter. Les variables de contrôles sont des variables globales qui permettent de contrôler des valeurs liées à des widgets : par exemple la valeur du champ de saisie pour un widget Entry ou d'une étiquette pour un widget Label.
Lecture / écriture dans une variable de contrôle
La syntaxe est spécifique pour une variable de contrôle tkinter :
🐍 Script Pythonmoyenne = StringVar() # définition de la variable de contôle de type chaîne de caractère valeur = moyenne.get() # lecture moyenne.set("nouvelle valeur")On peut modifier une variable globale de contrôle dans une fonction sans utiliser le mot clef
global. -
On a une autre variable de contrôle
mentionqui contrôle la valeur affichée dans le widget Label. Dans la fonctioncalcul_mentionet la variablemoyenneest lue et la variablementionest modifiée.
-
Modifier la fonction
calcul_mention()pour qu'elle traite tous les cas possibles de mention au bac.Mentions du bac
Source : https://www.education.gouv.fr/reussir-au-lycee/le-baccalaureat-general-10457
Les mentions ne sont attribuées qu'aux candidats obtenant le baccalauréat à l'issue du premier groupe d'épreuves, en fonction de la moyenne obtenue :
- mention assez bien (AB) : pour une moyenne entre 12 et 14 ;
- mention bien (B) : pour une moyenne entre 14 et 16 ;
- mention très bien (TB) : pour une moyenne entre 16 et 18.
Depuis la session 2021, la mention très bien avec les félicitations du jury peut être accordée aux candidats présentant une moyenne supérieure à 18.
Correction des exercices 1 à 4
Il n'est pas possible d'exécuter le module tkinter dans Capytale. Pour exécuter tkinter dans un navigateur, vous pouvez utiliser la plateforme replit.com, il existe une version gratuite mais il faut se créer un compte. Sinon il faut installer une distribution Python sur son PC et dans ce cas Edupyter est un très bon choix car c'est une distribution portable que pouvez exécuter depuis une clef USB sur n'importe quel poste sous Windows.
Code source hello_world.py
from tkinter import *
fen = Tk()
msg = Label(fen, text="Hello World")
msg.pack()
fen.mainloop()
Code source controle_barre.py
from tkinter import *
### Constantes (valeurs non modifiables à l'exécution)
LARG_BARRE = 60 # largeur de la barre en pixels
HAUT_BARRE = 20 # hauteur de la barre en pixels
DX = 10 # déplacement horizontal en pixels pour un appui sur touche
DY = 10 # déplacement vertical en pixels pour un appui sur touche
HAUT_FENETRE = 400
LARG_FENETRE = 400
### Variables globales (valeurs modifiables à l'exécution)
x_barre = 160
y_barre = 180
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Controle Barre")
# Canevas d'affichage de la barre
can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE)
can.pack()
# Barre rectangulaire dessinée dans le canevas
barre = can.create_rectangle(x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE, fill="white")
### Modèle
def deplacement(event):
"""Fonction gestionnaire de l'événement
appui sur une touche"""
global x_barre, y_barre
if event.keysym == 'Up':
y_barre = max(0, y_barre - DY)
elif event.keysym == 'Down':
y_barre = min(y_barre + DY, HAUT_FENETRE - HAUT_BARRE)
elif event.keysym == 'Right':
x_barre = min(x_barre + DX, LARG_FENETRE - LARG_BARRE,)
elif event.keysym == 'Left':
x_barre = max(0, x_barre - DX)
can.coords(barre, x_barre, y_barre, x_barre + LARG_BARRE, y_barre + HAUT_BARRE)
fen.update()
### Controleur
# Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement
fen.bind("<KeyPress>", deplacement)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Code source clic_ballon.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
import random
### Constantes (valeurs non modifiables à l'exécution)
HAUT_FENETRE = 400
LARG_FENETRE = 400
### Fonctions :boite à outils
def from_rgb(r, g, b):
"""
Description :
Renvoie le codage RGB d'une couleur en notation HTML au format #FF00AA
à partir des composantes rouge r, vert v et bleue b
Paramètre :
r : entier entre 0 et 255
g : entier entre 0 et 255
b : entier entre 0 et 255
Renvoie :
une chaien de caractères de type str
"""
# Préconditions sur les paramètres
assert isinstance(r, int) and 0 <= r <= 255, "r doit être un entier entre 0 et 255"
assert isinstance(g, int) and 0 <= g <= 255, "g doit être un entier entre 0 et 255"
assert isinstance(b, int) and 0 <= g <= 255, "b doit être un entier entre 0 et 255"
# valeur renvoyée
return f'#{r:02x}{g:02x}{b:02x}'
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Clic ballon")
# Canevas d'affichage de la barre
can = Canvas(fen, background="#000000", width=LARG_FENETRE, height=HAUT_FENETRE)
can.pack()
### Modèle
def ballon(event):
"""Fonction gestionnaire de l'événement
clic sur bouton gauche de souris"""
rouge = random.randint(0, 255)
vert = random.randint(0, 255)
bleu = random.randint(0, 255)
can.create_oval(event.x - 10, event.y - 10, event.x + 10, event.y + 10, fill=from_rgb(rouge, vert, bleu))
fen.update()
### Controleur
# Si l'événement "Appui sur une touche" est intercepté, on appelle la fonction deplacement
fen.bind("<ButtonPress-1>", ballon)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Code source mention.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
### Modèle
def calcul_mention():
m = float(moyenne.get())
if 0 <= m <= 20:
if m < 8:
mention.set("Refusé")
elif m < 10:
mention.set("Passe second groupe")
elif m < 12:
mention.set("Passable")
elif m < 14:
mention.set("Mention Assez Bien")
elif m < 16:
mention.set("Mention Bien")
elif m < 18:
mention.set("Mention Très Bien")
else:
mention.set("Mention Très Bien avec félicitations du jury")
else:
mention.set("Valeur non conforme")
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Mention du bac")
fen.geometry("600x300")
# Variables de contrôle (variables globales)
moyenne = StringVar() # variable de contrôle du widget saisie_moyenne
mention = StringVar() # variable de contrôle du widget etiq_mention
# Etiquette du champ de saisie
etiq_moy = Label(fen, text="Moyenne : ", font=("Arial", 20))
etiq_moy.pack()
# Champ de saisie de la moyenne
saisie_moyenne = Entry(
fen, textvariable=moyenne, font=("Arial", 20), fg="blue"
)
saisie_moyenne.pack()
# Bouton de commande
bouton = Button(
fen, text="Calcul mention", command=calcul_mention, font=("Arial", 20)
)
bouton.pack()
# Etiquette d'affichage de la mention
etiq_mention = Label(fen, textvariable=mention, font=("Arial", 20), fg="red")
etiq_mention.pack()
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Nouvelle version⚓︎
À partir de l'année scolaire 2026/2027, on introduit les projets Tkinter avec le TP en lien ci-dessous.
TP de prise en main de Tkinter
Projets⚓︎
Exemple de gestion d'une grille
Le code ci-dessous permet de dessiner une grille de \(20 \times 30\) carrés de côté 20 pixels (fonds blanc et bord rouge) et de colorier en bleu un carré choisi aléatoirement chaque fois qu'on appuie sur le bouton.
Les carrés sont dessinés avec la méthode create_rectangle du canevas et sont stockés dans une liste de listes grille :
id_rec = grille[y][x]permet de récupérer l'identifiant du carré en ligneyet colonnexcan.itemcget(id_rec, "fill")permet de lire la couleur de ce carrécan.itemconfig(id_rec, fill="#0000ff")permet de changer en bleu la couleur de fonds du carré (on utilise le formar hexadécimal du codage RGB)
Résultat attendu

from tkinter import *
import random
def action_bouton():
y = random.randint(0, 19)
x = random.randint(0, 29)
id_rec = grille[y][x]
print(can.itemcget(id_rec, "fill"))
can.itemconfig(id_rec, fill="#0000ff")
# Définition de l'interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Crible aléatoire")
# Canevas
can = Canvas(fen, width=300, height=200)
can.pack() # positionnement
# dessin d'une grillee de 20 x 30 rectangles de coté 10 pixels stockés dans une liste de listes
# pour récupérer leurs identifiants
grille = [
[
can.create_rectangle(
x * 10,
y * 10,
(x + 1) * 10,
(y + 1) * 10,
outline="red",
fill="#ffffff",
)
for x in range(30)
]
for y in range(20)
]
# Bouton de contrôle
bouton = Button(fen, text="Crible !", command=action_bouton)
bouton.pack()
fen.mainloop()
💯 Grille d'évaluation sur 10 points
Tâches à réaliser :
- Réaliser un code répondant au cahier des charges.
- Présentation orale du projet de \(3 \times 4\) minutes maximum. Exemple de répartition de la parole pour un un binôme :
- Introduction avec présentation du cahier des charges (élève 1)
- Premier focus sur une partie du code (élève 1)
- Second focus sur une autre partie du code (élève 2)
- Conclusion : bilan sur les compétences acquises (élève 2)
| Domaine | Compétence | Points |
|---|---|---|
| Global, code | Exécution conforme et sans bugs du code | 1 |
| Global, code | Découpage en fonctions pertinent | 0,5 |
| Global, code | Choix pertinent des structures de données (types de base, liste, dictionnaire ...) | 0,5 |
| Global, code | Choix pertinent des structures de contrôle (boucles, tests ...) | 1 |
| Global, code | Lisibilité du code et commentaires | 1 |
| Global, oral | Répartition équilibrée et fluide de la parole dans le groupe | 1 |
| Individuel, oral | Prise de parole fluide, sans hésitations, sans notes | 1 |
| Individuel, oral | Positionnement de la voix et du corps | 1 |
| Individuel, oral | Précision du vocabulaire | 1 |
| Individuel, oral | Qualité des explications en tenant compte de la difficulté du contenu | 2 |
Projet 1 : color picker
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui propose trois échelles graduées de 0 à 255 pour choisir les composantes de Rouge, Vert et Bleu et affiche le codage en hexadécimal #RRVVBB de la couleur (R, V, B) et un canevas rectangulaire colorié avec cette couleur
Résultat attendu

- Scale
- Label
- Canvas
- Variables de contrôle
- Contrôleur pour l'événement
"<Motion>"de déplacement avec la souris :
fen.bind_all("<Motion>", changer_canevas) # fen étant la fenêtre racine
- Codage d'un entier positif en base 16 (hexadécimal) : voir page 7 du Cours sur la représentation des entiers
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
#%% Constantes
CHIFFRES_HEX = "0123456789ABCDEF"
#%% Modèle / Fonctions
def dec_to_hex(d):
"""Convertit un entier compris entre 0 et 255
en un nombre hexadécimal sur deux chiffres
Renvoie une chaîne de caractères
"""
# à compléter
def test_dec_to_hex():
assert dec_to_hex(4) == "04"
assert dec_to_hex(10) == "0A"
assert dec_to_hex(255) == "FF"
assert dec_to_hex(166) == "A6"
def changer_canevas(event):
# à compléter
# ne pas oublier de mettre à jour l'affichage de la fenêtre
fen.update()
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Color_picker")
fen.geometry("400x500")
# Echelle de graduation du rouge
rouge = IntVar()
graduation_rouge = Scale(
fen,
orient="horizontal",
from_=0,
to=255,
resolution=1,
tickinterval=50,
length=350,
variable=rouge,
label="Rouge",
)
graduation_rouge.pack()
# Echelle de graduation du vert
# à compléter
# Echelle de graduation du bleu
# à compléter
# Code HTML de la couleur
# à compléter
# Canevas d'affichage de la couleur
# à compléter
#%% Controleur
fen.bind_all("<Motion>", changer_canevas)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 2 : chiffrement de César
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui permet de saisir un mot source en lettre capitale, de sélectionner un décalage entre 0 et 25 et d'appuyer sur un bouton pour afficher le chiffrement du mot source avec l'algorithme de chiffrement par décalage de César.
![]()
Source : Wikipedia Le chiffre de César fonctionne par décalage des lettres de l'alphabet. Par exemple dans l'image ci-dessus, il y a une distance de 3 caractères, donc B devient E dans le texte codé.
Résultat attendu

-
Codage des caractères, voir le cours. Vous aurez surtout besoin des fonctions
chretord:🐍 Script Python>>> ord('A') 65 >>> chr(65) 'A' >>> chr(ord('A') + 3) 'D' >>> chr(ord('A') + (3 + 26) % 26) 'D' -
Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
#%% Modèle / Fonctions
def chiffrement():
"""
Fonction de rappel appelée par un appui sur le bouton bouton_chiffre
Returns
-------
Modifie le contenu de la variable de contrôle mot_chiffre
avec le chiffrement de la variable de contrôle mot_source
par décalage donné par la variable de contrôle decalage
"""
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Chiffrement de César")
fen.geometry("600x400")
# Cadre source
cadre_source = Frame(fen)
etiq_source = Label(cadre_source, text="Mot source : ", font=("Arial", 20))
mot_source = StringVar()
saisie_source = Entry(
cadre_source, textvariable=mot_source, font=("Arial", 20)
)
etiq_source.pack(side=LEFT)
saisie_source.pack(side=LEFT)
cadre_source.pack(anchor=W)
# Cadre décalage
cadre_decalage = Frame(fen)
decalage = IntVar()
# à compléter
# Cadre bouton
cadre_bouton = Frame(fen)
# à compléter
# Cadre chiffre
cadre_chiffre = Frame(fen)
mot_chiffre = StringVar()
# à compléter
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 3 : alternatif (jeu)
Niveau de difficulté
Facile, moins de 80 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter qui présente une grille carrée avec un des cases noires délimitées par des traits rouges. Lorsque le joueur clique sur une case il change sa couleur ainsi que celle des cases de son voisinage vertical ou horizontal. La couleur d'une case peut alterner entre noir et blanc. Le but du jeu est de colorier toute la grille en blanc.
Résultat attendu

- Canvas
- Rectangle dessiné dans un canevas
- Contrôleur pour l'événement
"<ButtonPress-1>"clic gauche de la souris :
fen.bind_all("<ButtonPress-1>", clic) # fen étant la fenêtre racine
- liste de listes pour créer une grille de rectangles
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
### Constantes
COTE_CASE = 100 # coté d'une case en pixels
NB_LIG = 4 # nombre de lignes
NB_COL = 4 # nombre de colonnes
#%% Modèle / Fonctions
def voisinage(x, y):
"""
Renvoie la liste des coordonnées des cases voisines
de la case de coordonnées (x, y)
Parameters
----------
x : abscisse de type int
y : ordonnée de type int
Returns
-------
lis : liste des coordonnées des cases voisines
"""
# à compléter
def clic(event):
"""
Gestionnaire de l'événement clic avec bouton gauche sur une case
"""
# à compléter
# ne pas oublier de mettre à jour la fenêtre
fen.update()
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("ALTERNATIF JEU")
# Canevas
# à compléter
# grille de rectangles dessinés dans le canevas
# à compléter
#%% Controleur
fen.bind_all("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 4 : calculateur d'adresses IP
Niveau de difficulté
Difficulté moyenne mais long, 200 lignes de codes voir plus.
Cahier des charges
Réaliser une interface graphique en tkinter qui présente :
- deux widgets d'entrée : un champ de saisie d'adresse IP en notation décimale pointée et une échelle de sélection de la longueur du masque réseau
- plusieurs widgets de sortie de type étiquettes avec l'affichage :
- de l'adresse du réseau
- de la première et de la dernière adresse de machine du réseau
- du nombre d'adresses de machines disponibles dans ce réseau
- de l'adresse de
broadcast(dernière adresse de la plage)
Modèle de l'outil : Calculateur de masque réseau du CRIC de Grenoble
Résultat attendu

- Codage en base deux et conversion d'une écriture en base dix sous la forme d'un entier à une écriture en base deux sous forme de liste de bits (0 ou 1). Voir le cours sur la représentation des entiers
- Format des adresses IPV4 : voir le Point de Cours 5 du Cours sur les Réseaux
- Listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
On propose plusieurs fonctions outils : pour passer de l'écriture décimale d'un entier en base dix à son écriture en base deux sous forme de liste de bits et vice-versa etc ...
from tkinter import *
#%% Modèle / Fonctions
def dec_to_bits(dec):
"""
Convertit en binaire un entier
Parameters
----------
dec :un entier entre 0 et 255
Returns
-------
Représentation binaire de l'entier sous forme de liste de 8 bits
"""
bits = [0 for i in range(8)]
# à compléter
return bits
def test_dec_to_bits():
assert dec_to_bits(15) == [0, 0, 0, 0, 1, 1, 1, 1]
assert dec_to_bits(132) == [1, 0, 0, 0, 0, 1, 0, 0]
def bits_to_dec(bits):
"""
Convertit une liste de bits en décimal
Parameters
----------
bits : liste de 8 bits
Returns
-------
entier entre 0 et 255
"""
# à compléter
def test_bits_to_dec():
assert bits_to_dec([0, 0, 0, 0, 1, 1, 1, 1]) == 15
assert bits_to_dec([1, 0, 0, 0, 0, 1, 0, 0]) == 132
def ip_to_bits(ip):
"""
Convertit une adresse ip sous forme de str en liste de 32 bits
Parameters
----------
ip : adresse ip sous forme de str comme '172.17.232.6'
Returns
-------
adresse ip sous forme de liste de 32 bits
"""
# à compléter
def test_ip_to_bits():
assert ip_to_bits("172.17.232.6") == [
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
]
def bits_to_ip(ip_bits):
"""
Convertit une adresse ip sous forme de str en liste de 32 bits
Parameters
----------
ip_bits : adresse ip sous forme de liste de 32 bits
Returns
-------
adresse ip sous forme de str comme '172.17.232.6'
"""
# à compléter
def test_bits_to_ip():
assert (
bits_to_ip(
[
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
]
)
== "172.17.232.6"
)
def verifier_ip(ip):
"""
Vérifie si une adresse ip sous forme de str comme '172.17.232.6'
est valide
Parameters
----------
ip : adresse ip sous forme de str comme '172.17.232.6'
Returns
-------
booléen
"""
# à compléter
def test_verifier_ip():
assert verifier_ip("172.17.232.6") == True
assert verifier_ip("172.17.256.6") == False
assert verifier_ip("172.17.256") == False
assert verifier_ip("172.17.256") == False
def prefixe_reseau(ip_bits, mask):
"""
Renvoie le préfixe réseau d'une adresse IP sous forme de liste de 32 bits
à partir de la longueur du masque réseau
Parameters
----------
ip_bits : liste de bits (0 ou 1)
masque : entier entre 1 et 31 bits
Returns
-------
Renvoie une liste de 32 bits
"""
prefixe = [0 for i in range(32)]
ip_masque = [1 for i in range(mask)] + [0 for j in range(32 - mask)]
# à compléter
def test_prefixe_reseau():
assert prefixe_reseau(
[
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
1,
1,
0,
],
21,
) == [
1,
0,
1,
0,
1,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
1,
1,
1,
1,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
]
def calcul_ip():
"""Fonction de rappel du bouton bouton_calcul
Récupère l'adresse IP de la variable de contrôle adresse_ip
Vérifie si cette adresse est valide avec verifier_ip
Si elle est valide, convertit l'adresse en liste de 32 bits,
puis calcule le préfixe réseau, la première adresse, la dernière adresse
et l'adresse de broadcast (dernière adresse de la plage)
Modifie pour cela les variables de contrôles liées aux étiquettes
d'affichage de ces différents éléments
"""
ip = adresse_ip.get()
if not verifier_ip(ip):
message_erreur.set("Adresse non conforme")
else:
message_erreur.set("")
ip_bits = ip_to_bits(ip)
adresse = prefixe_reseau(ip_bits, masque.get())
adresse_reseau.set("Adresse réseau : " + bits_to_ip(adresse))
adresse[31] = 1
premiere_adresse.set("Première adresse : " + bits_to_ip(adresse))
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Calculateur d'adresses IP")
fen.geometry("600x400")
### Variables de contrôles
adresse_ip = StringVar()
premiere_adresse = StringVar()
premiere_adresse.set("Première adresse : ")
adresse_reseau = StringVar()
adresse_reseau.set("Adresse réseau : ")
derniere_adresse = StringVar()
derniere_adresse.set("Dernière adresse : ")
adresse_broadcast = StringVar()
adresse_broadcast.set("Adresse broadcast : ")
message_erreur = StringVar()
nombre_adresses_ip = StringVar()
nombre_adresses_ip.set("Nombre d'adresses IP : ")
masque = IntVar()
# Cadre adresse
cadre_adresse = Frame(fen)
etiq_adresse = Label(cadre_adresse, text="Adresse IP : ", font=("Arial", 20))
saisie_adresse = Entry(
cadre_adresse, textvariable=adresse_ip, font=("Arial", 20)
)
etiq_adresse.pack(side=LEFT)
saisie_adresse.pack(side=LEFT)
cadre_adresse.pack()
# Cadre masque
cadre_masque = Frame(fen)
# à compléter
# Cadre calcul
cadre_calcul = Frame(fen)
# à compléter
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 5 : jeu de pendu
Niveau de difficulté
Difficile, 200 lignes de codes voir plus.
Cahier des charges
Réaliser une interface graphique en tkinter pour un jeu de pendu avec :
- un alphabet de 26 lettres cliquables, chaque lettre déjà sélectionnée s'affichant en rouge
- un bouton pour choisir un nouveau mot secret (dans le fichier dico_pendu.txt que vous téléchargerez)
- un affichage des lettres du mot secret sous la forme de rectangles qui font apparaître les lettres découvertes
- un champ de saisie du mot complet proposé par le joueur et en dessous un bouton pour valider sa saisie
- une étiquette d'affichage des messages : nombre d'esssais restants (10 au départ), résultat et révélation du mot en fin de partie
Résultat attendu

- Canvas
- Rectangle dessiné dans un canevas
- Texte dessiné dans un canevas
- Label
- Button
- Entry
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import random
#%% Constantes
# Récupération de la listes des mots
f = open("dico_pendu.txt")
LISTE_MOTS = [ligne.rstrip() for ligne in f]
f.close()
#%% Variables globales
mot_secret = (
"SECRET" # valeur initiale du mot secret au lancement de l'interface
)
nb_essais = 0 # nombre d'essais
jouer = False # un jeu est en cours
#%% Modèle / Fonctions
def clic(event):
"""Gestionnaire de clic sur une lettre de l'alphabet"""
global selection, nb_essais, nb_carac_restants, jouer
if jouer:
x = (
event.x
) // 50 # si on considère qu'on trace les lettres dans des carrés de coté 50 pixels (à voir)
y = (event.y) // 50
# à compléter
def choix_mot():
"""Fonction de rappel pour le bouton nouveau_mot"""
global mot_secret, lettre_mot, jouer, nb_carac_restants, nb_essais
jouer = True
can2.delete("all")
can1.delete("all")
generer_alphabet()
# à compléter
# on n'oublie pas de rafraichir la fenêtre
fen.update()
def valider_proposition():
"""Fonction de rappel pour le bouton bouton_proposition"""
global jouer, proposition_mot, nb_essais
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Pendu")
fen.geometry("1000x900")
# Consigne
consigne = Label(
fen,
text="Choisissez une lettre en cliquant dessus",
font=("Arial Bold", 30),
pady=10,
)
consigne.pack()
# Cadre pour l'alphabet
cadre_alphabet = Frame(fen)
can1 = Canvas(cadre_alphabet)
def generer_alphabet():
"""Dessine un nouvel alphabet dans le canevas can1"""
global alphabet, selection
# liste de listes des lettres de l'alphabet qui seront dessinées dans des rectangles
alphabet = [[None for x in range(7)] for y in range(4)]
# liste de listes de boolennes indiquant pour chaque lettre si elle a été sélectionnée
selection = [[False for x in range(7)] for y in range(4)]
# à compléter
generer_alphabet()
can1.pack()
cadre_alphabet.pack()
# Cadre pour le mot à deviner
cadre_mot = Frame(fen)
etiq_mot = Label(
cadre_mot, text="Mot à deviner", font=("Arial Bold", 30), pady=10
)
nouveau_mot = Button(
cadre_mot, text="Nouveau mot", font=("Arial Bold", 30), command=choix_mot
)
# les cases des lettres du mot à deviner seront dessinées dans le Canevas can2
# après appel de la fonction choix_mot
can2 = Canvas(cadre_mot, height=200)
# à compléter
# Etiquette du champ de saisie saisie_mot
# Champe d saisie saisie_mot
# Bouton bouton_proposition
# Essais
msg_essais = StringVar()
# à compléter
# Etiquette de message au joueur
#%%Controleurs
can1.bind("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 6 : jeu de la vie, automate cellulaire en deux dimensions
Niveau de difficulté
Moyennement difficile, entre 150 et 200 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter pour un jeu de la vie, automate cellulaire en deux dimensions.
- une grille de cases blanches délimitées pas un bord rouge : chaque case représente une cellule, morte si blanche et vivante si noire
- la possibilité d'initialiser une configuration en cliquant sur chaque case pour changer sa couleur de blanc à noir ou vice versa
- un bouton pour lancer le jeu et l'affichage des générations successives par application des trois règles ci-dessous du jeu de la vie. Lorsque le jeu est lancé il n'est plus possible de changer la couleur des cases.
- un bouton pour arrêter le jeu en cours
Jeu de la vie
Le jeu de la vie inventé par le mathématicien anglais J. H. Conway en 1970, est un automate cellulaire en deux dimensions.
Les automates cellulaires et le jeu de la vie en particulier sont des systèmes complexes étudiés en mathématiques et en informatique théorique. Ainsi, l'automate de Conway a été prouvé turing-complet c'est-à-dire qu'il peut exécuter les mêmes algorithmes qu'un ordinateur.
L'univers du jeu est une grille infinie de cases appelées cellules, en pratique on utilise une grille finie dont les bords se rejoignent (haut <-> bas et gauche <-> droite) : il suffit d'effectuer les calculs de voisinage modulo le nombre de lignes ou de colonnes.
Au départ un nombre fini de cellules sont vivantes et toutes les autres sont mortes.
Chaque cellule possède un voisinage de 8 cellules voisines (sauf les cellules sur les bords dans notre simulation).

A chaque génération, l'univers évolue selon trois règles simples de changement d'état pour chaque cellule :
- Règle 1 : Une cellule vivante qui n'a pas au moins 2 voisines vivantes meurt par isolement.
- Règle 2 : Une cellule vivante qui possède 4 voisines vivantes ou plus meurt par étouffement.
- Règle 3 : Une cellule morte qui possède exactement trois cellules vivantes, devient vivante, sinon elle reste morte.
Résultat attendu
La configuration iniitiale est celle d'un planeur.

- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes de listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import time
# CONSTANTES
COTE_CASE = 15
NB_LIG = 50
NB_COL = 50
VIVANTE = "#000000"
MORTE = "#ffffff"
VOISINAGE = [
(-1, 0),
(-1, -1),
(-1, 1),
(0, -1),
(0, 1),
(1, -1),
(1, 0),
(1, 1),
]
# Variables globales
jouer = False
generation = 0
## Modèle / Foctions
def clic(event):
"""Gestionnaire de clic avec le bouton gauche de la souris
sur une case"""
if not jouer:
x = event.x // COTE_CASE
y = event.y // COTE_CASE
# à compléter
# on n'oublie pas de mettre à jour la fenêtre
fen.update()
def nb_voisins_vivants(x, y):
"""
Renvoie le nombe de voisins vivants de la case
de coordonnées (x, y)
"""
# à compléter
def vie():
"""
Permet de changer la variable globale jouer
avant de lancer le jeu de la vie
"""
global jouer
jouer = True
jeu_vie()
def jeu_vie():
"""
Lance un jeu de la vie à partir de la configuration de la grille
"""
global generation
while jouer:
generation = generation + 1
# à compléter
# on n'oublie pas de mettre à jour la fenêtre
fen.update()
# on attend 0.1 secondes entre chaque génération
time.sleep(0.1)
def mort():
global jouer
jouer = False
### Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("JEU DE LA VIE")
# Canevas de dessin de la grille
can = Canvas(fen, width=COTE_CASE * NB_COL, height=COTE_CASE * NB_LIG)
can.pack()
# à compléter
### Controleur
can.bind("<ButtonPress-1>", clic)
### Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 7 : automate cellulaire à une dimension
Niveau de difficulté
Difficile, entre 150 et 200 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter pour un automate cellulaire à une dimension.
- une grille de cases blanches délimitées pas un bord rouge : chaque case représente une cellule, morte si blanche et vivante si noire
- la possibilité d'initialiser une configuration en cliquant sur chaque case de la première ligne pour changer sa couleur de blanc à noir ou vice versa
- un champ de saisie du numéro de la règle
- un bouton pour lancer le jeu et l'affichage des générations successives sur les lignes suivantes
- un bouton pour réinitialiser le jeu en cours : il efface toutes les cases noires
Automate cellulaire à une dimension
Prenons la règle numéro 90. On commence par écrire 90 en base deux sur 8 bits
| Poids \(2^{7}\) | Poids \(2^{6}\) | Poids \(2^{5}\) | Poids \(2^{4}\) | Poids \(2^{3}\) | Poids \(2^{2}\) | Poids \(2^{1}\) | Poids \(2^{0}\) |
|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
Ensuite, considérons une case de la première ligne. Elle peut être vivante (codage 1) ou morte (codage 0) et de même pour sa voisine de gauche (on repart de l'autre bout avec un modulo pour la case la plus à gauche) ou de droite (on repart au début pour la case la plus à droite). Il existe donc \(2^{3} = 8\) triplets de 0 ou 1 représentant tous les états possibles de ce voisinage de trois cases (gauche, case considérée, droite). Par exemple si (gauche, case considérée, droite) = (1 ,1 0), vu comme une écriture en base deux cela donne \(1 \times 2^{2}+1 \times 2^{1}+ 0 \times 2^{0}= 6\). Le coefficient du poids de \(2^{6}\) dans l'écriture binaire du numéro de règle \(90\) est \(1\). Ce sera l'état de la cellule en dessous de la case considérée. On procède de même pour chaque valeur du triplet (gauche, case considérée, droite) : on le regarde comme une écriture binaire d'un entier \(k\) entre 0 et 7, on lit la valeur du coefficient de \(2^{k}\) dans l'écriture binaire du numéro de la règle et on obtient l'état de la case du dessous. Tout repose sur le codage binaire !
Résultat attendu
Exemple des premières générations pour la règle numéro 90.

- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variables de contrôle
- Frame pour créer des cadres permettant de regrouper plusieurs widgets et de mieux organiser l'interface.
- Listes de listes et chaînes de caractères
- Fonctions, variables, tests, boucles, fondamentaux sur tkinter vus dans les quatre exercices d'introduction.
from tkinter import *
import time
#%% Constantes et variables globales
# CONSTANTES
COTE_CASE = 20
NB_LIG = 31
NB_COL = 31
VIVANTE = "#000000"
MORTE = "#ffffff"
# Variables globales
action = False
#%% Modèle / Fonctions
def dec_to_bits(n):
"""
Convertit en binaire un entier
Parameters
----------
dec :un entier entre 0 et 255
Returns
-------
Représentation binaire de l'entier sous forme de liste de 8 bits
"""
# précondition
assert isinstance(n, int) and 0 <= n < 256
t = [0 for _ in range(8)]
# à compléter
return t
def test_dec_to_bits():
assert dec_to_bits(15) == [0, 0, 0, 0, 1, 1, 1, 1]
assert dec_to_bits(132) == [1, 0, 0, 0, 0, 1, 0, 0]
def generer_table(regle):
"""
Renvoie la table de correspondance pour un numéro de règle entre 0
et 255. C'est la liste des bits de l'écriture binaire du numéro
"""
return dec_to_bits(regle)
def cellule_dessous(gauche, milieu, droite, table):
"""
Renvoie l'état 1 pour vivant ou 0 pour mort de la cellule en dessous
de la cellule du milieu en fonction des états (1 ou 0) de la cellule
du milieu, de sa voisine de gauche et de celle de droite
"""
# à compléter
#%% Modèle
def clic(event):
"""Gestionnaire de clic avec le bouton gauche sur une case
de la première ligne (lesautres cases ne peuvent être sélectionnées)"""
# à compléter
def vie():
"""Met à jour la variable globale action avant de lancer l'automate"""
global action
action = True
automate()
def automate():
"""
Lance l'automate et génère les lignes suivantes
à la vitesse de 1 ligne par seconde
"""
# à compléter
def reset():
"""Met à jour la variable globale action avant de lancer l'automate"""
global action
action = False
can.delete(ALL)
# à compléter : il faut redessiner toutes les cases blanches à bords rouges
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("AUTOMATE CELLULAIRE")
# Canevas
can = Canvas(fen, width=COTE_CASE * NB_COL, height=COTE_CASE * NB_LIG)
can.pack()
# à compléter
# Cadre de commande
cadre = Frame(fen)
cadre.pack()
# Bouton de lancement de l'automate
# à compléter
# Bouton de réinitialisation (reset)
# à compléter
# Etiquette de règle
etiq_regle = Label(cadre, text="Numéro de règle :")
etiq_regle.pack()
# Champ de saisie de règle
regle = StringVar()
saisie_regle = Entry(cadre, textvariable=regle)
saisie_regle.pack()
#%% Controleur
can.bind("<ButtonPress-1>", clic)
#%% Boucle infinie , réceptionnaire d'événement
fen.mainloop()
Projet 8 : jeu du recto-verso
Niveau de difficulté
Difficulté moyenne, entre 100 et 150 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter pour jouer au jeu Recto-Verso.
Le jeu se compose de neuf jetons disposés sur un plateau carré de trois lignes et trois colonnes. Chaque jeton possède deux faces :
- une face Recto, représentée en noir ;
- une face Verso, représentée en blanc.
Le but du jeu est de placer tous les jetons sur leur face Recto. À chaque tour, le joueur choisit une ligne, une colonne ou une diagonale, puis retourne simultanément les trois jetons correspondants.
L'interface doit comporter :
- une grille de neuf jetons dessinés dans un canevas ;
- huit boutons radio permettant de sélectionner l'une des trois lignes, l'une des trois colonnes ou l'une des deux diagonales ;
- un bouton
Action !qui retourne les trois jetons sélectionnés et met à jour l'affichage. - un bouton
Nouveau jeuqui génère une nouvelle configuration pouvant être résolue.
Représentation du plateau
L'état du plateau est représenté par une liste de neuf bits, lue ligne par ligne :
| Position dans la grille | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
Coordonnées (ligne, colonne) |
(0, 0) |
(0, 1) |
(0, 2) |
(1, 0) |
(1, 1) |
(1, 2) |
(2, 0) |
(2, 1) |
(2, 2) |
Le bit 0 représente une face noire et le bit 1 une face blanche. Ainsi, la liste
[0, 1, 0, 1, 0, 0, 0, 1, 1]
représente le plateau suivant :
| noir | blanc | noir |
|---|---|---|
| blanc | noir | noir |
| noir | blanc | blanc |
Retourner un jeton avec le XOR
Une action est également représentée par une liste de neuf bits. Un 1 indique que le jeton doit être retourné et un 0 qu'il doit rester inchangé.
Pour retourner un jeton, on applique l'opération logique XOR entre son état et le bit de l'action :
a |
b |
a XOR b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
La première ligne est donc représentée par l'action :
[1, 1, 1, 0, 0, 0, 0, 0, 0]
Quelques questions pour préparer la réalisation
- Quel est l'indice, dans la liste représentant le plateau, de la case située à la ligne
yet à la colonnex? - Écrire les listes de neuf bits qui représentent la deuxième diagonale et la troisième colonne.
- Vérifier, à l'aide de la table du XOR, qu'appliquer deux fois la même action ramène le plateau dans son état initial.
- Dans quel ordre faut-il modifier le modèle du jeu et actualiser son affichage ?
- Pour générer une grille dont on est certain qu'elle possède une solution, pourquoi peut-on partir de la grille noire et lui appliquer plusieurs actions choisies au hasard ?
Résultat attendu

- Canvas
- Rectangle dessiné dans un canevas
- Radiobutton
- Button
- Frame pour regrouper les boutons de commande.
- Variables de contrôle : une variable
IntVarpermet de mémoriser l'action sélectionnée.
- Listes et listes de listes.
- Parcours d'une liste par indice et construction d'une liste par compréhension.
- Opérateurs logiques et tables de vérité.
- Fonctions, variables, tests, boucles et fondamentaux sur tkinter vus dans les exercices d'introduction.
- Séparation entre le modèle, qui contient l'état du jeu, et la vue, qui assure son affichage.
from tkinter import *
import random
#%% Constantes
COTE_CASE = 100
NB_LIGNES = 3
NB_COLONNES = 3
#%% Variables globales et modèle
# Chaque sous-liste indique les jetons retournés par une action.
liste_action = [
[1, 1, 1, 0, 0, 0, 0, 0, 0], # ligne 1
[0, 0, 0, 1, 1, 1, 0, 0, 0], # ligne 2
[0, 0, 0, 0, 0, 0, 1, 1, 1], # ligne 3
[1, 0, 0, 1, 0, 0, 1, 0, 0], # colonne 1
[0, 1, 0, 0, 1, 0, 0, 1, 0], # colonne 2
[0, 0, 1, 0, 0, 1, 0, 0, 1], # colonne 3
[1, 0, 0, 0, 1, 0, 0, 0, 1], # diagonale 1
[0, 0, 1, 0, 1, 0, 1, 0, 0], # diagonale 2
]
# Au départ, les neuf jetons présentent leur face noire.
grille_modele = [0 for _ in range(9)]
#%% Modèle / Fonctions
def xor(a, b):
"""Renvoie le XOR des bits a et b."""
# à compléter
def test_xor():
assert xor(0, 0) == 0
assert xor(0, 1) == 1
assert xor(1, 0) == 1
assert xor(1, 1) == 0
def xor_liste(liste1, liste2):
"""Renvoie le XOR terme à terme de deux listes de bits."""
# à compléter
...
def test_xor_liste():
assert xor_liste([0, 1, 1], [1, 1, 0]) == [1, 0, 1]
assert xor_liste([1, 0, 1], [1, 0, 1]) == [0, 0, 0]
def maj_modele():
"""Applique au plateau l'action sélectionnée."""
global grille_modele
# Récupérer l'indice de l'action sélectionnée.
# Appliquer cette action à la grille avec xor_liste.
# à compléter
def maj_vue():
"""Actualise la couleur des rectangles à partir du modèle."""
# Parcourir les neuf cases de la grille.
# Colorier chaque rectangle en noir ou en blanc selon
# la valeur correspondante dans grille_modele.
# à compléter
def maj():
"""Met à jour le modèle, puis la vue."""
# à compléter
def nouveau_jeu():
"""Génère une configuration aléatoire qui possède une solution."""
global grille_modele
# Repartir de la grille entièrement noire, puis appliquer
# au hasard certaines des huit actions de liste_action.
# Recommencer si la grille obtenue est entièrement noire.
# Enfin, actualiser la vue.
# à compléter
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("RECTO-VERSO")
# Cadre contenant les commandes
cadre_action = Frame(fen)
cadre_action.pack()
# L'entier mémorisé désigne l'action sélectionnée :
# 0, 1, 2 pour les lignes ; 3, 4, 5 pour les colonnes ;
# 6 et 7 pour les diagonales.
index_action = IntVar(value=0)
ligne1 = Radiobutton(
cadre_action,
text="Ligne 1",
value=0,
variable=index_action,
)
ligne1.pack()
# Créer et placer les sept autres boutons radio.
# à compléter
# Canevas contenant la grille de jeu
can = Canvas(
fen,
width=COTE_CASE * NB_COLONNES,
height=COTE_CASE * NB_LIGNES,
)
can.pack()
# grille_vue contient les identifiants des neuf rectangles.
grille_vue = [
[
can.create_rectangle(
x * COTE_CASE,
y * COTE_CASE,
(x + 1) * COTE_CASE,
(y + 1) * COTE_CASE,
outline="red",
fill="black",
)
for x in range(NB_COLONNES)
]
for y in range(NB_LIGNES)
]
#%% Controleurs
# Bouton qui déclenche l'action sélectionnée
bouton_action = Button(cadre_action, text="Action !", command=maj)
bouton_action.pack()
# Bouton qui génère une nouvelle grille à résoudre
# à compléter avec une bouton qui génère une nouvelle grille
#%% Boucle infinie, réceptionnaire d'événements
fen.mainloop()
Conseil de réalisation
Commencez par compléter et tester les fonctions xor et xor_liste avant de construire la mise à jour graphique. Une grille formée de neuf bits choisis indépendamment au hasard ne possède pas nécessairement une solution. Pour le bouton Nouveau jeu, partez donc du plateau entièrement noir et appliquez-lui des actions légales choisies au hasard.
Prolongement facultatif
Ajouter une étiquette qui affiche le nombre de coups joués et un message lorsque les neuf jetons sont noirs.
Projet 9 : jeu du morpion
Niveau de difficulté
Difficulté moyenne, entre 100 et 150 lignes de code.
Cahier des charges
Réaliser une interface graphique en tkinter permettant à deux joueurs de jouer au morpion sur une grille de \(3 \times 3\) cases.
Les joueurs jouent à tour de rôle :
- le joueur 1 colorie ses cases en rouge ;
- le joueur 2 colorie ses cases en bleu ;
- un clic sur une case déjà occupée ne produit aucun effet ;
- le premier joueur qui aligne trois cases horizontalement, verticalement ou en diagonale remporte la partie ;
- si les neuf cases sont occupées sans alignement, la partie est déclarée nulle ;
- après la victoire d'un joueur ou un match nul, aucun nouveau coup ne peut être joué ;
- un bouton Nouvelle partie permet de vider la grille et de recommencer.
Une étiquette placée au-dessus de la grille indique le joueur dont c'est le tour, puis le résultat de la partie.
Représentation de la grille
La grille du jeu est représentée par une liste de trois listes :
grille_modele = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
]
La valeur 0 représente une case vide, 1 une case occupée par le joueur 1 et 2 une case occupée par le joueur 2.
Une seconde liste de listes, grille_vue, contient les identifiants des neuf rectangles dessinés dans le canevas. Elle permet de modifier leur couleur avec la méthode itemconfig.
Quelques questions préparatoires
- Un clic a pour coordonnées en pixels
(event.x, event.y). Comment calculer la ligne et la colonne de la case sélectionnée ? - La variable
joueurvaut toujours1ou2. Vérifier que l'instructionjoueur = 3 - joueurpermet de changer de joueur. - Depuis la dernière case jouée, dans quelles quatre directions suffit-il de rechercher un alignement ?
- Pourquoi faut-il tester la victoire avant de tester si toutes les cases sont occupées ?
Résultat attendu

- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variable de contrôle
StringVarpour mettre à jour le message affiché -
Contrôleur pour l'événement
"<ButtonPress-1>"correspondant au clic gauche de la souris :🐍 Script Pythoncan.bind("<ButtonPress-1>", clic)
- listes et listes de listes ;
- fonctions, variables globales, tests et boucles ;
- quotient entier
//; - fondamentaux sur tkinter vus dans les exercices d'introduction ;
- parcours d'une grille et vérification qu'un indice appartient à ses limites.
from tkinter import *
#%% Constantes
COTE_CASE = 100
NB_LIGNES = 3
NB_COLONNES = 3
VIDE = 0
JOUEUR1 = 1
JOUEUR2 = 2
COULEUR = ["white", "red", "blue"]
# Les quatre directions possibles d'un alignement :
# verticale, horizontale et les deux diagonales.
DIRECTIONS = [[1, 0], [0, 1], [1, 1], [1, -1]]
#%% Variables globales et modèle
joueur = JOUEUR1
nb_cases_libres = 9
partie_terminee = False
grille_modele = [
[VIDE for colonne in range(NB_COLONNES)]
for ligne in range(NB_LIGNES)
]
#%% Modèle / Fonctions
def coup_gagnant(ligne, colonne):
"""Renvoie True si le dernier coup forme un alignement
de trois cases, et False sinon.
Pour chaque direction, on examine les deux sens à partir
de la dernière case jouée.
"""
for dligne, dcolonne in DIRECTIONS:
nb_cases_joueur = 1
for sens in [-1, 1]:
# Calculer le déplacement dans le sens choisi.
# à compléter
# Coordonnées de la première case à examiner.
# à compléter
# Avancer tant que la case existe et appartient
# au joueur courant.
while False: # condition à remplacer
# à compléter
pass
# Tester si trois cases sont alignées.
# à compléter
return False
def maj_modele(ligne, colonne):
"""Joue un coup puis met à jour l'état de la partie."""
global joueur, nb_cases_libres, partie_terminee
# Placer le coup du joueur dans grille_modele et diminuer
# le nombre de cases libres.
# à compléter
# Traiter dans cet ordre : victoire, match nul,
# puis changement de joueur.
# Mettre à jour etat_jeu dans chaque cas.
# à compléter
def maj_vue():
"""Actualise la couleur des neuf rectangles."""
for ligne in range(NB_LIGNES):
for colonne in range(NB_COLONNES):
# Modifier la couleur du rectangle correspondant
# à grille_modele[ligne][colonne].
# à compléter
pass
def clic(event):
"""Gestionnaire d'un clic gauche dans le canevas."""
# Convertir les coordonnées en pixels en indices de case.
ligne = 0 # à compléter
colonne = 0 # à compléter
# Jouer uniquement si la partie continue et si la case
# sélectionnée est vide.
# à compléter
def reinitialiser():
"""Replace le jeu dans son état initial."""
global joueur, nb_cases_libres, partie_terminee
# Vider les neuf cases de grille_modele.
# à compléter
# Réinitialiser les trois variables globales et le message.
# à compléter
maj_vue()
#%% Vue : interface graphique
# Fenêtre racine
fen = Tk()
fen.title("Morpion")
# État du jeu
etat_jeu = StringVar()
etat_jeu.set("Joueur " + str(joueur))
label_etat = Label(fen, textvariable=etat_jeu)
label_etat.pack()
# Canevas
can = Canvas(
fen,
width=COTE_CASE * NB_COLONNES,
height=COTE_CASE * NB_LIGNES,
)
can.pack()
# Grille de rectangles dessinés dans le canevas
grille_vue = [
[
can.create_rectangle(
colonne * COTE_CASE,
ligne * COTE_CASE,
(colonne + 1) * COTE_CASE,
(ligne + 1) * COTE_CASE,
outline="black",
fill=COULEUR[VIDE],
)
for colonne in range(NB_COLONNES)
]
for ligne in range(NB_LIGNES)
]
#%% Contrôleur
# Bouton de réinitialisation
# à compléter avec un bouton pour réinitialiser une partie
# gestion du clic droit de la souris
can.bind("<ButtonPress-1>", clic)
#%% Boucle de réception des événements
fen.mainloop()
Projet 10 : sokoban (jeu)
Niveau de difficulté
Difficulté moyenne, entre 130 et 180 lignes de code.
Cahier des charges
Réaliser une version simplifiée du jeu Sokoban avec une interface graphique en tkinter.
Le joueur se déplace dans un entrepôt à l'aide des quatre touches fléchées du clavier. Il doit pousser toutes les caisses sur les emplacements prévus.
Les règles sont les suivantes :
- le joueur peut se déplacer sur une case libre ou sur un emplacement ;
- il ne peut pas traverser un mur ;
- il peut pousser une caisse uniquement si la case située derrière elle est libre ;
- il ne peut pas tirer une caisse ;
- la partie est gagnée lorsque chaque emplacement contient une caisse ;
- un bouton Nouvelle partie permet de replacer le joueur et les caisses dans leur position initiale.
La carte du jeu est enregistrée dans le fichier sokoplan1.xsb, placé dans le même dossier que le programme Python. Chaque caractère du fichier représente le contenu d'une case :
| Caractère | Élément | Couleur |
|---|---|---|
| espace | case libre | blanc |
# |
mur | noir |
. |
emplacement d'une caisse | vert |
$ |
caisse | rouge |
@ |
personnage | bleu |
Le plan est chargé dans une liste de listes appelée GRILLE. Par exemple, GRILLE[2][5] contient le symbole de la case située sur la ligne 2 et la colonne 5.
Le programme utilisera deux grilles :
grille_modelecontient les symboles décrivant l'état courant du jeu ;grille_vuecontient les identifiants des rectangles dessinés dans leCanvas.
Modèle et vue
Lors d'un déplacement, on modifie d'abord grille_modele. La fonction maj_vue() parcourt ensuite cette grille et actualise la couleur des rectangles de grille_vue.
La liste LIS_EMPLACEMENT mémorise séparément les coordonnées des emplacements. Elle permet de retrouver un emplacement lorsqu'un personnage ou une caisse quitte cette case.
Quelques questions pour préparer la réalisation⚓︎
- Ouvrir le fichier sokoplan1.xsb (cliquer sur le lien pour le télécharger) dans un éditeur de texte. Déterminer le nombre de lignes et de colonnes du plan, puis repérer les coordonnées initiales du personnage.
- Si le personnage est en
(lig_perso, col_perso), quelles sont les coordonnées de la case voisine après un déplacement décrit par(dlig, dcol)? Donner les quatre couples associés aux touches fléchées. - Lorsqu'une caisse occupe cette case voisine, quelles coordonnées faut-il tester pour savoir si elle peut être poussée ?
- Pourquoi faut-il restaurer le symbole
".", et non une espace, lorsque le personnage quitte une case appartenant àLIS_EMPLACEMENT? - Expliquer comment vérifier que toutes les caisses sont placées sur les emplacements sans comparer toute la grille à une grille finale.
Résultat attendu

- Canvas
- Rectangle dessiné dans un canevas
- Label
- Button
- Variable de contrôle
StringVar -
Contrôleur pour l'appui sur une touche du clavier :
🐍 Script Pythonfen.bind("<KeyPress>", deplacement)Le gestionnaire reçoit un objet
event. Le nom de la touche utilisée est donné parevent.keysym:"Up","Down","Left"ou"Right".
- lecture d'un fichier texte ligne par ligne ;
- chaînes de caractères et listes de listes ;
- parcours d'une grille avec deux boucles imbriquées ;
- fonctions, variables, tests et boucles ;
- fondamentaux de tkinter vus dans le TP d'introduction ;
- coordonnées d'un rectangle dans un
Canvas.
Le fichier sokoplan1.xsb (cliquer sur le lien pour le télécharger) doit être placé dans le même dossier que ce programme.
from tkinter import *
# %% Constantes et chargement du plan
CHEMIN_PLAN = "sokoplan1.xsb"
fichier = open(CHEMIN_PLAN, encoding="utf-8")
GRILLE = [list(ligne.rstrip("\n")) for ligne in fichier]
fichier.close()
NBLIG = len(GRILLE)
NBCOL = len(GRILLE[0])
COTE_CASE = 25
LIBRE = "white"
MUR = "black"
CAISSE = "red"
EMPLACEMENT = "green"
PERSO = "blue"
# Repérage des emplacements et de la position initiale du personnage
LIS_EMPLACEMENT = []
for lig in range(NBLIG):
for col in range(NBCOL):
# à compléter
pass
# %% Modèle / Fonctions
def couleur_case(symb):
"""Renvoie la couleur associée au symbole symb."""
# à compléter
pass
def case_libre(lig, col):
"""Indique si la case (lig, col) est libre."""
# Une case libre peut être une case blanche ou un emplacement.
# à compléter
pass
def case_avec_caisse(lig, col):
"""Indique si la case (lig, col) contient une caisse."""
# à compléter
pass
def fin_partie():
"""Indique si chaque emplacement contient une caisse."""
# à compléter
pass
# %% Mise à jour de la vue
def maj_vue():
"""Actualise la couleur de tous les rectangles du Canvas."""
for lig in range(NBLIG):
for col in range(NBCOL):
# à compléter avec can.itemconfig(...)
pass
# %% Gestion des déplacements
def deplacement(event):
"""Gère l'appui sur une touche fléchée du clavier."""
global lig_perso, col_perso
# 1. Associer à la touche un déplacement (dlig, dcol).
if event.keysym == "Up":
dlig, dcol = -1, 0
elif event.keysym == "Down":
# à compléter
pass
# à compléter
# 2. Calculer les coordonnées de la case voisine.
nouvelle_lig = lig_perso + dlig
nouvelle_col = col_perso + dcol
# 3. Traiter le déplacement vers une case libre.
if case_libre(nouvelle_lig, nouvelle_col):
# Restaurer la case que le personnage quitte.
# Mettre à jour sa position puis la nouvelle case.
# à compléter
pass
# 4. Traiter le cas d'une caisse qui peut être poussée.
elif case_avec_caisse(nouvelle_lig, nouvelle_col):
# Calculer la case située derrière la caisse.
# Si elle est libre, déplacer le personnage et la caisse.
# à compléter
pass
# 5. Actualiser l'affichage et tester la victoire.
maj_vue()
if fin_partie():
etat_jeu.set("Fin de partie")
def nouvelle_partie():
"""Restaure le plan et les positions initiales."""
global grille_modele, lig_perso, col_perso
# Construire une copie de GRILLE et non un simple alias.
# à compléter
grille_modele = []
lig_perso = LIG_PERSO_INIT
col_perso = COL_PERSO_INIT
etat_jeu.set("Partie en cours")
maj_vue()
# %% Vue : interface graphique
fen = Tk()
fen.title("Sokoban")
etat_jeu = StringVar(value="Partie en cours")
etiquette_etat = Label(fen, textvariable=etat_jeu)
etiquette_etat.pack()
can = Canvas(
fen,
width=NBCOL * COTE_CASE,
height=NBLIG * COTE_CASE,
)
can.pack()
# Chaque élément de grille_vue doit être l'identifiant d'un rectangle.
grille_vue = []
for lig in range(NBLIG):
ligne_vue = []
for col in range(NBCOL):
identifiant = can.create_rectangle(
col * COTE_CASE,
lig * COTE_CASE,
(col + 1) * COTE_CASE,
(lig + 1) * COTE_CASE,
outline="black",
fill=couleur_case(GRILLE[lig][col]),
)
ligne_vue.append(identifiant)
grille_vue.append(ligne_vue)
# %% Contrôleur
# à compléter avec un bouton pour lancer une nouvelle partie
...
# Gestion du déplacement du personnage (rectangle bleu)
fen.bind("<KeyPress>", deplacement)
# %% Lancement de la première partie
nouvelle_partie()
# %% Boucle de réception des événements
fen.mainloop()
Ordre de réalisation conseillé
- Charger le plan et afficher correctement la grille.
- Programmer le déplacement du personnage sur une case libre.
- Empêcher le passage à travers les murs.
- Ajouter la poussée d'une caisse lorsque la case suivante est libre.
- Détecter la victoire et programmer le bouton Nouvelle partie.