C'est simple: ce code n'est pas du C++.
Ce n'est même pas du C standard. Sûr qu'à l'époque, strcpy_s n'était pas normalisé.
Je ne connais pas le nom du bouquin, mais tu peux déjà le mettre au fond de ton étagère.
(Sinon, le code "fonctionne".)
Version imprimable
Aussi, le nom de la fonction est mauvais: La sortie est peut-être les N premiers nombres naturels, mais ce n'est certainement pas des nombres premiers.
C'est typiquement le genre d'exemples qui 1) sont plus du C que du C++ et 2) qui donnent l'impression qu'on ne peut rien faire en C++ en moins de deux-cents lignes de code. En C++ moderne on écrirait:Citation:
#include "stdafx.h"
#include <iostream>
#include <string>
int main()
{
char prenom[] = "Jojo";
char initiales[] = "J";
char nom[] = "Lapatate";
char nomcomplet[50];
int offset = 0;
strcpy_s(nomcomplet, prenom);
offset = strlen(prenom);
strcpy_s(nomcomplet + offset, " ");
offset++;
strcpy_s(nomcomplet + offset, initiales);
offset += strlen(initiales);
strcpy_s(nomcomplet + offset, ". ");
offset += 2;
strcpy_s(nomcomplet + offset, nom);
std::cout << "Vous vous appelez: " << nomcomplet << std::endl;
return 0;
}
Code:
1
2
3
4
5
6
7
8 #include <iostream> #include <string> int main() { std::string prenom("Jojo"), initiale("J"), nom("Lapatate"); auto nom_complet = prenom + " " + initiale + ". " + nom; std::cout << "Votre nom est: " << nom_complet << std::endl; }
Pour un tâche où la performance importe peu, il faut choisir le code le plus clair et le plus court.
Fais-moi signe quand tu veux passer à une autre leçon et d'autres exercices (de C++ vraiment moderne)
Pfff... Le pire c'est que j'avais à peu près ca en tête pour faire l'exo à la différence de "string" plutôt que "auto" dans la déclaration de la variable "nomcomplet"
J'me disais bien que c'était un code bien trop complexe pour un effet tout bête. Le livre que j'ai suivi c'est: http://cpp.developpez.com/livres/ind...us#L2744025461 .....en édition mise à jour pour le c++11.
Apparemment, les Français sont pas très bon pour faire des bouquin sur le C++ :/
Stend, je suis actuellement en train de bucher sur la suite des exos que tu m'a déjà donné ! Attendons que je finisse ceux là ^^
Ok tu me diras pour les exos, comme j'ai un peu de temps aujourd'hui je t'écris la leçon 2 de Stendhal's modern C++, le tutoriel en français qui ne pense pas qu'apprendre le C++ c'est jouer avec des bits et des pointeurs sans recourir aux bibliothèques standard!
Je ne fais qu'un exposé rapide. S'il y des choses que tu ne comprends pas, ce n'est pas grave! N'hésite pas à poser des questions. Les exercices viendront un peu plus tard quand tu auras terminé ceux de la leçon précédente (ou si tu en as marre). On pourra revenir plus en profondeur sur des points si tu veux.
Big leçon n°2: Itérateurs, voitures et fonctions lambda
Comme tu l'as justement remarqué dans le code:
on peut utiliser auto pour demander au compilateur de déduire le type de la variable qu'on déclare. C'est vrai depuis C++11 (donc depuis 2011).Code:auto nom_complet = prenom + " " + initiale + ". " + nom;
En l'occurrence, le compilateur sait que l'opérateur + dont une opérande est: std::string et l'autre: const char* retourne une std::string.
Utiliser auto est recommandé pour trois raisons:
- cela évite de taper des types particulièrement compliqués, comme std::unordered_map<std::maxint_t, std::vector<std::pair<std::fast_uint16_t, std::shared_ptr<my_own_hash_map_type>>>>::iterator
- si on modifie le type à un endroit, le compilateur infère le type modifié partout ailleurs. Par exemple, si je change le type de nom et prenom pour en faire une std::wstring, le type de nom_complet est modifié aussi, par le compilateur. Je ne risque pas d'oublier de le faire avec peut-être une conversion inopportune ou une erreur de compilation à la clé
- il arrive qu'on ne connaisse pas le type de la variable qu'on déclare. C'est le cas pour les fonctions lambda: il n'y a pas deux lambdas du même type, un type spécifique est créé chaque fois par le compilateur. Mais qu'est-ce qu'une fonction lambda?
Les fonctions lambda
Tu sais ce que c'est qu'une fonction. Prenons un exemple:
Que peut-on faire de cette fonction? C'est assez limité: elle doit être déclarée dans l'espace global (pas à l'intérieur d'une autre fonction, par exemple), ce qui veut dire qu'elle ne peut pas non plus être générée par une autre fonction. On peut essentiellement l'appeler et prendre son adresse.Code:
1
2
3 auto fois_deux(int i) { // auto peut aussi inférer le type de retour d'une fonction, à certaines conditions return i*2; }
- l'appeler:
- prendre son adresse:Code:auto deux_x = fois_deux(x);
Code:
1
2 auto fp = fois_deux; auto deux_x = fp(x); // on l'appelle via le pointeur sur son adresse
Une fonction lambda c'est une fonction qui peut être utilisée comme une variable ordinaire. Voici un exemple, à méditer un bon moment parce-que, mine de rien, ça retourne un peu le cerveau:
Code:
1
2
3
4
5
6
7
8 auto fois_x(int x) { // une fonction qui retourne une fonction lambda return [=](int y) { return y*x; }; // c'est la fonction lambda. Elle n'a pas de nom } int main() { auto fois_10 = fois_x(10); // fn contient une fonction lambda créée par fois_x(10), qui multiplie par 10 son argument std::cout << fois_10(5); // affiche 5*10 = 50 }
Un petit mot sur la syntaxe des fonctions lambda:
Code:
1
2
3 [=] // entre crochet ce qu'il faut faire du contexte: = pour en faire une copie, & pour prendre en référence et pouvoir le modifier (int x) // l'argument de la lambda, il peut y en avoir plusieurs. Il peut même être déduit dans certains cas { return y*x; } // le corps de la fonction.
Les fonctions lambda sont extrêmement utiles pour tirer parti de la bibliothèque standard. Pour cela, il faut connaître également les itérateurs. Mais que sont les itérateurs?
Les itérateurs
Un itérateur est une abstraction. Elle permet de parcourir des choses très dissemblables, comme un vecteur, une liste, un dictionnaire, un fichier... comme s'ils étaient semblables. Par exemple:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 #include <iostream> #include <string> #include <vector> #include <set> #include <list> int main() { std::string str = "abcdef"; std::vector<int> vec = {1,2,3,4,5}; std::list<double> lst = {6., 7., 8., 9.}; std::set<char> ensemble = { 'g', 'h', 'i'}; for (auto it = std::begin(str); it != std::end(str); ++it) { // it est un itérateur std::cout << *it << ' '; } for (auto it = std::begin(vec); it != std::end(vec); ++it) { // la boucle est identique std::cout << *it << ' '; } for (auto it = std::begin(lst); it != std::end(lst); ++it) { // idem std::cout << *it << ' '; } for (auto it = std::begin(ensemble); it != std::end(ensemble); ++it) { // idem std::cout << *it << ' '; } }
Un itérateur pointe sur un élément d'une séquence. Pour connaître l'élément pointé, on utilise l'opérateur * . Par exemple, std::cout << *iterateur;Pour avancer d'un élément, on utilise l'opérateur ++. Par exemple: ++iterateurLes fonctions std::begin et std::end renvoie un itérateur respectivement sur le début et la fin d'une séquence.
Pour reprendre l'exercice 4 du précédent épisode, tu aurais pu écrire:
Code:
1
2
3
4
5
6
7
8 void print_vec(const std::vector<int>& vec) { auto it = std::begin(vec); std::cout << '{' << *it++; for ( ; it != std::end(vec); ++it) { std::cout << ", " << *it; } std::cout << '}'; }
NB: l'opérateur ++ peut se placer avant ou après son opérande. Quand il est placé avant, il incrémente son opérande et la renvoie incrémentée:
Quand il est placé après, il copie son opérande, l'incrémente et renvoie la copie non-incrémentée:Code:
1
2 auto x = 0; std::cout << ++x << " -> " << x; // 1 -> 1
Code:
1
2 auto x = 0; std::cout << x++ << " -> " << x; // 0 -> 1
Itérateurs et fonctions lambda
Itérateurs et fonctions lambda permettent de tirer partie de la bibliothèque standard. En particulier des algorithmes qu'elle définit. C'est un des points-clé du C++ moderne de bien utiliser ces algorithmes. Ils sont dans l'en-tête <algorithm>. Prenons en exemple un des plus souvent utiles: std::sort:
std::sort peut prendre un troisième argument pour préciser comment on compare deux éléments. Par défaut, on prend le plus petit élément. Mais on pourrait vouloir trier dans l'ordre inverse. Il faut alors fournir un autre critère de comparaison. On peut utiliser une fonction, un foncteur (on verra plus tard) ou une fonction lambda. L'avantage de la fonction lambda c'est qu'on peut la définir à l'endroit où on l'utilise et qu'on a pas besoin de lui donner un nom:Code:
1
2
3 std::vector<int> v = {9,3,7,3,1}; std::sort(std::begin(v), std::end(v)); print_vec(v); // {1, 3, 3, 7, 9}
Code:
1
2
3 std::vector<int> v = {9,3,7,3,1}; std::sort(std::begin(v), std::end(v), [](int a, int b) { return a > b; }); print_vec(v); // {9, 7, 3, 3, 1}
C'est tout pour aujourd'hui!!
N'hésite pas à poser des questions.
Je n'ai rien lu de la conversion ni du message mais dans la lambda passée à std::sort :
- Pas besoin de capture donc autant mettre [] et non [=] (coding style)
- Le 3ème argument passé doit respecter le concept de comparateur établissant un ordre total strict ce que ne fais pas pas >=.
(Si a == b, a >= b et b >= a renvoie true)
Il faut donc utiliser > au lieu de >=.
C'est vraiment un super geste que tu me fais là! Merci Stend, bien que c'est pas facile à faire rentrer dans le cerveau, j'ai de quoi bucher! :)
J'ai des questions mais on verra après les premiers exos car sinon je vais trop m'embrouiller...
Voici mon programme pour le crible d’Ératosthène:
Il fonctionne bien, maintenant à voir si j'ai pas fait d'erreur ou mal compris la consigne! :DCode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 #include "stdafx.h" #include <iostream> #include <vector> #include <string> #include <random> std::vector<bool> nombresPremiers(int choix) //Fonction appelé dans le main par la création du tableau "tabPremiers" qui permet de differencier les nombres premiers { std::vector<bool> tab(choix); tab[0] = false; tab[1] = false; tab[2] = true; for (int x = 3; x < tab.size(); x++) { tab[x] = true; } for (int a = 2; a < tab.size(); a++) { if (tab[a] == true) { for (int b = a*2; b < tab.size(); b += a) { tab[b] = false; } } } return tab; } int nombreAleatoire(int choix) //Fonction appelé dans le main pour obtenir un nombre aléatoire par rapport à la taille du tableau { std::random_device a; std::default_random_engine b(a()); std::uniform_int_distribution<int> uniform_dist(2, choix); int nombre = uniform_dist(b); return nombre; } void reponseProg(std::vector<bool> tab,std::string choix,int nombreTab) //Fonction qui donne une réponse au joueur par rapport a son choix { if (choix == "o" || choix == "O") { if (tab[nombreTab] == 1) { std::cout << "Bravo! " << nombreTab << " est bien un nombre premier :)" << std::endl << std::endl; } else if (tab[nombreTab] == 0) { std::cout << "Aie, desole! " << nombreTab << " n'est pas un nombre premier :'(" << std::endl << std::endl; } } else if (choix == "n" || choix == "N") { if (tab[nombreTab] == 1) { std::cout << "Aie, desole! " << nombreTab << " est bien un nombre premier :'(" << std::endl << std::endl; } else if (tab[nombreTab] == 0) { std::cout << "Bravo! " << nombreTab << " n'est pas un nombre premier :)" << std::endl << std::endl; } } } int main() { std::vector<bool> tabPremiers(nombresPremiers(256)); bool recommencer(true); std::cout << "Bienvenue dans le jeu du crible d'Eratosthene!\n\nLa regle est simple, je vous donne si un nombre et"; std::cout << " vous me dites si c'est un\nnombre premier ou pas. Repondez simplement par O pour oui ou par N pour non.\n\nC'est parti!" << std::endl << std::endl; do { int nombreTableau; std::string choixQuestion, choixRecommencer; nombreTableau = nombreAleatoire(255); //Appel de la fonction nombreAleatoire pour obtenir un nombre entre 0 et 255 std::cout << "Le nombre " << nombreTableau << " est-il un nombre premier?" << std::endl; std::cin >> choixQuestion; //L'utilisateur répond à la question std::cout << std::endl; reponseProg(tabPremiers, choixQuestion, nombreTableau); //Appel la fonction reponseProg pour donne rune réponse à l'utilisateur par rapport à son choix std::cout << "Voulez vous recommencer une partie? o/n" << std::endl; std::cin >> choixRecommencer; if (choixRecommencer == "n" || choixRecommencer == "N") //Si l'utilisateur répond "n", la partie s'arrête { recommencer = false; } } while (recommencer); return 0; }
J'ai piqué la fonction random sur cppreference car j'ai vu que ce que j'utilisais avant était obsolète (rand de <cstdlib>) et j'avoue avoir recopié bêtement car je ne l'ai pas très bien comprise :/
Très bien! J'ai compilé et testé, il marche "au poil".Citation:
Voici mon programme pour le crible d’Ératosthène:
Pour la correction, je pense que tu auras des remarques des uns et des autres. Je te donne une solution qui n'est pas meilleure que la tienne pour le crible lui-même. J'ai choisi de ne traiter que les cas où n > 2 car 0 et 1 ne sont pas premiers, tout le monde le sait, mais avec le recul je pense que ta sémantique est plus consistante et évite des complications.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13 std::vector<bool> erathostene(unsigned n) { // un nombre premier est positif, donc j'utilise un entier non-signé, unsigned if (n < 2) return std::vector<bool>(); auto zn = n-2; // on décale de deux rangs à gauche: 0 == 2 std::vector<bool> crible(zn+1, true); // le constructeur à deux arguments: taille et valeur par défaut. Tous les éléments sont true for (unsigned i = 0; i < zn/2+1 ; ++i) { // zn/2 est le dernier à pouvoir être doublé en restant < zn if (crible[i] == true) { for (unsigned j = 2*i+2; j < zn+1; j += (i+2)) { crible[j] = false; } } } return crible; }
Pour l'aspect aléatoire:
Très bien!! c'est comme ça qu'on apprend! Et en recopiant la référence tu as peu de chances de te tromper ;)Citation:
J'ai piqué la fonction random sur cppreference car j'ai vu que ce que j'utilisais avant était obsolète (rand de <cstdlib>) et j'avoue avoir recopié bêtement car je ne l'ai pas très bien comprise :/
Pour l'interface utilisateur, quelques remarques:
La signature de ta fonction implique que tu copies les arguments. Lorsque l'argument n'est pas d'un type primitif (int, bool, char, etc.), il peut être de taile importante et long à copier. On utilise alors une référence. Si on ne compte pas modifier l'argument, on utilise une référence constante:Code:void reponseProg(std::vector<bool> tab,std::string choix,int nombreTab)
A retenir:Code:void reponseProg(const std::vector<bool>& tab, const std::string& choix,int nombreTab)
Autres remarques:Code:
1
2
3 void f(std::string s); // réalise une copie de la string passée en argument void f(std::string& s); // référence sur la string qui pourra être modifiée par la fonction void f(const std::string& s); // référence sur la string qui ne pourra pas être modifiée. S'écrit aussi (std::string const&)
Préférer:Code:if (tab[nombreTab] == 1)
Cela indique plus clairement qu'on a affaire à un boolCode:
1
2
3 if (tab[nombreTab] == true) // ou if (tab[nombreTab]) // et de la même façon if (tab[nombreTab] == false) // ou if (!tab[nombreTab])
Sur:
La bonne pratique est d'initialiser immédiatement les variables (surtout celles de type primitif qui n'ont pas de valeur par défaut). D'où:Code:
1
2
3 int nombreTableau; std::string choixQuestion, choixRecommencer; nombreTableau = nombreAleatoire(255);
Quand tu commenceras à utiliser des exceptions, tu verras que ça peut éviter des gros bugs bien m...iques.Code:auto nombreTableau = nombreAleatoire(255);
Et maintenant que tu t'y connais en auto:
Bon il est un peu tard...Code:std::vector<bool> tabPremiers(nombresPremiers(256)); // pourquoi pas auto tabPremiers = nombresPremiers(256); ?
Bon courage et bravo, reste à persévérer!
J'ai modifié mon code avec tes recommandations et en effet c'est plus rapide et plus simple à lire! :)
Super exo, ca m'a fait travailler pas mal de chose! J'en attends des nouveaux alors ^^Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92 #include "stdafx.h" #include <iostream> #include <vector> #include <string> #include <random> std::vector<bool> nombresPremiers(int choix) //Fonction appelé dans le main par la création du tableau "tabPremiers" qui permet de differencier les nombres premiers { std::vector<bool> tab(choix,true); tab[0] = false; tab[1] = false; for (int a = 2; a < tab.size(); a++) { if (tab[a] == true) { for (int b = a*2; b < tab.size(); b += a) { tab[b] = false; } } } return tab; } int nombreAleatoire(int choix) //Fonction appelé dans le main pour obtenir un nombre aléatoire par rapport à la taille du tableau { std::random_device a; std::default_random_engine b(a()); std::uniform_int_distribution<int> uniform_dist(2, choix); int nombre = uniform_dist(b); return nombre; } void reponseProg(const std::vector<bool>& tab, const std::string& choix, const int& nombreTab) //Fonction qui donne une réponse au joueur par rapport a son choix { if (choix == "o" || choix == "O") { if (tab[nombreTab] == true) { std::cout << "Bravo! " << nombreTab << " est bien un nombre premier :)" << std::endl << std::endl; } else if (tab[nombreTab] == false) { std::cout << "Aie, desole! " << nombreTab << " n'est pas un nombre premier :'(" << std::endl << std::endl; } } else if (choix == "n" || choix == "N") { if (tab[nombreTab] == true) { std::cout << "Aie, desole! " << nombreTab << " est bien un nombre premier :'(" << std::endl << std::endl; } else if (tab[nombreTab] == false) { std::cout << "Bravo! " << nombreTab << " n'est pas un nombre premier :)" << std::endl << std::endl; } } } int main() { auto tabPremiers(nombresPremiers(256)); bool recommencer(true); std::cout << "Bienvenue dans le jeu du crible d'Eratosthene!\n\nLa regle est simple, je vous donne si un nombre et"; std::cout << " vous me dites si c'est un\nnombre premier ou pas. Repondez simplement par O pour oui ou par N pour non.\n\nC'est parti!" << std::endl << std::endl; do { std::string choixQuestion, choixRecommencer; auto nombreTableau = nombreAleatoire(255); //Appel de la fonction nombreAleatoire pour obtenir un nombre entre 0 et 255 std::cout << "Le nombre " << nombreTableau << " est-il un nombre premier?" << std::endl; std::cin >> choixQuestion; //L'utilisateur répond à la question std::cout << std::endl; reponseProg(tabPremiers, choixQuestion, nombreTableau); //Appel la fonction reponseProg pour donne rune réponse à l'utilisateur par rapport à son choix std::cout << "Voulez vous recommencer une partie? o/n" << std::endl; std::cin >> choixRecommencer; if (choixRecommencer == "n" || choixRecommencer == "N") //Si l'utilisateur répond "n", la partie s'arrête { recommencer = false; } } while (recommencer); return 0; }
Alors en voilà un autre. Il tourne autour du jeu de poker. L'idée est d'utiliser les concepts de la leçon n°2: les algorithmes de la bibliothèque standard, les itérateurs et, quand nécessaire, les fonctions lambda.
Le jeu de poker (étape 1);
Une carte est représentée par deux entiers non-signés contenus dans une paire (std::pair):
- regarder la documentation de std::pair dans la référence
- la première valeur de la paire représente le rang de la carte: 0 = 2, 1 = 3, ... , 9 = valet, 10 = dame, ... , 12 = as. La deuxième valeur contient la couleur: 0 = carreau, ... 3 = trèfle.
- générer le jeu de 52 cartes dans l'ordre:
a) utiliser la fonction standard std::iota (dans le header <numeric>) pour générer 52 paires: { {0,0}, {1,1}, ... {51,51}}. Pour que iota fonctionne pour des paires, il faut d'abord définir un opérateur ++ pour les paires. En voici une implémentation possible:
b) utiliser la fonction standard std::transform (dans le header <algorithm>) pour transformer le tableau de paires croissantes en un jeu de cartes. Il est possible de recourir à une lambda, utilisant pourquoi pas l'opérateur modulo (11 % 2 == "11 modulo 2" == reste de la division de 11 par 2 == 1)Code:
1
2
3
4
5 std::pair<unsigned, unsigned>& operator++(std::pair<unsigned, unsigned>& p) { ++p.first; ++p.second; return p; }
- écrire une fonction pour mélanger le paquet de cartes. S'appuyer sur l'algorithme std::shuffle.
- écrire une fonction pour "couper" le paquet de cartes. S'appuyer sur l'algorithme std::rotate
- écrire une fonction pour transformer une paire d'entiers non signés en une string de type "as de trèfle", "deux de carreau", etc.
T'as monté la barre de plusieurs niveaux là! :D
J'vais d'abord bucher chaque point individuellement avant de tester ton exo car là pour le moment, ca donnerait rien ^^
Et sinon j'ai une question, je l'ai posé à Gbdivers sur le forum d'OC et je voudrais avoir votre point de vue aussi:
Nul part je n'ai justifié du pourquoi j'apprenais le C++ et ca serait bien d'en parler avant de continuer. J'aimerais acquérir les capacités pour coder des jeux vidéo 2D voir 2D en 3D.
Je me suis dit, quitte à apprendre un langage, autant apprendre un comme le C++ qui peut faire un peu de tout mais maintenant que je me rend compte de la complexité, je me
dis que peut être un langage plus haut niveau serait peut être plus adapté et serait moins dur. Qu'en pensez-vous?
Le peu que j'ai appris en C++, j'ai complétement accroché mais j'ai l'impression qu'il me manque des bases d'algo et autre pour vraiment en profiter pleinement.
Oui c'est plus dur. L'avantage du forum c'est qu'on peut faire interactif: si tu bloques ou que tu as besoin de compléments, tu peux me demander. Je ferai peut-être un vrai tutoriel pour le site à partir de nos échanges.Citation:
T'as monté la barre de plusieurs niveaux là!
J'vais d'abord bucher chaque point individuellement avant de tester ton exo car là pour le moment, ca donnerait rien ^^
Oui, C++ est un bon choix pour écrire des jeux 2D/3D. Une série d'articles très bien va paraître à ce sujet sur le site dans quelques temps: les premiers sont en phase de relecture technique. Cela étant, en C++ ou dans un autre langage, c'est un gros morceau.Citation:
Et sinon j'ai une question, je l'ai posé à Gbdivers sur le forum d'OC et je voudrais avoir votre point de vue aussi:
Nul part je n'ai justifié du pourquoi j'apprenais le C++ et ca serait bien d'en parler avant de continuer. J'aimerais acquérir les capacités pour coder des jeux vidéo 2D voir 2D en 3D.
L'avantage d'être autodidacte, c'est qu'on peut choisir un, plusieurs langages, passer de l'un à l'autre selon l'inspiration du moment et son bon plaisir. Si tu sens que C++ n'est pas fait pour toi en ce moment, il y a en effet des langages plus simples, comme Python ou, dans une moindre mesure, Java. Ils sont populaires, de nombreuses bibliothèques sont disponibles, y compris pour écrire des jeux. Tu peux regarder Pygame, par exemple: http://pygame.org/hifi.html. Il y a un sous-forum sur le site qui propose des discussions sur les mérites relatifs des différents langages. C'est difficile de se faire un avis à partir de ce genre de discussions mais tu peux au moins voir l'avis des autres.Citation:
Je me suis dit, quitte à apprendre un langage, autant apprendre un comme le C++ qui peut faire un peu de tout mais maintenant que je me rend compte de la complexité, je me
dis que peut être un langage plus haut niveau serait peut être plus adapté et serait moins dur. Qu'en pensez-vous?
Les algorithmes c'est de toute façon quelque chose à voir à un moment ou à un autre, quel que soit le langage. Peut-être même plus dans des langages plus lents que C++: quand on est déjà un peu handicapé par le langage, il faut être certain d'utiliser l'algorithme le plus efficace.Citation:
Le peu que j'ai appris en C++, j'ai complétement accroché mais j'ai l'impression qu'il me manque des bases d'algo et autre pour vraiment en profiter pleinement.
Cependant -et c'est mon objectif de te le montrer- on peut repousser ce moment en utilisant les bibliothèques déjà disponibles, qui offrent des algorithmes ou des briques d'algorithme prêts à l'emploi. La bibliothèque standard de C++ est, à mon avis, un des sommets de la programmation. Boost met aussi beaucoup de ressources à disposition. Donc je dirais que tu auras plutôt besoin de bases d'algo pour continuer que pour commencer.
Salut!
Merci, je voulais juste être sûr du chemin que je prends, car si c'est au bout d'un an que je me rend compte que j'ai fais le mauvais choix, ca la fou mal..
Je vais rester sur du C++, c'est p'tete un des plus dur mais au moins j'aurais plus de portes ouvertes. Je vais faire un peu de cours d'algo débutant à coter et commencer
à toucher un peu de unreal 4. Ca me permettra de varier un peu et de pas toujours faire la même chose.
J’espère juste ne pas m'y prendre trop tard, à 27 ans, j'commence à me faire fais vieux ^^
Je sais que ca va être long à apprendre tout ça mais j'ai surtout peur que tout seul, je n'arrive jamais au bout... Heureusement qu'il y a les forum pour que des personnes
comme vous, aide les personnes comme moi.. Merci les gars ;)
Pas forcément. Si tu travailles régulièrement c'est l'affaire de quelques mois pour être capable de faire un jeu pas trop difficile.Citation:
Je sais que ca va être long à apprendre tout ça
Tu t'en sors avec l'exo? J'ai peut-être frappé trop fort sans m'en rendre compte?
Alors j'ai buché la leçon 2, j'ai compris les iterateurs mais les lambda j'avoue que je suis un peu encore dessus... Mais pour le début de l'exo, j'ai compris a
quoi sert chaque fonction, je sais utiliser chaque partie indépendamment mais j'avoue pas trop savoir par quoi commencer, j'ai pas trouvé beaucoup de détail
sur std::pair et std::iota :/
std::pair est un type générique, comme std::vector; mais au lieu de n'avoir qu'un autre type en paramètre, il en a deux:
Un type pour chacun des membres de la paire (il y en a deux comme le nom l'indique). On peut créer une paire avec std::make_pair:Code:
1
2
3 std::pair<int, char> p1; std::pair<std::string, std::vector<int>> p2; etc.
Le premier membre s'appelle first, le second second:Code:auto contact = std::make_pair("Fred", "0612345678"); // les types paramétrant pair sont déduits par make_pair
il y a un mot-clé très pratique de c++: using, qui permet de donner un nom plus expressif ou plus court à un type compliqué:Code:std::cout << contact.first << " Tel: " << contact.second; // Fred Tel: 0612345678
Mettons maintenant qu'on veuille créer un paquet de cartes pour le jeu de poker. On peut choisir un std::array, puisque le jeu a toujours 52 cartes:Code:
1
2 using Card = std::pair<unsigned, unsigned>; Card card; // card est de type std::pair<unsigned, unsigned>
L'idée est d'utiliser les algorithmes de la bibliothèque standard pour faire l'exercice. Pour initialiser les cartes on peut utiliser std::iota et std::transform.Code:
1
2 using Deck = std::array<Card, 52>; // Deck = paquet de cartes en anglais // Pour l'instant les cartes sont toutes égales à {0,0}
std::iota fournit une suite croissante à partir d'une valeur intiale:
Pour connaître la prochaine valeur de la série, iota utilise l'opérateur++ (voir la leçon n°2) qui incrémente de 1 son opérande.Code:
1
2
3 std::vector<unsigned> entiers_positifs(5); // de la place pour 5 entiers positifs std::iota(std::begin(entiers_positifs), std::end(entiers_positifs), 1); // 1 est le premier entier positif print_vec(entiers_positifs); // {1,2,3,4,5}
La difficulté est que les paires n'ont pas d'opérateur ++ prédéfini.Code:
1
2
3 auto x = 2; ++x; std::cout << x; // 3
C'est la raison pour laquelle il faut en définir un; les opérateurs en c++ peuvent être redéfinis pour des types non primitifs (c'est-à-dire tous les types qui ne sont pas int, char, float, etc.). Voici la syntaxe, qui est très proche d'une fonction normale:
Dans l'énoncé de l'exercice, je te proposais une définition de l'opérateur++ pour les paires. Tu peux y jeter un coup d'oeil maintenant. Il incrémente chacun des membres de la paire de 1. Donc {0,0} donne {1,1}, {1,1} donne {2,2}, etc.Code:
1
2
3 type_retour operator++(type_operande); type_retour operator/(type_operande1, type_operande2); etc.
Du coup, après avoir défini cet opérateur tu peux écrire:
Bien entendu cela ne forme pas un paquet de carte. Pour faire une carte valide il faut un premier membre entre 0 et 12 (c-à-d entre 2 et as) et un deuxième entre 0 et 3 (c'est-à-dire entre "coeur" et "trèfle"). Il nous faut un moyen de réduire les valeurs des paires que nous avons créées pour qu'elles rentrent entre ces bornes.Code:
1
2 Deck deck; std::iota(std::begin(deck), std::end(deck), Card(0,0)); // deck = { {0,0}, {1,1}, ..., {51,51}}
L'opérateur % ("modulo") peut être utilisé pour cela. Cet opérateur donne le reste de la division entière entre ses opérandes:
11 / 2 = 5 reste 1 <=> 11 % 2 = 1
14 / 2 = 7 reste 0 <=> 14 % 2 = 0
Comme il s'agit du reste d'une division, il est nécessairement compris entre 0 et l'opérande de droite - 1
Prenons la 42ème carte. Elle est égale à {41,41}. Si j'applique l'opérateur % 13 (le nombre de cartes par couleur) au premier membre, j'aurai une carte de rang valide; si j'applique l'opérateur % 4 au deuxième membre, j'obtiens une couleur valide. D'où:
std::transform permet d'appliquer cette fonction à tout le paquet de cartes créé:Code:
1
2
3
4
5
6 Card make_valid(Card c) { c.first = c.first % 13; // plus court et équivalent: c.first %= 13 c.second = c.second % 4; // c.second %= 4 return c; }
Néanmoins il est un peu ennuyeux de devoir définir des fonctions comme make_valid: elles servent une seule fois, leur définition sera éloignée de l'endroit où elles sont utilisées, et il faut leur trouver un nom; s'il y a d'autres choses à valider dans le programme, le nom pourrait déjà être pris... D'où l'intérêt d'utiliser une lambda:Code:std::transform(std::begin(deck), std::end(deck), make_valid);
Maintenant je pense que tu es paré pour faire la suite des exercices...Code:std::transform(std::begin(deck), std::end(deck), [](Card c) { c.first %= 13; c.second %= 4; return c});
Coucou!
Merci pour ces détails en plus ça m'a bien aidé à comprendre mais malgré que tu m’aies mâché le travail à 95%, je bloque sur std::iota
et l'operator++, je sais pas ou placer la fonction ou comment l'appeler.
Et tant qu'à faire, j'ai des question pour qui peut y répondre:Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 #include "stdafx.h" #include <iostream> #include <utility> #include <array> #include <numeric> using Carte = std::pair<unsigned, unsigned>; using Deck = std::array<Carte, 52>; Carte& operator++(Carte& p) { ++p.first; ++p.second; return p; } int main() { Deck jeuDeCarte; std::iota(std::begin(jeuDeCarte), std::end(jeuDeCarte), Carte(0, 0)); std::transform(std::begin(jeuDeCarte), std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; }); for (int x = 0; x < jeuDeCarte.size(); x++) { std::cout << jeuDeCarte[0].first << jeuDeCarte[0].second << std::endl; } }
1- A quoi sert std::print (ou printf ou print_vec)? Ca ressemble à std::cout?
2- Typedef est obsolète par rapport à Using? On doit les utiliser avant le main ou après?
3- J'ai pas saisie la différence entre std::pair<*,*>; et std::make_pair(*,*);
Merci :)
0) L'opérateur ++ est appelé par la fonction std::iota, qui est définie dans la bibliothèque standard: c'est normal que tu ne vois pas l'appel à l'opérateur puisqu'il est fait dans la définition (cachée) de cette fonction. Il faut qu'il soit défini avant l'appel de la fonction iota pour qu'elle puisse l'appeler à son tour. Fais-le test, enlève la définition de l'opérateur avant d'appeler std::iota: que se passe-t-il? que te dit le compilateur?
1) std::print, je ne connais pas. printf est la fonction standard de C pour afficher à l'écran; il n'est pas recommandé de l'utiliser en C++. print_vec est une fonction que j'ai définie, qui ressemble à la fonction que tu avais définie dans le premier exercice pour afficher les éléments d'un vecteur, séparés par une virgule, entre accolades.
2) oui, typedef est plutôt obsolète. using est plus puissant. En particulier on peut paramétrer une directive using (c'est une notion un peu plus avancée, mais ça ne peut pas te faire de mal de voir à quoi cela ressemble):
Code:
1
2
3
4
5
6
7 template <type T> using Tableau = std::vector<T>; ... Tableau<unsigned> nombres_premiers; // nombres premiers est de type std::vector<unsigned> // au contraire, un typedef par type de std::vector: typedef std::vector<unsigned> Tableau_entiers_nonsignes;
On peut les utiliser avant le main, dans le main, dans une fonction... Ce qui change c'est leur portée. La portée va de la directive using à la fin du bloc où elle est utilisée:
Code:
1
2
3
4
5
6
7
8 using Tag = std::string; // Tag est utilisable dans tout le fichier à partir de cette ligne int main() { Tag a; using Bi_tag = std::pair<Tag, int>; // Bi_tag est utilisable de cette ligne jusqu'à la fin de main } Bi_tag mon_bi_tag; // erreur!
3) std::pair<T, U> est un type; std::make_pair(T t, U u) est une fonction.
Code:
1
2
3 std::pair<unsigned, unsigned> p1(0,1); // syntaxe avant C++11 std::pair<unsigned, unsigned> p2 = {0,1}; // syntaxe C++11 premier choix. {0,1} peut être autre chose qu'une paire, il faut donc déclarer le type de p2 auto p3 = std::make_pair(0u,1u); // u après un littéral (ex: 56u) signifie que ce littéral est de type unsigned int. on connaît le type de retour de make_pair, on peut donc utiliser auto
Ok mais alors qu'est-ce qui cloche dans mon code? Mon std::array reste à 0,0 dans chaque case grrr
A oui aussi, j'ai du écrire ça:
au lieu de comme toi:Code:std::transform(std::begin(jeuDeCarte), std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; });
Il me demandait 4 arguments obligatoires alors j'ai doublé le premier mais sans trop savoir pourquoi.Code:std::transform(std::begin(jeuDeCarte), std::end(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; });
C'est dommage, parce que les bons arguments sont source.begin, source.end, target.begin, functor, ce qui, dans ton cas, correspond à:
Regarde scrupuleusement la documentation de std::transformCode:std::transform(std::begin(jeuDeCarte), std::end(jeuDeCarte), std::begin(jeuDeCarte), [](Carte c) { c.first %= 13; c.second %= 4; return c; });
Oups, en effet, j'ai fait une erreur de copie...
mea culpa
il y a deux possibilités pour utiliser transform:
Code:
1
2
3 std::transform(std::begin(input), std::end(input), std::begin(output), function); // output est la séquence résultat de fonction(élément de l'input) appliquée au éléments de input std::transform(std::begin(input1), std::end(input1), std::begin(input2), std::begin(output), function); // output est le résultat de fonction(élément input1, élément input2) appliquée aux élements de input1 et 2
On est d'accord que std::transform n'a d'effet qu'une fois que std::iota a fonctionné? Car avant de me pencher sur tranform et bien le bucher, j'voudrais réussir à faire fonctionner iota ^^
J'ai relu plusieurs fois tes indications et regardé mon code et pourtant je n'arrive pas a faire que std::iota incrémente mes pairs. Mon tableau est toujours à {{0,0},{0,0},{0,0}...{0,0}}... Quel galère !! x)
C'est étonnant. Je te laisse faire de l'auto-contrôle avec la solution que j'ai testée (qui donc fonctionne):
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 constexpr unsigned NB_COLORS = 4; constexpr unsigned NB_CARDS_BY_COLOR = 13; using Card = std::pair<unsigned, unsigned>; using Deck = std::array<Card, NB_CARDS>; auto& operator++(Card& c) { ++c.first; ++c.second; return c; } auto generate_deck() { Deck d; std::iota(std::begin(d), std::end(d), Card()); std::transform(std::begin(d), std::end(d), std::begin(d), [](auto&& c) { c.first %= NB_CARDS_BY_COLOR; c.second %= NB_COLORS; return c; }); return d; }
Tu nous diras ce qui ne va pas.
J'en profite pour te signaler un compilateur en-ligne qui peut-être très pratique pour tester des petits bouts de code: http://melpon.org/wandbox/
Quand un résultat est erroné, il y a trois possibilités:
- soit le calcul est mal fait: ca serait ton utilisation de transform, ou la lambda.
- soit les données sont mal choisies: plutot du coté de iota et ++
- soit le résultat est mal observé: regarde donc ta boucle d'affichage :mrgreen:
Pfff bien vu Leternel!! C'est ma boucle d'affichage qui était erroné...
Je cherche l'erreur dans les calculs compliqués alors qu'elle est toute bête !!! Grrr mon code et ton code fonctionne Stend sauf que toi tu penses à utiliser les constantes alors que moi j'y pense pas... ^^
Leternel, je ne connaissais pas ta boucle, encore une chose d'apprise! :)
Leternel, malgré ses millions d'années, a des yeux de lynx!
J'ai assez écrit d'aneries pour avoir pris pour habitude de vérifier d'abord mes meessages de débugs :D
Jusqu'à l'année dernière, je m'arretai à deux raisons: le calcul ou les données.
Puis j'ai fais cette erreur.
Et je ne suis pas vieux... il faut juste que je fasse changer mon pseudo (qui signifie "le ter Nel")
Et que signifie le ter Nel?
@toto81: d'ailleurs cela confirme l'utilité des algorithmes tout fait de la bibliothèque standard; si tu avais utilisé for_each:
Code:std::for_each(std::begin(deck), std::end(deck), [](Card c) { std::cout << '(' << c.first << ',' << c.second << ") "; });
ce ne serait pas arrivé ;)
Le mot "ter" désigne dans un français archaïque et légèrement déformé, le troisième d'un ensemble ou d'un groupe, en l'occurence des cinq Nels qui son (ou plus exactement étaient) les gardiens d'un ordre secret dans un jeu de rôle (inventé par un ami).
Par ailleurs, je suis contre ce for_each, puisqu'on a for(:).
Autant j'aime beaucoup les algorithmes, autant je désapprouve for_each en C++11(et +). Il est plus lourd syntaxiquement, et n'apporte pas d'avantage sinificatif en performance (pour ce que j'en ai vu)
Tout s'explique. J'ai l'impression que votre maître de jeu connaissant bien son affaire...Citation:
Le mot "ter" désigne dans un français archaïque et légèrement déformé, le troisième d'un ensemble ou d'un groupe, en l'occurence des cinq Nels qui son (ou plus exactement étaient) les gardiens d'un ordre secret dans un jeu de rôle (inventé par un ami).
for_each est plus flexible puisqu'on peut fixer le point de départ et le point d'arrivée, y compris par composition d'algorithme:Citation:
Par ailleurs, je suis contre ce for_each, puisqu'on a for(:).
Autant j'aime beaucoup les algorithmes, autant je désapprouve for_each en C++11(et +). Il est plus lourd syntaxiquement, et n'apporte pas d'avantage sinificatif en performance (pour ce que j'en ai vu)
Code:
1
2
3 std::for_each( std::find(std::begin(vec), std::end(vec), x), std::find(std::begin(vec), std::end(vec), x+y), faire_qqc );
L'autre bon point c'est qu'avec un alias de namespace on pourra faire basculer son code vers les versions parallèles des algorithmes disponibles dans le TS
Plus flexible, car il y a un manque du côté de la stl: un conteneur sur 2 itérateurs:
Il y a quand même un énorme avantage d'utiliser for_each plutôt qu'une boucle: la stratégie utilisée pour le parcours (séquentielle, parallèle) comme dans parallelism TS.Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 template<class It> struct Range { using iterator = It; iterator first; iterator last; iterator begin() const { return first; } iterator end() const { return last; } }; template<class Cont, class T, class U> auto find_range(Cont & cont, T const & x, U const & y) -> Range<decltype(begin(cont))> { auto first = std::find(begin(cont), end(cont), x); return Range<decltype(first)>{first, std::find(first, end(cont), y)}; } for (auto && x : find_range(vec, x, x+y))) { faire_qqc; }
J'ai avancé encore un peu! Création du deck avec tri des carte dans le bon ordre, fonction de mélange et fontion pour couper en deux fonctionnel:
Voilà il me manque que la dernière étape, convertir mon tableau en string.. Un petit indice pour m'aider? ^^ J'ai test plein de truc sans aucun résultat concluant...Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 #include <iostream> #include <utility> #include <array> #include <numeric> #include <algorithm> #include <random> const auto nbCarte = 52; const auto nbCarteParSigne = 13; const auto nbCouleur = 4; using Carte = std::pair<unsigned, unsigned>; using Deck = std::array<Carte, nbCarte>; Carte& operator++(Carte& p) //Ajoute un operateur ++ pour la fonction std::iota qui est appelé dans la fonction "creationDeck" { ++p.first; ++p.second; return p; } auto creationDeck() //Creation d'un array de 52 emplacements qui contient chacun une paire de deux unsigned int { Deck tab; std::iota(std::begin(tab), std::end(tab), Carte()); std::transform(std::begin(tab), std::end(tab), std::begin(tab), [](Carte c) { c.first %= nbCarteParSigne; c.second %= nbCouleur; return c; }); std::sort(std::begin(tab), std::end(tab)); return tab; } void melangerCarte(Deck &tab) //Fonction de mélange aleatoire { std::random_device random; std::shuffle(std::begin(tab), std::end(tab), random); } void couperCarte(Deck &tab) //Fonction qui coupe les carte en deux { std::rotate(tab.begin(), tab.begin() + 27, tab.end()); } int main() { auto jeuDeCarte = creationDeck(); //Crétion du jeu de carte dans un array en appelant la fonction "creationDeck" melangerCarte(jeuDeCarte); //Melange les cartes aléatoirement en appelant la fonction "melangerCarte" couperCarte(jeuDeCarte);//Coupe le jeu de carte au milieu en appelant la fonction "couperCarte" for (const auto& carte : jeuDeCarte) //Affiche le résultat std::cout << carte.first << ',' << carte.second << ' '; return 0; }
Eh bien, tu as des cartes représentées par une paire de nombre:
- un qui va de 0 à 12
- l'autre de 0 à 3
qu'est-ce qui a une taille qu'on peut définir à 13, à 4, à n'importe-quel nombre d'ailleurs et qui pourrait contenir le nom des cartes ou des couleurs?
Un vector de type string à plusieurs dimensions? ^^'
ou deux vectors de string plus simplement?
Oue et j'aime quand c'est simple! ^^
Du coup je dois utiliser une fonction spéciale? J'ai essayé de faire une sorte de copie dans un tableau mais j'ai eu a chaque fois des problèmes de type incompatible etc..
J'ai vu des gens parler de ostream ou je sais plus, une sorte de copie mais entre les truc obsolète ou autre, j'ai dis je vais pas encore afficher un vieux code de louis 14.. x)
Il y a beaucoup plus simple: tu crées 2 tableaux de string avec respectivement les noms des cartes et des couleurs, et tu te sers des valeurs dans les cartes comme index pour trouver le nom correspondant dans les tableaux. Les tableaux peuvent être dans la fonction ou dans l'espace global
Salut!
Mais je suis pas sûr de moi car ca affiche bien le nom nom d'une carte mais je vois pas trop comment affiché une carte précise, par exemple, si tu me demande
d'afficher le 7 de carreau, je ne saurais pas qu'elle est son numéro dans le tableau, surtout si le tableau de base est mélangé et coupé ^^
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 #include <iostream> #include <utility> #include <array> #include <numeric> #include <algorithm> #include <random> #include <string> const auto nbCarte = 52; const auto nbCarteParSigne = 13; const auto nbCouleur = 4; using Carte = std::pair<unsigned, unsigned>; using Deck = std::array<Carte, nbCarte>; Carte& operator++(Carte& p) //Ajoute un operateur ++ pour la fonction std::iota qui est appelé dans la fonction "creationDeck" { ++p.first; ++p.second; return p; } auto creationDeck() //Creation d'un array de 52 emplacements qui contient chacun une paire de deux unsigned int { Deck tab; std::iota(std::begin(tab), std::end(tab), Carte()); std::transform(std::begin(tab), std::end(tab), std::begin(tab), [](Carte c) { c.first %= nbCarteParSigne; c.second %= nbCouleur; return c; }); std::sort(std::begin(tab), std::end(tab)); return tab; } void melangerCarte(Deck &tab) //Fonction de mélange aleatoire { std::random_device random; std::shuffle(std::begin(tab), std::end(tab), random); } void couperCarte(Deck &tab) //Fonction qui coupe les carte en deux { std::rotate(tab.begin(), tab.begin() + 27, tab.end()); } void afficherCarte(Deck &tab, int n) //Fonction qui affiche le nom d'une carte { std::array<std::string, nbCarteParSigne> numero = { "As","Deux","Trois","Quatre","Cinq","Six","Sept","Huit","Neuf","Dix","Valet","Dame","Roi" }; std::array<std::string, nbCouleur> couleur = { "coeur","carreau","pique","trefle" }; std::cout << numero[tab[n].first] << " de " << couleur[tab[n].second] << std::endl; } int main() { auto jeuDeCarte = creationDeck(); //Crétion du jeu de carte dans un array en appelant la fonction "creationDeck" melangerCarte(jeuDeCarte); //Melange les cartes aléatoirement en appelant la fonction "melangerCarte" couperCarte(jeuDeCarte); //Coupe le jeu de carte au milieu en appelant la fonction "couperCarte" afficherCarte(jeuDeCarte, 0); //Permet d'affiché le nom de la carte demandé en deuxieme argument contenu dans un array en premier argument return 0; }
C'est presque ça!
Trois points:
- La signature de ta fonction devrait prendre une carte pas un Deck et le numéro de la carte dedans: void afficher_carte(Carte c);. D'ailleurs l'exercice ne demandait pas d'afficher la carte mais de renvoyer une string contenant sa description.
- L'as devrait être placé en dernier dans ton vecteur des noms. Comme ça l'ordre des noms dans le array<string> est identique à l'ordre en valeur des cartes. Cela simplifiera la comparaison de deux cartes. La carte la plus faible est 2, elle est représentée par 0 dans le premier membre d'une Carte, d'où: numeros[0] == "deux";. De même la carte la plus puissante est l'as, d'où: numeros[NB_CARDS_BY_COLOR-1] == "as";. Pour les couleurs c'est purement conventionnel donc tu peux choisir l'ordre que tu veux.
- Ta fonction va créer les deux tableaux à chaque fois que tu l'appelles, ce n'est pas optimal ;) Tu as deux solutions: ou bien définir les tableaux dans l'espace global, c'est-à-dire en dehors de la fonction. Ou bien tu les déclares static: ils ne seront créés qu'une fois, au premier appel de la fonction: void afficher_carte(Carte c) { static std::array<std::string, 4> couleurs = { ... }; ... }
Bon, on continue!
Jeu de poker, étape 2:
facile-moyen
- Une main est composée de 5 cartes. Avec l'algorithme std::copy_n, copier les n premières cartes d'un paquet dans une main. Ecrire une fonction pour trier la main en fonction du rang (numéro) des cartes en utilisant std::sort.
- En utilisant (facultatif) l'algorithme std::all_of, vérifier si la main contient une suite (5 cartes qui se suivent) ou une couleur (5 cartes de couleur identique).
moyen-difficile:
- mettons qu'on choisisse de définir une main comme un vecteur de Cartes:
Ecrire une fonction qui divise la main en un vecteur de mains où les cartes sont identiques:Code:using Hand = std::vector<Card>;
difficile:Code:std::vector<Hand> split_by_rank(const std::Hand& hand); // dans chaque Hand du vector de résultat, toutes les cartes ont le même numéro
écrire split_by_rank en utilisant l'algorithme std::accumulate.
facile-moyen:
- écrire une fonction qui trie le std::vector<Hand> par ordre décroissant de taille (les groupes de cartes contenant le plus de cartes d'abord).
difficile:
- écrire une fonction qui, en utilisant, les fonctions définies préalablement, permette d'attribuer un score à chaque main.