La solution (1 : le code du jeu)



Si vous lisez ces lignes, c'est soit que vous avez terminé le programme, soit que vous n'arrivez pas à le terminer.

J'ai personnellement mis plus de temps que je ne le pensais pour réaliser ce petit jeu apparemment tout bête. C'est souvent comme ça : on se dit « bah c'est facile » alors qu'en fait, il y a plusieurs cas à gérer.

Je persiste toutefois à dire que vous êtes tous capables de le faire. Il vous faudra plus ou moins de temps (quelques minutes, quelques heures, quelques jours ?), mais ça n'a jamais été une course. Je préfère que vous y passiez beaucoup de temps et que vous y arriviez, plutôt que vous n'essayiez que 5 minutes et que vous regardiez la solution.

N'allez pas croire que j'ai écrit le programme d'une traite. Moi aussi, comme vous, j'y suis allé pas à pas. J'ai commencé par faire quelque chose de très simple, puis petit à petit j'ai amélioré le code pour arriver au résultat final.


J'ai fait plusieurs erreurs en codant : j'ai oublié à un moment d'initialiser une variable correctement, j'ai oublié d'écrire le prototype d'une fonction ou encore de supprimer une variable qui ne servait plus dans mon code. J'ai même – je l'avoue – oublié un bête point-virgule à un moment à la fin d'une instruction.

Tout ça pour dire quoi ? Que je ne suis pas infaillible et que je vis à peu près les mêmes frustrations que vous (« ESPÈCE DE PROGRAMME DE ***** TU VAS TE METTRE À MARCHER, OUI OU NON !? »).

Je vais vous présenter la solution en deux temps.

  1. D'abord je vais vous montrer comment j'ai fait le code du jeu lui-même, en fixant le mot caché directement dans le code. J'ai choisi le mot MARRON car il me permet de tester si je gère bien les lettres en double, comme le R ici.
  2. Ensuite, je vous montrerai comment dans un second temps j'ai ajouté la gestion du dictionnaire de mots pour tirer au sort un mot secret pour le joueur.


Bien sûr, je pourrais vous montrer tout le code d'un coup mais… ça ferait beaucoup à la fois, et nombre d'entre vous n'auraient pas le courage de se pencher sur le code.

Je vais essayer de vous expliquer pas à pas mon raisonnement. Retenez que ce qui compte, ce n'est pas le résultat, mais la façon dont on réfléchit.

Analyse de la fonction main


Comme tout le monde le sait, tout commence par un main. On n'oublie pas d'inclure les bibliothèques stdiostdlib et ctype (pour la fonction toupper()) dont on aura besoin :

#include <stdio.h>

#include <stdlib.h>

#include <ctype.h>

   

int main(int argc, char* argv[])

{

    

    return 0;

}


Ok, jusque-là tout le monde devrait suivre.


Notre main va gérer la plupart du jeu et faire appel à quelques-unes de nos fonctions quand il en aura besoin.

Commençons par déclarer les variables nécessaires. Rassurez-vous, je n'ai pas pensé de suite à toutes ces variables, il y en avait un peu moins la première fois que j'ai écrit le code !

#include <stdio.h>

#include <stdlib.h>

#include <ctype.h>

   

int main(int argc, char* argv[])

{

    char lettre = 0; // Stocke la lettre proposée par l'utilisateur (retour du scanf)

    char motSecret[] = "MARRON"; // C'est le mot à trouver

    int lettreTrouvee[6] = {0}; // Tableau de booléens. Chaque case correspond à une lettre du mot secret. 0 = lettre non trouvée, 1 = lettre trouvée

    int coupsRestants = 10; // Compteur de coups restants (0 = mort)

    int i = 0; // Une petite variable pour parcourir les tableaux

    return 0;

}



J'ai volontairement écrit une déclaration de variable par ligne ainsi que plusieurs commentaires pour que vous compreniez l'intérêt de chaque variable. En pratique, vous n'aurez pas forcément besoin de mettre tous ces commentaires et vous pourrez grouper plusieurs déclarations de variables sur la même ligne.

Je pense que la plupart de ces variables semblent logiques : la variable lettre stocke la lettre que l'utilisateur tape à chaque fois, motSecret le mot à trouver, coupsRestants le nombre de coups, etc.
La variable i est une petite variable que j'utilise pour parcourir mes tableaux avec des for. Elle n'est donc pas extrêmement importante mais nécessaire si on veut faire nos boucles.

Enfin, la variable à laquelle il fallait penser, celle qui fait la différence, c'est mon tableau de booléens lettreTrouvee. Vous remarquerez que je lui ai donné pour taille le nombre de lettres du mot secret (6). Ce n'est pas un hasard : chaque case de ce tableau de booléens représente une lettre du mot secret. Ainsi, la première case représente la première lettre, la seconde la seconde lettre, etc.


Les cases du tableau sont au départ initialisées à 0, ce qui signifie « Lettre non trouvée ». Au fur et à mesure de l'avancement du jeu, ce tableau sera modifié. Pour chaque lettre du mot secret trouvée, la case correspondante du tableau lettreTrouvee sera mise à 1.

Par exemple, si à un moment du jeu j'ai l'affichage M*RR*N, c'est que mon tableau d'int a les valeurs 101101 (1 pour chaque lettre qui a été trouvée).


Il est ainsi facile de savoir quand on a gagné : il suffit de vérifier si le tableau de booléens ne contient que des 1.


En revanche, on a perdu si le compteur coupsRestants tombe à 0.

Passons à la suite :

printf("Bienvenue dans le Pendu !\n\n");



C'est un message de bienvenue, il n'y a rien de bien palpitant. En revanche, la boucle principale du jeu est plus intéressante :

while (coupsRestants > 0 && !gagne(lettreTrouvee))

{


Le jeu continue tant qu'il reste des coups (coupsRestants > 0) et tant qu'on n'a pas gagné.


Si on n'a plus de coups à jouer, c'est qu'on a perdu. Si on a gagné, c'est… qu'on a gagné. Dans les deux cas, il faut arrêter le jeu, donc arrêter la boucle du jeu qui redemande à chaque fois une nouvelle lettre.

gagne est une fonction qui analyse le tableau lettreTrouvee. Elle renvoie « vrai » (1) si le joueur a gagné (le tableau lettreTrouvee ne contient que des 1), « faux » (0) si le joueur n'a pas encore gagné.


Je ne vous explique pas ici le fonctionnement de cette fonction en détail, on verra cela plus tard. Pour le moment, vous avez juste besoin de savoir ce que fait la fonction.

La suite :

printf("\n\nIl vous reste %d coups a jouer", coupsRestants);

printf("\nQuel est le mot secret ? ");


/* On affiche le mot secret en masquant les lettres non trouvées

        Exemple : *A**ON */

        for (i = 0 ; i < 6 ; i++)

        {

            if (lettreTrouvee[i]) // Si on a trouvé la lettre n° i

                printf("%c", motSecret[i]); // On l'affiche

            else

                printf("*"); // Sinon, on affiche une étoile pour les lettres non trouvées

        }


On affiche à chaque coup le nombre de coups restants ainsi que le mot secret (masqué par des * pour les lettres non trouvées).


L'affichage du mot secret masqué par des * se fait grâce à une boucle for. On analyse chaque lettre pour savoir si elle a été trouvée (if lettreTrouvee[i]). Si c'est le cas, on affiche la lettre. Sinon, on affiche une * de remplacement pour masquer la lettre.

Maintenant qu'on a affiché ce qu'il fallait, on va demander au joueur de saisir une lettre :

printf("\nProposez une lettre : ");

lettre = lireCaractere();



Je fais appel à notre fonction lireCaractere(). Celle-ci lit le premier caractère tapé, le met en majuscule et vide le buffer, c'est-à-dire qu'elle vide les autres caractères qui auraient pu persister dans la mémoire.

// Si ce n'était PAS la bonne lettre

if (!rechercheLettre(lettre, motSecret, lettreTrouvee))

    {

        coupsRestants--; // On enlève un coup au joueur

    }

}


On vérifie si la lettre entrée se trouve dans motSecret. On fait appel pour cela à une fonction maison appelée rechercheLettre. Nous verrons peu après le code de cette fonction.


Pour le moment, tout ce que vous avez besoin de savoir, c'est que cette fonction renvoie « vrai » si la lettre se trouve dans le mot, « faux » si elle ne s'y trouve pas.

Mon if, vous l'aurez remarqué, commence par un point d'exclamation ! qui signifie « non ». La condition se lit donc « Si la lettre n'a pas été trouvée ».


Que fait-on si la lettre n'a pas été trouvée ? On diminue le nombre de coups restants.


Notez que la fonction rechercheLettre met aussi à jour le tableau de booléens lettreTrouvee. Elle met des 1 dans les cases des lettres qui ont été trouvées.


La boucle principale du jeu s'arrête là. On recommence donc au début de la boucle et on vérifie s'il reste des coups à jouer et si on n'a pas déjà gagné.

Lorsqu'on sort de la boucle principale du jeu, il reste à afficher si on a gagné ou non avant que le programme ne s'arrête :

if (gagne(lettreTrouvee))

    printf("\n\nGagne ! Le mot secret etait bien : %s", motSecret);

else

    printf("\n\nPerdu ! Le mot secret etait : %s", motSecret);


return 0;

}


On fait appel à la fonction gagne pour vérifier si on a gagné. Si c'est le cas, alors on affiche le message « Gagné ! » ; sinon, c'est qu'on n'avait plus de coups à jouer, on a été pendu.

Analyse de la fonction gagne


Voyons maintenant le code de la fonction gagne :

int gagne(int lettreTrouvee[])

{

    int i = 0;

    int joueurGagne = 1;


    for (i = 0 ; i < 6 ; i++)

    {

        if (lettreTrouvee[i] == 0)

            joueurGagne = 0;

    }


    return joueurGagne;

}


Cette fonction prend le tableau de booléens lettreTrouvee pour paramètre. Elle renvoie un booléen : « vrai » si on a gagné, « faux » si on a perdu.

Le code de cette fonction est plutôt simple, vous devriez tous le comprendre. On parcourt lettreTrouvee et on vérifie si UNE des cases vaut « faux » (0). Si une des lettres n'a pas encore été trouvée, c'est qu'on a perdu : on met alors le booléen joueurGagne à « faux » (0). Sinon, si toutes les lettres ont été trouvées, le booléen vaut « vrai » (1) et la fonction renverra donc « vrai ».

Analyse de la fonction rechercheLettre


La fonction rechercheLettre a deux missions :

  • renvoyer un booléen indiquant si la lettre se trouvait bien dans le mot secret ;
  • mettre à jour (à 1) les cases du tableau lettreTrouvee correspondant aux positions de la lettre qui a été trouvée.

int rechercheLettre(char lettre, char motSecret[], int lettreTrouvee[])

{

    int i = 0;

    int bonneLettre = 0;


    // On parcourt motSecret pour vérifier si la lettre proposée y est 

    for (i = 0 ; motSecret[i] != '\0' ; i++)

    {

        if (lettre == motSecret[i]) // Si la lettre y est

        {

            bonneLettre = 1; // On mémorise que c'était une bonne lettre

            lettreTrouvee[i] = 1; // On met à 1 la case du tableau de booléens correspondant à la lettre actuelle

        }

    }


    return bonneLettre;

}



On parcourt donc la chaîne motSecret caractère par caractère. À chaque fois, on vérifie si la lettre que le joueur a proposée est une lettre du mot. Si la lettre correspond, alors on fait deux choses :

  • on change la valeur du booléen bonneLettre à 1, pour que la fonction retourne 1 car la lettre se trouvait effectivement dans motSecret ;
  • on met à jour le tableau lettreTrouvee à la position actuelle pour indiquer que cette lettre a été trouvée.


L'avantage de cette technique, c'est qu'ainsi on parcourt tout le tableau (on ne s'arrête pas à la première lettre trouvée). Cela nous permet de bien mettre à jour le tableau lettreTrouvee, au cas où une lettre serait présente en plusieurs exemplaires dans le mot secret, comme c'est le cas pour les deux R de MARRON.

Créé avec HelpNDoc Personal Edition: Créer des fichiers d'aide Qt Help multi-plateformes