Partage
  • Partager sur Facebook
  • Partager sur Twitter

[Exercices] Venez vous entraîner !

(v2)

26 juillet 2011 à 11:31:39

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
  • Partager sur Facebook
  • Partager sur Twitter
3 août 2011 à 23:31:28

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 >_<
  • Partager sur Facebook
  • Partager sur Twitter
4 août 2011 à 3:44:03

Oui j'imagine, sinon, la note ne reflète pas le véritable niveau.
  • Partager sur Facebook
  • Partager sur Twitter
🍊 - Étudiant - Codeur en C | Zeste de Savoir apprenez avec une communauté | Articles  - ♡ Copying is an act of love.
5 août 2011 à 20:49:22

Exercice du mois d'août 2011


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 :diable:

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

$> en_lettres 34
trente-quatre
$> en_lettres 81
huitante-et-un
$> _

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

edit1: orthographe
  • Partager sur Facebook
  • Partager sur Twitter
7 août 2011 à 13:12:22

Cet exo me rappelle quelque chose. :)
z0zéro
  • Partager sur Facebook
  • Partager sur Twitter
Zeste de Savoir, le site qui en a dans le citron !
9 août 2011 à 21:33:15

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.
  • Partager sur Facebook
  • Partager sur Twitter
9 août 2011 à 23:46:58

Des petits commentaires:

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 :p

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.
  • Partager sur Facebook
  • Partager sur Twitter
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
10 août 2011 à 20:40:21

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. :)
  • Partager sur Facebook
  • Partager sur Twitter
14 août 2011 à 11:32:32

Hum, je vais peut-être me le faire cet exercice, c'est sympa :)
  • Partager sur Facebook
  • Partager sur Twitter
15 août 2011 à 16:03:50

@germino: Est-ce que je propose une solution pour ça: http://www.siteduzero.com/forum-83-664 [...] html#r6453933
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 août 2011 à 16:10:26

Ce serait bien :)
C'est un exo que je trouve intéressant ;)
  • Partager sur Facebook
  • Partager sur Twitter
15 août 2011 à 18:51:28

TP - Le site du zéro

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é. :pirate:

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.

Quel type d'héritage choisir ?
L'héritage public se justifie ici puisque un article EST UN tutoriel.

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 :

Image utilisateur


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 :

Image utilisateur


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


#ifndef VOSTUTOS_HPP_INCLUDED
#define VOSTUTOS_HPP_INCLUDED

#include "Tutos.hpp"

class VosTutos: private std::vector<Tuto*>{

    static const double version; // La version actuelle du module.

public:

    VosTutos();

    void affiche() const;

    size_t nbTutos() const;

    void ajouteTuto(Tuto* tuto);

    void supprimeTuto(size_t index);

    virtual ~VosTutos();

private:

    VosTutos(const VosTutos&);
    VosTutos& operator=(const VosTutos&);
 };


#endif //VOSTUTOS_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 :

Article::Article(const std::string& titre, const std::string& lien)
    :Tuto(titre),
    m_lien(lien)
{}


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.

void Tuto::affiche() const
{
    cout << "Titre : " << m_titre << endl;
}

void Article::affiche() const
{
    cout << "ARTICLE" << endl;
    Tuto::affiche();
    cout << "Lien : " << m_lien << endl;
}


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 BigTuto::ajouteChapitre(MiniTuto* tuto)
{
    m_chapitres.push_back(tuto);
}


Avec cela, nous avons terminé le code des différents tutoriels. Passons donc au module VosTutos.

VosTutos



Les constructeurs et destructeurs n'ont rien de particulier à faire, on peut donc les écrire comme suit :

VosTutos::VosTutos()
    :std::vector<Tuto*>()
{}

VosTutos::~VosTutos()
{}


On aurait aussi pu ne pas écrire de constructeur. Celui fourni par le compilateur étant identique à celui que nous avons écrit.


Afficher le nombre de tutoriels ne devrait pas être un problème, puisque cela consiste à appeler la fonction size() de la classe mère (std::vector).

size_t VosTutos::nbTutos() const
{
    return size();
}


De même pour la fonction ajouteTuto().

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 :
  1. Inverser le tutoriel à supprimer avec le dernier du vector.
  2. 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.
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 août 2011 à 18:51:54

Vous avez déjà les headers complets ainsi que la fonction main(). Voici donc les 2 autres fichiers .cpp.

Les tutos



#include "Tutos.hpp"

#include <iostream>
using namespace std;

// ################################################################################

Tuto::Tuto(const std::string& titre)
    :m_titre(titre)
{
    cout << "Le tutoriel '" << m_titre << "' a bien été créé." << endl;
}

void Tuto::affiche() const
{
    cout << "Titre : " << m_titre << endl;
}

Tuto::~Tuto()
{
    cout << "Le tutoriel '" << m_titre << "' a été correctement détruit." << endl;
}

// ################################################################################

Article::Article(const std::string& titre, const std::string& lien)
    :Tuto(titre),
    m_lien(lien)
{}

void Article::affiche() const
{
    cout << "ARTICLE" << endl;
    Tuto::affiche();
    cout << "Lien : " << m_lien << endl;
}

Article::~Article()
{}

// ################################################################################

MiniTuto::MiniTuto(const std::string& titre, unsigned int nbParties)
    :Tuto(titre),
    m_nbParties(nbParties)
{}

void MiniTuto::affiche() const
{
    cout << "MINI-TUTO" << endl;
    Tuto::affiche();
    cout << "Nombre de sous-parties : " << m_nbParties << endl;
}

MiniTuto::~MiniTuto()
{}

// ################################################################################

BigTuto::BigTuto(const std::string& titre)
    :Tuto(titre),
    m_chapitres()
{}

void BigTuto::affiche() const
{
    cout << "BIG-TUTO" << endl;
    Tuto::affiche();
    cout << "Nombre de chapitres : " << m_chapitres.size() << endl;
    cout << "Sommaire : " << endl;

    for(size_t i(0); i<m_chapitres.size() ; ++i)
        m_chapitres[i]->affiche();
}

void BigTuto::ajouteChapitre(MiniTuto* tuto)
{
    m_chapitres.push_back(tuto);
}

BigTuto::~BigTuto()
{}


Le module Vos Tutos



#include "VosTutos.hpp"
#include <iostream>
#include <stdexcept>
using namespace std;

const double VosTutos::version = 3.5;

VosTutos::VosTutos()
    :std::vector<Tuto*>()
{}

VosTutos::~VosTutos()
{}

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

void VosTutos::ajouteTuto(Tuto* tuto)
{
    push_back(tuto);
    cout << "Votre tutoriel a bien été ajouté." << endl;
}

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

size_t VosTutos::nbTutos() const
{
    return size();
}
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 août 2011 à 18:52:12

Pour aller plus loin



Voici quelques idées :

  • Améliorer les tutos en leur donnant plus d'attributs (Auteur, Texte...).
  • Proposer des fonctions pour modifier le contenu des tutos.
  • Proposer des fonctions pour changer l'ordre des tutos dans le module VosTutos.
  • Afficher le nombre de tutoriels de chaque type depuis le module VosTutos.
  • Afficher un tutoriel donné du module VosTutos plutôt que la liste complète.
  • Ajouter un attribut « Date » en utilisant une classe de votre propre cru.

Et bien sûr, tout ce que votre imagination fertile vous suggèrera. :D

Amusez-vous bien !
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 août 2011 à 19:24:12

Bon, il y a peut-être quelques trucs à améliorer. Par exemple des constructeurs et destructeurs vides à enlever. Je verrai ça demain.
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
15 août 2011 à 20:48:40

Je rajouterai cet exo et sa correction au premier post dès que j'en ai le temps. Merci de te donner de la peine ;)
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 6:46:36

Equinoxe : Ajouté l'exercice "Le Site du Zéro" et sa correction

(Il était temps que je revienne sur ce topic, vous alliez finir par me croire mort !)
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 8:50:29

J'aurais plutôt mis dans une catégorie "polymorphisme". Je ne sais pas où vous avez vu une base de donnée là-dedans.
  • Partager sur Facebook
  • Partager sur Twitter
Co-auteur du cours de C++. ||| Posez vos questions sur le forum ||| Me contacter.
16 août 2011 à 9:38:33

Effectivement, je le remet à sa place ;)
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 9:48:50

germino: création de la section Polymorphisme et déplacement du "Site du Zéro"
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 9:56:09

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à. ;)
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 18:57:45

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 o_O ?).

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é.

Image utilisateur

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.
Image utilisateur
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 :
Image utilisateur
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 :
Image utilisateur
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) :
Image utilisateur
Vous pouvez remarquer qu'on perd très facilement "l'avance" qu'on a gagnée.
Fin de la partie :
Image utilisateur
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 ??? :D


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 :lol: ) 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.
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 19:06:52

Mmh... un peu de A* serait pas mal... ou Dijkstra si pas trop de possibles...
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 19:08:33

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...
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 19:17:36

Ben en fait, il vont bien être obligé de faire du pathfinding ^^
edit: c'est pas très dur à faire en console, hein !
  • Partager sur Facebook
  • Partager sur Twitter
16 août 2011 à 19:40:42

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.
  • Partager sur Facebook
  • Partager sur Twitter
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
17 août 2011 à 0:31:23

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).
  • Partager sur Facebook
  • Partager sur Twitter
17 août 2011 à 1:08:02

Pas forcément car j'ai l'impression que les premiers coups ne servent à rien -- hormis remplir la grille pour permettre des supers sauts par la suite.
  • Partager sur Facebook
  • Partager sur Twitter
C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
17 août 2011 à 10:59:50

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...
  • Partager sur Facebook
  • Partager sur Twitter

[Exercices] Venez vous entraîner !

× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
  • Editeur
  • Markdown