TD 10 : compilation séparée, graphiques#

Exercice 1 : Compilation séparée

Annotez les quatre fichiers suivants en précisant leurs rôles respectifs et en indiquant où se trouvent entête, définition, documentation, tests et utilisation de la fonction factorielle.

Code source 13 factorielle.hpp#
/** La fonction factorielle
 *  @param n un nombre entier positif
 *  @return n!
 **/
int factorielle(int n);
Code source 14 factorielle.cpp#
#include "factorielle.hpp"

int factorielle(int n) {
    int resultat = 1;
    for ( int k = 1; k <= n; k++ )
        resultat = resultat * k;
    return resultat;
}
Code source 15 factorielle-test.cpp#
#include <iostream>
#include "factorielle.hpp"
using namespace std;

/** Infrastructure minimale de test **/
#define CHECK(test) if (!(test)) cerr << "Test failed in file " \
         << __FILE__ << " line " << __LINE__ << ": " #test << endl

/** Les tests de la fonction factorielle **/
void factorielleTest() {
    CHECK( factorielle(0) == 1  );
    CHECK( factorielle(1) == 1  );
    CHECK( factorielle(2) == 2  );
    CHECK( factorielle(3) == 6  );
    CHECK( factorielle(4) == 24 );
}

/** Cette fonction main ne sert qu'à lancer les tests **/
int main() {
    factorielleTest();
    return 0;
}

Code source 16 factorielle-exemple.cpp#
#include <iostream>
#include "factorielle.hpp"
using namespace std;

int main() {
    int n;
    cout << "Entrez un entier n" << endl;
    cin >> n;
    cout << n << "! = " << factorielle(n) << endl;
    return 0;
}

/// BEGIN SOLUTION

Ensemble, ces quatre fichiers forment une bibliothèque, qui implante une fonction factorielle, ainsi que des tests unitaires et un exemple d’utilisation de la fonction. Le fichier factorielle.cpp contient la définition (le code) de la fonction factorielle. Le fichier factorielle.hpp est le fichier d’entête : il contient la déclaration (l’entête) de la fonction factorielle, avec sa documentation. Ce fichier permet de pouvoir compiler des programmes qui utilisent la fonction factorielle, même sans avoir le code de la fonction factorielle. Pour cela il faut avoir mis la ligne #include "factorielle.hpp" dans le programme. Le fichier factorielle-exemple.cpp donne un exemple de programme utilisant la fonction factorielle. Enfin le fichier factorielle-test.cpp contient les tests de la fonction factorielle, ainsi qu’un programme qui lance ces tests.

/// END SOLUTION

Graphiques#

En TP, nous utiliserons la bibliothèque SFML 1, une bibliothèque largement utilisée facilitant le développement de jeux ou d’applications multimédias grâce à une grande panoplie de modules (fenêtrage, graphismes, audio, réseau, …), principalement développée par Laurent Gomila. Nous allons nous en servir pour créer une fenêtre et dessiner dedans.

La SFML n’étant pas immédiatement intuitive à utiliser, nous avons écrit une micro bibliothèque composée de quelques primitives pour vous simplifier les premiers pas. Cette bibliothèque, documentée dans primitives.hpp, a vocation à être la plus transparente possible. À vous de la lire en détail pour vous l’approprier et, par la suite, vous en passer.

Voici un exemple de programme utilisant la SFML et notre bibliothèque primitives :

Code source 17 exemple-graphisme1.cpp#
#include <SFML/Graphics.hpp>
using namespace sf;

#include "primitives.hpp"

int main() {
    // Crée une fenêtre de taille 640x480
    RenderWindow window(VideoMode(640, 480), "Ma super fenêtre");

    // Remplit la fenêtre de blanc
    window.clear(Color::White);

    // Affiche un point de couleur rouge
    draw_point(window, {120, 5}, Color::Red);

    // Actualise la fenêtre
    window.display();

    // Attend 10 secondes
    sleep(seconds(10));

    return 0;
}

Notez dans cet exemple le nouveau type RenderWindow représentant une fenêtre et les fonctions clear pour effacer la fenêtre, sleep pour attendre un temps donné et draw_point (qui vient de primitives.cpp) pour dessiner un point.

Exercice 2 : Premiers dessins

En vous inspirant de l’exemple fourni, écrivez des instructions (fragments de programme) qui, respectivement,

  1. dessinent un point noir (Black) de coordonnées \((418, 143)\);

    /// BEGIN SOLUTION

        draw_point(window, {418, 143}, Color::Black );
    

    /// END SOLUTION

  2. dessinent un segment blanc (White) reliant les points \((100, 200)\) et \((200, 200)\);

    /// BEGIN SOLUTION

        for (int x = 100; x <= 200; x++)
            draw_point(window, {x, 200}, Color::White );
    

    /// END SOLUTION

  3. dessinent un segment rouge (Red) reliant les points \((200, 300)\) et \((200, 400)\);

    /// BEGIN SOLUTION

        for (int y = 300; y <= 400; y++)
            draw_point(window, {200, y}, Color::Red );
    

    /// END SOLUTION

  4. dessinent le rectangle horizontal vide de contour rouge dont les sommets diagonaux sont \((200, 200)\) et \((400, 300)\);

    /// BEGIN SOLUTION

        for ( int x = 200; x <= 400; x++ ) {
            draw_point(window, {x, 200}, Color::Red);
            draw_point(window, {x, 300}, Color::Red);
        }
        for ( int y = 200; y <= 300; y++ ) {
            draw_point(window, {200, y}, Color::Red);
            draw_point(window, {400, y}, Color::Red);
        }
    

    /// END SOLUTION

  5. dessinent un rectangle horizontal plein noir dont les sommets diagonaux sont \((400, 150)\) et \((500, 200)\);

    /// BEGIN SOLUTION

        for ( int x = 200; x <= 400; x++ ) {
            draw_point(window, {x, 200}, Color::Red);
            draw_point(window, {x, 300}, Color::Red);
        }
        for ( int y = 200; y <= 300; y++ ) {
            draw_point(window, {200, y}, Color::Red);
            draw_point(window, {400, y}, Color::Red);
        }
    

    /// END SOLUTION

  6. dessinent un segment rouge reliant les points \((400, 300)\) et \((500, 400)\);

    /// BEGIN SOLUTION

        for ( int t = 0; t <= 100; t++ )
            draw_point(window, {400 + t, 300 + t}, Color::Red);
    

    /// END SOLUTION

  7. dessinent un cercle noir de centre \((415, 145)\) et de rayon \(10\);
    Indication : Utiliser les fonctions cos et sin.

    /// BEGIN SOLUTION

        double pi = 3.14159;
        int rayon = 10;
        double dt = .5 / rayon;
        for ( double t = 0; t <= 2 * pi; t += dt )
            draw_point(window, {415 + rayon * cos(t),
                                145 + rayon * sin(t)
                               },
                       Color::Black);
    

    /// END SOLUTION

  8. dessinent un disque jaune (Yellow) de centre \((700, 100)\) et de rayon \(50\);
    Indication : Utiliser la définition d’un disque.

    /// BEGIN SOLUTION

        rayon = 50;
        for ( int x = -rayon; x <= rayon; x++ )
            for ( int y = -rayon; y <= rayon; y++ )
                if ( x * x + y * y <= rayon * rayon )
                    draw_point(window, {550 + x, 75 + y}, Color::Yellow);
    

    /// END SOLUTION

Exercice 3 : \(\clubsuit\) Fonctions de dessin

Notre bibliothèque primitives contient entre autres les fonctions suivantes :

/** Affiche une ligne de couleur entre deux positions données
 * @param w une fenêtre ouverte dans laquelle dessiner
 * @param pos1 les coordonnées du premier point de la ligne
 * @param pos1 les coordonnées du dernier point de la ligne
 * @param color la couleur de la ligne
 */
void draw_line (RenderWindow& w, Point pos1, Point pos2, Color color);

/** Affiche un cercle coloré vide
 * @param w une fenêtre ouverte dans laquelle dessiner
 * @param center la position du centre du cercle
 * @param r le rayon du cercle
 * @param color la couleur du trait
 */
void draw_circle (RenderWindow& w, Point center, int r, Color color);

/** Affiche un cercle coloré plein 
 * @param w une fenêtre ouverte dans laquelle dessiner
 * @param center la position du centre du cercle
 * @param r le rayon du cercle
 * @param color la couleur du trait et du remplissage
 */
void draw_filled_circle(RenderWindow& w, Point center, int r, Color color);

À noter qu’elles prennent la fenêtre où dessiner comme premier paramètre (w). Le symbole & indique que cette fenêtre est passée par référence afin que les fonctions puissent la modifier. Vous pouvez essentiellement ignorer ce détail technique dont vous verrez les tenants et les aboutissants au deuxième semestre.

Notez aussi les variables de type Point. Ces dernières représentent des coordonnées. On peut créer un point et accéder à ses coordonnées comme suit :

Point p = {42, 2713};
int x = p.x; // 42
int y = p.y; // 2713

Techniquement parlant, il s’agit d’un enregistrement (struct en anglais); vous en verrez les détails au deuxième semestre.

Dans notre bibliothèque, ces fonctions sont implémentées via des appels directs à des fonctions de la SFML. Par exemple :

void draw_filled_circle(RenderWindow &w, Point center, int r, Color color) {
    CircleShape shape(r);
    shape.setPosition(center);
    shape.setOutlineThickness(0.f);
    shape.setFillColor(color);
    w.draw(shape);
}
  1. Réimplantez ces fonctions, en n’utilisant que des appels à draw_point.

    /// BEGIN SOLUTION

    void cercle(RenderWindow &window, Point centre, int rayon) {
        double pi = 3.14159;
        double dt = .5 / rayon;
        for ( double t = 0; t <= 2 * pi; t += dt )
            draw_point(window, {centre.x + rayon * cos(t),
                                centre.y + rayon * sin(t)
                               }, Color::Red);
        window.display();
    }
    
    void disque(RenderWindow &window, Point centre, int rayon) {
        for ( int x = -rayon; x <= rayon; x++ )
            for ( int y = -rayon; y <= rayon; y++ )
                if ( x * x + y * y <= rayon * rayon )
                    draw_point(window, {centre.x + x, centre.y + y}, Color::Red);
        window.display();
    }
    
    void segment(RenderWindow &window, Point point1, Point point2) {
        double ii, jj, dt;
        int i, j, var_x, var_y, nbpoints;
        var_x = point2.x - point1.x;
        var_y = point2.y - point1.y;
        if ( var_x > var_y ) {
            nbpoints = var_x;
        } else {
            nbpoints = var_y;
        }
        dt = 1.0 / nbpoints;
        for ( int k = 0; k <= nbpoints; k++ ) {
            ii = point1.x + k * dt * var_x;
            jj = point1.y + k * dt * var_y;
            i = ii + 0.5; // on prend le i le plus proche des coordonnées calculées
            j = jj + 0.5; // on prend le j le plus proche des coordonnées calculées
            draw_point(window, {i, j}, Color::Red);
        }
        window.display();
    }
    

    /// END SOLUTION

Exercice 4 : Souris et Clavier

Voici maintenant un fragment de programme interactif utilisant la SFML et notre bibliothèque primitives pour réagir à des actions avec la souris ou le clavier.

Code source 18 exemple-graphisme2.cpp#
    // Crée une fenêtre de taille 640x480
    RenderWindow window(VideoMode(640, 480), "Ma super fenêtre");
    window.clear(Color::White);
    window.display();

    // Attend que l'utilisateur clique sur le bouton gauche de la souris
    // et stocke les coordonnées du point  a cliqué l'utilisateur dans
    // la variable point.
    Point point = wait_mouse(window);

    // Affiche les coordonnées du point
    cout << point.x << " " << point.y << endl;

    // Attend que l'utilisateur tape sur une touche
    // et stocke la touche et d'autres informations dans
    // la variable evenement
    Event::KeyEvent evenement = wait_keyboard(window);

    // Affiche bonjour si la touche est o
    if ( evenement.code == Keyboard::Key::O )
        cout << "Bonjour" << endl;
  1. En vous inspirant du programme précédent, écrivez un programme qui attend que l’utilisateur clique sur deux points de l’écran puis qui trace le segment les reliant.

    /// BEGIN SOLUTION

        RenderWindow window(VideoMode(640, 480), "Ma super fenêtre");
        window.clear(Color::White);
        window.display();
    
        Point point1 = wait_mouse(window);
        Point point2 = wait_mouse(window);
        draw_line( window, point1, point2, Color::Red );
        window.display();
        sleep(seconds(10));
    

    /// END SOLUTION

  2. Écrivez un programme qui attend que l’utilisateur clique sur quatre points puis qui dessine le quadrilatère ayant ces quatre points comme sommets.

    /// BEGIN SOLUTION

        RenderWindow window(VideoMode(640, 480), "Ma super fenêtre");
        window.clear(Color::White);
        window.display();
    
        Point point1 = wait_mouse(window);
        Point point2 = wait_mouse(window);
        draw_line( window, point1, point2, Color::Red );
        window.display();
    
        Point point3 = wait_mouse(window);
        draw_line( window, point2, point3, Color::Red );
        window.display();
    
        Point point4 = wait_mouse(window);
        draw_line( window, point3, point4, Color::Red );
        draw_line( window, point4, point1, Color::Red );
        window.display();
    
        sleep(seconds(10));
    

    /// END SOLUTION

  3. Écrivez un programme qui dessine des polygones de la façon suivante : l’utilisateur clique sur des points successifs. À chaque clic, le programme relie les deux derniers points. Si l’utilisateur clique près du point initial, le polygone se ferme, et le programme commence un nouveau polygone.

    /// BEGIN SOLUTION

        RenderWindow window(VideoMode(640, 480), "Ma super fenêtre");
        window.clear(Color::White);
        window.display();
    
        while (true) {
            Point initial = wait_mouse(window);
            Point point1 = initial;
            Point point2;
            bool continuer = true;
    
            do {
                point2 = wait_mouse(window);
                if ( abs(point2.x - initial.x) <= 5
                     and abs(point2.y - initial.y) <= 5 ) {
                    point2 = initial;
                    continuer = false;
                }
                draw_line(window, point1, point2, Color::Red);
                window.display();
                point1 = point2;
            } while ( continuer );
        }
    

    /// END SOLUTION


1

https://www.sfml-dev.org