Programmation C++ : TP séance 04
- Rendu du TP sur Ametice :
- Suivez les instructions pour le rendu du TP en mettant le bon numéro de séance dans le nom du répertoire.
- N'oubliez pas de rendre votre travail à la fin de la séance comme indiqué, même si vous n'avez pas fini la planche de TP, puis de rendre la version finale avant le début de la séance suivante.
Exercice 1. Jeu de Dominos, encapsulation et polymorphisme
On se propose d'améliorer le programme du TP précédent, qu'il faut évidemment avoir complètement achevé pour aborder cette planche.
1.a. Encapsulation
Nous allons protéger les données membre des classes Domino
et Board
.
On commence par la classe Domino
. Passer toutes les données membre
actuelles en private
, et laisser toutes les méthodes public
.
Rajouter des accesseurs pour chacune des données membre,
comme vu dans le cours en faisant
des surcharges de fonctions. On utilisera comme nom de méthode, le nom
de la donnée membre sans le préfixe m_
.
Étant donné que ces accesseurs seront des fonctions sur une ligne,
sans validation pour le moment, ils pourront être directement implémentés dans le fichier
.hpp
(autrement dit, il n'y aura pas de déclaration forward, leur corps
sera placé dans le fichier .hpp
, sans rien mettre dans le fichier .cpp
).
Compiler le programme, puis, dans les endroits où il y a des erreurs provoquées
par le passage en private
, utiliser ces accesseurs.
Faire de même dans la classe Board
, mais uniquement pour
m_domino_num_clicked
(on laissera m_dominos
en public
).
1.b. Gestion de pièces
Dans le but de pouvoir gérer simultanément plusieurs types d'éléments graphiques,
nous allons créer une classe polymorphe dont dérivera (par la suite) la classe
Domino
. Nous allons procéder en 2 temps, pour le moment on ne touche pas au code des
dominos.
Créer dans des fichiers piece.hpp
et piece.cpp
une classe Piece
,
dont les données membre, toutes privées, sont :
- les coordonnées
m_xb
etm_yb
, de typedouble
, du coin en haut à gauche de la pièce ; - la largeur
m_width
et la hauteurm_height
de la pièce, de typedouble
.
Rajouter des accesseurs pour chacune des données membre, de la même manière que dans la question précédente. Rajouter un constructeur avec 4 paramètres tous optionnels, qui seront stockés dans les données membre.
Rajouter les méthodes virtuelles publiques suivantes :
void draw (const CairoContext& cr)
pour dessiner la pièce, qui sera ici un simple rectangle vert pâle bordé de gris ;bool is_clicked (double ev_x, double ev_y)
qui reçoit les coordonnées de la souris, et renvoietrue
si elles sont à l'intérieur ou au bord du rectangle délimité par(m_xb, m_yb)
et(m_xb+m_width, m_yb+m_height)
;void on_button_press (double ev_x, double ev_y, int ev_button, bool& raise_on_top)
qui recevra les coordonnées et le numéro du bouton enfoncé de la souris, et indiquera au retour dansraise_on_top
si la pièce doit être mise au-dessus des autres ;void on_button_release (double ev_x, double ev_y, int ev_button)
qui recevra les coordonnées et le numéro du bouton relâché de la souris ;void on_motion_notify (double ev_x, double ev_y, double prev_x, double prev_y,
int ev_button)
qui recevra les coordonnées courantesev_x,ev_y
et précédentesprev_x,prev_y
de la souris, ainsi que le numéro du bouton enfoncé (0 si aucun).
Les trois dernières méthodes affichent les paramètres reçus dans le terminal.
Dans la class Board
nous allons maintenant recopier tout le code pour les
dominos en un code équivalent pour les pièces :
-
rajouter un
vector
m_pieces
dePiece
en membre publique, et une méthodepopulate_pieces
, dans laquelle on insérera 3 pièces dansm_pieces
; appeler la méthode dans le constructeur deBoard
; -
rajouter une méthode
draw_pieces (const CairoContext& cr)
qui dessine les pièces dem_pieces
de la dernière à la première ; -
rajouter une donnée membre privée
m_piece_num_clicked
initialisée à-1
, et les accesseurs ; -
rajouter une méthode
bool find_piece_clicked (double ev_x, double ev_y)
qui cherche la première pièce cliquée et stocke son numéro dansm_piece_num_clicked
(par défaut-1
) ; elle renvoietrue
si une pièce est cliquée ; -
enfin, rajouter la méthode
void move_piece_to_top (int piece_num)
qui déplace la pièce numéropiece_num
en tête dem_pieces
.
Finalement, attaquons-nous à la classe MainWindow
:
-
dans
darea_on_draw
, afficher les pièces stockées dans le board, après le dessin des dominos (les pièces seront dessus) ; -
dans
darea_on_button_press
, avant le traitement sur les dominos : si une pièce est cliquée, appeler sa méthodeon_button_press
en lui passant une variable booléenneraise_on_top
; si après l'appel elle est àtrue
, mettre la pièce en tête dem_pieces
et actualiserm_piece_num_clicked
à 0 ; forcer le réaffichage ; -
dans
darea_on_button_release
, avant le traitement sur les dominos : si une pièce était cliquée, appeler sa méthodeon_button_release
, puis forcer le réaffichage. À la fin, mettre systématiquementm_piece_num_clicked
à-1
; -
enfin dans
darea_on_motion_notify
, avant le traitement sur les dominos : si une pièce est cliquée, appeler sa méthodeon_motion_notify
(avec les bons paramètres), puis forcer le réaffichage. Placer le code permettant de déplacer la pièce au niveau de la méthodePiece::on_motion_motify
.
À ce stade, les pièces doivent pouvoir être déplacées à la souris ; mais elles seront toujours au-dessus des dominos, voire seront déplacées avec un domino si la souris est cliquée à la fois sur une pièce et un domino.
1.c. Dérivation des dominos
Dans cette partie nous allons réécrire la classe Domino
en la dérivant
de la classe Piece
et en surchargeant certaines méthodes virtuelles.
Par prudence, mettre en commentaire sans le supprimer, tout le code relatif aux
dominos dans les fichiers domino.?pp
, board.?pp
, main_window.cpp
;
tester que le programme compile.
Dans domino.hpp
, faire dériver la classe Domino
de la classe Piece
,
dé-commenter tous les membres (sauf les coordonnées, que l'on supprime),
et leurs accesseurs.
Dans domino.cpp
, dé-commenter le constructeur, en rajoutant
en tête de l'initialisation la classe mère, avec en paramètres
les coordonnées 0,0
et comme largeur et hauteur, les dimensions d'un domino en
position verticale. Tester la compilation.
Au niveau des coordonnées il y a un problème : les pièces ont comme coordonnées
le coin en haut à gauche m_xb,m_yb
alors que les dominos avaient comme
coordonnées leur centre. Le problème est facile à régler en adaptant les
accesseurs des coordonnées pour les dominos :
-
pour définir les getters
xc()
etyc()
il suffit de renvoyerm_xb + m_width/2
etm_yb + m_height/2
, respectivement ; -
pour définir les setters
xc(double xc)
etyc(double yc)
il suffit d'affecterxc - m_width/2
àm_xb
etyc - m_height/2
àm_yb
, respectivement.
Ce code ne compilera pas car m_xb
et m_yb
sont privés ! Utiliser
leurs accesseurs respectifs, ou encore, passer leurs définitions
en protected
dans la classe Piece
.
Dans la classe Board
, pour que la liaison dynamique fonctionne sur
les dominos, dé-commenter la définition de m_dominos
, et remplacer celle
de m_pieces
par std::vector<Piece*>
(ceci aura pour conséquence de devoir
remplacer tous les .
par des ->
dans les fichiers qui utilisent m_pieces
).
Dé-commenter populate_dominos
; dans populate_pieces
, supprimer le code
des trois pièces qui ont précédemment servi de test, et recopier dans m_pieces
les adresses des éléments de m_dominos
.
Enfin dans le constructeur de Board
, appeler populate_dominos
avant
populate_pieces
.
Aucun new !
Le but est ne faire aucun new
dans tout le programme, pour éviter des
problèmes de gestion de mémoire.
On utilise ici un vecteur m_dominos
de dominos pour les instancier,
et un vecteur m_pieces
de pointeurs sur ces dominos pour la résolution
virtuelle.
Dans la classe Domino
, surcharger la méthode virtuelle draw
(en rajoutant
override
dans sa définition) ; dé-commenter le corps de la méthode, en remplaçant
les ex-données membre m_xc,m_yc
par les getters xc(),yc()
.
À ce stade, les dominos doivent être affichés et déplaçables (si vous voyez encore
des rectangles vert cela signifie que la liaison dynamique n'est pas au point).
De plus, si dans Piece::on_button_press
vous avez pris le soin de mettre
raise_on_top
à true
, ils doivent être placés au-dessus des autres lorsqu'ils
sont cliqués.
Procédons au nettoyage du code :
-
dans
Board
, supprimer les méthodes suivantes (actuellement commentées) :draw_dominos
,find_domino_clicked
,move_domino_to_top
, ainsi quem_domino_num_clicked
et ses accesseurs ; -
dans
Domino
, supprimer la méthodedomino_is_clicked
; -
dans
MainWindow
, supprimer tout le code mis en commentaire relatif aux dominos.
Finalement, il ne reste plus qu'à remettre en fonction la rotation des
dominos lorsque le rivet est cliqué.
Dans Domino
,
surcharger la
méthode virtuelle on_button_release
(avec override
dans la définition) ;
dans son corps, si rivet_is_clicked
renvoie true
, augmenter l'angle
de 90
modulo 360.
Tester la rotation en cliquant sur un rivet. Il reste un soucis : la rotation n'a pas changé les coordonnées de la classe mère, ni la hauteur et largeur, si bien que un domino horizontal peut être cliqué dans l'espace qu'il occupait lorsqu'il était vertical.
Pour corriger cela, après la modification de l'angle, mettre à jour les coordonnées et échanger la largeur et hauteur de la classe mère du domino.
Vous êtes arrivé au bout, félicitations ! Suite au prochain TP avec l'implémentation d'un sabot de dominos et les règles du jeu.