Partage

Un pointeur générique

Sujet résolu
28 mai 2010 à 12:24:40

Bonjour tout le monde :)

Je viens vous consulter pour un problème contraignant, dans l'espoir que vous aurez quelques idées sur la manière de le résoudre, car force de plusieurs heures de cogitations, je sèche >.<

Pour reprendre l'exemple de Mateo dans ses cours, en gros, dans mon application client/serveur, le client peut être soit un guerrier, soit un magicien, tout deux héritant de la classe Personnage.
Par exemple, je reçois un paquet m'informant que mon Personnage doit mourir. Et là, la solution est lourde. Car, imaginons que je suis un magicien, j'ai donc stocké mon magicien dans la classe de ma partie.
Il faut que j'appelle : myMagicien.Die();
Seulement j'aurais bien pu être un guerrier, et j'aurais donc dû appeler : myGuerrier.Die();
Il faudrait que je teste "Si je suis un guerrier" ou "Si je suis un magicien". . . :euh:

Quelque chose de bien aurait été que je puisse avoir un pointeur "sans type", que j'assigne au moment voulu à mon guerrier ou mon magicien de sorte à pouvoir faire, à la réception du packet : myPersonnage.Die();

Or, je ne pense pas qu'on puisse changer le type d'une variable après l'avoir déclarée :-°
De plus, les templates ne s'accordent pas avec ce genre de classe sans opérateur.

Donc voilà, si vous avez une idée sur comment résoudre ce problème, je suis tout ouï :D
Merci d'avance :)

28 mai 2010 à 12:53:44

Die() est une méthode de personnage non?
Or ton client ne représentera qu'un joueur qui sera un guerrier ou un magicien.
Ces deux heritants de personnage, je ne vois pas pourquoi tu ne pourrais pas utiliser la méthode Die().
28 mai 2010 à 12:56:01

Polymorphisme, myPersonnage->Die() utilisera la méthode de la bonne classe si celui-ci a été alloué dynamiquement avec une des deux classes filles.
28 mai 2010 à 12:56:33

Polymorphisme ?
class Personnage {
    public :
        virtual void die() = 0;
};

class Magicien : public Personnage
{
    public :
        virtual void die()
        {
            std::cout << "Un magicien est mort." << std::endl;
        }
};

class Guerrier : public Personnage
{
    public :
        virtual void die()
        {
            std::cout << "Un guerrier est mort." << std::endl;
        }
};

void kill(Personnage& perso)
{
    perso.die();
}

int main()
{
    Magicien mago;
    Guerrier pabo;
    kill(mago);
    kill(pabo);
}


Edit : mais à mon avis, il ne faut pas raisonner comme cela pour les classes de personnages. Il faudrait plutôt avoir une seule classe Classe générique contenant toutes les informations disponibles que tu ajoutes à ta classe Personnage et que tu construit à l'initialisation du perso. Comme ça tu peux avoir les info sur tes classes stockées dans un fichier texte et en rajouter autant que tu veux après la compilation.
Un petit exemple pour illustrer ma pensée (j'ai fait les fichiers de config avec Lua pour ne pas m'embêter avec la lecture des fichiers et réduire le code, mais toute autre organisation est possible) :

Code spaghetti inside, pas taper.
#include <iostream>
#include <map>
#include <lua.hpp>

class Ability {
    std::string m_description;
    public :
        explicit Ability(const std::string& de) : m_description(de) {}
        const std::string& description() const {return m_description;}
};

class Classe {
    std::string m_name;
    unsigned int m_hitdice;
    std::map<std::string, Ability> m_abilities;

    public :
        bool loadFromFile(const std::string& str)
        {
            lua_State* L = luaL_newstate();
            bool result = luaL_dofile(L, str.c_str());
            if(result) std::cout << "Error : " << lua_tostring(L, -1) << std::endl;
            else
            {
                lua_getglobal(L, "class");
                int index = lua_gettop(L);

                lua_getfield(L, index, "name");
                if(lua_isstring(L, -1)) m_name = lua_tostring(L, -1);
                else m_name = "Pas de classe";

                lua_getfield(L, index, "hitdice");
                if(lua_isnumber(L, -1)) m_hitdice = lua_tonumber(L, -1);
                else m_hitdice = 8;

                lua_getfield(L, index, "abilities");
                m_abilities.clear();
                if(lua_istable(L, -1))
                {
                    int abilitiesIndex = lua_gettop(L);
                    lua_pushnil(L);
                    while(lua_next(L,abilitiesIndex))
                    {
                        if(lua_isstring(L, -2))
                        {
                            std::string desc = "Aucune information sur cette capacité";
                            if(lua_isstring(L, -1)) desc = lua_tostring(L, -1);
                            m_abilities.insert(std::make_pair(lua_tostring(L, -2), Ability(desc)));
                        }
                        lua_pop(L, 1);
                    }
                }
            }
            lua_close(L);
            return !result;
        }
        const std::string& name() const {return m_name;}
        unsigned int hitDice() const {return m_hitdice;}
        const std::map<std::string, Ability>& abilities() const {return m_abilities;}
};

class Personnage {
    std::string m_name;
    Classe m_class;
    public :
        Personnage(const std::string& name, const Classe& classe) : m_name(name), m_class(classe) {}
        const std::string& name() const {return m_name;}
        const std::string& className() const {return m_class.name();}
        unsigned int hitDice() const {return m_class.hitDice();}
        const std::map<std::string, Ability>& abilities() const {return m_class.abilities();}
};

void display(const Personnage& perso)
{
    std::cout << "Nom : " << perso.name() << std::endl;
    std::cout << "Classe : " << perso.className() << std::endl;
    std::cout << "Dés de vie : " << perso.hitDice() << std::endl;
    std::cout << "Capacités : " << std::endl;
    if(perso.abilities().empty()) std::cout << "\tAucune" << std::endl;
    else
        for(std::map<std::string, Ability>::const_iterator i = perso.abilities().begin();
            i != perso.abilities().end();
            ++i) std::cout << "\t" << i->first << " : " << i->second.description() << std::endl;
    std::cout << std::endl;
}

int main()
{
    Classe maClasse;
    maClasse.loadFromFile("guerrier");
    Personnage guerrier("Un guerrier lambda", maClasse);
    display(guerrier);
    maClasse.loadFromFile("magicien");
    Personnage magicien("Un magicien pas très courageux", maClasse);
    display(magicien);
    maClasse.loadFromFile("paysan");
    Personnage paysan("Un paysan robuste", maClasse);
    display(paysan);
}

Fichiers de classe :
guerrier :
class = { ["name"] = "Guerrier",
	  ["hitdice"] = 10,
	  ["abilities"] = {["Taper comme un sourd"] = "Frappe comme un bourrin.", 
	  		   ["Mourir en heros"]      = "Un dernier baroud d'honneur."
	  		  }
	}

magicien
class = { ["name"] = "Magicien",
	  ["hitdice"] = 4,
	  ["abilities"] = {["Lancer un super sort"] = "De la magie à gogo !", 
	  		   ["Mourir en lâche"]      = "Périr bêtement en voulant prendre ses jambes à son cou.",
	  		   ["Se planquer"]	    = "« Oh, je me demande ce qu'il y a derière ce rocher ! »"
	  		  }
	}

paysan
class = { ["name"] = "Paysan",
	  ["hitdice"] = 6,
	  ["abilities"] = {}
	}


Sortie :
Nom : Un guerrier lambda
Classe : Guerrier
Dés de vie : 10
Capacités : 
        Mourir en heros : Un dernier baroud d'honneur.
        Taper comme un sourd : Frappe comme un bourrin.

Nom : Un magicien pas très courageux
Classe : Magicien
Dés de vie : 4
Capacités : 
        Lancer un super sort : De la magie à gogo !
        Mourir en lâche : Périr bêtement en voulant prendre ses jambes à son cou.
        Se planquer : « Oh, je me demande ce qu'il y a derière ce rocher ! »

Nom : Un paysan robuste
Classe : Paysan
Dés de vie : 6
Capacités : 
        Aucune
Comme ça, il suffit de rajouter des fichiers contenant la description de la classe que tu veux, et elles seront automatiquement générées par la classe Classe. Ce que je n'ai pas fait mais que j'aurais du pour rendre le code vraiment extensible c'est d'avoir un fichier dans lequel je stockerais l'ensemble des classes (Edit : ou simplement lister le contenu d'un dossier contenant tous mes fichiers. A noter qu'il aurait aussi été possible (et pratique) d'autoriser la définition de plusieurs classes par fichier), et une petite boucle qui irais charger tout ça en mémoire. L'avantage de ce système, c'est que tu n'as pas besoin de recompiler tout à chaque fois que tu veux changer un détail d'une classe, en ajouter une, etc. En plus ça permet aux utilisateurs de développer leurs propres modules et augmente donc considérablement la durée de vie de ton jeu :-°
28 mai 2010 à 13:46:47

Citation : Punkside

Polymorphisme, myPersonnage.Die() utilisera la méthode de la bonne classe si celui-ci a été alloué dynamiquement avec une des deux classes filles.



L'allocation dynamique n'est pas une condition à l'utilisation du polymorphisme. C'est d'ailleurs assez clair dans le code de De passage.
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
28 mai 2010 à 13:57:21

Je sais, mais il demandait "un pointeur générique", je n'ai fait que répondre à son attente.
De plus j'ai donné le lien vers la première partie du chapitre sur le polymorphisme de Nanoc, donc ton intervention n'a pas vraiment lieu d'être, il suffit d'aller le lire...
28 mai 2010 à 14:20:42

Même en utilisant le polymorphisme à travers un pointeur, l'objet ne doit pas nécessairement être alloué dynamiquement contrairement à ce que tu as dit plus haut. Ce que tu as dit est donc faux. L'intervention a lieu d'être s'il y a erreur dans une réponse, non ? Après, j'ai peut-être mal compris, tout est possible. En tout cas, il n'y a pas mort d'homme...
Inkamath on GitHub - Interpréteur d'expressions mathématiques. Reprise du développement en cours.
28 mai 2010 à 20:35:24

@De passage: J'ai pas regardé en détail ce que tu proposes, mais si le sujet t'interesse, Luc avait fait un post (plusieurs même) sur une facon de faire qui garantirait l'extensibilité (race/classe des perso), et sur la manière d'utiliser des classes (dans le code, centre de décision). (mots_clé : lmghs, rpg, centre de décison)
FaQ : Fr | En 1 2 | C++11 | Template || Blog : Deloget | C++|Boost--Dev | C++Next | GotW || Installer Boost
28 mai 2010 à 21:28:08

D'ailleurs, pour les intéressés, je l'ai en lien (si je me trompe pas).

http://www.siteduzero.com/forum-83-301 [...] html#r2792537
28 mai 2010 à 21:32:39

@Freedom : J'ai déjà lu brièvement le post dont tu parles, et il me semble être proche de la méthode qu'il proposait (même si je ne l'ai pas implémentée in extenso dans mon exemple), en ce qui concerne la gestion des classes/races/... en tout cas.
(Après je n'en voudrais à personne de ne pas regarder mon code, il n'est pas super bien présenté et nécessite de connaître l'API de Lua pour la partie intéressante. Je l'ai codé pour le plaisir, mais maintenant que c'est fait, autant partager :) )
Je n'arrive pas à remettre la main sur le topic que j'avais lu dans l'immédiat, mais je persiste. Si quelqu'un a un lien ça peux toujours m'aider (et d'autres par la même occasion).
@Punkside : c'était ça, merci.
28 mai 2010 à 22:34:43

@De passage, ton code est intéressant.
Je pense que l'on se rejoint complètement dans nos approches.
Des fois je me dis que cela mériterait une grosse tartine à la tuto -- mais bon mon plugin de refactoring pour vim est prioritaire pour l'instant ... ^^'
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.
1 juin 2010 à 10:08:58

Merci beaucoup pour vos réponses qui m'ont beaucoup aidé car j'ai finalement utilisé le polymorphisme, avec une classe personnage contenant toutes les fonctions virtuelles potentielles des classes filles.
Je crée un pointeur sur Personnage que j'initialise à NULL et au moment voulu, j'assigne à ce pointeur un pointeur vers un Guerrier ou un Magicien afin qu'il se comporte tel. Voilà donc mon "pointeur générique" :D

Merci Punkside également pour ton lien dont je risque de me servir d'ici peu je pense.

Sur ce, salutations. :)

Un pointeur générique

× 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.
  • Editeur
  • Markdown