germino : ajout de la section BDD et de l'exercice Biblio++
germino : ajout d'une note à la fin concernant les exercices sans difficulté
germino : ajout de deux solutions proposées par lmghs
En fait j'ai une question : Les notes des exercices "appartiennent" à quel moment? Je veux dire par là que la note par exemple : "CRYPTO 2: Chiffre de Vigenère" est difficile à 4/5 quand on est allé jusqu'au niveau 3? Je sais pas comment poser cette question
Nom :En lettres ! Sujets : Chaînes de caractères, algorithmes
Qui ne se souviens pas le jour horrible où, à l'école, il a fallu écrire nos chers nombres (1, 2, 24, 1184 et pleins d'autres) en toutes lettres ?
Oui je pense que vous vous en souvenez, toutes ces règles: accords, trait d'union, ...
Et si vous ne vous en souvenez pas (donc que vous êtes jeunes), ne vous inquiétez pas. Ca viendra
L'exercice
Vous l'aurez compris, il faut faire un programme auquel l'on passe notre nombre en paramètre et qui nous l'écrira en toutes lettres
L'exemple parle de lui-même (notez que je suis suisse )
Il convient naturellement à vous de choisir les règles d'écriture de votre choix
Je vous donne ce lien qui me semble fort intéressant et complet : Nombres en français (Wikipédia)
Bonne chance
PS: N'hésitez pas à proposer des améliorations pour cet exercice que j'ai rédigé rapidement sur un coup de tête, donc pas parfait
Voilà la solution que je propose : trois fonctions de simplification (digit2fr qui fait 0-9 -> lettres, ten2fr qui fait 0-19 -> lettres, et ty2fr (de twenTY) qui fait 0-99 -> lettres), une fonction qui assemble (num2fr, aidée par la macro NUMNAME (nom numérique) pour éviter les répétitions), et enfin la fonction main, qui gère la ligne de commande et l'affichage.
On peut afficher n'importe quel nombre gérable par l'ordinateur (de -2^63 à 2^63-1 chez moi, soit jusqu'à neuf-trillions-deux-cent-vingt-trois-billiards-trois-cent-soixante-douze-billions-trente-six-milliards-huit-cent-cinquante-quatre-millions-sept-cent-soixante-quinze-mille-huit-cent-sept).
Enfin, j'ai pris la réforme de 1990, par flemmardise aiguë.
#include <cassert>
#include <cstdint>
#include <iostream>
#include <sstream>
#include <string>
std::string digit2fr(unsigned char digit) {
static const std::string numbers[] = {
"zero", "un" , "deux", "trois", "quatre",
"cinq", "six", "sept", "huit" , "neuf"
};
assert(digit < 10);
return numbers[digit];
}
std::string ten2fr(unsigned char nb) {
static const std::string numbers[] = {
"dix", "onze", "douze", "treize",
"quatorze", "quinze", "seize"
};
assert(nb < 20);
if (nb < 10) return digit2fr(nb);
else if (nb < 17) return numbers[nb - 10];
else return "dix-" + digit2fr(nb - 10);
}
std::string ty2fr(unsigned char nb) {
static const std::string numbers[] = {
"vingt", "trente", "quarante", "cinquante", "soixante",
"soixante-dix", "quatre-vingt", "quatre-vingt-dix"
};
assert(nb < 100);
if (nb < 20) return ten2fr(nb);
else if (nb == 71) return "soixante-et-onze";
else if (71 < nb && nb < 80) return "soixante-" + ten2fr(nb - 60);
else if (nb == 80) return "quatre-vingts";
else if (nb == 81) return "quatre-vingt-un";
else if (90 < nb && nb < 100) return "quatre-vingt-" + ten2fr(nb - 80);
else if (nb % 10 == 1) return numbers[nb / 10 - 2] + "-et-un";
else if (nb % 10 != 0) return numbers[nb / 10 - 2] + "-" + ten2fr(nb % 10);
else return numbers[nb / 10 - 2];
}
std::string num2fr(std::intmax_t num) {
std::string ret;
std::uintmax_t nb;
if (num < 0) {
ret += "moins ";
nb = -num;
} else {
nb = num;
}
auto numname = [&ret, &nb](unsigned long long Nb, const std::string & Lt) {
if (nb / Nb >= 1) {
if (nb / Nb == 80) {
ret += "quatre-vingt-";
} else {
ret += num2fr(nb / Nb) + "-";
}
if (nb / Nb == 1) {
ret += Lt + "-";
} else {
ret += Lt + "s-";
}
nb %= Nb;
}
};
//numname(1000000000000000000000, "trilliard"); // Bigger than biggest possible integer
numname(1000000000000000000, "trillion");
numname(1000000000000000, "billiard");
numname(1000000000000, "billion");
numname(1000000000, "milliard");
numname(1000000, "million");
if (nb / 1000 >= 1) {
if (nb / 1000 == 80) {
ret += "quatre-vingt-";
} else if (nb / 1000 > 1) {
ret += num2fr(nb / 1000) + "-";
}
ret += "mille-";
nb %= 1000;
}
if (nb / 100 >= 1) {
if (nb / 100 == 1) {
ret += "cent-";
} else {
if (nb % 100 != 0) {
ret += ten2fr(nb / 100) + "-cent-";
} else {
ret += ten2fr(nb / 100) + "-cents-";
}
}
nb %= 100;
}
if (nb > 0 || ret.empty()) {
ret += ty2fr(nb);
} else {
ret.resize(ret.size() - 1);
}
return ret;
}
std::string usage(const std::string & arg) {
return "Usage : " + arg + " NOMBRE";
}
int fail(const std::string & msg) {
std::cerr << msg << std::endl;
return EXIT_FAILURE;
}
int main(int argc, char** argv) {
if (argc <= 1) return fail(usage(argv[0]));
std::intmax_t nb;
std::istringstream iss(argv[1]);
iss >> nb;
if (iss.fail()) return fail(usage(argv[0]));
if (!iss.eof()) return fail(usage(argv[0]));
if ( iss.bad()) return fail("Erreur inconnue");
std::cout << num2fr(nb) << std::endl;
return EXIT_SUCCESS;
}
Edit : Code corrigé selon les indications de lmghs, post juste en-dessous.
a- Les tableaux statiques devraient être const aussi
b- Nul besoin de préciser leur taille -- surtout quand ils ne correspondent pas aux nombre d'éléments dedans
c- L'utilisation d'une macro me perturbe un peu. (je préfèrerai encore passer par une fonction inline prenant ses paramètres en in/out, voire une lambda commentée qui modifie son contexte)
d- Le string::pop_back, ça s'émule mieux avec un resize(size -1)
e- Attention, les assertions c'est pour les erreurs de programmation. Si argv[] ne contient pas un nombre du bon format, ce n'est pas une erreur de programmation.
b- Oups, et dire que j'avais mis ça pour être sûr de ne pas me tromper au moment de remplir, pensant que g++ me préviendrait (même si finalement il était volontaire de ne pas tous les mettre).
e- En fait, c'est par paresse que j'ai collé des assertions dans le main().
Merci de ces commentaires en tout cas !
Je vais corriger ça et je reviens éditer mon message précédent.
Bon ! Vous avez terminé ? Il est temps de dévoiler une solution au TP "Le site du zéro"
Partie « Papier - Crayon »
Dans un premier temps, il faut commencer par réfléchir à l'architecture avant de programmer, sinon c'est l'échec quasiment assuré.
Les tutos
Nous avons trois types de tutoriels différents ayant chacun un point commun. Cela suggère donc une relation d'héritage. Est-elle justifiée ?
La question que l'on doit se poser est de savoir si un article EST UN tutoriel ou pas. La réponse est assez facile à trouver ici. C'est oui. Et cela est aussi valable pour les mini-tutos et les big-tutos.
Que mettre dans la classe mère (Tuto) ?
On va regrouper les points communs des trois types dans la classe mère. En l'occurrence, une chaîne de caractère pour l'attribut titre et une fonction affiche().
Cela suggère donc une architecture de ce genre :
Sont-ce des classes concrètes ou des classes d'entités ?
Ici, il s'agit clairement de classes d'entités. Nous pourrons donc directement appliquer la « recette » qui consiste à mettre les constructeurs de copies et opérateurs = dans la partie privée de la classe. Nous pourrons aussi écrire un destructeur virtuel.
La fonction affiche() doit-elle être déclarée const ?
Cette fonction ne doit pas modifier les attributs (puisqu'elle doit juste les afficher). On peut (et on doit ) donc la déclarer const.
Quelles fonctions doivent-être virtuelles ?
La fonction affiche() doit avoir un comportement différent pour les différentes classes filles. Nous devons donc la déclarer virtuelle. De plus, nous ne voulons pas que l'on puisse créer de Tuto, mais seulement des BigTuto, MiniTuto ou Articles. Il nous faut donc déclarer la fonction affiche() comme virtuelle pure dans la classe Tuto.
Le module VosTutos
L'autre partie du problème est le module VosTutos. Nous voulons pouvoir utiliser le polymorphisme pour avoir un comportement spécifique de la fonction « afficher » des différents tutos. Il nous faut donc utiliser un tableau de pointeurs vers des Tutos. Cela peut se faire de deux manières différentes :
Mettre un attribut std::vector<Tuto*> dans la classe.
Hériter de std::vector<Tuto*> car VosTutos EST IMPLÉMENTÉE sous forme de std::vector<Tuto*>
J'ai choisi la deuxième manière puisque la première manière est déjà présente dans la classe BigTuto. Ce qui nous donne le diagramme suivant :
Les headers
Cette discussion n'a pas été entièrement inutile, puisque nous pouvons directement traduire le diagramme en code C++.
#ifndef TUTOS_HPP_INCLUDED
#define TUTOS_HPP_INCLUDED
#include <string>
#include <vector>
class Tuto{
public:
Tuto(const std::string& titre); // Construit un tutoriel avec un titre donné.
virtual ~Tuto();
virtual void affiche() const = 0;
private:
std::string m_titre;
Tuto(const Tuto&);
Tuto& operator=(const Tuto&);
};
// ################################################################################
class Article: public Tuto{
public:
Article(const std::string& titre, const std::string& lien);
virtual void affiche() const;
virtual ~Article();
private:
std::string m_lien;
Article(const Article&);
Article& operator=(const Article&);
};
// ################################################################################
class MiniTuto: public Tuto{
public:
MiniTuto(const std::string& titre, unsigned int nbParties);
virtual void affiche() const;
virtual ~MiniTuto();
private:
unsigned int m_nbParties;
MiniTuto(const MiniTuto&);
MiniTuto& operator=(const MiniTuto&);
};
// ################################################################################
class BigTuto: public Tuto{
public:
BigTuto(const std::string& titre);
virtual void affiche() const;
void ajouteChapitre(MiniTuto* tuto);
virtual ~BigTuto();
private:
std::vector<MiniTuto*> m_chapitres;
BigTuto(const BigTuto&);
BigTuto& operator=(const BigTuto&);
};
#endif // TUTOS_HPP_INCLUDED
Je n'ai ici que retranscrit en C++ ce que nous avions écrit sur papier précédemment.
Le corps des fonctions
Passons donc au « remplissage » du corps des fonctions. Pour cela, il faut s'aider du main() fourni et surtout du résultat affiché.
Les Tutos
Quel que soit le type de tutoriel construit, un message s'affiche. On peut donc mettre ce message dans le constructeur de Tuto. Et de même pour le destructeur.
Tuto::Tuto(const std::string& titre)
:m_titre(titre)
{
cout << "Le tutoriel '" << m_titre << "' a bien été créé." << endl;
}
Tuto::~Tuto()
{
cout << "Le tutoriel '" << m_titre << "' a été correctement détruit." << endl;
}
On peut alors définir les constructeurs des classes filles en appelant le constructeur de Tuto :
Qu'en est-il de la fonction d'affichage ? Elle contient une partie commune (l'affichage du titre) et une partie qui diffère pour chaque classe fille. On peut donc mettre la partie commune dans la classe Tuto et utiliser le démasquage dans les classes filles.
Cela permet de créer un code facilement maintenable et générique.
Il ne nous reste plus que la fonction ajouteChapitre() à écrire. Pour cela, rien de plus facile. Il suffit d'utiliser la fonction push_back() des std::vector.
void VosTutos::ajouteTuto(Tuto* tuto)
{
push_back(tuto);
cout << "Votre tutoriel a bien été ajouté." << endl;
}
La fonction affiche se complique un peu, car nous allons avoir besoin d'utiliser l'opérateur [] de std::vector. Pour ce faire, on ne peut pas simplement écrire []->affiche(). Il faut passer par l'opérateur de résolution de portée et utiliser le nom complet de l'opérateur : operator[]->affiche().
void VosTutos::affiche() const
{
cout << "\nLe Site du zéro contient les tutoriels suivants :\n" << endl;
for(size_t i(0); i<size(); ++i)
{
cout << i << ") ";
std::vector<Tuto*>::operator[](i)->affiche();
cout << endl;
}
}
Finalement, pour supprimer un tutoriel, le plus simple et d'utiliser la procédure suivante :
Inverser le tutoriel à supprimer avec le dernier du vector.
Utiliser pop_back() sur le vector.
Il faut aussi penser à vérifier que le tutoriel existe bien avant de le supprimer. Pour cela, rien de tel qu'une exception.
void VosTutos::supprimeTuto(size_t index)
{
if(index > size())
throw std::runtime_error("ERREUR : VosTutos::supprimeTuto() : index trop grand.");
operator[](index) = back();
pop_back();
cout << "Votre tutoriel a été supprimé correctement du site" << endl;
}
On aurait aussi pu utiliser at() au lieu de l'opérateur [] et ainsi laisser le vector lancer lui-même l'exception le cas échéant.
Voilà. Je crois bien qu'on a terminé. Le code complet est dans le message suivant.
C'est vrai que c'est plus du polymorphisme, mais le principe de base reste tout de même de gérer une base de donnée (des tutoriels). C'est pour ça que je l'avais mis là.
Exercice d'algorithmie : Le jeu du "football sur papier"
Bonjour, je vous propose un exercice basé sur l'algorithmie.
Présentation du jeu
Tout d'abord, je tiens à vous préciser que vous aurez du mal à trouver les règles du jeu sur Internet, car ce jeu n'est référencé nulle part (ou alors sous un autre nom ?).
Le jeu du football sur papier se joue à deux joueurs, chacun possédant un crayon de couleur différente. On dessine une grille de 10 cases par 9, et deux buts à chaque extrémité.
Chaque joueur choisit un but, l'objectif étant bien sûr de marquer chez l'adversaire.
On part du point rouge (au milieu à gauche). Les deux joueurs, chacun leur tour, ont le droit de se déplacer en respectant les règles suivantes :
- Le joueur ne peut avancer que d'un point à la fois, verticalement, horizontalement ou diagonalement.
- Si, à la fin de son tour, le joueur se situe sur un mur ou sur un point déjà utilisé, il doit "rebondir", et ce plusieurs fois, tant qu'il n'a pas trouvé d'intersection libre.
- On ne peut pas repasser deux fois sur le même trait.
- Au tour suivant, le deuxième joueur repart du point précédent.
Au départ, le joueur numéro 1, en rouge, peut choisir entre ces trois actions. Admettons qu'il doive marquer en bas. Comme il n'est pas trop bête, il décide d'aller en bas :
Sur cette image, on peut voir que le joueur 2, en bleu, repart de l'endroit ou s'est arrêté le premier joueur, en essayant à son tour de "tirer" le ballon vers le haut. Cet enchaînement se répète plusieurs fois, mais à la fin, le joueur rouge atteint le mur et a donc le droit de rebondir.
Voici ce qui se passe au tour suivant :
C'est au tour du joueur 2, normalement en bleu, mais j'ai ici représenté son déplacement en violet, pour plus de clarté. Le joueur 2 décide donc de remonter. Mais il atteint une intersection déjà utilisée, et peut donc rebondir.
Notez qu'on peut rebondir plusieurs fois de suite, comme le joueur rouge le fait ici (à nouveau, je l'ai représenté en violet) :
Vous pouvez remarquer qu'on perd très facilement "l'avance" qu'on a gagnée.
Fin de la partie :
A la fin de cette partie (assez complexe je vous l'accorde), c'est le tour du joueur bleu. Il voit qu'il s'approche du but. Il rebondit plusieurs fois (on peut également rebondir sur les coins), et marque. La partie ne se termine que lorsque le joueur a touché la ligne du fond du but. Dans d'autres cas, un des joueurs peut être bloqué, par exemple s'il se retrouve dans un coin de la grille. Ne pouvant pas passer par les murs ou revenir sur ses pas, il perd.
Ce jeu peut paraître difficile à comprendre, mais au bout de quelques parties, on constate qu'il est très simple, tout est dans la stratégie. Ce jeu est très prenant;
Bon, vous vous doutez que je ne vous présente pas ce jeu pour rien...
Enoncé de l'exercice
L'objectif va être de réaliser ce jeu en C++. Vous ne vous en doutiez pas hein ???
Niveau 1
Pour commencer doucement, réalisez la partie "simple" du jeu. L'ordinateur doit détecter si un des deux joueurs a gagné, et savoir quel joueur est en train de jouer. Il doit pouvoir changer le joueur actif a chaque fois qu'une intersection libre est atteinte. Pour vous déplacer, utilisez le pavé numérique si vous en avez un (n'oubliez pas qu'on peut se déplacer en diagonale). Sinon, utilisez un bloc de 3x3 touches. Vous pouvez réaliser le jeu en console (voir les commentaires des Zér0s), même s'il est sans doute aussi facile de le faire avec une librairie 2D comme la QFML ou Qt, si vous les connaissez.
Niveau 2
Toujours rien d'insurmontable, essayez d'ajouter la fonction qui détecte si le joueur est bloqué. Vous pouvez également demandez aux deux joueurs leur nom avant la partie et permettre aux deux joueurs d'utiliser chacun une partie différente du clavier pour se déplacer
Niveau 3
Là, ça se corse un peu. Cette partie est difficile à réaliser, mais si vous y arrivez, vous serez fiers de vous. Ajoutez une IA assez basique, qui essaye par exemple de se déplacer toujours en direction du but adverse, sauf si ce n'est pas possible, dans ce cas se déplacer dans la seule direction restante.
Niveau 4
Ajoutez un menu simple, qui permette de choisir entre 3 modes : Joueur contre IA, Joueur contre Joueur, ou IA contre IA (dans ce dernier cas, mettez un temps d'attente entre les coups joués, pour que l'utilisateur puisse suivre le déroulement de la partie).
Niveau 5
Ajouter une véritable IA, beaucoup plus complexe, qui utilise un algorithme comme le Min/Max (vous trouverez un tutoriel ici). Personnalisez le menu en permettant au joueur de choisir le niveau de l'IA.
Personnellement, je trouve que cet exercice est difficile (mais il paraît que ce n'est pas à moi de juger ça ) mais particulièrement intéressant, et il y a différentes approches pour y parvenir, et je vous promet que vous serez fiers si vous y arriverez.
Quelques idées : Essayez de faire quelques parties sur une feuille, contre des amis par exemple. Vous pourrez ainsi comprendre le jeu et découvrir différentes techniques couramment utilisées dans le jeu, et ainsi les implémenter dans votre IA plus tard. Les niveaux 1 et 2 sont plutôt faciles, mais il y a vraiment un palier à franchir à partir du niveau 3. Ceci dit, je n'aurais pas posté ce sujet s'il n'y avait pas de solution !!!
Allez, tous à vos claviers !
P.S.: Si vous avez eu du mal à suivre les règles du jeu (ce que je conçois), n'hésitez pas à me demander.
EDIT : Corrigé 2 fautes, et modifié une phrase erronée
EDIT : Je ne vais pas rajouter d'autres niveaux, car je pense que 5 suffisent, mais si vous voulez des idées d'améliorations :
- Utiliser une bibliothèque graphique (SFML)
- Ajouter un système de score et de meilleurs scores enregistrées (par exemple, si on a perdu, le score est égal au nombre de coups auxquels on a survécu, et si on a gagné, 1000 - le nombre de coup auxquels l'adversaire a survécu)
- Ajouter un module réseau pour pouvoir jouer à plusieurs, avec un mini-chat intégré
Tout ceci ne sont que des idées, mais elle peuvent améliorer le jeu et le rendre plus agréable.
J'ai faille le mettre dans les idées, mais à mon avis il serait plutôt difficile de mélanger pathfinding et algorithme d'IA. Sinon, l'énoncé est pas trop dur à comprendre ? J'ai vu que certains sujets avaient été un peu lésés par ce problème...
Humm. Je serais parti avec un pur minmax (negamax avec élagage) vu qu'il s'agit d'un jeu à deux (et qu'il est "symétrique"). Reste à voir jusqu'à quelle profondeur on peut aller (avec le tic-tac-toe, je saturai vite).
Dijkstra en heuritique ? Mouais. À voir.
Un min/max, avec une profondeur de 5 coups au tout début (là où il y a le moins de possibilités), il y a déjà au moins 3*8*8*8*8 = 12288 évaluations. Avec 10 coups (sans rebonds, donc je dirais environ 4/5 coups avec rebonds), il y a 3*(8^9) > 400 millions de cas. Et je n'ai même pas compté les rebonds.
Je pense que, même avec un bon élagage, on risque d'avoir une IA moyenne (bon, après, en comparaison à une IH - intelligence humaine - on ne peut pas savoir).
Faites des tests sur papiers, vous verrez c'est assez distrayant
En fait, j'ai remarqué deux stratégies adoptables en jouant :
1) Boucher le plus possible la grille
2) Ne pas faire plus que nécessaire
Le premier objectif à moyen terme d'une IA (et d'un humain !) est de se retrouver le premier à rebondir sur le mur d'en face. A condition d'être deuxième à jouer et de savoir compter, c'est très simple. (même si vous pouvez vous faire avoir par une manœuvre inattendue...)
Dans ce jeu, on apprend beaucoup de ses erreurs, car des positions similaires reviennent souvent...
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles - ♡ Copying is an act of love.