Envoyé par
beware
Je ne doute pas que ton code soit bien meilleur et plus complet que le mien, mais il y a plein de chose que je comprends pas à cet instant.
Je n'en suis qu'au début de mon apprentissage.
Mais merci quand même cela m'a donnée quelques idées.
Là est peut être tout le problème... (soit dit sans vouloir t'offencer)
Tout à "l'excitation" d'apprendre un langage de programmation, j'ai l'impression que tu te lances dans un projet pour lequel tu n'as qu'une idée bien trop imprécise de ce que tu veux, et de la manière de l'obtenir, alors qu'il te manque des concepts "de bases".
Il n'est donc pas impossible que, pour un premier projet, tu ne sois occupée à viser "un peu trop haut" par rapport à tes connaissances .
Comprenons nous bien, le but de ces écrit n'est pas de te décourager de mener ton projet à bien, le but est de te faire prendre conscience que, si tu démarres un projet en n'ayant pas les bases suffisantes, tu risques très fort soit d'être bloquée en cours de route, soit, parce que (c'est du moins tout le mal que je te souhaites) tes connaissances auront évolué et que tu en sera réduite, pour faire évoluer ton projet, à "tout casser"... Ce qui est particulièrement agaçant
Contrairement à ce que l'on croit (mais c'est une erreur que tout programmeur débutant a faite au moins une fois) la programmation ce n'est pas se "ruer" sur le clavier et commencer à écrire du code, en espérant que ca marche (ou même que ca fasse ce que l'on attend de lui)...
En effet, l'expérience montre que les besoins que doit rencontrer ton projet évoluent en (quasi) permanence.
Si tu ne tiens pas suffisamment compte de l'évolution future de ton projet, il arrivera rapidement un moment où tu ne pourra plus le faire évoluer sans "tout casser".
Et la question n'est pas de savoir si cela va arriver, mais quand cela arrivera
En fait, une fois que l'on a "l'idée de base" d'un projet, il y a quatre grandes étapes à suivre (et tu remarquera rapidement qu'il faut régulièrement revenir sur plusieurs de ces étapes):
- Exprimer les besoins "ressentis" qui sous-tendent à ton idée de base
- Analyser ces besoins pour déterminer quels types de données permettront de les rejoindre
- Concevoir la manière dont les différents types seront mis en oeuvre et réagiront en vue de satisfaire les besoins
- Implémenter les différents types et les fonctions qui vont bien pour obtenir le résultat souhaité
Evidemment, il est possible à tout moment de se rendre compte qu'il faut rajouter un besoin à satisfaire, ce qui nécessite de recommencer le processus en essayant de garder le maximum de l'existant.
Dans ton cas, tu as déjà "épinglé" trois acteurs important:
- Le combat
- Le joueur
- Les armes
Il est donc important de s'intéresser au "kifékoi" afin que chaque acteur ait une responsabilité qui lui est propre.
Le combat va réunir... deux (ou plusieurs peut être dans les évolutions) combattants.
C'est donc lui qui doit s'occuper de gérer les combattants, mais, cela implique que sa responsabilité est aussi celle de... donner l'arme au(x) combattant(s)
C'est donc à lui que revient la responsabilité de demander à chaque joueur quelle arme il choisi pour le combattant qui le représente
Le combattant, quant à lui, utilise une arme et sa responsabilité est... d'attaquer l'autre (les autres) combattant(s).
Il dispose également d'autres caractéristiques particulières (ses points de vie et de mana, principalement, parce que tu les a déjà représentées), et de comportements qui lui permettent de gérer ces caractéristiques
Enfin, l'arme dispose d'une caractéristique représentant les dégâts qu'elle peut infliger, et sa responsabilité principale est... d'infliger ces dégâts... au combattant attaqué (ce qui revient à dire au combattant attaqué qu'il vient de subir XXX points de dégats ).
Par la suite, il n'est pas impossible que tu décide qu'une partie se fait en trois manches (trois combats), que tu décides de garder "les meilleurs scores", que tu veuille rajouter de la musique, voire, des graphismes etc...
Mais ces aspects devront donc être pris en charge (si tu décide de les ajouter à ton projet) par... une ou plusieurs classes qui s'occupera(ront) de gérer le combat
Tu remarquera que j'ai "naturellement" commencer à parler de combattant et non de joueur...
C'est parce que le joueur est ce qui... manipule le personnage qui combat
Il n'est en effet pas impossible que, plus tard, tu décide de permettre à un combattant géré par... un joueur "humain" de combattre contre un combattant géré par... l'intelligence artificelle (l'ordinateur )
Cela nous ouvre la voie royale pour faire la distinction entre les deux: les PJ (Personnages Joueurs) et les PNJ (Personnages Non Joueur, NPC en anglais)
De plus, si tous les personnages joueurs sont (surement) des combattants, il n'est pas impossible que tu décide que certains PNJ ne le soient pas, mais qu'il soient là soit pour faire "simplement" partie du décors, soit, pour aider le personnage joueur (en lui donnant des quêtes, en lui vendant des pieces d'équipement, ou en lui rachetant ce que le personnage joueur a trouvé)...
Si tu en arrives à cette situation, il est "logique" que tu décide de considérer, que les armes sont... des pièces d'équipement un peu particulières (dans le sens où elles servent à... attaquer)
Et tu décidera sans doute que d'autres pièces d'équipements permettent de modifier les caractéristiques du joueur
Du coup, tu ne pourra plus *décemment* estimer que les dégats infligés par une arme ne dépendent que de l'arme utilisée (parce que la force du personnage qui la tient, ou la connaissance que le personnage qui la tient a du maniement de l'arme influeront énormément sur les dégâts réellement infligés)
Dans le même ordre d'idée, comme tu as prévu trois armes différentes, il n'est pas impossible que tu décide à un moment donné d'en prévoir d'autres, voire, de prévoir la possibilité pour certaines d'entre-elles, d'être améliorées.
Il n'est pas impossible que tu décide, à un moment donné, de faire la distinction entre les armes "à une main" (les épées, et les haches "courtes") et les armes "à deux mains" (les arcs, certains épées ou haches longues, les lances,...)
Voire, comme je viens de parler d'arc, que tu veuille faire la distinction entre les armes "de mélée" (corps à corps) et les armes "à distance"...
Tu me diras sans doute que toute cette réflexion revient, en gros, à tirer des plans sur la comète...
Mais, si tu ne prévois pas ce genre d'évolution dés le départ (cela ne veut pas dire que tu dois directement mettre ces évolution en oeuvre, uniquement que tu dois te laisser l'occasion d'évoluer ), tu auras énormément de mal à faire évoluer ton projet si l'une ou l'autre de ces évolutions vient à s'avérer intéressante ou utile
Mais n'allons pas trop vite en besogne, et intéressons nous à ce que tu veux pour l'instant...
Tu veux donc un combat, dont nous avons déterminé que, comme il doit gérer les combattants, il doit prendre en charge la création des armes que les combattants utilisent.
Comme il doit gérer les combattants, il doit:
- Pouvoir créer les combattants et les armer
- Pouvoir être prévenu lorsqu'un combattant meure (ce qui met fin au combat )
Tu auras donc une classe proche de
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
| /* indiquons juste ici qu'il existe un type "Combattant" et un type "Arme" (1)
*/
class Combattant;
class Arme;
class Combat
{
/* cela risque de changer par la suite, mais, pour l'instant
* il ne te faut "que" deux combattants
*/
/* Vu qu'on ne sais pas, dés le départ, quelle arme chaque
* combattant utilisera, ils ne peuvent pas être créés "d'office"
* (dans le constructeur du combat)
*/
Combattant * m_premier;
Combattant * m_second;
/* une variable qui indique si le combat doit continuer */
bool encore;
public:
/* Le constructeur rappelle que les deux combattants n'existent
* pas encore
*/
Combat():m_premier(0),m_second(0), encore(false){} /* (2) */
/* lorsque l'on détruit le combat, on détruit les combattants,
* car ils n'ont aucune raison de continuer à exister ;)
*/
~Combat()
{
delete m_premier;
delete m_second;
}
/* on doit pouvoir lui demander de créer un combattant
* en indiquant si c'est le premier ou le second
*/
bool creerCombattants(int);
/* on doit pouvoir le prévenir qu'un combattant est mort */
void combattantMeurt(Combattant*);
/* on doit pouvoir lui demander de détruire le(s)
* combattant(s) restant(s)
*/
void detruitCombattants();
/* on doit pouvoir lui donner le signal de début de combat */
void debutCombat();
/* et enfin, on doit pouvoir obtenir l'adversaire d'un combattant */
Combattant adversaire(Combattant * qui)
{return qui==m_premier? m_second: m_premier; /* (3) */}
}; |
NOTA:
(1) Cela s'appelle une
déclaration anticipée et permet juste au compilateur de savoir qu'un type existe... la FAQ a une entrée sur le sujet
(2) représente ce que l'on appelle
une liste d'initialisation... Fais un tour du coté de la FAQ, tu trouvera toutes les informations utiles sur le sujet
(3) cette syntaxe est ce qu'on appelle l'opérateur ternaire et correspond à un if else
(il ne faut pas en abuser, mais ici, elle est intéressante
Dans le fichier combat.cpp, nous trouverons:
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 100 101 102 103 104 105 106
|
/* nous allons utiliser une "fabrique d'armes" de manière à ce que
* le combat ne doive pas connaitre l'ensemble des armes existantes
* (il doit juste savoir que ce sont des armes ;))
*/
/* Pour la définition de la classe, le seul fait de savoir qu'il un type
* existe Arme et un type Combattant suffisait...
*
* une déclaration anticipée était suffisante
* ici, nous devons tout savoir sur sur le type Combattant...
*
* Par contre, pour ce qui concerne l'arme, nous devons juste savoir
* que notre fabrique d'arme la fournit
*/
#include <Combattant.h>
#include <FabriqueArme.h>
/* nous avons besoin de cin et de cout */
#include <iostream>
/* numCombattant: le numéro d'ordre du combattant
* (1 pour m_premier 2 pour m_second)
* renvoie : vrai si le combattant a été créé
* faux autrement
*/
bool Combat::creerCombattant(int numCombattant)
{
/* si le combattant existe déjà, ou si on demande de créer
* un combattant dont le numéro est supérieur à deux
* on refuse de le créer et on
* renvoie false
*/
if((numCombattant==1 && m_premier) ||
(numCombattant==2 && m_second) ||
numCombattant >2)
return false;
/* si on arrive ici, on peut créer notre combattant
* Il nous faut son arme... C'est la fabrique d'armes
* qui s'en charge
*/
Arme* arme=FabriqueArmes::creeArme();
/* nous créons notre combattant en lui donnant l'arme de son
* choix
*/
Combattant * cbt =new Combattant(arme);
/* et nous l'assignons à sa place */
if(nbCombattant==1)
m_premier = cbt;
else
m_second = cbt;
/* on indique que tout s'est bien passé ;) */
return true;
}
void Combat::CombattantMeure(Combattant *mort)
{
/* tiens, amusons nous et gardons trace du vainqueur */
int vainqueur;
/*Quand un combattant meure, il est détruit */
if(mort==m_premier)
{
delete m_premier;
m_premier = 0;
vainqueur = 2;
}
else
{
delete m_second ;
m_second = 0;
vainqueur = 1;
}
/* le combat s'arrete */
encore = 0;
/* et on annonce le vainqueur */
std::cout<<" !!! And the winner is... "<<vainqueur
<<" !!! "<<std::endl;
}
void Combat::detruitCombattants()
{
/* détruits les combattants */
delete m_premier;
delete m_second;
/* s'assurer que nous n'essayerons pas de les détruire une
* seconde fois
*/
m_premier = 0;
m_second = 0;
/* et met le combat dans un état d'arrêt */
encore = false;
}
/*Le début du combat est aussi le déroulement du combat */
void Combat::debutCombat()
{
/* ici, j'envisage un mode "tour à tour", le premier choix
* au premier combattant
*/
Combattant * un =m_premier;
Combattant * deux = m_second;
/* le combat commence */
encore = true;
while (encore)
{
/* celui qui a la main joue */
un->jouer(this);
/* et on inverse */
std::swap(un,deux);
}
} |
La fabrique d'armes serait composée d'une seule fonction, qui créera l'arme d'après le choix de l'utilisateur.
Comme il ne nous sert à rien de disposer d'une instance de la fabrique d'armes, la fonction sera déclarée statique.
Cela nous donne un code proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| /* nous devons, ici, juste prévenir le compilateur qu'il existe
* un type "Arme"
*/
class Arme;
class FabriqueArmes
{
private:
/* on empêche la construction, la copie, l'assignation et la
* destruction de la fabrique d'arme en déclarant le constructeur,
* le constructeur par copie, l'opérateur d'assignation et le
* destructeur privés et en ne les implémentant pas
*/
FabriqueArmes();
~FabriqueArmes();
FabriqueArmes(FabriqueArmes const &);
FabriqueArmes& operator=(FabriqueArmes const&);
public:
static Arme* creeArme();
}; |
et l'implémentation (FabriqueArme.cpp) sera proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| /* nous avons besoin de cin et de cout */
/* nous incluons ici tous les fichiers d'en-tête qui définissent nos armes */
#include <EpeeBronze.h>
#include <HacheFer.h>
#include <Lance.h>
/* si tu décide de créer de nouvelles armes, il "suffira" de rajouter
* le fichier d'en-tête correspondant et de mettre cette fonction à jour ;)
*/
Arme * creeArme()
{
std::cout << "Quel arme choisissez-vous pour le joueur "
<< numJoueur<< " ?" << end
<< "1 - La lance (dégats : 18 pts)" << endl
<< "2 - L'epée en bronze (dégats : 19 pts)" << endl
<< "3 - La hache de fer (dégats : 20 pts)" << endl
<< "Votre choix : ";
std::cin >> choixArme;
return (choixArme==1 ?
new Lance :
choixArme==3 ?
new HacheFer :
new EpeeBronze);
} |
Le combattant va donc recevoir une Arme créée par la fabrique d'armes (et dont il n'a, en définitive que faire de savoir de quel type elle est, vu que le joueur l'a choisie) pour laquelle il sera responsable de la destruction.
On doit pouvoir lui faire savoir qu'il subit des dégâts (que, par la suite, tu peux décider d'atténuer en fonction de son équipement) et lui donner la possibilité de jouer (qui pourrait lui donner la possibilité de se mettre en mode "défensif")
Cela prendrait la forme de
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
| /* les déclarations anticipées nécessaires */
class Combat;
class Arme;
class Combattant
{
private:
/* il a une arme */
Arme m_arme:
/* et des points de vie et de mana */
int m_vie;
int m_mana;
public:
/* le constructeur */
Combattant(Arme*);
/* et le destructeur */
~Combattant();
/* La possibilité de jouer */
void jouer(Combat*);
/* la possibilité de lui infliger des dégats
* Si il meure, il doit prévenir le combat...
*/
void subirAttaque(int, Combat* );
/* la possibilité de l'interroger sur ses points de vie */
int pointVie() const{return m_vie;}
/* la possibilité de l'interroger sur ses points de mana */
int pointMana() const{return m_mana;}
}; |
qui serait implémenté (Combattant.cpp) sous une forme proche de
/
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
| /* nous devons connaitre la définition de Combattant ainsi que
*le contenu de combat, pour lui demander de nous rappeler l'adversaire
*/
#include <Combattant.h>
#include <Combat.h>
/* On a aussi besoin de savoir les fonctions que l'on peut utiliser avec
* notre arme... mais on n'a absolument pas besoin de connaitre l'ensemble
* des armes, c'est pourquoi, seule l'inclusion de <Arme.h> est utile ici
*/
#include <Arme.h>
/* Lorsqu'il est créé, le combattant a 100 points de vie et 80 points de mana
* (décidé tout à fait arbitrairement ;)), et on lui donne une arme
*/
Combattant::Combattant(Arme* a):m_arme(a),m_vie(100),m_mana(80)
{
/* plus tard, il sera sans doute utile de préciser à l'arme
* qui la tient en main sous une forme proche de
* m_arme->setUser(this);
*/
}
/* Quand le combattant est détruit, on détruit son arme */
Combattant::~Combattant()
{
delete m_arme;
}
void Combattant::jouer(Combat* cbt)
{
/* Nous pourrions modifier le comportement pour permettre
* au combattant de se mettre en position défensive, ou
* d'utiliser un sort de soin, ou ... que sais-je...
*
* Dans l'immédiat, jouer revient à attaquer son aversaire
* (mais c'est l'arme qui s'en occupe)
*/
m_arme->attaquer(cbt->adversaire(this), cbt); /* (4) */
}
void Combattant::subirAttaque(int degats, Combat* cbt)
{
/* ici aussi, il est possible de modifier le comportement par la suite...
* à la base, le combattant subit les dégats de plein fouet
* et prévient le combat qu'il meure si ses points de vie
* atteignent 0 ou moins
*/
m_vie-=degat;
if(m_vie<=0)
cbt->combattantMeurt(this);
} |
Nota
(4) demande à l'arme tenue par le combattant en cours d'attaquer le combattant que le combat désigne comme étant l'adversaire du combattant en cours... Cela revient à peut de choses près (en tous les cas dans le fond) au même que si on avait écrit:
1 2
| Combattant * temp=cbt->aversaire(this);
m_arme->attaquer(temp,cbt); |
Il reste enfin le problème de nos armes...
Si tu veux te contenter d'une seule structure nommée Arme pour gérer tous les types d'armes actuels et futurs, tu va en perdre ton latin parce que tu ne tardera pas à te rendre compte que certaines armes doivent avoir un comportement adapté qui leur est propre...
Par contre, on peut jouer sur le fait que, bien qu'une épée et une lance soient toutes les deux des armes, une épée n'est pas une lance, ni inversement.
Jusqu'à présent, nous avons travaillé avec des relations "faibles" entre nos objets où l'un disposait de l'autre (le combat dispose du combattant et le combattant dispose de l'arme), que nous appelons en programmation aggrégation.
Ici, nous allons utiliser la relation la plus forte qui existe en programmation: l'héritage.
Cela permet d'indiquer explicitement qu'un type particulier (la lance ou l'épée, par exemple) est une arme.
C'est à dire que, bien que la lance ou l'épée aient toutes les deux des caractéristiques qui leur sont propres, nous pouvons appeler tous les comportements que l'on peut attendre d'une arme.
L'arme est ici tout à fait générique, alors que la lance ou l'épée sont considérées comme des armes... "spécialisées".
La classe la plus générique (ici l'arme) est appelée "classe mère", "classe de base", ou, si il y a plusieurs niveaux d'héritage "classe ancêtre".
Inversément, la classe spécialisée est appelée "classe enfant" ou "classe dérivée".
Il n'est pas impossible d'envisager plusieurs niveaux d'héritages, par exemple:
- Nous aurions une classe Arme qui serait l'ancêtre de toutes les autres
- Nous aurions ArmeUneMain et ArmeDeuxMains qui descendraient directement de Arme (la différence entre les deux serait l'utilisation d'une ou de deux mains pour la manier)
- Nous aurions ensuite des épées et des haches "suffisamment légères" pour être maniée à une main et d'autres "tellement lourdes" qu'elles devraient l'être ) deux mains (HacheLegere dérivant de ArmeUneMain et EpeeLourde dérivant de ArmeDeuxMains)
- Il serait ensuite possible de dériver nos haches et nos épées en fonction de leur forme, de leur origine ou de la matière dont elles sont faite de HacheLourde, HacheLegere, EpeeLegere ou EpeeLourde, selon la catégorie à laquelle la hache ou l'épée se raccroche
- Les combinaisons du genre ont pour seule limite ta propre inspiration
Simplement, il n'est pas exclu que nous décidions d'adapter certains des comportements que l'on peut attendre d'une arme au type particulier d'arme réellement utilisé.
Il y a cependant une restriction importante: Si on fait passer une épée pour une arme (sans précision), nous ne pourrons appeler que les comportements propres aux armes (sans précision), et, si l'épée dispose d'un comportement supplémentaire, nous ne pourrons pas utiliser ce comportement sans "retransformer" (on parle de transtyper ou de caster) notre arme en... épée...
Une telle adaptation s'appelle le polymorphisme (on parle aussi de comportement polymorphe ou polymorphique)
Lorque l'on identifie un comportement devant être polymorphe, on le signale au compilateur en déclarant la fonction virtual (on parle alors de fonction virtuelle ou de méthode)
Si, pour une raison ou une autre, on se rend compte qu'il faut disposer d'un comportement (comprend: être en mesure d'appeler la fonction correspondante) dans la classe de base alors que nous ne disposons pas des données nécessaires pour implémenter le comportement (la raison la plus fréquente est que les données nécessaires n'ont "rien à faire" dans la classe de base), nous pouvons rendre une méthode virtuelle pure, ce qui se fait en rajoutant un " = 0" après la déclaration de la méthode (qui est, rappelons le, une fonction virtuelle )
Seulement, le compilateur a horreur du vide, et, s'il rencontre une méthode virtuelle pure, il t'interdira de créer une instance de la classe, ainsi que de toute classe dérivée qui ne définirait pas la méthode virtuelle pure.
Nous parlons alors de classe abstraite (par opposition aux classes concrètes qui peuvent être instanciées)
Inversement, si tu veux interdire à l'utilisateur de créer une instance d'une classe de base, il te suffira de créer une méthode virtuelle pure.
La méthode de choix dans ce cas là est... le destructeur de la classe (que tu définira quand même s'il y a des choses à faire dedans, mais que tu peux déclarer virtuel pur)
Nous utilisons régulièrement le terme d'interface lorsqu'il s'agit de désigner une classe qui présente principalement des fonctions virtuelles pures.
La classe Arme dans notre exemple est un candidat idéal à être une telle interface.
Et si un jour tu en viens à faire la distinction entre les armes à une main et les armes à deux main, ArmeUneMain et ArmeDeuxMains seront des candidates idéales pour rester abstraites (elle hériteront de cette qualité par le fait qu'elles n'implémenteront sans doutes pas tous les comportement de la classe Arme).
Tu trouvera pour ces concepts (héritage et classe abstraites) pas mal d'informations dans la FAQ
Maintenant que la théorie de base a (très rapidement, je l'accorde) été abordée, il est temps de nous intéresser à ce que l'on attend réellement de notre arme (comprend: de l'arme dont on ne connait a priori pas le type réel)...
Hé bien, nous allons attendre d'elle... qu'elle attaque notre adversaire...
Et, tant qu'à faire, qu'elle nous permette de calculer qu'elle est capable d'infliger.
Par la suite, tu pourrais vouloir permettre d'apporter des améliorations, savoir qui l'utilise, l'utiliser pour parer une attaque, ou la vendre... Mais ca, c'est encore pures supputations
Bien que nous ne sachions pas quel sera le type réel de l'arme, si nous faisons en sorte que le calcul des dégâts soit un comportement polymorphe, nous pouvons parfaitement déterminer un comportement d'attaque de l'adversaire qui ne changera pas quel que soit le type réel de l'arme.
Par contre, nous allons partir du principe que nous ne disposons d'aucune information permettant d'implémenter le calcul des dégâts pour une arme "générique"...
La fonction correspondante sera donc déclarée virtuelle pure, ce qui rendra notre classe Arme abstraite.
Voici à quoi elle pourrait très bien ressembler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
/* toujours nos déclarations anticipées */
class Combat;
class Combattant;
class Arme
{
public:
virtual ~Arme(); /* nécessaire pour assurer le polymorphisme, même
* s'il ne fait rien
*/
void attaquer(Combattant*, Combat) const ;
/* le comportement polymorphe intéressant */
virtual int calculerDegats() const = 0; /* ce const permet juste de dire
* que la fonction ne modifie pas l'objet ;)
* le = 0 indique que nous n'implémentons pas
* la fonction, ce qui va de facto rendre la classe
* abstraite
*/
}; |
qui présentera les implémentations (Armes.cpp) proches de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /* Il nous faut la définition de la classe Arme ainsi que celle de la classe
* Combattant
*/
#include <Arme.h>
#include <Combattant.h>
/* le destructeur ne fait strictement rien
* mais, comme il a été déclaré, il faut l'implémenter
*/
Arme::~Arme(){}
/* Nous savons exactement ce que fera attaquer
* Par contre, si un jour, tu décidais que le fait d'attaquer ton
* adversaire réduit la résistance ou les dégats infligés par l'arme
* à l'attaque suivante, il faudrait enlever le const
*/
void attaquer(Combattant* adversaire, Combat * cbt) const
{
adversaire->subirAttaque(calculerDegats(),cbt);
}
/* nous ne sommes pas en mesure d'implémenter calculerDegat()
* parce qu'on ne dispose d'aucune donnée pour le faire
*/ |
Il ne nous reste plus qu'à créer nos classes d'armes réelles (ici, je vais montrer la manière avec EpeeBronze, mais ce sera pareil pour toutes les autres armes, à moins que tu ne décide de distinguer les armes à une main et les armes à deux mains )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| /* Ici, la déclaration anticipée ne suffit plus: il faut inclure l'en-tete
* Arme.h
*/
#include <Arme.h>
class EpeeBronze : public Arme /* on signale que EpeeBronze hérite de
* de manière publique de Arme
*/
{
private:
/* les dégâts d'origine qui seront infligés */
int m_degats;
public:
/* le constructeur */
EpeeBronze();
/* et le destructeur (qui doit être virtuel )*/
virtual ~EpeeBronze();
/* et, surtout, n'oublions pas le comportement polymorphe */
virtual int claculerDegats() const;
} |
et l'implémentation qui va avec (EpeeBronze.cpp):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| /*Tout ce dont nous avons besoin ici, c'est... de la définition de EpeeBronze
* :D
*/
#include <EpeeBronze.h>
/* définissons le constructeur (les dégats par défaut ont été arbitrairement
* définis à 19 ;) )
*/
EpeeBronze::EpeeBronze():m_degats(19){}
/* Le destructeur ne fait toujours rien, mais nous devons
* le définir, vu que nous l'avons déclaré
*/
EpeeBronze::~EpeeBronze(){}
/* et enfin le comportement polymorphe */
int EpeeBronze::calculerDegats() const
{
/* Il n'est pas exclu que, par la suite, du modifie la manière
* de calculer les dégâts, pour l'instant, nous renvoyons
* simplement la valeur de base ;)
*/
return m_degats;
} |
Il ne reste "plus" qu'à utiliser le tout dans la fonction principale...
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
| /* tout ce qu'il nous faut ici, c'est la définition de Combat... */
#include <Combat.h>
int main()
{
/* Nous créons un combat */
Combat cbt;
/* nous lui demandons de créer le premier combattant */
cbt.creerCombattant(1);
/* puis le deuxième */
cbt.creerCombattant(2);
/* et maintenant, que le combat commence :D */
cbt.debutCombat();
/* il y a eu un mort, et le vainceur a été désigné...
* il va partir (sans avoir été soigné, le pauvre) sur un deuxième combat...
*/
if(!cbt.creerCombattant(1))
cbt.creerCombattant(2);
/* c'est reparti :D */
cbt.debutCombat();
/* Allez, soyons sympa, permettons aux deux joueurs de partir sur
* un pied d'égalité
*/
cbt.detrutCombatant();
/*...*/
return 0;
} |
Et voilà... Un véritable roman qui, je l'espère, t'aura donné les informations qui te manquaient en plus de te donner un aperçu d'une manière correcte d'envisager ton projet.
PS: d'habitude, je mets des liens vers la FAQ, mais là, je viens de passer pres de deux heures à écrire ce message, et j'ai un peu la flegme... Sorry
Par contre, si je suis passé un peu trop vite sur quelque chose, n'hésite pas à demander des informations supplémentaires
Partager