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
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’appelafficheDes({1,2,3,5,4})
devra entraîner l’affichage1 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
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 renvoyer2
,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
Spécifiez et implantez une fonction
nombreOccurrences
qui prend en paramètre un tableau d’entierst
et un entierv
, et qui renvoie le nombre d’occurrences dev
danst
(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 :
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}) );
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 dé de 1 à 6 int indice = des[i] - 1; res[indice] += 1; } return res; }
/// END SOLUTION
Exercice 4 : Yam’s!
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 dé est différent du premier alors pas de yams return 0; } } return val * 5 + 60 ; }
/// END SOLUTION
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
Pour simplifier la fonction
pointsFigureYams
, on peut utiliser les fonctionscompteDes
etchercheDansTableau
. Donnez une nouvelle implantation depointsFigureYams
./// 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
À 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
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
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 fonctionpointsFigureBrelan
ou la fonctionpointsFigureYams
. 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
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 fonctionint 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
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
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.
Ajoutez une fonction
vector<int> relance(int numDe, vector<int> des)
qui «relance» uniquement le dé numéronumDe
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
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.
Ajoutez dans la fonction
main
un tableau de scores contenant une case pour chaque figure./// BEGIN SOLUTION /// END SOLUTION
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
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