TP : le jeu de Yams#

Rappels d’utilisation des tableaux

  • Déclarer un tableau d’entiers tab :

    vector<int> tab;
    
  • Allouer 5 cases au tableau tab :

    tab = vector<int>(5);
    
  • Initialiser ses cases :

    tab[0] = 42; tab[1] = 3; ... ; tab[4] = 36;
    
  • Déclarer, allouer et initialiser en une seule instruction :

    vector<int> tab = {25,-3,10,7};
    
  • Il est également possible de construire un tableau sans lui donner de nom pour le passer à une fonction de la manière suivante :

    maFonction({17,9,-3,42});
    

Astuce

Lorsque vous avez besoin de redémarrer le noyau, utilisez le menu Noyau > Redémarrer le noyau et exécutez jusqu'à la cellule selectionnée.

Ainsi vous disposez d’un environnement d’exécution propre avec toutes les fonctions précédentes bien définies, ainsi que les instructions d’initialisation qui suivent.

#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;

using tableau = vector<int>;

// Initialisation de la fonction rand (pour les nombres aléatoires) :
srand(time(0));

Exercice 1 : Reconnaître les yam’s#

pointsFigureYams#

Complétez la documentation de la fonction pointsFigureYams ébauchée dans la cellule suivante, puis implémentez cette fonction dans la cellule d’après. Pour rappel, la fonction pointsFigureYams doit vérifier si les cinq valeurs de dés fournies forment un yam’s ou non. Si un yam’s est trouvé on renvoie le score correspondant (somme des dés + 60), sinon on renvoie 0.

/** Fonction pointsFigureYams
 * @param des
 * @return 
 **/
/// 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 dé est différent du premier alors pas de yams
      return 0;
    }
  }
  
  return (val * 5 + 60); 
}
/// END SOLUTION

La cellule suivante fournit deux tests pour la fonction pointsFigureYams. Dans la cellule qui la suit, écrivez aux moins deux autres tests, puis exécutez les deux cellules pour vérifier que votre fonction pointsFigureYams a bien le comportement attendu.

CHECK( pointsFigureYams({1,1,1,1,1}) == 65 );
CHECK( pointsFigureYams({2,1,1,1,1}) ==  0 );
/// BEGIN SOLUTION
CHECK( pointsFigureYams({2,2,2,2,2}) == 70 );
CHECK( pointsFigureYams({3,3,3,3,3}) == 75 );
CHECK( pointsFigureYams({4,4,4,4,4}) == 80 );
CHECK( pointsFigureYams({5,5,5,5,5}) == 85 );
CHECK( pointsFigureYams({6,6,6,6,6}) == 90 );
/// END SOLUTION

Compilation et tests en dehors de Jupyter#

Indication

Recommandation

Faites avec soin les questions de compilation pour vous y entraîner : cela vous servira pour la suite du semestre et notamment pour le projet qui sera entièrement sous forme compilée.

  • Ouvrez le fichier yams.cpp.

  • Copiez-collez votre fonction pointsFigureYams à un endroit adapté.

  • Insérez la ligne suivante à l’endroit indiqué dans la fonction main :

    cout << pointsFigureYams({1,1,1,1,1}) << endl;
    
  • Enregistrez votre fichier yams.cpp.

  • Ouvrez un terminal

  • Compilez votre fichier yams.cpp en lançant, depuis le répertoire Semaine6, la commande :

    clang++ yams.cpp -o yams
    

    Attention

    • En salle de TP : pour avoir accès à clang++, nous vous recommadons d’utiliser le terminal intégré dans JupyterLab afin d’être dans le bon environnement logiciel.

    • La commande doit être lancée dans le répertoire contenant yams.cpp. Si nécessaire, déplacez vous y avec la commande cd (par exemple cd ~/ProgImperative/Semaine6).

    • Pour alterner efficacement entre ce document contenant les consignes, l’éditeur de fichier contenant yams.cpp et le terminal, nous vous recommandons de disposer les onglets correspondants de sorte qu’ils soient tous visibles simultanément (voir la feuille Jupyter de la semaine dernière.

  • Exécutez le programme ainsi généré à l’aide de la commande ./yams. Votre terminal doit afficher 65.

Exercice 2 : Utilitaires#

Dans cet exercice, vous allez implanter plusieurs utilitaires qui pourront ensuite être combinés pour reconnaître les yams, les autres figures, et implanter le jeu lui-même.

aleaInt#

La fonction aleaInt(int a, int b) proposée ci-dessous permet de tirer au hasard un nombre entier compris entre a et b :

/** Fonction aleaInt
 * @param a un entier représentant le minimum du nombre aléatoire généré
 * @param b un entier représentant le maximum du nombre aléatoire généré
 * @return un entier aléatoire n tel que a <= n <= b
 **/
int aleaInt(int a, int b) {
    return rand() % (b - a + 1) + a;
}

lanceDes#

Complétez la documentation de la fonction lanceDes puis implémentez-la dans la case suivante en utilisant aleaInt. Pour rappel, la fonction lanceDes doit renvoyer un tableau contenant 5 valeurs tirées aléatoirement entre 1 et 6. (Comme la semaine dernière, pour contourner un bug dans cling on va mettre le type tableau à la place de vector<int>.)

/** Fonction lanceDes
 * @return 
 **/
tableau lanceDes() {
    /// BEGIN SOLUTION
    vector<int> t;
    t = vector<int>(5);

    for ( int i = 0; i < t.size(); i++ ) {
        t[i] = aleaInt(1, 6);
    }

    return t;
    /// END SOLUTION
}

Comme lanceDes renvoie des valeurs aléatoires, c’est un peu plus difficile de la tester. Exécutez à plusieurs reprises la cellule suivante :

lanceDes()

On vérifie qu’elle renvoie toujours un tableau de longueur \(5\) :

CHECK( lanceDes().size() == 5 )

afficheDes#

Pour afficher le contenu d’une variable, notamment dans un programme compilé, vous avez maintenant l’habitude d’utiliser cout. Cependant, par défaut, cout ne permet pas d’afficher un tableau. Vous pouvez le constater en décommentant la deuxième ligne de la cellule ci-dessous (enlever les // en début de ligne) et en exécutant la cellule. Remettez ensuite la deuxième ligne en commentaire et redémarrez le noyau.

vector<int> t = {5,-13,0,27};
// cout << t << endl;

Aussi, pour pouvoir afficher le contenu d’un tableau de dés, on va écrire une fonction afficheDes.

Complétez la documentation de la fonction afficheDes puis définissez la dans la cellule suivante. Pour rappel, cette fonction doit afficher les valeurs des dés qui lui sont donnés en entrée.

/** Fonction afficheDes
 * @param
 **/
/// BEGIN SOLUTION
void afficheDes(vector<int> des) {
    for ( int i = 0; i < des.size(); i++ ) {
        cout << des[i] << " ";
    }
    cout << endl;
}
/// END SOLUTION

Pour tester votre fonctions afficheDes, exécuter la cellule suivante et vérifier l’affichage obtenu.

afficheDes({4, 21, -1, 2});

compteDes#

Après avoir complété sa spécification, implémentez la fonction compteDes vue en TD.

/** Fonction compteDes
 * @param 
 * @return 
 **/
tableau compteDes(vector<int> des) {
    /// BEGIN SOLUTION
    vector<int> res = {0, 0, 0, 0, 0, 0};
    for ( int i = 0; i < des.size(); i++ ) {
        int indice = des[i] - 1;
        res[indice] += 1;
    }
    return res;
    /// END SOLUTION
}

Vérifiez que votre fonction compteDes est correcte à l’aide des tests suivants vus en TD :

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}) );

chercheDansTableau#

Au tour maintenant de chercheDansTableau qui étant donné un entier et un tableau cherche si cet entier est présent ou non dans le tableau, renvoie son indice s’il est présent, et renvoie -1 sinon.

/** Fonction chercheDansTableau
 * @param 
 * @param 
 * @return 
 **/
/// BEGIN SOLUTION
int chercheDansTableau(int n, vector<int> tab) {
    for ( int i = 0; i < tab.size(); i++ ) {
        if ( tab[i] == n ) {
            return i;
        }
    }
    // Si on n'a pas trouvé l'entier on renvoie -1 :
    return -1;
}
/// END SOLUTION

Complétez les tests de la cellule suivante et exécutez-les pour vérifier que votre fonction chercheDansTableau est correcte :

CHECK( chercheDansTableau( 3, {42,5,3,6,7}) ==  2);
CHECK( chercheDansTableau(56, {42,3,5,6,7}) == -1);
/// BEGIN SOLUTION
CHECK( chercheDansTableau( 2, {1,2,3,4,5,6}) ==  1);
CHECK( chercheDansTableau(99, {4,7,3,2,9  }) == -1);
/// END SOLUTION

main#

Écrivez ci-dessous les instructions pour :

  • tirer 5 dés aléatoirement et stocker le résultat dans la variable des;

  • afficher le résultat du tirage;

  • afficher le nombre de points obtenus en cherchant un yam’s dans ce tirage.

Testez votre chance en exécutant plusieurs fois la cellule!

vector<int> des;
/// BEGIN SOLUTION
des = lanceDes();
afficheDes(des);
cout << "Vous marquez " << pointsFigureYams(des) << " points." << endl;
/// END SOLUTION

Notes aux enseignants

Erreur classique : oublier de copier l’appel à srand en début de main(); d’où comportement déterministe.

Compilation et tests en dehors de Jupyter#

  • Ouvrez votre fichier yams.cpp.

  • Copiez-collez les fonctions afficheDes, lanceDes, compteDes et chercheDansTableau à un endroit adapté.

  • Remplacez la ligne d’affichage contenue dans la fonction main par le code des deux cellules précédentes.

  • Enregistrez votre fichier yams.cpp

  • Compilez-le à l’aide de la commande clang++ yams.cpp -o yams

  • Exécutez le programme ainsi généré : ./yams. Le résultat est-il bien celui attendu ? Si votre programme tire toujours la même série de dés, que manque-t-il dans la fonction main, qui était bien présent dans la première cellule de code, en haut de cette page ?

Exercice 3 : Les autres figures#

pointsFigureBrelan#

Voici une fonction pointsFigureBrelan un peu différente de celle vue en TD :

/** Fonction pointsFigureBrelan
 * @param des un tableau de 5 entiers (les dés)
 * @return 0 si aucun brelan est trouvé, la somme des dés formant le brelan + 10 sinon
 **/
int pointsFigureBrelan(vector<int> des) {
    vector<int> compte = compteDes(des);
    
    int valeur = (chercheDansTableau(3, compte) + 1)
               + (chercheDansTableau(4, compte) + 1)
               + (chercheDansTableau(5, compte) + 1);

    if (valeur > 0) {
        return (valeur * 3) + 10;
    } else {
        return 0;
    }
}

Analysez son fonctionnement puis écrivez quelques tests pour vérifier qu’elle se comporte comme prévu :

/// 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

pointsFigureCarre & pointsFigureFull#

Sur le même modèle, donnez deux fonctions pointsFigureCarre et pointsFigureFull répondant aux documentations ci-dessous. Rappel : full : 3 dés identiques et 2 dés identiques, carré : 4 dés identiques.

/** Fonction pointsFigureCarre
 * @param des un tableau de 5 entiers (les dés)
 * @return 0 si aucun carre (4 dés identiques) est trouvé, la somme des dés formant le carré + 30 sinon
 **/
/// BEGIN SOLUTION
int pointsFigureCarre(vector<int> des) {
  // On commence par compter les dés.
  vector<int> compte = compteDes(des);

  // On cherche si on a un carré ou un yams.
  int val = (chercheDansTableau(4, compte) + 1) + (chercheDansTableau(5, compte) + 1);

  if(val > 0)
    return (val * 4 + 30);
  
  // Si on ne trouve pas de brelan on renvoie 0.
  return 0;
}
/// END SOLUTION
/** Fonction pointsFigureFull,
 * @param des un tableau de 5 entiers contenant les valeurs des dés
 * @return 0 si pas de full, somme des dés composant le full + 20 si full il y a
 **/
/// BEGIN SOLUTION
int pointsFigureFull(vector<int> des) {
  // On commence par conmpter les dés :
  vector<int> compte = compteDes(des);

  // On regarde si on a une paire et un brelan :
  int valPaire = chercheDansTableau(2, compte) + 1;
  int valBrelan = chercheDansTableau(3, compte) + 1;

  if(valPaire > 0 and valBrelan > 0)
    return (valPaire * 2 + valBrelan * 3 + 20);
  // Si on ne trouve pas de brelan on renvoie 0 :
  return 0;
}
/// END SOLUTION

Testez-les :

CHECK( pointsFigureFull({4,4,4,1,1}) == 34 );
CHECK( pointsFigureFull({1,1,4,4,5}) ==  0 );

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

pointsFigure#

Nous vous donnons la fonction pointsFigure vue en TD qui a pour but de chercher la figure choisie par l’utilisateur (via une chaîne de caractères) et de renvoyer le nombre de points ainsi obtenu.

/** Fonction pointsFigure,
 * @param des un tableau de 5 entiers contenant les valeurs des dés
 * @param figure une chaine de caractères contenant le nom de la figure a chercher
 * @return 0 si figure introuvable ou inconnue, le score correspondant sinon
 **/
int pointsFigure(vector<int> des, string figure) {
    if        ( figure == "brelan" ) {
      return pointsFigureBrelan(des);
    } else if ( figure == "full"   ) {
        return pointsFigureFull(des);
    } else if ( figure == "carre"  ) {
        return pointsFigureCarre(des);
    } else if ( figure == "yams"   ) {
        return pointsFigureYams(des);
    } 
    return 0;
}

Compilation et tests en dehors de Jupyter#

  • Ouvrez votre fichier yams.cpp.

  • Copiez-collez les fonctions aleaInt, pointsFigureCarre et pointsFigureFull, pointsFigureBrelan et pointsFigure à un endroit adapté.

  • Enregistrez votre fichier yams.cpp.

  • Vérifiez qu’il compile toujours à l’aide de la commande clang++ yams.cpp -o yams

  • Exécuter le programme tel quel ne changera rien à l’exécution précédente car nous n’avons pas modifié la fonction main ; c’est ce que nous allons faire dans la partie suivante.

Exercice 4 : La boucle de jeu#

La boucle de jeu, infinie, se décompose de la façon suivante.

Avant la boucle

  • Déclaration des variables permettant de stocker la valeur des dés, le score et la reponse écrite du joueur.

Pendant la boucle

  • On lance les dés et on stocke le résultat dans la variable appropriée.

  • On affiche le résultat du lancer à l’utilisateur et on demande quelle figure il choisit.

  • On boucle tant que l’utilisateur donne une réponse qui n’est ni une figure (brelan, full, carre, yams) ni exit.

  • Si l’utilisateur a répondu exit on sort de la boucle principale avec l’instruction return 0;

  • Sinon, c’est que l’utilisateur a donné le nom d’une figure, on appelle donc pointsFigure de façon à récupérer le nombre de points correspondant à cette figure et à ce lancer de dés. On ajoute ces points au score du joueur, et on affiche le résultat au joueur.

Complétez la fonction main ci-dessous :

int main() {
    // Initialisation de la fonction rand (pour les nombres aléatoires).
    srand(time(0));

    // AVANT LA BOUCLE PRINCIPALE
    vector<int> des;
    int score = 0;
    string reponse = "";

    // BOUCLE PRINCIPALE
    while ( true ) {
        // On lance les dés et on stocke le résultat dans la variable appropriée.
        /// BEGIN SOLUTION
        des = lanceDes();
        /// END SOLUTION

        // On affiche le résultat du lancer et on demande quelle figure y chercher.
        /// BEGIN SOLUTION
        afficheDes(des);
        cout << "Quelle figure choisissez-vous ? (brelan, carre, full ou yams, exit pour quitter)" << endl;
        /// END SOLUTION

        // On boucle tant que la réponse de l'utilisateur n'est pas parmi
        // "brelan", "carre", "full", "yams" et "exit".
        do {
            // L'instruction suivante attend que le joueur entre du texte au clavier
            // et stocke sa réponse dans la chaine de caractères "reponse"
            cin >> reponse;
        } while ( reponse != "brelan"
            and reponse != "full" 
            and reponse != "carre" 
            and reponse != "yams" 
            and reponse != "exit" );

        // Si la réponse est "exit", on annonce la fin du jeu, on affiche le score et on termine.
        if ( reponse == "exit" ) { 
            /// BEGIN SOLUTION
            cout << "Fin du jeu, votre score final est de " << score << "points" << endl;
            /// END SOLUTION
            return 0; 
        }

        // L'utilisateur a donné le nom d'une figure.
        // On appelle **pointsFigure** de façon à récupérer le nombre de points
        // correspondant à cette figure et à ce lancer de dés. On ajoute ces
        // points au score du joueur, et on affiche le résultat au joueur.
        /// BEGIN SOLUTION
        int points = pointsFigure(des, reponse);
        score += points;
        cout << "Vous marquez " << points << " points, votre score total est de " << score << " points." << endl; 
        /// END SOLUTION
    }
    // FIN DE LA BOUCLE PRINCIPALE
    return 0;
}

puis testez-la :

// Cette incantation magique court-circuite l'appel à `main` -- qui
// requiert des interactions -- dans la correction automatique
#include <cstdlib>
if ( not getenv("NBGRADER_EXECUTION") )
  main()

Compilation et test en dehors de Jupyter#

Complétez yams.cpp afin de pouvoir jouer en l’exécutant (après l’avoir compilé) depuis votre terminal.

Indication : Si jamais vous entrez dans une boucle infinie, vous pouvez stopper votre programme à tout moment en pressant les touches CtrlC

Exercice 5 : ♣ De la suite dans les idées#

Dans le jeux de Yam’s complet d’autres figures sont reconnues comme la Grande Suite (5 dés qui se suivent comme 2,3,4,5,6) et la Petite Suite (4 dés qui se suivent comme 2,3,4,5). Attention, l’ordre des dés n’a pas d’importance, les dés 2,4,3,1,5 forment donc une grande suite, même s’ils sont dans le désordre !

Donnez deux fonctions répondant aux spécifications ci-dessous et vérifier que vos implémentations passent les tests :

/** Fonction pointsFigureGrandeSuite
 * @param des un tableau de 5 entiers (les dés)
 * @return 0 si aucune grande suite (5 dés qui se suivent) n est trouvée,
 *   et sinon la somme des dés formant la grande suite + 50
 **/
/// BEGIN SOLUTION
int pointsFigureGrandeSuite(vector<int> des) {
    vector<int> compte = compteDes(des);
    
    // Seulement 2 grandes suites possibles :
    if(compte == vector<int>({0,1,1,1,1,1}) || compte == vector<int>({1,1,1,1,1,0})) {
        int somme = 0;
        for(int i = 0; i < des.size(); i++) somme += des[i];
        return somme + 50;
    }
    return 0;
}
/// END SOLUTION
CHECK( pointsFigureGrandeSuite({4,3,2,1,5}) == 15+50 );
CHECK( pointsFigureGrandeSuite({2,3,4,5,6}) == 20+50 );
CHECK( pointsFigureGrandeSuite({2,3,4,5,2}) ==     0 );
/** Fonction pointsFigurePetiteSuite
 * @param des un tableau de 5 entiers (les dés)
 * @return 0 si aucune petite suite (4 dés qui se suivent) n est trouvée, 
 *   la somme des dés formant la petite suite  + 40 sinon
 **/
/// BEGIN SOLUTION
int pointsFigurePetiteSuite(vector<int> des) {
   vector<int> compte = compteDes(des);
   int nbSuite = 0;
   int sommeSuite = 0;
   for(int i = compte.size()-1; i >= 0; i--) {
       if ( compte[i] > 0 ) {
           nbSuite++;
           sommeSuite += i + 1;
           if ( nbSuite == 4 ) {
               return sommeSuite + 40;
           }
       } else { // suite brisée
           nbSuite = 0;
           sommeSuite = 0;
       }
   }
   return 0;
}
/// END SOLUTION
CHECK( pointsFigurePetiteSuite({2,3,4,5,2}) == 14+40 );
CHECK( pointsFigurePetiteSuite({3,3,4,5,6}) == 18+40 );
CHECK( pointsFigurePetiteSuite({2,3,4,2,2}) == 0 );
CHECK( pointsFigurePetiteSuite({2,3,2,5,4}) == 14+40 );
CHECK( pointsFigurePetiteSuite({2,6,4,5,3}) == 18+40 );

Modifiez maintenant la boucle de jeu réalisée un peu plus haut afin de prendre en compte ces deux nouvelles figures. Testez-la en exécutant les cellules dans le bon ordre.

Compilation et tests en dehors de Jupyter#

  • Ajoutez à votre fichier yams.cpp les fonctions que vous venez de réaliser.

  • Modifiez la fonction main en conséquence.

  • Compilez votre programme et testez-le en l’exécutant.

Tests#

Cette section vérifie que le programme yams compile et s’exécute sans erreur immédiate. Les commandes suivantes ne devraient pas déclencher d’erreur.

CHECK( not system("clang++ yams.cpp -o yams") )
CHECK( not system("echo exit | ./yams") )