TD 6 : Jeu de Yam’s#

Notes aux enseignants

Le but du petit exercice suivant est de montrer aux élèves que les boucles imbriquées n’ont rien de magique ou mystérieux, qu’il s’agit juste d’appliquer rigoureusement ce qu’ils ont déjà vu à propos des boucles for: on fait d’abord une unique fois l’initialisation, puis on teste la condition, et si elle est vraie on fait le corps de la boucle, puis l’incrémentation, puis on re-teste la condition, etc. Simplement dans le cas des boucles imbriquées, notre boucle contient une boucle, donc quand on en est à l’étape « faire le corps de la boucle », eh bien on fait la boucle interne, tout simplement.

Exécutez pas à pas ce petit bout de programme pour bien leur faire comprendre le principe, sans y passer trop de temps (environ 5 minutes).

Exercice 1 : Aparté: boucles imbriquées

Devinez l’affichage du fragment de programme mystère suivant :

for ( int i = 1; i <= 4; i++ ) {
    for ( int j = 1; j <= i; j++ ) {
        cout << "(" << i << "," << j << ") ";
    }
    cout << endl;
}

/// BEGIN SOLUTION

(1,1) 
(2,1) (2,2) 
(3,1) (3,2) (3,3) 
(4,1) (4,2) (4,3) (4,4)

/// END SOLUTION

Problème : le jeu de Yam’s#

Notes aux enseignants

Le problème est relativement long, mais sera également le sujet du TP. L’idée du TD est de leur présenter les différentes fonctions utilitaires, quelques cas de reconnaissance de figures ainsi que le fonctionnement global du jeu.

S’ils ont bien compris le fonctionnement global les étudiants devraient avoir suffisamment de temps pour y re-réfléchir et implémenter complètement le jeu pendant le TP, sous forme de fiche Jupyter et d’un code compilé de manière traditionnelle.

Notes aux enseignants

Si besoin, motivez vos élèves en leur disant que l’examen mi-semestre est très bientôt et que ce sujet de TD/TP est adapté d’un sujet des années précédentes.

Cette fiche de révision est l’occasion d’insister sur certains points encore mal compris de beaucoup d’élèves, par exemple la notion de paramètre, qui correspond à ce que la fonction a besoin qu’on lui fournisse quand on l’appelle. En particulier quand quelque chose est pris en paramètre, par exemple un tableau, la fonction ne doit pas redéclarer ni re-allouer ni ré-initialiser le paramètre (exemple : paramètre des de la fonction afficheDes ou de la fonction compteDes). Par contre quand une fonction est censée renvoyer un nouveau tableau, c’est à elle de déclarer, allouer et initialiser le tableau (exemple : variable res de la fonction compteDes).

Autre notion fondamentale à rappeler : un return stoppe une fonction, on ne doit donc jamais écrire quelque chose du style : for(...){ if(...) return ... else return ... }. Exemple : fonction chercheDansTableau qui si elle est mal écrite ne teste que la première case du tableau.

Enfin profitez de chaque fonction pour insister sur les règles d’écriture de l’en-tête d’une fonction, et la présence ou absence de return.

Une idée pour faire comprendre aux élèves la différence entre return et cout (affichage): c’est comme la différence entre une voiture et une photo de voiture. Si on a une voiture on peut l’utiliser, tandis que si on a une photo de voiture on peut juste la regarder. Faire référence à ce qui avait été fait en TP, où on pouvait écrire abs(a) + abs(b) tandis que afficheAbs(a) + afficheAbs(b) donne une erreur.

Le jeu de Yam’s (ou Yahtzee) est un jeu de dés dont le but est d’enchaîner les combinaisons à l’aide de cinq dés pour remporter un maximum de points.

Nous ne nous intéressons ici qu’à une version simplifiée du Yam’s et chercherons à reconnaître les figures suivantes :

  • brelan: 3 dés identiques parmi les 5 dés

  • yam’s: les 5 dés identiques.

Les figures permettent de marquer des points. À chacune de ces figures sont associés des bonus : 10 pour le brelan, et 60 pour le yam’s. À cela, on ajoute la somme des dés qui composent la figure. Par exemple, les dés \(\lbrace 2, 5, 3, 5, 5 \rbrace\) permettent de marquer 10 points de bonus (brelan) et \(5 + 5 + 5 = 15\) points, soit au total 25 points. Le but des prochains exercices est de commencer l’implantation d’un jeu de Yam’s basique.

Exercice 2 : Échauffement : trois fonctions utilitaires

  1. Spécifiez (documentation) et implantez (code) une fonction afficheDes qui affiche le contenu d’un tableau d’entiers, en affichant chaque entier du tableau suivi d’un espace. Ainsi l’appel afficheDes({1,2,3,5,4}) devra entraîner l’affichage 1 2 3 5 4 .

    /// BEGIN SOLUTION

    void afficheDes(vector<int> des) {
        for (int i = 0; i < des.size(); i++) {
            cout << des[i] << " ";
        }
        cout << endl;
    }
    

    /// END SOLUTION

  2. Spécifiez et implantez une fonction chercheDansTableau qui cherche l’emplacement d’un entier donné dans un tableau d’entiers. Si l’entier est présent dans le tableau, chercheDansTableau renvoie l’indice d’une case du tableau le contenant. Si l’entier n’est pas présent dans le tableau on renverra -1. Ainsi:

    • l’appel chercheDansTableau(3, {1,2,3}) devra renvoyer 2,

    • l’appel chercheDansTableau(4, {1,2,3}) devra renvoyer -1, etc.

    /// BEGIN SOLUTION

    
    /** Cherche si un entier est présent dans un tableau
     * @param n un entier
     * @param des un tableau d'entiers
     * @return -1 si l'entier n n'est pas dans le tableau,
     *         l'indice de son emplacement dans le tableau sinon
     **/
    int chercheDansTableau(int n, vector<int> des) {
        for (int i = 0; i < des.size(); i++) {
            if (des[i] == n) {
                return i;
            }
        }
        // Si on n'a pas trouvé l'entier on renvoie -1.
        return -1;
    }
    

    /// END SOLUTION

  3. Spécifiez et implantez une fonction nombreOccurrences qui prend en paramètre un tableau d’entiers t et un entier v, et qui renvoie le nombre d’occurrences de v dans t (combien de fois il apparaît).

    /// BEGIN SOLUTION

    
    /** Compte le nombre d'occurrences d'un entier dans un tableau
     * @param un tableau d'entiers t
     * @param un entier v
     * @return un entier
     **/
    int nombreOccurences(vector<int> des, int v) {
        int res = 0;
        for (int i = 0; i < des.size(); i++) {
            if ( des[i] == v ) {
                res ++;
            }
        }
        return res;
    }
    

    /// END SOLUTION

Exercice 3 :

  1. Observez les tests suivants et déduisez-en la spécification (rôle, entrées et sortie) de la fonction compteDes:

    CHECK( compteDes({1,1,1,1,1}) == vector<int>({5,0,0,0,0,0}) );
    CHECK( compteDes({2,2,2,2,2}) == vector<int>({0,5,0,0,0,0}) );
    CHECK( compteDes({3,3,3,3,3}) == vector<int>({0,0,5,0,0,0}) );
    CHECK( compteDes({4,4,4,4,4}) == vector<int>({0,0,0,5,0,0}) );
    CHECK( compteDes({5,5,5,5,5}) == vector<int>({0,0,0,0,5,0}) );
    CHECK( compteDes({6,6,6,6,6}) == vector<int>({0,0,0,0,0,5}) );
    CHECK( compteDes({1,2,3,4,5}) == vector<int>({1,1,1,1,1,0}) );
    CHECK( compteDes({2,2,6,2,2}) == vector<int>({0,4,0,0,0,1}) );
    CHECK( compteDes({4,1,4,1,1}) == vector<int>({3,0,0,2,0,0}) );
    
  2. Proposez une implantation de cette fonction.

    /// BEGIN SOLUTION

    vector<int> compteDes(vector<int> des) {
        vector<int> res = {0, 0, 0, 0, 0, 0};
        for (int i = 0; i < des.size(); i++) {
            // les indices des cases vont de 0 à 5, les valeurs de  de 1 à 6
            int indice = des[i] - 1;
            res[indice] += 1;
        }
        return res;
    }
    

    /// END SOLUTION

Exercice 4 : Yam’s!

  1. Le yam’s (cinq chiffres identiques) est la figure la plus facile à reconnaître. Spécifiez et implantez une fonction pointsFigureYams qui, lorsqu’on lui donne en entrée un tableau contenant 5 entiers, renvoie les points obtenus (somme des 5 dés + 60) s’il s’agit d’un yam’s, 0 sinon.

    /// BEGIN SOLUTION

    int pointsFigureYams(vector<int> des) {
        // Variable locale :
        int val = des[0];  // Pour vérifier que tous les dés sont égaux au premier
    
        for (int i = 1; i < des.size(); i++) {
            if (val != des[i]) {
                // Si un  est différent du premier alors pas de yams
                return 0;
            }
        }
        return val * 5 + 60 ;
    }
    

    /// END SOLUTION

  2. Complétez la liste de tests suivante avec au moins deux autres cas que vous jugez intéressants :

    CHECK( pointsFigureYams({4,4,4,4,4}) == 80 );
    CHECK( pointsFigureYams({1,1,1,1,1}) == 65 );
    

    /// BEGIN SOLUTION

        CHECK( pointsFigureYams({4, 4, 4, 4, 5}) == 0 );
        CHECK( pointsFigureYams({2, 1, 1, 1, 1}) == 0 );
    

    /// END SOLUTION

  3. Pour simplifier la fonction pointsFigureYams, on peut utiliser les fonctions compteDes et chercheDansTableau. Donnez une nouvelle implantation de pointsFigureYams.

    /// BEGIN SOLUTION

    int pointsFigureYams2(vector<int> des) {
        int indiceYams = chercheDansTableau(5, compteDes(des));
    
        if (indiceYams >= 0) {
            return (indiceYams + 1) * 5 + 60 ;
            // +1 car l'indice d'une case du tableau correspond à la valeur - 1
        } else {
            return 0;
        }
    }
    

    /// END SOLUTION

Exercice 5 : Brelan

  1. À l’image de la question 2 de l’exercice précédent, proposez des tests pour une fonction pointsFigureBrelan qui, lorsqu’on lui donne en entrée un tableau contenant 5 entiers, renvoie les points obtenus (somme des 3 dés qui forment un brelan + 10) s’il s’agit d’un brelan, 0 sinon.

    /// BEGIN SOLUTION

        CHECK( pointsFigureBrelan({4, 4, 4, 1, 1}) == 22 );
        CHECK( pointsFigureBrelan({1, 1, 4, 4, 5}) == 0 );
        CHECK( pointsFigureBrelan({1, 1, 1, 1, 5}) == 13 );
    

    /// END SOLUTION

  2. Spécifiez et implantez cette fonction en vous aidant de la fonction compteDes.

    /// BEGIN SOLUTION

    int pointsFigureBrelan(vector<int> des) {
        // On commence par compter les dés.
        vector<int> compte = compteDes(des);
    
        // On regarde si on a un brelan (3 valeurs identiques au moins).
        for (int i = 0; i < compte.size(); i ++) {
            if (compte[i] >= 3) {
                // Si oui, on renvoie la valeur du brelan correspondant.
                // Rappelons que l'entier stocké en position i du tableau "compte"
                // correspond au nombre de dés qui affichent (i + 1)
                return (i + 1) * 3 + 10;
            }
        }
        // En l'absence de brelan on renvoie un score nul.
        return 0;
    }
    

    /// END SOLUTION

Exercice 6 : Le jeu

  1. Implantez une fonction pointsFigure qui, étant donné un tableau de cinq dés et le nom d’une figure parmi «brelan» et «yams», renvoie le score associé en appelant respectivement la fonction pointsFigureBrelan ou la fonction pointsFigureYams. Cette fonction doit renvoyer 0 si le nom de figure entré n’est pas valide.

    /// BEGIN SOLUTION

    int pointsFigure(vector<int> des, string figure) {
        if (figure == "brelan") {
            return pointsFigureBrelan(des);
        } else if (figure == "yams") {
            return pointsFigureYams(des);
        } else if (figure == "full") {  // En question supplémentaire du TP
            return pointsFigureFull(des);
        } else if (figure == "carre") { // En question supplémentaire du TP
            return pointsFigureCarre(des);
        }  else {
            return 0;
        }
    }
    

    /// END SOLUTION

  2. Donner la spécification et l’implantation d’une fonction lanceDes qui renvoie un tableau contenant cinq entiers choisis aléatoirement entre 1 et 6. Pour cela vous pouvez utiliser une fonction int aleaInt(int a, int b) qui étant donné deux entiers \(a\) et \(b\) renvoie un entier aléatoire \(n\) tel que \(a \leq n \leq b\).

    /// BEGIN SOLUTION

    vector<int> lanceDes() {
        vector<int> des;
        des = vector<int>(5);
    
        for (int i = 0; i < des.size(); i++) {
            des[i] = aleaInt(1, 6);
        }
    
        return des;
    }
    

    /// END SOLUTION

  3. Complétez le squelette de code ci-dessous (3 endroits à modifier) pour :

    • lancer les dés;

    • afficher le tirage au joueur et lui demander d’entrer une figure tant que sa réponse est différente de «brelan», «yams» et «exit»;

    • si le joueur choisit «brelan» ou «yams», afficher les points qu’il marque.

    string reponseJoueur = "";
    vector<int> des;
    // INSERER VOTRE CODE ICI
    
    while (     reponseJoueur != "brelan"
                and reponseJoueur != "yams"
                and reponseJoueur != "exit") {
    
        // INSERER VOTRE CODE ICI
    
        // L'instruction suivante permet d'attendre que le joueur
        // entre une phrase dans le terminal et stocke sa réponse
        // dans la chaine de caractères "reponseJoueur"
        cin >> reponseJoueur;
    }
    // INSERER VOTRE CODE ICI
    

    /// BEGIN SOLUTION

        string reponseJoueur = "";
        vector<int> des;
    
        des = lanceDes();
    
        while (reponseJoueur != "brelan"
               and reponseJoueur != "yams"
               and reponseJoueur != "exit") {
    
            afficheDes(des);
            cout << "Quelle figure choisissez-vous ? (brelan ou yams, exit pour quitter) "
                 << endl;
    
            // L'instruction suivante attend que le joueur tape
            // une phrase au clavier, puis stocke cette phrase
            // dans une chaine de caractères "reponseJoueur"
            cin >> reponseJoueur;
        }
    
        if (reponseJoueur != "exit") {
            cout << "Vous marquez " << pointsFigure(des,
                                                    reponseJoueur) << " points !" << endl;
        }
        return 0;
    

    /// END SOLUTION

  4. Une partie de Yam’s consiste en de nombreux lancers successifs des dés. Introduisez une boucle supplémentaire pour refléter ce comportement tant que le joueur ne tape pas «exit». Ajoutez un calcul du score total de la partie, qui est la somme des scores de chaque lancer.

    /// BEGIN SOLUTION

        string reponseJoueur = "";
        int score = 0;
        vector<int> des;
    
        do {
            reponseJoueur = "";
            des = lanceDes();
    
            while (reponseJoueur != "brelan"
                   and reponseJoueur != "yams"
                   and reponseJoueur != "exit") {
    
                afficheDes(des);
                cout << "Quelle figure choisissez-vous ? (brelan ou yams, exit pour quitter) "
                     << endl;
    
                // L'instruction suivante permet d'attendre que le joueur
                // entre une phrase dans le terminal et stocke sa réponse
                // dans une chaine de caractères "reponseJoueur"
                cin >> reponseJoueur;
            }
    
            if (reponseJoueur != "exit") {
                int points = pointsFigure(des, reponseJoueur);
                score += points;
                cout << "Vous marquez " << points << " points. Score total: " << score <<
                     endl;
            }
        } while (reponseJoueur != "exit");
    

    /// END SOLUTION

Exercice 7 : \(\clubsuit\) Relance

Dans le vrai jeu de Yam’s, le joueur peut relancer jusqu’à trois fois un ou plusieurs dés avant de choisir une figure.

  1. Ajoutez une fonction vector<int> relance(int numDe, vector<int> des) qui «relance» uniquement le dé numéro numDe choisi en premier argument et le remplace donc par un nouvel entier aléatoire entre 1 et 6.

    /// BEGIN SOLUTION

    vector<int> relance(int numDe, vector<int> des) {
        des[numDe - 1] = aleaInt(1, 6);
        return des;
    }
    

    /// END SOLUTION

  2. Dans la boucle de jeu, ajoutez les instructions nécessaires pour que le joueur puisse choisir jusqu’à trois dés à relancer et les relancer.

Exercice 8 : \(\clubsuit\) Scores

La partie de Yam’s se termine lorsqu’un joueur a marqué des points pour toutes les figures possibles.

  1. Ajoutez dans la fonction main un tableau de scores contenant une case pour chaque figure.

    /// BEGIN SOLUTION /// END SOLUTION

  2. Lorsque le joueur choisit une figure, les points qu’il gagne doivent être stockés dans la partie correspondante du tableau. Une fois une case du tableau remplie, elle ne peut plus être modifiée.

    /// BEGIN SOLUTION /// END SOLUTION

  3. La partie se termine lorsque toutes les cases du tableau sont remplies. Le score du joueur correspond à la somme des cases du tableau.

    /// BEGIN SOLUTION /// END SOLUTION