Le projet

Ce projet est une copie d’un document initial de Didier Müller.

Le jeu du pendu consiste à retrouver un mot le plus vite possible (avant que le dessin du pendu soit terminé) en proposant des lettres. Si la lettre appartient au mot, elle est écrite aux bons emplacements, sinon on continue de dessiner le pendu.

Code du programme

In [ ]:
# Le jeu du pendu
 
from tkinter import *
from random import choice
 
fichier = open("liste_mots.txt", "r")
liste_mots = fichier.readlines()    # met tous les mots du fichier dans une liste
fichier.close()


def lettre_dans_mot(lettre) :
    global partie_en_cours, mot_partiel, mot_choisi, nb_echecs, image_pendu
    if partie_en_cours : 
        nouveau_mot_partiel = ""
        lettre_dans_mot = False
        i=0
        while i<len(mot_choisi):
            if mot_choisi[i]==lettre:
                nouveau_mot_partiel = nouveau_mot_partiel + lettre
                lettre_dans_mot = True 
            else:
                nouveau_mot_partiel = nouveau_mot_partiel + mot_partiel[i]
            i+=1
        mot_partiel = nouveau_mot_partiel  
        afficher_mot(mot_partiel)
        if not lettre_dans_mot :        # lettre fausse. Changer le dessin.
            nb_echecs += 1
            nomFichier = "pendu_"+str(nb_echecs)+".gif"
            photo=PhotoImage(file=nomFichier)
            image_pendu.config(image=photo)
            image_pendu.image=photo
            if nb_echecs == 7:  # trop d'erreurs. Fini.
                partie_en_cours = False
                afficher_mot(mot_choisi)
        elif mot_partiel == mot_choisi:  # le mot a été trouvé !
            partie_en_cours = False
 

def afficher_mot(mot):
    global lettres
    mot_large = ""
    i=0
    while i<len(mot):  # ajoute un espace entre les lettres
        mot_large = mot_large + mot[i] + " "
        i+=1
    canevas.delete(lettres)
    lettres = canevas.create_text(320,60,text=mot_large,fill='black',font='Courrier 30') 

    
def init_jeu():
    global mot_choisi, mot_partiel, image_pendu, lettres
    global nb_echecs, partie_en_cours, liste_mots
    nb_echecs = 0
    partie_en_cours = True
    mot_choisi = choice(liste_mots).rstrip()
    mot_choisi = mot_choisi.upper()
    mot_partiel = "-" * len(mot_choisi)
    afficher_mot(mot_partiel)
    photo=PhotoImage(file="pendu_0.gif")
    image_pendu.config(image=photo)
    image_pendu.image=photo
        

# création du widget principal

fenetre = Tk()
fenetre.title("Le jeu du pendu")

canevas = Canvas(fenetre, bg='white', height=500, width=620)
canevas.pack(side=BOTTOM)

bouton = [0]*26
for i in range(26):
    bouton[i] = Button(fenetre,text=chr(i+65),command=lambda x=i+65:lettre_dans_mot(chr(x)))
    bouton[i].pack(side=LEFT)

bouton2 = Button(fenetre,text='Quitter',command=fenetre.quit)
bouton2.pack(side=RIGHT)
bouton1 = Button(fenetre,text='Recommencer',command=init_jeu)
bouton1.pack(side=RIGHT)

photo=PhotoImage(file="pendu_0.gif")
image_pendu = Label(canevas, image=photo, border=0)
image_pendu.place(x=120, y=140)
lettres = canevas.create_text(320,60,text="",fill='black',font='Courrier 30') 

init_jeu()

fenetre.mainloop()
fenetre.destroy()

Il faut bien sûr télécharger les images nécessaires et les placer dans le même répertoire que le script.

Analyse du programme

Le programme est divisé en cinq parties. Tout d'abord, on charge les mots lus dans un fichier externe dans la liste $\texttt{liste_mots}$.

In [ ]:
fichier = open("liste_mots.txt", "r")
liste_mots = fichier.readlines()    # met tous les mots du fichier dans une liste
fichier.close()

La procédure $\texttt{lettre_dans_mot()}$ se charge de vérifier que la lettre proposée figure dans le mot choisi. Si c'est le cas, la lettre sera placé au bon endroit dans le mot qui sera affiché.

In [ ]:
def lettre_dans_mot(lettre) :
    global partie_en_cours, mot_partiel, mot_choisi, nb_echecs, image_pendu
    if partie_en_cours : 
        nouveau_mot_partiel = ""
        lettre_dans_mot = False
        i=0
        while i<len(mot_choisi):
            if mot_choisi[i]==lettre:
                nouveau_mot_partiel = nouveau_mot_partiel + lettre
                lettre_dans_mot = True 
            else:
                nouveau_mot_partiel = nouveau_mot_partiel + mot_partiel[i]
            i+=1
        mot_partiel = nouveau_mot_partiel  
        afficher_mot(mot_partiel)
...

Dans le cas contraire, le dessin du pendu sera continué. En fait, l'image du pendu sera remplacée par une autre plus complète.

In [ ]:
        if not lettre_dans_mot :        # lettre fausse. Changer le dessin.
            nb_echecs += 1
            nomFichier = "pendu_"+str(nb_echecs)+".gif"
            photo=PhotoImage(file=nomFichier)
            image_pendu.config(image=photo)
            image_pendu.image=photo
...

Si le dernier dessin est affiché, la partie s'arrête et le mot complet et affiché.

In [ ]:
            if nb_echecs == 7:  # trop d'erreurs. Fini.
                partie_en_cours = False
                afficher_mot(mot_choisi)
        elif mot_partiel == mot_choisi:  # le mot a été trouvé !
            partie_en_cours = False

Comme son nom l'indique, la procédure $\texttt{afficher_mot()}$ écrit sur l'écran un mot partiel composé de tirets et des lettres qui ont été trouvées.

In [ ]:
def afficher_mot(mot):
    global lettres
    mot_large = ""
    i=0
    while i<len(mot):  # ajoute un espace entre les lettres
        mot_large = mot_large + mot[i] + " "
        i+=1
    canevas.delete(lettres)
    lettres = canevas.create_text(320,60,text=mot_large,fill='black',font='Courrier 30') 

Nous reviendrons sur la dernière ligne de cette procédure. Nous verrons ce que signifie le $\texttt{chr}$ et le $\texttt{lambda}$.

Remarquons que l'ancien mot partiel, qui a comme nom de variable lettres, est effacé par l'instruction $\texttt{canevas.delete(lettres)}$, avant que le nouveau soit réécrit au même endroit par l'instruction $\texttt{canevas.create_text()}$.

La procédure $\texttt{init_jeu()}$ initialise toutes les variables globales avant de (re)commencer une partie, tire au hasard un nouveau mot dans la liste et écrit des tirets correspondant au nombre de lettres, et enfin affiche le premier dessin du pendu, qui se nomme $\texttt{pendu_0.gif}$.

In [ ]:
def init_jeu():
    global mot_choisi, mot_partiel, image_pendu, lettres
    global nb_echecs, partie_en_cours, liste_mots
    nb_echecs = 0
    partie_en_cours = True
    mot_choisi = choice(liste_mots).rstrip()
    mot_choisi = mot_choisi.upper()
    mot_partiel = "-" * len(mot_choisi)
    afficher_mot(mot_partiel)
    photo=PhotoImage(file="pendu_0.gif")
    image_pendu.config(image=photo)
    image_pendu.image=photo

Dans le dernière partie, on met en place les différentes composantes de l'interface graphique.

La fenêtre

La première ligne crée la fenêtre, la seconde lui donne un titre. Notez que l'on ne donne pas les dimensions de la fenêtre : elle est « élastique » et s'adaptera automatiquement au contenu.

In [ ]:
fenetre = Tk()
fenetre.title("Le jeu du pendu")

Le canevas

Un canevas est une surface réservée aux éléments graphiques. On indique dans quelle fenêtre il devra se trouver, sa couleur de fond (blanc), et ses dimensions (500 pixels de haut et 620 de large). La seconde ligne indique que ce canevas sera placé en bas de la fenêtre. La fonction $\texttt{pack}$ sera expliquée plus en détail...

In [ ]:
canevas = Canvas(fenetre, bg='white', height=500, width=620)
canevas.pack(side=BOTTOM)

Les boutons

Les 26 boutons lettres seront placés dans la fenêtre, au-dessus du canevas (puisqu'on a dit que le canevas occupe le bas de la fenêtre) et tassés à gauche (la fonction $\texttt{chr}$ sera expliquée plus loin ...).

In [ ]:
bouton = [0]*26
for i in range(26):
    bouton[i] = Button(fenetre,text=chr(i+65),command=lambda x=i+65:lettre_dans_mot(chr(x)))
    bouton[i].pack(side=LEFT)

un bouton Quitter, tassé à droite

In [ ]:
bouton2 = Button(fenetre,text='Quitter',command=fenetre.quit)
bouton2.pack(side=RIGHT)

et un bouton Recommencer, tassé à droite

In [ ]:
bouton1 = Button(fenetre,text='Recommencer',command=init_jeu)
bouton1.pack(side=RIGHT)

Notez que le bouton le plus à droite (Quitter) doit être écrit en premier.

Le dessin du pendu

Le dessin du pendu, qui est un $\texttt{Label}$, est placé dans la canevas aux coordonnées $(120,140)$. La fonction $\texttt{place}$ sera expliquée plus en détail ...

In [ ]:
photo=PhotoImage(file="pendu_0.gif")
image_pendu = Label(canevas, image=photo, border=0)
image_pendu.place(x=120, y=140)

Le mot à découvrir

Le mot à découvrir est lui aussi placé dans le canevas, aux coordonnées $(320, 60)$. Il sera écrit en noir, avec la police « Courrier 30 ».

In [ ]:
lettres = canevas.create_text(320,60,text="",fill='black',font='Courrier 30') 

Remarquez bien que le dessin du pendu et le mot à découvrir sont placés dans le canevas, contrairement aux boutons.

Le code ASCII

Le code ASCII (American Standard Code for Information Interchange) est une norme d'encodage informatique des caractères alphanumériques de l'alphabet latin. La norme ASCII établit une correspondance entre une représentation numérique des caractères de l'alphabet latin ainsi que les symboles et les signes. Par exemple, le caractère « A » est associé à $65$ et « a » à $97$. Les nombres du code ASCII vont de $0$ à $127$.

Le codage ASCII (voir tableau ci-dessous) est souvent complété par des correspondances supplémentaires afin de permettre l'encodage informatique d'autres caractères, comme les caractères accentués par exemple. Cette norme s'appelle $\texttt{ISO-8859}$ et se décline par exemple en $\texttt{ISO-8859-1}$ lorsqu'elle étend l'ASCII avec les caractères accentués d'Europe occidentale.

En Python, la fonction qui donne de code ASCII d'un caractère s'appelle $\texttt{ord}$. Ainsi :

In [1]:
ord('A')
Out[1]:
65

Inversement, pour écrire le caractère correspondant à un code ASCII, on utilisera la fonction $\texttt{chr}$ :

In [2]:
chr(65)
Out[2]:
'A'

On comprend maintenant mieux notre ligne mystérieuse :

In [ ]:
    bouton[i] = Button(fenetre,text=chr(i+65),command=lambda x=i+65:lettre_dans_mot(chr(x)))

Sur le bouton $0$ sera écrit la lettre 'A' ($\texttt{chr(65)}$), sur le bouton $1$ la lettre 'B' ($\texttt{chr(66)}$), etc.

Les fonctions lambda

Par défaut, le paramètre $\texttt{command}$ d'un $\texttt{Button}$ nécessite une fonction sans argument. Vous pouvez cependant utiliser les fonctions $\texttt{lambda}$ pour passer des arguments supplémentaires, ce qui peut être utile si vous avez plusieurs $\texttt{Button}$ qui pointent sur la même fonction.

L'instruction $\texttt{pack}$ de $\texttt{tkinter}$

Le module $\texttt{tkinter}$ contient trois fonctions permettant de positionner les éléments d'une interface graphique :

$\texttt{grid}$ (que nous avons déjà vue au projet 4), $\texttt{pack}$ et $\texttt{place}$.

Lorsqu'on fait appel à $\texttt{pack()}$, $\texttt{tkinter}$ réduit la fenêtre pour épouser au mieux les éléments qu'elle contient. Chaque ajout d'un widget se fera de l'une des quatre manières ci-dessous, selon le côté choisi ($\texttt{LEFT}$, $\texttt{RIGHT}$, $\texttt{TOP}$ ou $\texttt{BOTTOM}$).

Le terme widget désigne toute entité susceptible d'être placée dans une fenêtre (un bouton, une image, etc)

Nous utiliserons le terme de cavité pour désigner l'espace qui sera prochainement occupé par un widget.

On divise la cavité restante en deux, pour créer une nouvelle cavité restante, à laquelle on appliquera le même schéma lors de l'ajout d'un nouveau widget.

Dans notre programme, on a d'abord placé un canevas en bas de la fenêtre. La cavité restante est donc la partie supérieure de la fenêtre. Ensuite, on a ajouté l'un après l'autre les 26 boutons lettres à gauche ; la cavité restante était donc la partie supérieure droite de la fenêtre. Puis on a ajouté à droite le bouton Quitter. La cavité restante se trouve donc toujours dans la partie supérieure de la fenêtre, coincée entre le bouton Z et le bouton Quitter.

C'est donc là que sera ajouté le dernier bouton Recommencer. À noter qu'il reste une cavité entre Z et Recommencer.

L'instruction $\texttt{place}$ de $\texttt{tkinter}$

La commande $\texttt{place}$ permet de placer un widget à la position $(x,y)$. Pour être précis, c'est le coin supérieur gauche du widget qui est positionné en $(x,y)$.

In [ ]:
image_pendu.place(x=120, y=140)

Attention ! Les coordonnées $(0,0)$ se trouvent en haut à gauche du canevas (et non en bas à gauche comme on pourrait s'y attendre).

Il est possible que l'image sorte du canevas : cela ne provoquera pas d'erreur, mais votre image sera tronquée.

Votre exercice dans ce projet


**Créez** une interface graphique pour le jeu Motus.

Ce que vous avez appris dans ce projet

  • Les caractères et autres symboles sont codés grâce au code $\texttt{ASCII}$.

  • Un canevas est une surface réservée aux éléments graphiques.

  • Il existe trois manières de positionner les éléments d'une interface graphique : $\texttt{grid}$, $\texttt{pack}$ et $\texttt{place}$.

  • Une chaîne de caractères peut être transformée en un élément graphique avec la fonction $\texttt{create_text()}$.