Consignes

Vous devrez utiliser l'interface TPLab pour envoyer vos TP aux encadrants de TP.

La note ne valide pas seulement le résultat de votre programme, mais également son style :

Vérifiez ces points avant de demander à votre intervenant de valider votre code.

Liens utiles

Objectifs du TP

Travailler avec :

1. Préliminaires (45 minutes)

1.1. Lecture de fichiers

Pour ouvrir un fichier fichier.txt en lecture, il suffit de faire

  f = open("fichier.txt")

Ceci permet d'initialiser un objet de type "fichier". (Si le fichier n'existe pas, l'expression open("fichier.txt") provoque une erreur.

On peut récupérer une ligne d'un fichier texte avec f.readline(). Des appels successifs à cette méthode permettent de récupérer des lignes successives.

Attention : lorsqu'il n'y a plus de lignes dans le fichier, la méthode f.readline renvoie la chaîne de caractères vide. Dans tous les autres cas, la chaîne de caractères contient un saut de ligne ('\n') final. Pour supprimer ce saut de ligne, ainsi que tous les autres caractères blancs (espaces ou tabulations) en début ou fin de ligne, il faut utiliser la méthode "strip" :

>>> f = open("test.txt")
>>> premiere_ligne = f.readline()
>>> premiere_ligne = premiere_ligne.strip()

Utilisez la fonction open et la méthode readline avec une boucle while pour compter le nombre de lignes dans un fichier texte.

Vous pouvez par exemple utiliser le fichier littre.txt qui contient exactement 73198 lignes.

Si vous êtes sous Windows et que vous avez des problèmes avec les accents, il est possible de spécifier l'encodage des accents lorsque vous ouvrez le fichier. Il suffit de remplacer la ligne

f = open("littre.txt")

par

f = open("littre.txt", encoding="UTF-8")

1.2. Dictionnaires et chaînes

Les dictionnaires ressemblent un peu aux tableaux, mais les cases ne sont pas numérotées par des entiers. Par exemple :

jours = {
  'janvier': 31,
  'février': 28,
  'mars':31,
  'avril':30,
  'mai':31,
  'juin':30,
  'juillet':31,
  'aout':31,
  'septembre':30,
  'octobre':31,
  'novembre':30,
  'decembre':31
}

On peut accéder à une case de deux manières :

L'intérêt de la méthode get est que l'on peut préciser une valeur à utiliser si la case n'existe pas. (La première méthode provoque une erreur si la case n'existe pas, et la seconde renvoie None si la case n'existe pas et que l'on n'a pas préciser de valeur par défaut.) Par exemple :

>>> print(jours["january"])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'january'
>>> print(jours.get("january"))
None
>>> print(jours.get("january", 0))
0

Écrivez une fonction nb_jours qui prend en argument une liste de mois (des chaînes de caractères) et renvoie le nombre de jours total de ces mois. Si un mois est invalide, on ne compte pas de jours :

>>> print(nb_jours([]))
0
>>> print(nb_jours(['mars', 'avril']))
61
>>> print(nb_jours(['mars', 'april']))
31
>>> print(nb_jours(['avril', 'février', 'novembre', 'octobre', 'aout', 'mai',
                    'juillet', 'janvier', 'juin', 'mars', 'septembre', 'decembre']))
365

2. Le jeu du pendu (1 heure)

Le but du jeu du pendu est de découvrir un mot en devinant ces lettres. L'objectif de ce TP est de pouvoir jouer contre l'ordinateur. Pour cela, on va partir d'un fichier qui contient tous les mots du célèbre dictionnaire d'Émile Littré.

2.1. Gestion des mots disponibles

Pour les questions qui suivent, vous pourrez utiliser le fichier littre.txt qui contient les 73198 mots du dictionnaire « Littré ». Ce fichier contient un mot par ligne, et en voici un extrait:

ABAQUE
ABAS
ABASOURDI
ABASOURDIR
ABAT
ABATAGE
ABATANT
ABATELLEMENT
ABATIS
ABATTABLE

La première étape consiste à récupérer tous les mots du dictionnaire. Compléter le code de la fonction "lire_mots" qui prends en argument le nom du fichier.

def lire_mots(fichier):
    """fonction qui récupère la liste des mots dans un fichier

paramètre
  - fichier, de type chaîne de caractère : nom du fichier contenant les mots
    (un par ligne)

retour : liste de chaîne de caractères
"""
  ...

Pour ajouter des valeurs dans une liste, il est beaucoup plus rapide de faire

liste.append(nouvelle_valeur)

plutôt que

liste = liste + [nouvelle_valeur]

La raison est que dans le second cas, la liste est recopiée. Comme nous allons récupérer les 70000 mots du Littré, la différence entre les deux variantes serait (très) visible.

2.2. État du jeu

Pendant une partie, on représentera le mot par une liste de caractères. Sa taille sera le nombre de lettres du mots à trouver, et les lettres inconnues seront remplacées par le caractère "_".

Remarque : pour éviter de tester les minuscules / majuscules, vous pouvez toujours convertir un caractère (ou une chaîne de caractères) en majuscules avec

c = c.upper()

Écrivez la fonction test_lettre :

def test_lettre(mot, etat, c):
    """fonction qui vérifie si une lettre apparait dans un mot, et lorque
c'est le cas, ajoute les lettres correspondantes dans l'état.

paramètres :
  - mot, de type chaîne de caractères,
  - etat, de type liste de caractères : les lettres inconnues sont
    représentées par des '_'. Cette liste a la même longueur que le paramètre
    mot,
  - c, de type caractère : la lettre proposée.

retour : "True" si la lettre est présente dans le mot, et False sinon.

L'état est mis à jour en remplaçant les "_" correspondant à la lettre trouvée.
"""
    ...

Pour ceci, le plus simple est de parcourir les caractères de la chaîne mot :

Attention, comme le mot peut contenir plusieurs fois la même lettre, il ne faut pas s'areter à la première occurence de c qu'on trouve !

Voici un exemple d'execution :

mot = "CANARI"
etat = [ "C", "_", "_", "_", "R", "I"]
if test_lettre(mot, etat, "a"):
    print("Bravo !")
else:
    print("Dommage...")
print("mot :", " ".join(etat))

devra afficher

Bravo !
mot : C A _ A R I

2.3. Première version

On peut maintenant écrire une première version du jeu du pendu :

Pour tirer un mot au hasard, il suffit de récuperer la liste de tous les mots, et de tirer un numéro de case au hasard avec la fonction randint. (N'oubliez pas de faire un

from random import randint

Voici un exemple de partie :

>>> pendu_version1("littre.txt")

_ _ _ _ _ _ _ _
Vous pouvez faire encore 8 erreurs.
Entrez une lettre, suivie d'un saut de ligne : e
Dommage...

_ _ _ _ _ _ _ _
Vous pouvez faire encore 7 erreurs.
Entrez une lettre, suivie d'un saut de ligne : a
Bravo !

_ A _ _ A _ _ _
Vous pouvez faire encore 7 erreurs.
Entrez une lettre, suivie d'un saut de ligne : l
Dommage...

_ A _ _ A _ _ _
Vous pouvez faire encore 6 erreurs.
Entrez une lettre, suivie d'un saut de ligne :

...
...

Perdu...
Le mot secret était 'RAMPANT'.

Complétez la procédure pendu_version1. Cette procédure prend en argument le nombre maximal d'erreurs autorisées. Si cette valeur n'est pas donnée, votre procédure utilisera la valeur par défaut de 8.

3. Deuxième version : gestion des accents (30 minutes)

La partie précédente ne prenait pas les accents en compte : si la lettre proposée est "e", il faut en fait vérifier si le mot contient des "é", "è", "ê" ou "ë". D'autre part, le français contient deux lettre doubles : le "æ" et le "œ. Lorsque la lettre proposée est "e", il faut donc aussi vérifier si le mot contient des "æ" ou "œ".

Pour gérer tout ça, nous allons utiliser un dictionnaire qui associe aux caractères ASCII les caractères accentués correspondants.

correspondance = {
    'A': 'AÀÄÂÆ',
    'C': 'CÇ',
    'E': 'EÊÈÉËÆŒ',
    'I': 'IÎÏ',
    'O': 'OÔÖŒ',
    'U': 'UÙÜÛ' }

Notez en particulier que les lettres doubles ont deux lettres correspondantes.

Modifier la fonction test_lettre pour qu'elle prenne les accents et lettres doubles en compte.

Pour ceci, il suffit de modifier le test qui vérifie si le ième caractère de mot est égal à c par un test qui vérifie si le ième caractère du mot apparait dans la chaîne contenant toutes les variantes du c. (Attention, certains caractères n'ont aucune variante et n'apparaissent donc pas dans le dictionnaire correspondance.)

Voici un exemple d'execution :

mot = "DÉSŒUVRER"
etat = [ "D", "_", "_", "_", "_", "V", "R", "_" , "R" ]
if test_lettre(mot, etat, "e"):
    print("Bravo !")
else:
    print("Dommage...")
print("mot :", " ".join(etat))

devra afficher

Bravo !
mot : D É _ Œ _ V R E R

Programmez maintenant la procédure pendu_version2 similaire à pendu_version1, mais qui prend les accents et lettres doubles en compte.

4. Troisième version : mots encore disponibles (30 minutes)

Nous allons améliorer la version précédente du jeu en ajoutant :

>>> pendu_version3("littre.txt")

_ _ _ _ _ _ _ _
Vous pouvez faire encore 8 erreurs.
Il y a encore 11595 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : e
Bravo !

É _ _ _ _ _ _ E
Vous pouvez faire encore 8 erreurs.
Il y a encore 196 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : a
Bravo !

É _ _ _ _ A _ E
Vous pouvez faire encore 8 erreurs.
Il y a encore 46 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : l
Dommage...

É _ _ _ _ A _ E
Vous pouvez faire encore 7 erreurs.
Il y a encore 46 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : p
Bravo !

É _ _ _ P A _ E
Vous pouvez faire encore 7 erreurs.
Il y a encore 5 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : e
Vous avez déjà proposé cette lettre...

É _ _ _ P A _ E
Vous pouvez faire encore 7 erreurs.
Il y a encore 5 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : e
Vous avez déjà proposé cette lettre...

...

Perdu...
Le mot secret était 'ÉTOUPAGE'.

Commencez par écrire la fonction :

def mots_nb_lettres(mots, n):
    """fonction qui renvoie la liste des chaînes d'une taille donnée dans une
    liste

paramètres :
  - mots, de type liste de chaînes de caractères
  - n, de type entier

retour : liste de chaînes de caractères: tous les éléments de mots qui ont la
taille n
"""

Cette fonction vous permettra de savoir combien de mots sont possible au début d'une partie de pendu : il suffit de garder les mots de même taille que le mot secret.

Écrivez la procédure pendu_version3 et testez la. N'oubliez pas de

Note : pour pouvoir compter le nombre de mots restants qui sont compatibles avec les lettres devinées, il est conseillé d'écrire des fonctions auxiliaires :

def contient_lettre(mot, c):
    """fonction qui renvoie True ou False suivant qu'une chaîne contient un
    caractère.

paramètres :
    - mot, de type chaîne de caractères
    - c, de type caractère

retour : booléen
"""
    ....


def mots_sans_lettre(mots, c):
    """fonction qui renvoie la liste des éléments d'une liste contenant un
caractère donné

paramètres :
    - mots, de type liste de chaînes de caractères
    - c, de type caractère

retour : liste de chaîne de caractères
"""
    ...


def mots_avec_lettre(mots, c):
    """fonction qui renvoie la liste des éléments d'une liste qui ne
contiennent pas un caractère donné

paramètres :
    - mots, de type liste de chaînes de caractères
    - c, de type caractère

retour : liste de chaîne de caractères
"""
    ...

Ces fonctions vous permettront de mettre à jours la liste des mots possible suivant que la lettre proposée était présente ou pas dans le mot secret.

5. Un peu d'"ASCII art"

Il serait possible de faire une version graphique du jeu du pendu afin de pouvoir réellement dessiner le pendu. Pour faire plus simple, nous allons faire une version en "ASCII art". Voici un exemple de partie possible :

  --------------
    |
    |
    |
    |
    |
    |
    |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ _ _ _
Vous pouvez faire encore 7 erreurs.
Il y a encore 10232 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : a
Dommage...

  --------------
    |        |
    |        |
    |
    |
    |
    |
    |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ _ _ _
Vous pouvez faire encore 6 erreurs.
Il y a encore 5037 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : e
Bravo !

  --------------
    |        |
    |        |
    |
    |
    |
    |
    |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ É _ É
Vous pouvez faire encore 6 erreurs.
Il y a encore 28 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : l
Dommage...

  --------------
    |        |
    |        |
    |       / \
    |       \_/
    |
    |
    |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ É _ É
Vous pouvez faire encore 5 erreurs.
Il y a encore 26 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : t
Dommage...

  --------------
    |        |
    |        |
    |       / \
    |       \_/
    |        |
    |        |
    |        |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ É _ É
Vous pouvez faire encore 4 erreurs.
Il y a encore 17 mots possibles...
Entrez une lettre, suivie d'un saut de ligne : p
Dommage...

  --------------
    |        |
    |        |
    |       / \
    |       \_/
    |        |__
    |        |
    |        |
    |
   /|\
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
_ _ _ _ É _ É
Vous pouvez faire encore 3 erreurs.
Il y a encore 9 mots possibles...

...
...

Pour pouvoir afficher le pendu, nous allons utiliser une fonction auxiliaire :

def affiche_pendu(n=10):
    """procédure qui affiche le pendu en fonction du nombre d'erreurs

paramètre :
    - n, de type entier
"""

Une manière de faire est d'initialiser deux chaînes de caractères : (notez le r précédent les trois guillemets : il permet de ne pas avoir besoin d'échapper les caractères \ présents dans les chaînes)

    pendu_final = r"""
  --------------
    |        |
    |        |
    |       / \
    |       \_/
    |      __|__
    |        |
    |        |
    |       / \
   /|\     /   \
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
"""
    pendu_numero = r"""
  --------------
    |        1
    |        1
    |       2 2
    |       222
    |      55344
    |        3
    |        3
    |       6 7
   /|\     6   7
  / | \
 /  |  \
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~
"""

La première contient les caractère que l'on doit afficher, et la seconde (de même taille) contient donne quels caractères on doit afficher pour un nombre d'erreurs données :

Écrivez la procédure affiche_pendu ainsi que la procédure pendu_version4(dictionnaire) correspondante. (Le nombre d'erreurs autorisées est forcément 7.)

Lorsqu'on dessine le pendu, le nombre d'erreurs maximal est forcément 7. On peut modifier ceci en commençant à dessiner la potence. Écrivez un procédure affiche_pendu qui permet de commencer à afficher la potence.

Écrivez une procédure pendu_version5(dictionnaire, nb_erreurs) correspondante qui permet de choisir un nombre d'erreurs maximal compris entre 7 (on ne dessine que le pendu) et 11 (on dessine les différents morceaux de la potence).