Aller au contenu

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 et m_yb, de type double, du coin en haut à gauche de la pièce ;
  • la largeur m_width et la hauteur m_height de la pièce, de type double.

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 renvoie true 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 dans raise_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 courantes ev_x,ev_y et précédentes prev_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 de Piece en membre publique, et une méthode populate_pieces, dans laquelle on insérera 3 pièces dans m_pieces ; appeler la méthode dans le constructeur de Board ;

  • rajouter une méthode draw_pieces (const CairoContext& cr) qui dessine les pièces de m_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 dans m_piece_num_clicked (par défaut -1) ; elle renvoie true 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éro piece_num en tête de m_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éthode on_button_press en lui passant une variable booléenne raise_on_top ; si après l'appel elle est à true, mettre la pièce en tête de m_pieces et actualiser m_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éthode on_button_release, puis forcer le réaffichage. À la fin, mettre systématiquement m_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éthode on_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éthode Piece::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() et yc() il suffit de renvoyer m_xb + m_width/2 et m_yb + m_height/2, respectivement ;

  • pour définir les setters xc(double xc) et yc(double yc) il suffit d'affecter xc - m_width/2 à m_xb et yc - 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 que m_domino_num_clicked et ses accesseurs ;

  • dans Domino, supprimer la méthode domino_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.