Examen de Conception et Programmation orientées objets
Ce document présente un examen de Conception et Programmation orientées objets, portant sur la modélisation UML et le codage en C++ d’un jeu d’échecs. Il teste les compétences en analyse de diagrammes de classes, conception orientée objets, programmation C++ et compréhension des règles du jeu d’échecs.
D'après le document Examen de Conception et Programmation orientées objets
Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.
Document source
Programmation, Mathématiques · École Nationale des Sciences de l'Informatique (ENSI) · PDF · 12 pages · 2008
Afficher l'aperçu du document
Ce document présente un examen de Conception et Programmation orientées objets, portant sur la modélisation UML et le codage en C++ d’un jeu d’échecs. Il teste les compétences en analyse de diagrammes de classes, conception orientée objets, programmation C++ et compréhension des règles du jeu d’échecs.
Partie 1 : Modélisation UML
Question 1.1
Énoncé : Compléter le diagramme de classes UML proposé en ajoutant (1) les cardinalités entre les classes Piece et Case, (2) les associations entre la classe Coup et les autres classes, et (3) les liens unidirectionnels. Puis indiquer l’ordre de navigation pour savoir si une case est jouable pour une pièce donnée et si une pièce est en risque d’attaque.
Travail :
- (1) Cardinalités Piece - Case : Une pièce est positionnée sur zéro ou une case (car une pièce peut être capturée et donc hors échiquier), et une case contient au plus une pièce. Donc cardinalité Piece → Case : 0..1, et Case → Piece : 0..1.
- (2) Associations Coup - autres classes : La classe Coup doit être associée à la Case de départ, à la Case d’arrivée, à la pièce déplacée et éventuellement à la pièce capturée. Ces associations sont unidirectionnelles depuis Coup vers ces classes.
- (3) Liens unidirectionnels : Par exemple, de Piece vers Case (pour connaître la position), de Case vers Echiquier (pour navigation), de Coup vers Case et Piece.
Ordre de navigation :
- Pour savoir si une case est jouable par une pièce donnée : partir de la Piece, accéder à sa Case actuelle, puis à l’Echiquier, puis aux cases de l’échiquier (collection de Case) pour vérifier la jouabilité.
- Pour savoir si une pièce est en risque d’attaque : partir de la Piece, accéder à sa Case, puis à l’Echiquier, parcourir les autres cases, et vérifier si une pièce adverse peut se déplacer ou capturer cette case.
Réponse finale : Le diagramme doit comporter les cardinalités Piece 0..1 → Case 0..1, les associations unidirectionnelles Coup → Case (départ et arrivée) et Coup → Piece (déplacée et capturée). L’ordre de navigation est Piece → Case → Echiquier → (cases) pour la jouabilité, et Piece → Case → Echiquier → (cases) → Piece pour le risque d’attaque.
Question 1.2
Énoncé : Élaborer le diagramme d’objets reflétant la disposition initiale du jeu d’échecs, en ne représentant que les cases occupées.
Travail : À partir du diagramme de classes, créer des instances d’objets Piece et Case correspondant à la position initiale des pièces blanches et noires sur l’échiquier, en associant chaque Piece à une Case occupée.
Réponse : Le diagramme d’objets doit montrer les objets Case occupées par des objets Piece, avec les références croisées Piece → Case et Case → Piece. Par exemple, Case e2 contient un pion blanc, Case e7 un pion noir, etc., pour toutes les cases initialement occupées.
Question 1.3
Énoncé : Élaborer le diagramme d’objets reflétant une évolution du jeu après deux coups, en ne représentant que les cases occupées.
Travail : À partir du diagramme précédent, modifier les associations Piece-Case pour refléter les déplacements effectués, ainsi que les éventuelles captures.
Réponse : Le diagramme d’objets doit montrer les nouvelles positions des pièces déplacées, les cases libérées et les pièces capturées hors échiquier (sans case). Par exemple, si un pion blanc a avancé de e2 à e4, la Case e2 est vide, e4 contient le pion, etc.
Question 1.4
Énoncé : Donner les contraintes UML appropriées ({ordered}, {addOnly}, {frozen}, {notUnique}) pour les associations :
- JeuEchecs-Piece
- JeuEchecs-Coup
- Echiquier-Case
Réponse :
- JeuEchecs-Piece : {addOnly} (les pièces s’ajoutent sans suppression fréquente), catégoriquement non {notUnique} (les pièces sont uniques).
- JeuEchecs-Coup : {ordered} (la séquence des coups est ordonnée), catégoriquement non {frozen} (la liste peut évoluer).
- Echiquier-Case : {frozen} (les cases sont fixes), catégoriquement non {notUnique} ni {addOnly} (les cases sont uniques et fixes).
Partie 2 : Codage C++
Question 2.1
Énoncé : Proposer les structures C++ pour implémenter les collections des associations multiples :
- JeuEchecs-Piece
- JeuEchecs-Coup
- Echiquier-Case
Réponse :
- JeuEchecs-Piece : une collection séquentielle comme std::vector ou std::set, car les pièces sont uniques et l’ordre n’est pas forcément important.
- JeuEchecs-Coup : une collection ordonnée comme std::vector ou std::list, car l’ordre des coups est important.
- Echiquier-Case : un tableau bidimensionnel statique Case* tab[8][8], car la taille est fixe et connue, ou éventuellement un std::map si on souhaite un accès par coordonnées.
Question 2.2
Énoncé : Donner le code C++ de la classe abstraite Piece avec les méthodes virtuelles pures peutSeDeplacer et peutCapturer, ainsi que le constructeur et les accesseurs.
class Piece {
public:
// Constructeur
Piece(Symbole symbole, Couleur couleur, Case * caseInitiale = NULL)
: symbole(symbole), couleur(couleur), dans(caseInitiale) {}
virtual bool peutSeDeplacer(Case *) = 0;
virtual bool peutCapturer(Case * c) { return peutSeDeplacer(c); }
void setCase(Case * c) { dans = c; }
Case * getCase() const { return dans; }
Couleur getCouleur() const { return couleur; }
Symbole getSymbole() const { return symbole; }
private:
Symbole symbole;
Couleur couleur;
Case * dans;
};
Explication : Le constructeur initialise le symbole, la couleur et la case initiale. La méthode peutSeDeplacer est pure virtuelle, forçant les classes dérivées à la définir. La méthode peutCapturer par défaut appelle peutSeDeplacer.
Question 2.3
Énoncé : Discuter l’avantage et l’inconvénient de mémoriser ou non les coordonnées dans la classe Case, puis donner le code C++ de l’interface de la classe Case selon ce choix.
Réponse :
- Mémoriser les coordonnées : Avantage : une pièce peut connaître directement sa position via la case. Inconvénient : redondance des coordonnées, car l’échiquier indexe déjà les cases.
- Ne pas mémoriser : Avantages : code léger, pas de redondance. Inconvénient : pour connaître la position d’une case, il faut parcourir l’échiquier.
Code C++ de la classe Case (sans mémorisation des coordonnées) :
class Case {
public:
Case(Echiquier * e) : ech(e), contient(NULL) {}
void setPiece(Piece * p) { contient = p; }
Piece * getPiece() { return contient; }
Echiquier * getEchiquier() { return ech; }
private:
Piece * contient;
Echiquier * ech; // pour navigation
// Pas de coordonnées stockées
};
Question 2.4
Énoncé : Donner le code C++ de l’interface de la classe Coup, avec constructeur et accesseurs, sachant qu’un coup peut être un déplacement avec ou sans capture.
class Coup {
public:
Coup(Case * de, Case * vers, Piece * pb, Piece * pc)
: de(de), vers(vers), piece_bougee(pb), piece_capturee(pc) {}
Case * getDe() const { return de; }
Case * getVers() const { return vers; }
Piece * getPieceBougee() const { return piece_bougee; }
Piece * getPieceCapturee() const { return piece_capturee; }
private:
Case * de;
Case * vers;
Piece * piece_bougee;
Piece * piece_capturee;
};
Question 2.5
Énoncé : Donner le corps du constructeur de la classe Echiquier et les interfaces des méthodes estOccupe, placer, getCase, getLigne, getColonne.
class Echiquier {
public:
Echiquier() {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
tab[i][j] = new Case(this);
}
Case * getCase(char lig, char col);
bool estOccupe(Case *);
void initialiser();
char getLigne(Case *);
char getColonne(Case *);
~Echiquier();
private:
Case * tab[8][8];
};
Explication : Le constructeur crée un tableau 8x8 de pointeurs vers des objets Case, chaque Case connaissant son Echiquier. Les méthodes d’accès permettent d’obtenir une case par coordonnées, tester son occupation, initialiser la position, et obtenir les coordonnées d’une case.
Question 2.6
Énoncé : Donner les attributs de la classe JeuEchecs et indiquer comment distinguer les rois des deux joueurs.
class JeuEchecs {
private:
Echiquier ech;
std::vector<Piece *> pieces;
std::vector<Coup *> coups;
Couleur TourDeQui;
// Pour distinguer les rois, on peut :
// - les placer à des positions fixes dans le vecteur pieces
// - ou ajouter deux pointeurs spécifiques vers les rois blanc et noir
};
Question 2.7
Énoncé : Étudier la méthode jouer() de JeuEchecs qui lit une ancienne et une nouvelle position, valide le coup, l’ajoute à la liste des coups, déplace la pièce et capture si nécessaire, puis retourne vrai ou faux selon la validité.
bool JeuEchecs::jouer() {
char l1, c1, l2, c2;
std::cin >> l1 >> c1 >> l2 >> c2;
Case * source = getCase(l1, c1);
Case * cible = getCase(l2, c2);
Piece * piece_bougee = source->getPiece();
if ((piece_bougee == NULL) || (piece_bougee->getCouleur() != TourDeQui))
return false;
Piece * piece_Capturee = cible->getPiece();
if (piece_bougee->peutSeDeplacer(cible) || piece_bougee->peutCapturer(cible)) {
coups.push_back(new Coup(source, cible, piece_bougee, piece_Capturee));
source->setPiece(NULL);
cible->setPiece(piece_bougee);
piece_bougee->setCase(cible);
if (piece_Capturee != NULL)
piece_Capturee->setCase(NULL);
return true;
} else
return false;
}
Explication : La méthode vérifie la validité du coup selon la pièce et la couleur, met à jour les positions, gère la capture et ajoute le coup à l’historique.
Question 2.8
Énoncé : Écrire la classe Pion, justifier les attributs spécifiques, donner le constructeur et la méthode peutSeDeplacer.
a) Attributs spécifiques :
Oui, il faut mémoriser si le pion a déjà bougé, car son déplacement initial peut être de deux cases, puis un déplacement d’une case ensuite.
b) Code :
Pion::Pion(Couleur couleur, Case * c) : Piece('Pion', couleur, c) {
aFaitDeplacement = false;
}
bool Pion::peutSeDeplacer(Case * cible) {
Case * source = getCase();
Echiquier * ech = source->getEchiquier();
char l1 = ech->getLigne(source);
char c1 = ech->getColonne(source);
char l2 = ech->getLigne(cible);
char c2 = ech->getColonne(cible);
if (cible->getPiece() != NULL) return false;
if (c1 != c2) return false;
if (aFaitDeplacement) {
if (getCouleur() == blanc)
return ((l2 - l1) == 1);
else
return ((l1 - l2) == 1);
} else {
if (getCouleur() == blanc)
return ((l2 - l1) == 1) || ((l2 - l1) == 2);
else
return ((l1 - l2) == 1) || ((l1 - l2) == 2);
}
}
Explication : Le pion avance droit devant, une case sauf au premier déplacement où il peut avancer de deux cases. La couleur détermine le sens (blanc croissant, noir décroissant).
Question 2.9
Énoncé : Expliquer comment réutiliser les classes Tour et Fou pour écrire la classe Dame, et donner le corps de la méthode peutSeDeplacer de Dame.
Réponse : La Dame combine les déplacements de la Tour et du Fou. On utilise un héritage multiple de Tour et Fou. La méthode peutSeDeplacer de Dame retourne vrai si le déplacement est valide pour la Tour ou pour le Fou.
bool Dame::peutSeDeplacer(Case * c) {
return (Tour::peutSeDeplacer(c) || Fou::peutSeDeplacer(c));
}
Méthode
Ce sujet récompense une bonne maîtrise de la modélisation orientée objets, notamment la compréhension des associations, cardinalités et navigation entre objets. Il valorise aussi la capacité à traduire un modèle UML en code C++ clair et conforme, avec une bonne gestion des pointeurs et des collections. Les erreurs fréquentes punies sont :
- Omettre les cardinalités ou associations importantes dans le diagramme UML.
- Confondre les directions des associations ou oublier les liens unidirectionnels nécessaires.
- Ne pas respecter la distinction entre déplacement et capture dans les méthodes.
- Ignorer la gestion des cas particuliers comme le premier déplacement du pion.
- Ne pas gérer correctement la navigation entre objets (ex. Case vers Echiquier).
- Ne pas vérifier la validité des coups avant de modifier l’état du jeu.
Une démarche rigoureuse, avec des étapes claires et la justification des choix techniques, est essentielle pour réussir ce type d’exercice.
Commentaires
Aucun commentaire pour le moment. Posez la première question.