Exercice de Synthèse : Le jeu du pendu
Objectif du projet
Développer un jeu du pendu qui se joue dans le terminal. Le mot est choisi aléatoirement à partir d'un mini dictionnaire situé dans le fichier /data/dictionnaire.json. Les mots sont classés par catégories. Au lancement, l'utilisateur choisit l'une d'entre elles ou laisse le hasard décider. L'application doit pouvoir s'adapter à d'autres dictionnaires ayant la même structure mais un nombre de catégories différent.
L'interface du jeu affiche avant chaque tour
- La numérotation du tour de jeu (ex. : Tour 1).
- La catégorie sélectionnée (ex. : Catégorie: animaux).
- Le nombre de vies restantes (ex. : Vies restantes: 6/7).
- Les lettres déjà proposées (ex. : Lettres proposées: o, s, e).
- Les lettres découvertes et des tirets pour celles qui restent (ex. : -o-sso-).
- L'invitation à entrer une lettre (ex. : Proposez une lettre:).
Règles de saisie
- Seules les lettres en minuscules et sans accents sont autorisées. Sinon, un message d'avertissement s'affiche.
- Si l'utilisateur propose une lettre déjà proposée, il en est informé et ne perd pas de vie.
- En fin de partie, afficher victoire ou défaite, et le mot à découvrir.
Exemple d'interface
Voici un aperçu de l'interface textuelle telle qu'elle pourrait apparaître au cours des trois premiers tours d'une partie.

Comment concevoir un projet ?
Avant d'écrire la moindre ligne de code, il y a une étape que les développeurs professionnels font systématiquement : réfléchir au problème en français, avec des mots simples. C'est la compétence la plus importante en programmation, bien avant la syntaxe d'un langage.
Imaginez que vous devez expliquer le jeu du pendu à un enfant de 8 ans qui n'a jamais joué. Vous ne lui parleriez pas de tableaux associatifs ou de boucles while. Vous lui diriez :
Le jeu du pendu, c'est simple :
- On a un fichier avec plein de mots rangés par catégories.
- Le joueur choisit une catégorie (ou en prend une au hasard).
- L'ordinateur pioche un mot dans cette catégorie.
- Le joueur voit des tirets à la place des lettres.
- À chaque tour, le joueur propose une lettre.
- Si la lettre est dans le mot, on la révèle à sa place.
- Si la lettre n'est pas dans le mot, le joueur perd une vie.
- On continue jusqu'à ce que le joueur trouve tout le mot ou n'ait plus de vies.
- À la fin, on lui dit s'il a gagné ou perdu, et on révèle le mot."
Et voilà. Vous venez de concevoir votre programme. Chacune de ces phrases va se transformer en une portion de code.
Étape A : Transformer la description en "plan de code"
On va écrire une fonction principale, lancerJeu(), qui fait exactement les étapes décrites ci-dessus. Le but n'est pas d'écrire du code qui fonctionne tout de suite, mais d'écrire un plan qui décrit ce que le programme fera :
function lancerJeu(string $cheminDictionnaire): void
{
// 1. Charger les mots depuis le fichier
$dictionnaire = chargerDictionnaire($cheminDictionnaire);
// 2. Demander au joueur de choisir une catégorie
$categorie = demanderCategorie($dictionnaire);
// 3. Piocher un mot au hasard dans cette catégorie
$mot = tirerMotAuHasard($dictionnaire, $categorie);
// 4. Lancer la partie avec ce mot et cette catégorie
jouer($mot, $categorie);
}
Ce code ne fonctionne pas encore : les fonctions appelées n'existent pas. Et c'est parfaitement normal. On vient d'écrire le plan du programme. On sait maintenant exactement quelles briques construire.
Étape B : Descendre dans chaque brique
On prend la première fonction inconnue : chargerDictionnaire. On se demande : "Que doit-elle faire ?". Réponse : ouvrir un fichier JSON et le transformer en tableau PHP. On l'écrit, on la teste, et quand elle marche, on passe à la suivante.
Si en écrivant une fonction, on découvre qu'elle a besoin d'une autre fonction qui n'existe pas encore, on descend d'un niveau, on crée cette sous-fonction, on la teste, et on remonte.
Ce que cette méthode vous apprend
Cette façon de travailler s'appelle l'approche "descendante" (ou top-down). À chaque instant, vous savez pourquoi vous écrivez le code que vous êtes en train d'écrire. Vous ne suivez pas des étapes à l'aveugle — vous construisez un plan, puis vous le réalisez brique par brique.
Étape C : Identifier les fichiers et les regroupements
En regardant notre plan, on voit naturellement émerger des groupes de fonctions :
- pendu/outils.php : Des fonctions utilitaires (normaliser une lettre, vérifier un caractère, etc.)
- pendu/gestionDuDictionnaire.php : Des fonctions liées au dictionnaire (charger, lister, piocher)
- pendu/logiqueDuJeu.php : Des fonctions de logique de jeu (calculer les vies, traiter une proposition, etc.)
- pendu/interface.php : Des fonctions d'interface (afficher le menu, demander une lettre, etc.)
Principe important : les fonctions retournent les données modifiées
Dans ce projet, nous respecterons un principe simple : une fonction qui doit modifier un tableau (comme l'état du jeu) ne le modifie pas directement. Elle travaille sur sa propre copie, la modifie, et la retourne. C'est ensuite à l'appelant de récupérer cette nouvelle version.
En PHP, quand on passe un tableau à une fonction, celle-ci reçoit automatiquement une copie du tableau. Toute modification faite à l'intérieur de la fonction n'affecte pas le tableau d'origine. La fonction doit donc retourner le tableau modifié, et l'appelant doit stocker ce retour. Ce principe rend le code plus prévisible : on sait toujours d'où viennent les changements, car ils passent par un return explicite.
Quelques fonctions prédéfinies pouvant s'avérer utiles
- array_flip() : Permet d'inverser un tableau en utilisant les valeurs comme clés.
- array_keys() : Permet de récupérer toutes les clés d'un tableau.
- array_rand() : Permet de sélectionner aléatoirement une clé d'un tableau.
- count() : Permet de compter le nombre d'éléments dans un tableau.
- file_exists() : Permet de vérifier si un fichier existe.
- file_get_contents() : Permet de lire le contenu complet d'un fichier dans une chaîne.
- implode() : Permet de joindre les éléments d'un tableau dans une chaîne.
- in_array() : Permet de vérifier si une valeur existe dans un tableau.
- is_array() : Permet de vérifier qu'une variable est un tableau.
- is_numeric() : Permet de vérifier qu'une chaîne représente un nombre.
- is_readable() : Permet de vérifier qu'un fichier est lisible.
- isset() : Permet de vérifier si une clé existe dans un tableau et n'est pas à null.
- json_decode() : Permet de décoder une chaîne JSON en tableau (ou en objet).
- readline() : Permet de lire une ligne saisie dans le terminal.
- strlen() : Retourne le nombre d'octets d'une chaîne. En UTF-8, un caractère ASCII occupe 1 octet.
- str_repeat() : Permet de répéter une chaîne un certain nombre de fois.
- strpos() : Permet de trouver la position d'un caractère ou d'une sous-chaîne dans une chaîne.
- strtolower() : Permet de passer toutes les lettres d'une chaîne en minuscule.
- trim() : Permet de supprimer les espaces (et certains caractères invisibles) au début et à la fin d'une chaîne.
- N'oubliez pas de consulter la documentation officielle de PHP.
Instructions
Vous allez construire le projet en suivant l'approche descendante. Pour tester chaque fonction, vous utiliserez index.php comme script de test temporaire. À la dernière étape, il deviendra le vrai point d'entrée du jeu.
Le rythme de travail : on écrit une ligne, on rencontre une fonction inconnue, on descend pour la construire, on la teste dans index.php, on remonte, on écrit la ligne suivante.
Étape 01 : Mise en place du terrain de travail
Avant de coder, on prépare le terrain : créer l'arborescence du projet (dossiers et fichiers), copier le dictionnaire de mots, et vérifier que PHP peut exécuter notre fichier de test. C'est comme préparer son plan de travail avant de cuisiner : sans cette base, rien d'autre ne peut fonctionner.
-
Créer toute l'arborescence du projet. Créer un dossier le-jeu-du-pendu. À l'intérieur, créer un sous-dossier data avec un fichier JSON vide nommé dictionnaire.json. Créer un sous-dossier pendu. Dans pendu, créer quatre fichiers PHP vides : outils.php, gestionDuDictionnaire.php, logiqueDuJeu.php, interface.php.
📁 le-jeu-du-pendu/ ├── 📁 data/ │ └── 📄 dictionnaire.json ├── 📁 pendu/ │ ├── 📄 outils.php │ ├── 📄 gestionDuDictionnaire.php │ ├── 📄 logiqueDuJeu.php │ └── 📄 interface.php └── 📄 index.php -
Remplir le dictionnaire. Dans data, ouvrir dictionnaire.json et y coller le contenu fourni ci-dessous.
{ "nourriture": [ "pomme", "pain", "fromage", "poisson", "poulet", "salade", "carotte", "tomate", "chocolat", "yaourt", "jambon", "soupe", "riz", "pates", "haricots", "fraise", "banane", "raisin", "aubergine", "oignon" ], "animaux": [ "chien", "chat", "oiseau", "lion", "elephant", "tortue", "souris", "serpent", "abeille", "cheval", "papillon", "poisson", "poule", "gorille", "crocodile", "dauphin", "girafe", "hippopotame", "pingouin", "koala" ], "professions": [ "medecin", "enseignant", "policier", "avocat", "boulanger", "coiffeur", "architecte", "infirmier", "plombier", "photographe", "artiste", "cuisinier", "journaliste", "electricien", "veterinaire", "facteur", "agriculteur", "musicien", "acteur", "ingenieur" ], "sciences": [ "physique", "chimie", "biologie", "mathematiques", "astronomie", "geologie", "informatique", "medecine", "psychologie", "sociologie", "anthropologie", "ecologie", "botanique", "zoologie", "economie", "linguistique", "archeologie", "pharmacologie", "astrophysique", "entomologie" ] } - Créer index.php à la racine. Ce fichier servira de script de test tout au long du projet. C'est ici qu'on appellera chaque fonction pour vérifier qu'elle fonctionne correctement avant de passer à la suivante.
- Activer le typage strict. En haut de index.php, après la balise d'ouverture PHP, écrire declare(strict_types=1);. Pourquoi ? Le typage strict empêche PHP de convertir silencieusement les types. Si on envoie un nombre là où une chaîne est attendue, PHP affichera une erreur au lieu de deviner. Cela évite des bugs difficiles à repérer.
- Ajouter un message de test. Utiliser echo pour afficher "Le projet est prêt." suivi de PHP_EOL (constante de retour à la ligne).
- Exécuter le script. Dans le terminal, se placer à la racine du projet et lancer php ./index.php. Le message doit s'afficher. Si ce n'est pas le cas, vérifier que PHP est bien installé et que le chemin est correct.
Étape 02 : Le point d'entrée et le début de lancerJeu
On commence l'approche descendante. Dans index.php, on inclut pendu/interface.php et on appelle lancerJeu en lui passant le chemin du dictionnaire. Puis on crée cette fonction dans pendu/interface.php, mais on n'y écrit que la première ligne : charger le dictionnaire. On rencontre donc chargerDictionnaire, une fonction qui n'existe pas encore — on descend pour la construire.
2.1 : Préparer index.php
- Remplacer le message de test de l'étape 1 par le vrai contenu. Garder la balise d'ouverture PHP et declare(strict_types=1);.
- Inclure pendu/interface.php avec require_once en utilisant __DIR__ . '/pendu/interface.php'.
- Appeler lancerJeu en lui passant __DIR__ . '/data/dictionnaire.json'. Ce sera le point d'entrée unique du jeu.
2.2 : Préparer pendu/interface.php
- Ouvrir pendu/interface.php. Activer le typage strict. Inclure les trois autres fichiers du dossier pendu avec require_once en utilisant __DIR__ : __DIR__ . '/outils.php', __DIR__ . '/gestionDuDictionnaire.php', __DIR__ . '/logiqueDuJeu.php'. Pourquoi require_once et pas require ? Parce que require_once garantit qu'un fichier n'est inclus qu'une seule fois, même si plusieurs fichiers tentent de l'inclure. Cela évite des erreurs de redéclaration de fonctions.
- Créer la fonction lancerJeu. Elle reçoit un paramètre $cheminDictionnaire de type string et ne retourne rien (void).
- Écrire uniquement la première ligne du corps : appeler chargerDictionnaire en lui passant $cheminDictionnaire. Stocker le résultat dans $dictionnaire.
⚠️ chargerDictionnaire n'existe pas encore. Si on exécutait le script maintenant, PHP afficherait une erreur. C'est normal : on va la construire à l'étape suivante. C'est ça, l'approche descendante : on rencontre une fonction inconnue, on descend pour la créer.
Étape 03 : Charger le dictionnaire depuis un fichier JSON
On a rencontré chargerDictionnaire dans lancerJeu. On descend pour la construire dans pendu/gestionDuDictionnaire.php. Cette fonction ouvre le fichier JSON, vérifie qu'il est valide, le transforme en tableau PHP, et retourne le résultat. Si quelque chose se passe mal (fichier introuvable, illisible, contenu invalide), elle retourne null.
- Ouvrir pendu/gestionDuDictionnaire.php et activer le typage strict.
- Créer la fonction chargerDictionnaire. Elle reçoit $chemin (string). Elle retourne ?array (soit un tableau, soit null). Le ? devant le type signifie que null est aussi une valeur de retour possible.
- Vérifier que le fichier existe et est lisible. Appeler file_exists en lui passant $chemin. Appeler aussi is_readable en lui passant $chemin. Si l'une des deux échoue (vérifier avec !), retourner null. Pourquoi les deux ? Un fichier peut exister sans être lisible (par exemple si les permissions du système l'interdisent).
- Lire le contenu du fichier. Appeler file_get_contents en lui passant $chemin. Stocker le résultat dans $contenu. Cette fonction lit tout le fichier d'un coup et retourne son contenu sous forme de chaîne. Si la lecture échoue, elle retourne false. Vérifier avec === si $contenu vaut false, et si oui, retourner null.
- Décoder le JSON. Appeler json_decode avec deux arguments : $contenu et true. Le second argument true demande à PHP de créer un tableau associatif plutôt qu'un objet. Stocker le résultat dans $dictionnaire.
- Vérifier que le résultat est valide. Avec is_array, vérifier que c'est bien un tableau (et pas null si le JSON était malformé). Avec count, vérifier qu'il n'est pas vide. Si l'une des deux vérifications échoue, retourner null.
- Retourner $dictionnaire.
Compléter et tester lancerJeu
- Revenir dans lancerJeu (dans pendu/interface.php). Après l'appel à chargerDictionnaire, ajouter la vérification : si $dictionnaire vaut null, arrêter avec exit(1). Le chiffre 1 signale une erreur au système.
- Ajouter un echo temporaire après la vérification pour afficher le dictionnaire avec print_r($dictionnaire). Cela permet de vérifier que le chargement fonctionne.
- Exécuter php ./index.php. Les 4 catégories avec leurs mots doivent s'afficher. Tester aussi avec un chemin inexistant dans index.php pour vérifier que le programme s'arrête.
- Supprimer le echo temporaire une fois le test réussi.
Étape 04 : Ajouter la sélection de catégorie dans lancerJeu
On remonte dans lancerJeu pour écrire la deuxième ligne : $categorie = demanderCategorie($dictionnaire). On rencontre demanderCategorie, qui a besoin de trois sous-fonctions : obtenirCategories (extraire les noms de catégories du dictionnaire), selectionnerCategorie (interpréter le choix du joueur — par numéro, par nom, ou au hasard) et afficherMenuCategories (afficher la liste numérotée). On descend pour les construire une par une, puis on assemble demanderCategorie.
4.1 : obtenirCategories
- Dans pendu/gestionDuDictionnaire.php, créer la fonction. Elle reçoit $dictionnaire (array), retourne un array.
- Extraire les clés en appelant array_keys sur $dictionnaire. Retourner directement le résultat. Pourquoi les clés ? Dans notre dictionnaire JSON, les clés du tableau associatif sont les noms de catégories ('nourriture', 'animaux', etc.).
- Tester dans index.php : charger le dictionnaire, appeler obtenirCategories, afficher avec print_r. On doit obtenir nourriture, animaux, professions, sciences.
4.2 : selectionnerCategorie
Le joueur pourra choisir une catégorie de trois façons : en tapant un numéro (ex. : '2'), en tapant le nom (ex. : 'animaux'), ou en tapant 'hasard' ou '0' pour un choix aléatoire. Cette fonction interprète ce choix et retourne le nom de la catégorie correspondante, ou null si le choix est invalide.
- Créer la fonction. Elle reçoit $dictionnaire (array) et $choix (string). Elle retourne ?string.
- Récupérer la liste des catégories en appelant obtenirCategories avec $dictionnaire. Stocker dans $categories.
- Gérer le choix aléatoire. Si $choix vaut 'hasard' ou '0', appeler array_rand en lui passant $categories pour obtenir un index aléatoire. Retourner l'élément de $categories à cet index.
- Gérer un numéro. Vérifier avec is_numeric si $choix est un nombre. Si oui, convertir en entier avec (int)$choix et soustraire 1 pour obtenir l'index. Pourquoi soustraire 1 ? Parce que le menu affichera les catégories à partir de 1, mais les index PHP commencent à 0. Stocker dans $index. Si $index est entre 0 et count($categories) - 1, retourner $categories[$index]. Sinon, retourner null.
- Gérer un nom de catégorie. Appeler in_array en lui passant $choix et $categories. Si trouvé, retourner $choix.
- Sinon, retourner null.
- Tester : '1' → nourriture, 'sciences' → sciences, '99' → NULL, 'hasard' → aléatoire, '0' → aléatoire.
4.3 : afficherMenuCategories
- Dans pendu/interface.php, créer la fonction. Reçoit $categories (array). Retourne void.
- Afficher un en-tête : une ligne vide, puis === JEU DU PENDU ===, une ligne vide, Choisissez une catégorie :, une ligne vide, puis 0. hasard (catégorie aléatoire).
- Parcourir $categories avec une boucle for. À chaque tour, afficher le numéro ($i + 1) suivi d'un point et du nom. Ex. : 1. nourriture.
- Terminer par une ligne vide.
4.4 : Assembler demanderCategorie
- Dans pendu/interface.php, créer demanderCategorie. Reçoit $dictionnaire (array). Retourne string.
- Récupérer les catégories avec obtenirCategories($dictionnaire).
- Créer une boucle while (true) qui tourne jusqu'à un choix valide :
- Appeler afficherMenuCategories.
- Afficher Votre choix : (sans retour à la ligne).
- Lire la saisie avec readline(). Stocker dans $ligne. readline attend que l'utilisateur tape quelque chose et appuie sur Entrée. Si l'utilisateur fait Ctrl+D (ce qui ferme le flux d'entrée du terminal, signalant qu'il veut quitter), readline retourne false. Dans ce cas, afficher Partie interrompue. et arrêter avec exit(0).
- Nettoyer avec trim. Stocker dans $choix.
- Appeler selectionnerCategorie avec le dictionnaire et le choix. Stocker dans $categorie.
- Si $categorie n'est pas null, retourner $categorie.
- Sinon, afficher un message d'erreur et la boucle recommence.
Compléter et tester lancerJeu
- Revenir dans lancerJeu. Après le chargement et la vérification du dictionnaire, ajouter : $categorie = demanderCategorie($dictionnaire);.
- Ajouter un echo temporaire pour afficher la catégorie choisie.
- Exécuter php ./index.php. Le menu doit s'afficher. Tester : numéro valide, nom, hasard, 0, choix invalide (la boucle redemande).
- Supprimer le echo temporaire.
Étape 05 : Ajouter le tirage d'un mot dans lancerJeu
On écrit la troisième ligne de lancerJeu : $mot = tirerMotAuHasard($dictionnaire, $categorie). On rencontre tirerMotAuHasard — on descend pour la construire dans pendu/gestionDuDictionnaire.php. Elle vérifie que la catégorie existe et contient des mots, puis en retourne un au hasard (ou null). Avec cette fonction, gestionDuDictionnaire.php sera complet.
tirerMotAuHasard
- Dans pendu/gestionDuDictionnaire.php, créer la fonction. Reçoit $dictionnaire (array) et $categorie (string). Retourne ?string.
- Vérifier que la catégorie existe. Appeler isset avec $dictionnaire[$categorie]. Si non, retourner null.
- Récupérer les mots. Stocker $dictionnaire[$categorie] dans $mots. Vérifier avec is_array et count que c'est un tableau non vide. Sinon, retourner null. Pourquoi ? On ne contrôle pas le contenu du fichier JSON : une catégorie pourrait être vide.
- Tirer au hasard. Appeler array_rand en lui passant $mots. Retourner le mot à cet index.
- Tester avec 'animaux' (le mot change) et 'sports' (retourne NULL).
Compléter et tester lancerJeu
- Revenir dans lancerJeu. Ajouter $mot = tirerMotAuHasard($dictionnaire, $categorie);. Si $mot vaut null, arrêter avec exit(1).
- Ajouter un echo temporaire pour afficher le mot tiré.
- Exécuter, vérifier qu'un mot de la catégorie choisie s'affiche, puis supprimer le echo temporaire.
🎉 pendu/gestionDuDictionnaire.php est complet ! Il contient 4 fonctions : chargerDictionnaire, obtenirCategories, selectionnerCategorie et tirerMotAuHasard.
Étape 06 : Ajouter l'appel à jouer et commencer la boucle de jeu
Dernière ligne de lancerJeu : jouer($mot, $categorie). On descend pour créer cette fonction dans pendu/interface.php. Comme pour lancerJeu, on ne l'écrit pas en entier d'un coup : on commence par la première ligne (initialiser l'état du jeu), ce qui nous amènera à construire les fonctions utilitaires et la logique d'initialisation. La fonction jouer contient une boucle while : à chaque tour, elle affiche l'état, demande une lettre, traite la proposition, donne un feedback, et passe au tour suivant. On construira cette boucle étape par étape.
- Revenir dans lancerJeu. Ajouter la dernière ligne : jouer($mot, $categorie);.
- Dans pendu/interface.php, créer la fonction jouer. Elle reçoit $mot (string) et $categorie (string). Retourne void.
- Écrire uniquement la première ligne du corps : appeler initialiserJeu en lui passant $mot et $categorie. Stocker le résultat dans $etat.
⚠️ initialiserJeu n'existe pas encore. On descend ! Mais elle a besoin de fonctions utilitaires (normaliserLettre, compterLettresDifferentes) et de calculerVies. On va d'abord construire les outils, puis la logique d'initialisation.
Étape 07 : Les fonctions utilitaires
Pour créer initialiserJeu, on a besoin de normaliserLettre (passer en minuscule) et compterLettresDifferentes (compter les lettres distinctes d'un mot). On en profite pour créer aussi estLettreValide (vérifier qu'une saisie est bien une lettre de a à z) et lettreDejaProposee (vérifier si une lettre a déjà été proposée), car elles seront nécessaires très bientôt dans demanderLettre. Ces 4 fonctions utilitaires vont dans pendu/outils.php.
Ouvrir pendu/outils.php et activer le typage strict.
7.1 : normaliserLettre
Le joueur peut saisir en majuscule ou minuscule. Pour comparer, on ramène tout en minuscule. C'est la fonction la plus simple du projet, mais elle sera utilisée partout.
- Créer la fonction. Reçoit $lettre (string), retourne string.
- Appeler strtolower en lui passant $lettre. Retourner directement le résultat. Cette fonction transforme chaque majuscule en minuscule. Les caractères déjà en minuscule ne sont pas affectés.
- Tester : 'A' → a, 'z' → z, 'BONJOUR' → bonjour.
7.2 : estLettreValide
Le joueur pourrait taper un chiffre, un symbole ou plusieurs caractères. Il faut vérifier que c'est une seule lettre de a à z.
- Créer la fonction. Reçoit $input (string), retourne bool.
- Vérifier la longueur. Appeler strlen en lui passant $input. Si le résultat n'est pas 1, retourner false.
- Normaliser avec normaliserLettre($input). Stocker dans $lettre.
- Vérifier entre 'a' et 'z'. Retourner $lettre >= 'a' && $lettre <= 'z'. En PHP, on peut comparer des caractères : cela compare leur position dans la table ASCII.
- Tester : 'a' → true, 'Z' → true, '3' → false, 'ab' → false, '' → false, '!' → false.
7.3 : lettreDejaProposee
On ne veut pas que le joueur perde une vie en reproposant une lettre déjà tentée.
- Créer la fonction. Reçoit $lettre (string) et $lettresProposees (array). Retourne bool.
- Normaliser $lettre avec normaliserLettre. Stocker dans $lettreNormalisee.
- Parcourir $lettresProposees avec foreach. À chaque tour, normaliser la lettre du tableau. Si elle est identique à $lettreNormalisee, retourner true.
- Après la boucle, retourner false.
- Tester avec ['a','b','c'] : 'a' → true, 'A' → true, 'd' → false.
7.4 : compterLettresDifferentes
Compte le nombre de lettres distinctes dans un mot. "banane" contient 4 lettres différentes (b, a, n, e). Servira à adapter le nombre de vies à la difficulté du mot.
- Créer la fonction. Reçoit $mot (string), retourne int.
- Créer un tableau vide $lettres. L'astuce : on utilise un tableau associatif où chaque lettre sert de clé et la valeur est true. Si la même lettre apparaît deux fois, elle écrase la précédente sans créer de doublon. Pourquoi ? Un tableau associatif ne peut avoir qu'une seule entrée par clé.
- Récupérer la longueur avec strlen. Stocker dans $longueur.
- Parcourir chaque caractère avec une boucle for de 0 à $longueur - 1. Accéder au caractère avec $mot[$i]. Normaliser avec normaliserLettre. Stocker dans $lettre. Ajouter : $lettres[$lettre] = true;.
- Retourner count($lettres).
- Tester : 'banane' → 4, 'hippopotame' → 8, 'chat' → 4, 'aaa' → 1.
🎉 pendu/outils.php est complet !
Étape 08 : Initialiser l'état du jeu
On peut maintenant créer initialiserJeu dans pendu/logiqueDuJeu.php. Elle a besoin de calculerVies pour adapter le nombre de vies au mot (formule : (longueur + lettres différentes) / 2 + 2, minimum 5, maximum 12). Elle retourne un tableau associatif contenant 9 clés représentant l'état complet de la partie : le mot (en minuscule), la catégorie, les vies (restantes et max), les listes de lettres (proposées et trouvées, vides au départ), le tour (1), et deux booléens (terminé et victoire, à false).
Ouvrir pendu/logiqueDuJeu.php, activer le typage strict, et inclure outils.php avec require_once __DIR__ . '/outils.php'.
8.1 : calculerVies
- Créer la fonction. Reçoit $mot (string), retourne int.
- Récupérer la longueur avec strlen. Stocker dans $longueur.
- Compter les lettres différentes en appelant compterLettresDifferentes. Stocker dans $lettresDifferentes.
- Calculer : additionner $longueur et $lettresDifferentes, diviser par 2, convertir en entier avec (int), ajouter 2. Stocker dans $vies.
- Appliquer plancher et plafond. Si $vies < 5, le mettre à 5. Si > 12, le mettre à 12.
- Retourner $vies.
- Tester : 'riz' → 5, 'mathematiques' → 12.
8.2 : initialiserJeu
- Créer la fonction. Reçoit $mot (string) et $categorie (string). Retourne array.
- Normaliser le mot avec normaliserLettre($mot). Stocker dans $motNormalise.
- Calculer les vies avec calculerVies($motNormalise). Stocker dans $vies.
- Construire et retourner le tableau :
- 'mot' → $motNormalise
- 'categorie' → $categorie
- 'vies' → $vies (diminuera à chaque erreur)
- 'viesMax' → $vies (valeur initiale, pour l'affichage « 5/8 »)
- 'lettresProposees' → []
- 'lettresTrouvees' → []
- 'tour' → 1
- 'termine' → false
- 'victoire' → false
- Tester : initialiserJeu('ELEPHANT', 'animaux'). Vérifier que 'mot' vaut 'elephant', que les vies sont identiques, que les tableaux sont vides.
Tester dans jouer
- Revenir dans jouer. Après $etat = initialiserJeu($mot, $categorie);, ajouter un echo temporaire avec print_r($etat).
- Ajouter le message de début : === DÉBUT DE LA PARTIE ===, la catégorie et le nombre de vies.
- Exécuter, vérifier que l'état s'affiche correctement, puis supprimer le echo temporaire.
Étape 09 : Écrire la boucle de jeu dans jouer (affichage et saisie)
On continue dans jouer en écrivant la boucle while (!$etat['termine']). Les deux premières actions de la boucle sont : afficher l'état du jeu et demander une lettre. On descend pour créer afficherEtatJeu (qui a besoin de afficherMotMasque pour montrer les tirets et lettres trouvées) et demanderLettre (qui utilise estLettreValide et lettreDejaProposee créées à l'étape 7).
9.1 : afficherMotMasque
Génère une chaîne où les lettres trouvées sont visibles et les autres remplacées par des tirets (ex. : c-a-).
- Dans pendu/logiqueDuJeu.php, créer la fonction. Reçoit $etat (array), retourne string.
- Récupérer le mot depuis $etat['mot']. Créer un index avec array_flip($etat['lettresTrouvees']). Stocker dans $index. array_flip inverse clés et valeurs : ['c','a'] devient ['c'=>0, 'a'=>1]. L'intérêt : vérifier l'existence d'une lettre avec isset($index['c']) est très rapide.
- Construire l'affichage. Créer $affichage vide. Parcourir chaque caractère du mot avec for. Si la lettre est dans l'index, l'ajouter. Sinon, ajouter '-'. On peut utiliser l'opérateur ternaire : $affichage .= isset($index[$mot[$i]]) ? $mot[$i] : '-';
- Retourner $affichage.
9.2 : afficherEtatJeu
- Dans pendu/interface.php, créer la fonction. Reçoit $etat (array). Retourne void.
- Afficher :
- Une ligne de séparation de 50 signes = avec str_repeat('=', 50).
- Le tour, la catégorie, les vies restantes (format : 5 / 8).
- Les lettres proposées : si le tableau n'est pas vide, les joindre avec implode(', ', $etat['lettresProposees']). Sinon, afficher aucune.
- Le mot masqué : afficherMotMasque($etat).
- Une ligne de séparation pour fermer.
9.3 : demanderLettre
- Dans pendu/interface.php, créer la fonction. Reçoit $etat (array). Retourne string.
- Créer une boucle while (true).
- Afficher Proposez une lettre : .
- Lire avec readline(). Si false (Ctrl+D), afficher Partie interrompue. et exit(0).
- Nettoyer avec trim. Stocker dans $input.
- Vérifier les erreurs dans l'ordre (à chaque erreur, afficher un message et continue pour relancer la boucle) :
- Si $input est vide : Erreur : Vous devez saisir une lettre.
- Si strlen($input) > 1 : Erreur : Veuillez saisir une seule lettre à la fois.
- Si estLettreValide($input) est false : Erreur : Veuillez saisir uniquement des lettres de a à z.
- Si lettreDejaProposee($input, $etat['lettresProposees']) est true : Erreur : Vous avez déjà proposé cette lettre.
- Si tout passe, retourner $input.
Ajouter dans jouer et tester
- Revenir dans jouer. Après le message de début, écrire while (!$etat['termine']). Dans la boucle, écrire les deux premières lignes : afficherEtatJeu($etat); et $lettre = demanderLettre($etat);. Ajouter un echo temporaire pour afficher la lettre saisie, puis un break; pour sortir de la boucle après un seul tour (temporaire).
- Exécuter. On doit voir l'état du jeu (avec des tirets), pouvoir taper une lettre, et voir la lettre s'afficher. Tester les cas d'erreur (vide, chiffre, symbole). Supprimer le echo et le break.
Étape 10 : Traiter une proposition de lettre
La suite de la boucle de jouer appelle traiterProposition($etat, $lettre). C'est le cœur mécanique du jeu, dans pendu/logiqueDuJeu.php. Elle a besoin de lettreDansMot et de motComplete. Elle reçoit l'état et une lettre, travaille sur sa copie locale, et retourne un tableau avec deux clés : 'etat' (l'état mis à jour) et 'correcte' (booléen). L'appelant récupère le nouvel état avec $etat = $resultat['etat'].
10.1 : lettreDansMot
- Dans pendu/logiqueDuJeu.php, créer la fonction. Reçoit $lettre (string) et $mot (string). Retourne bool.
- Chercher la lettre. Appeler strpos avec $mot et normaliserLettre($lettre). Retourner strpos(...) !== false. Piège classique : utiliser !== et non !=. Si la lettre est en position 0, strpos retourne 0 et 0 != false vaut false en PHP. Avec !==, PHP vérifie aussi le type : 0 !== false vaut true.
- Tester : ('c','chat') → true, ('z','chat') → false, ('C','chat') → true.
10.2 : motComplete
- Créer la fonction. Reçoit $etat (array), retourne bool.
- Récupérer le mot. Créer un index avec array_flip($etat['lettresTrouvees']).
- Parcourir chaque caractère avec for. Si une lettre n'est pas dans l'index (!isset), retourner false.
- Après la boucle, retourner true.
10.3 : traiterProposition
En PHP, un tableau passé en paramètre est une copie. Les modifications dans la fonction n'affectent pas l'original. On retourne donc le tableau modifié.
- Créer la fonction. Reçoit $etat (array) et $lettre (string). Retourne array (clés 'etat' et 'correcte').
- Normaliser la lettre. Stocker dans $lettreNormalisee.
- Gérer le doublon. Si la lettre est déjà dans $etat['lettresProposees'] (in_array), retourner l'état inchangé avec 'correcte' valant lettreDansMot(...).
- Ajouter aux propositions : $etat['lettresProposees'][] = $lettreNormalisee;
- Si dans le mot : ajouter à lettresTrouvees si absente. Appeler motComplete. Si complet, mettre termine et victoire à true. Retourner ['etat' => $etat, 'correcte' => true].
- Si pas dans le mot : décrémenter $etat['vies']. Si vies ≤ 0, mettre termine à true, victoire à false. Retourner ['etat' => $etat, 'correcte' => false].
- Tester : initialiser avec 'chat'. Récupérer l'état à chaque appel ($etat = $resultat['etat'];). Proposer 'c' (correcte), 'z' (incorrecte, vies - 1), reproposer 'c' (doublon, état inchangé).
Étape 11 : Tour suivant et fin de partie
Deux dernières fonctions à créer. tourSuivant (dans pendu/logiqueDuJeu.php) retourne une copie de l'état avec le compteur de tour incrémenté. afficherFinPartie (dans pendu/interface.php) affiche victoire ou défaite et révèle le mot.
11.1 : tourSuivant
- Dans pendu/logiqueDuJeu.php, créer la fonction. Reçoit $etat (array). Retourne array.
- Incrémenter $etat['tour'] avec ++.
- Retourner $etat. L'appelant fera $etat = tourSuivant($etat);.
11.2 : afficherFinPartie
- Dans pendu/interface.php, créer la fonction. Reçoit $etat (array). Retourne void.
- Afficher : une ligne de séparation ; si $etat['victoire'] est true, afficher VICTOIRE ! Vous avez trouvé le mot ! ; sinon DÉFAITE ! Vous avez épuisé toutes vos vies. ; dans les deux cas, afficher Le mot était : suivi de $etat['mot'] ; une ligne de séparation.
🎉 pendu/logiqueDuJeu.php est complet ! Toute la mécanique du jeu fonctionne.
Étape 12 : Assembler la boucle de jeu et tester
Toutes les fonctions existent ! On remonte dans jouer pour compléter la boucle while avec le traitement de la proposition, le feedback (bonne/mauvaise lettre), le passage au tour suivant, et l'affichage de fin de partie. Puis on teste le jeu complet.
12.1 : Compléter la boucle dans jouer
- Revenir dans la boucle while de jouer. Après $lettre = demanderLettre($etat);, ajouter les lignes suivantes :
- Traiter la proposition : appeler traiterProposition($etat, $lettre). Stocker le résultat dans $resultat. Puis mettre à jour l'état : $etat = $resultat['etat'];. C'est essentiel car la fonction travaille sur une copie.
- Afficher le feedback : si $resultat['correcte'] est true, afficher "Bonne lettre !". Sinon, afficher "Mauvaise lettre ! Vous perdez 1 vie.".
- Passer au tour suivant : si la partie n'est pas terminée (!$etat['termine']), appeler tourSuivant($etat) et stocker le résultat dans $etat.
- Après la boucle, appeler afficherFinPartie($etat);.
12.2 : Tester le jeu complet
- Exécuter php ./index.php depuis la racine du projet.
- Choisir une catégorie et jouer une partie complète en vérifiant :
- Proposer une bonne lettre → "Bonne lettre !" et le mot masqué se complète.
- Proposer une mauvaise lettre → "Mauvaise lettre !" et les vies diminuent.
- Reproposer une lettre déjà proposée → message d'erreur, pas de perte de vie.
- Taper un chiffre, un symbole ou plusieurs lettres → message d'erreur approprié.
- Gagner une partie (trouver toutes les lettres).
- Perdre une partie (épuiser toutes les vies).
- Relancer plusieurs parties pour tester différentes catégories et l'option hasard.
🎉 Félicitations, le jeu du pendu est terminé ! Vous avez construit un projet complet en suivant l'approche descendante : à chaque fois que vous rencontriez une fonction inconnue, vous descendiez pour la construire, vous la testiez, et vous remontiez pour continuer. Vous n'avez jamais écrit de code sans savoir pourquoi.