Modèle UML : Composition entre Jeu et Chambre

Partie 1 : Modélisation UML Question 1.1 - Type d'association entre Jeu et Chambre Analyse de la question : Le document source original contenant le diagramme UML du développeur est manquant dans l'énoncé fourni. Il est donc impossible d'affirmer avec une certitude absolue quel était le type d'association initialement dessiné par le développeur.

D'après le document Modèle UML : Composition entre Jeu et Chambre

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Modèle UML : Composition entre Jeu et Chambre

Document source

Modèle UML : Composition entre Jeu et Chambre

Programming, Math, etc. · PDF · 2 pages

Afficher l'aperçu du document

Consulter le document original →

Partie 1 : Modélisation UML

Question 1.1 - Type d'association entre Jeu et Chambre

Analyse de la question : Le document source original contenant le diagramme UML du développeur est manquant dans l'énoncé fourni. Il est donc impossible d'affirmer avec une certitude absolue quel était le type d'association initialement dessiné par le développeur.

Cependant, nous pouvons répondre à la partie théorique de la question.

Définition : Une association entre un ensemble (le tout) et ses éléments (les parties) est typiquement une relation d'agrégation ou de composition.

  • La composition est une forme forte d'agrégation qui implique une dépendance de cycle de vie stricte : la partie n'existe que par l'existence du tout, et la destruction du tout entraîne la destruction de la partie. Une partie ne peut appartenir qu'à un seul composite à la fois.

Traduction en simple composition : Pour modéliser que le Jeu est composé de Chambres (et qu'une chambre n'a de sens que dans un jeu donné), on dessine un trait continu entre la classe Jeu et la classe Chambre. Du côté de la classe Jeu, on place un losange plein (noir). La multiplicité du côté de Chambre serait typiquement 1..* (ou 0..* selon la taille de la grille) et du côté de Jeu, elle doit être strictement 1.

Question 1.2 - Pertinence de l'utilisation de la composition

L'énoncé fait référence à un modèle UML proposé par un développeur, qui n'a pas été extrait dans le texte source. Il est par conséquent impossible de juger les compositions spécifiques dessinées par ce développeur.

Explication théorique pour évaluer une composition : Pour donner raison ou tort à l'affirmation, il faut vérifier la règle de la dépendance de cycle de vie.

  • Si le développeur a utilisé une composition entre Chambre et Occupant (Soldat, Ennemi), c'est généralement incorrect. Un soldat ou un ennemi peut se déplacer d'une chambre à l'autre ; sa destruction n'est pas liée à la destruction d'une chambre spécifique. Il s'agirait plutôt d'une simple association ou d'une agrégation faible.
  • Si la composition est utilisée entre Jeu et Chambre, c'est correct, car les chambres forment la grille de jeu et disparaissent avec lui.

Question 1.3 - Diagramme d'objets initial

La "première figure" de l'énoncé est manquante, ce qui empêche de connaître les positions exactes des entités sur la grille. Voici cependant comment construire le diagramme d'objets selon les données textuelles fournies.

Éléments à représenter :

  • 1 instance de Jeu (par exemple j1 : Jeu).
  • Plusieurs instances de Chambre (par exemple c0_0 : Chambre, c0_1 : Chambre, etc., selon les coordonnées x,y).
  • 1 instance de Soldat (par exemple s1 : Soldat).
  • 3 instances de Ennemi (par exemple e1 : Ennemi, e2 : Ennemi, e3 : Ennemi).
  • 3 instances de Arme (par exemple a1 : Arme, a2 : Arme, a3 : Arme).

Liens : Le jeu j1 est lié à toutes les instances de Chambre. Les objets Soldat, Ennemi et Arme sont liés aux objets Chambre correspondant à leurs coordonnées respectives (x pour les colonnes, y pour les lignes, avec 0,0 en haut à gauche).

Question 1.4 - Diagramme d'objets après actions du soldat

Comme pour la question précédente, l'absence des positions initiales nous oblige à décrire la structure logique des objets plutôt qu'à fournir un schéma spatial exact.

a) Situation avant les attaques :

  • Le Soldat a récupéré un fusil : Il existe un lien direct entre l'instance du soldat (s : Soldat) et l'instance du fusil (f : Fusil). L'attribut balles de l'objet f vaut 10.
  • Le soldat s'est déplacé : Le lien entre le soldat et son ancienne chambre est détruit. Un nouveau lien est créé entre s : Soldat et une instance c_cible : Chambre.
  • La chambre cible : L'objet c_cible : Chambre possède également des liens avec deux instances d'ennemis de type Kattel (k1 : Kattel, k2 : Kattel).

b) Situation après les 5 attaques :

  • L'arme a été utilisée : L'attribut balles de l'objet f : Fusil passe de 10 à 5 (en supposant qu'une attaque consomme 1 balle, selon la logique standard).
  • La mise à jour des occupants se fait après l'attaque. L'état de santé ou la présence des objets k1 : Kattel et k2 : Kattel dépendra des règles de dégâts (non fournies). Si les attaques sont mortelles, les liens entre c_cible et k1, k2 pourraient être détruits, et les objets supprimés.

Question 1.5 - Navigation des ennemis entre les chambres

Problème : Un ennemi doit pouvoir trouver les chambres contenant au moins un ennemi, sans ajouter de nouveau lien avec la classe Jeu (qui connaît toutes les chambres).

Solution proposée : Il faut que la classe Chambre permette une navigation globale, ou de proche en proche.

  1. Option 1 (Attribut statique) : Ajouter un attribut de classe (statique) dans Chambre qui référence toutes les instances créées. En UML, cela se note avec un attribut souligné, par exemple toutesLesChambres : Chambre [*].
  2. Option 2 (Auto-association) : Créer une association réflexive sur la classe Chambre pour représenter la contiguïté (Nord, Sud, Est, Ouest). Un ennemi peut alors interroger sa chambre actuelle pour naviguer vers les chambres adjacentes, de proche en proche.

L'Option 1 est la plus directe pour répondre au besoin "naviguer vers les chambres qui contiennent au moins un ennemi" sans algorithme de recherche complexe sur un graphe.

Partie 2 : Codage C++

Question 2.1 - Implémentation des associations avec cardinalité multiple

a) Règles de gestion : D'après l'énoncé, un jeu contient plusieurs chambres. Une chambre peut contenir plusieurs ennemis et plusieurs armes. De plus, un occupant (comme le soldat) peut potentiellement interagir avec plusieurs armes ou ennemis. L'énoncé précise le besoin d'avoir "deux collections distinctes une pour les ennemis et une autre pour les armes".

b) Choix des structures STL : Nous utiliserons le conteneur std::vector de la bibliothèque standard (STL).

  • Pour les ennemis : std::vector<Ennemi*> ennemis;
  • Pour les armes : std::vector<Arme*> armes;

Justification : L'utilisation de pointeurs (*) est obligatoire en C++ pour profiter du polymorphisme (stocker des instances de Kattel ou Balbaz dans une collection d'Ennemi). Le std::vector garantit un accès rapide (en temps constant) et une itération aisée, ce qui est idéal pour parcourir les occupants d'une chambre.

Question 2.2 - Classe Chambre

Voici la déclaration et l'implémentation de la classe Chambre, intégrant les vecteurs et des méthodes de base pour ajouter et retirer des éléments.

#include <iostream>
#include <vector>
#include <algorithm>

class Ennemi; // Déclaration anticipée
class Arme;   // Déclaration anticipée

class Chambre {
private:
    std::vector<Ennemi*> ennemis;
    std::vector<Arme*> armes;

    // Attribut statique pour répondre à la question 1.5
    static std::vector<Chambre*> toutesLesChambres;

public:
    Chambre() {
        toutesLesChambres.push_back(this);
    }

    ~Chambre() {
        // Nettoyage de la liste statique (simplifié)
        toutesLesChambres.erase(std::remove(toutesLesChambres.begin(), toutesLesChambres.end(), this), toutesLesChambres.end());
    }

    void ajouterEnnemi(Ennemi* e) {
        ennemis.push_back(e);
    }

    void retirerEnnemi(Ennemi* e) {
        ennemis.erase(std::remove(ennemis.begin(), ennemis.end(), e), ennemis.end());
    }

    void ajouterArme(Arme* a) {
        armes.push_back(a);
    }

    void retirerArme(Arme* a) {
        armes.erase(std::remove(armes.begin(), armes.end(), a), armes.end());
    }

    // Accesseurs pour la logique du jeu
    const std::vector<Ennemi*>& getEnnemis() const { return ennemis; }
    const std::vector<Arme*>& getArmes() const { return armes; }
    
    static const std::vector<Chambre*>& getToutesLesChambres() { return toutesLesChambres; }
};

// Initialisation de l'attribut statique
std::vector<Chambre*> Chambre::toutesLesChambres;

Question 2.3 - Correction du polymorphisme et attribut {frozen}

a) Pourquoi la partie de code ne fonctionne pas : Dans le code fourni, la méthode get_type() est définie dans la classe de base Arme et dans la classe de base Ennemi, mais elle n'est pas déclarée comme virtuelle (mot-clé virtual). Puisque les variables a et e sont des pointeurs sur les classes de base (Arme* et Ennemi*), l'appel a->get_type() appellera toujours la méthode de la classe de base, renvoyant "Arme", quel que soit l'objet réel pointé (Fusil ou Bombe). Le typage est résolu statiquement à la compilation (liaison précoce). Les conditions des if ne seront donc jamais vérifiées.

b) Constructeurs avec attribut {frozen} : En UML, un attribut {frozen} signifie qu'il est en lecture seule après son initialisation (équivalent à const en C++). Puisqu'on ne redéfinit plus get_type(), c'est la classe de base Occupant (et Arme) qui stocke cet attribut et le renvoie.

#include <string>

// --- Hiérarchie des Armes ---
class Arme {
private:
    const std::string type; // {frozen}
public:
    Arme(std::string t = "Arme") : type(t) {}
    std::string get_type() const { return type; }
    virtual ~Arme() {}
};

class Fusil : public Arme {
public:
    Fusil() : Arme("Fusil") {}
};

class Bombe : public Arme {
public:
    Bombe() : Arme("Bombe") {}
};

// --- Hiérarchie des Occupants ---
class Occupant {
private:
    const std::string type; // {frozen}
public:
    Occupant(std::string t) : type(t) {}
    std::string get_type() const { return type; }
    virtual ~Occupant() {}
};

class Ennemi : public Occupant {
public:
    Ennemi(std::string t = "Ennemi") : Occupant(t) {}
    virtual void attaquer() { std::cout << "Enm - attaque\n"; }
    virtual void fuir() { std::cout << "Enm - fuit\n"; }
};

class Kattel : public Ennemi {
public:
    Kattel() : Ennemi("Kattel") {}
    void attaquer() override { std::cout << "Ktl attaque\n"; }
    void fuir() override { std::cout << "Ktl - fuit\n"; }
};

class Balbaz : public Ennemi {
public:
    Balbaz() : Ennemi("Balbaz") {}
    void attaquer() override { std::cout << "Blz - attaque\n"; }
    void fuir() override { std::cout << "Blz - fuit\n"; }
};

Question 2.4 - Méthode reagir() et polymorphisme

Pour éviter les séries de if / else if sur les chaînes de caractères (qui est une mauvaise pratique orientée objet), nous allons implémenter reagir(Arme* a). Les comportements de fuite de Balbaz et Kattel nécessitent de parcourir les chambres.

class Ennemi : public Occupant {
public:
    Ennemi(std::string t = "Ennemi") : Occupant(t) {}
    virtual void attaquer() { std::cout << "Enm - attaque\n"; }
    virtual void fuir() { std::cout << "Enm - fuit\n"; }
    
    // Déclaration de la nouvelle méthode virtuelle pure
    virtual void reagir(Arme* a) = 0; 
};

class Kattel : public Ennemi {
public:
    Kattel() : Ennemi("Kattel") {}
    void attaquer() override { std::cout << "Ktl attaque\n"; }
    void fuir() override {
        // La fuite d'un Kattel se fait dans la chambre la moins peuplée d'ennemis
        const auto& chambres = Chambre::getToutesLesChambres();
        Chambre* chambreDest = nullptr;
        size_t minEnnemis = (size_t)-1; 

        for (Chambre* c : chambres) {
            size_t nbEnnemis = c->getEnnemis().size();
            if (nbEnnemis < minEnnemis) {
                minEnnemis = nbEnnemis;
                chambreDest = c;
            }
        }
        if (chambreDest) {
            std::cout << "Kattel fuit vers la chambre ayant " << minEnnemis << " ennemis.\n";
            // Logique de déplacement à intégrer ici
        }
    }

    void reagir(Arme* a) override {
        if (a->get_type() == "Fusil") {
            attaquer();
        } else if (a->get_type() == "Bombe") {
            fuir();
        }
    }
};

class Balbaz : public Ennemi {
public:
    Balbaz() : Ennemi("Balbaz") {}
    void attaquer() override { std::cout << "Blz - attaque\n"; }
    void fuir() override {
        // La fuite d'un Balbaz se fait dans la chambre qui contient le max de balles
        // Note : L'attribut 'balles' n'est pas déclaré dans l'énoncé de base pour Arme.
        // On suppose ici qu'une méthode hypothétique get_balles() existerait sur Arme ou Fusil.
        std::cout << "Balbaz cherche la chambre avec le maximum de balles pour fuir.\n";
        // L'implémentation complète nécessiterait un cast dynamique vers Fusil pour compter les balles.
    }

    void reagir(Arme* a) override {
        if (a->get_type() == "Fusil") {
            fuir();
        } else if (a->get_type() == "Bombe") {
            attaquer();
        }
    }
};

Question 2.5 - Classe Jeu

La classe Jeu est responsable de la grille (matrice) et du constructeur qui place les éléments initiaux. Sans la configuration exacte de la "première figure", le constructeur inclut une logique générique de peuplement.

#include <vector>

class Jeu {
private:
    int lignes;
    int colonnes;
    std::vector<std::vector<Chambre*>> grille;

public:
    // Déclaration
    Jeu(int l, int c);
    ~Jeu();
    void afficher(); // Non implémentée (selon énoncé)
    void gagnant();  // Non implémentée (selon énoncé)
    void commanditer(std::string nom);
};

// Implémentation
Jeu::Jeu(int l, int c) : lignes(l), colonnes(c) {
    grille.resize(lignes, std::vector<Chambre*>(colonnes, nullptr));
    
    // Création des chambres
    for (int i = 0; i < lignes; ++i) {
        for (int j = 0; j < colonnes; ++j) {
            grille[i][j] = new Chambre();
        }
    }
    
    // Placement des occupants selon l'énoncé du jeu (logique simulée ici)
    // 1 Soldat, 3 Ennemis, 3 Armes...
    if (lignes > 0 && colonnes > 0) {
        grille[0][0]->ajouterArme(new Fusil());
        grille[0][1]->ajouterEnnemi(new Kattel());
        grille[0][1]->ajouterEnnemi(new Balbaz());
    }
}

Jeu::~Jeu() {
    for (int i = 0; i < lignes; ++i) {
        for (int j = 0; j < colonnes; ++j) {
            delete grille[i][j];
        }
    }
}

Question 2.6 - Gestion de l'exception de débordement

L'énoncé indique que si le soldat sort de la grille, une exception est lancée au niveau de deplacer, capturée, et corrigée en inversant le mouvement. L'objet gérant la position semble s'appeler matrix dans le code source fourni, nous allons donc concevoir l'exception et un squelette pour l'objet qui gère le déplacement (potentiellement une classe GrilleManager ou le Soldat lui-même).

#include <exception>

enum sens { haut, bas, gauche, droite };

// Classe d'exception
class Debordement : public std::exception {
private:
    sens direction;
public:
    Debordement(sens d) : direction(d) {}
    
    sens getDirectionInverse() const {
        switch (direction) {
            case haut: return bas;
            case bas: return haut;
            case gauche: return droite;
            case droite: return gauche;
        }
        return haut; // par défaut
    }

    void corriger() {
        std::cout << "Debordement détecté. Correction par déplacement dans le sens inverse.\n";
        // La correction réelle nécessiterait un pointeur vers l'objet à déplacer
        // ou renvoyer la direction inverse à l'appelant.
    }
};

// Squelette de la classe simulant "matrix" mentionnée dans l'énoncé
class MatrixDeplacement {
public:
    void deplacer(sens d, int n) {
        // Logique fictive : on simule un débordement si n indique qu'on est au bord
        bool debordement = false; 
        
        // Conditions fictives de bordures
        // Si mouvement = haut et position_y == 0 -> debordement = true
        
        if (debordement) {
            throw Debordement(d);
        } else {
            std::cout << "Déplacement effectué avec succès.\n";
            // Mise à jour réelle des coordonnées
        }
    }
};

Note sur le code source de la question 2.6 : Le bloc try-catch de l'énoncé utilise e.corriger(). Notre implémentation de Debordement fournit cette méthode. Pour que corriger() effectue réellement l'action opposée sur l'objet ciblé, l'exception devrait idéalement être construite en recevant une référence à cet objet (ex: throw Debordement(d, this)).

Méthode

Face à ce type de sujet mêlant analyse conceptuelle (UML) et implémentation concrète (C++), voici la démarche recommandée :

  1. Traçabilité UML vers Code : Gardez toujours à l'esprit que les choix d'architecture (associations, agrégations, cardinalités) vus en UML dictent la conception des classes. Une multiplicité * en UML se traduit presque systématiquement par un conteneur standard en C++ (comme un std::vector).
  2. Maîtrise du polymorphisme : En C++, le typage dynamique et le polymorphisme exigent trois choses indissociables : des classes dérivées, des méthodes définies avec le mot-clé virtual dans la classe de base, et une manipulation des objets au travers de pointeurs (*) ou de références (&). C'est le piège classique de la question 2.3.
  3. Encapsulation et Cycle de vie : Lorsque vous créez des objets avec new dans un constructeur (comme pour les Chambre dans Jeu), vous devez impérativement rédiger le destructeur correspondant (~Jeu()) contenant les delete associés pour éviter les fuites de mémoire.
  4. Lecture attentive des règles métier : Dans les sujets à rallonge, les contraintes sont parfois dispersées. Les règles de "fuite" des ennemis (question 2.4) nécessitent un accès global ou relatif à d'autres objets, ce qui vous indique que votre conception initiale (question 1.5) doit rendre cette navigation techniquement possible (ex: liste statique des instances).
  5. Données manquantes : Lors d'un examen, si une figure annexe est manquante (comme ce fut le cas ici pour la question 1.3), énoncez clairement les hypothèses de base que vous posez pour pouvoir tout de même démontrer que vous maîtrisez la compétence évaluée (ici, tracer un diagramme d'objets logique).

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions