Introduction

Cette page contient les informations supplémentaires sur les cours “Programmation Concurrentielle des Interfaces Interactives” enseignée en L3 Informatique à l’université Paris-Saclay en 2015/2016.

Horaires et salles

Salle E205

  • Lundi 2 mai 9h00 – cours
  • Lundi 2 mai 14h00 – cours
  • Mardi 3 mai 9h00 – cours
  • Mardi 3 mai 14h00 – TD
  • Mercredi 4 mai 9h00 – cours
  • Mercredi 4 mai 14h00 – projet
  • Vendredi 6 mai 9h00 – projet

Salle C201

  • Lundi 9 mai 9h00 – projet
  • Lundi 9 mai 14h00 – projet
  • Mardi 10 mai 9h00 – projet
  • Mardi 10 mai 14h00 – projet
  • Mercredi 11 mai 9h00 – projet
  • Mercredi 11 mai 14h00 – projet
  • Jeudi 12 mai 9h00 – projet
  • Jeudi 12 mai 14h00 – projet
  • Vendredi 13 mai, 9h30 – soutenance

Équipe enseignante

Responsable du cours – Frédéric Vernier

Chargé du TP – Oleksandr Zinenko

Documents

Travail pratique

Ce travail pratique sert à vous familiariser avec la programmation des interfaces graphiques et les bases du dessin programmé.

Commentaire de l’énoncé.

Le travail consiste à réaliser un jeu très simple en utilisant le framework graphique Swing du langage Java. Ce jeu correspond à un modèle physique derrière le jeu Flappy Bird. Cette tâche ne nécessite pas de parallélisme, elle est proposée pour vous familiariser avec la programmation des interfaces graphiques.

Précision de travail à faire

Il est nécessaire de réaliser:

  • la mise à jour de l’écran principal ;
  • la réaction aux touches appuyées ;
  • l’écran d’invitation pour démarrer le jeu ;
  • l’écran de la fin du jeu avec le score et la possibilité de redémarrer le jeu.

Génération de la ligne continue

La génération de ligne avec des points aléatoires peut entraîner une variation importante des position vericale et, par conséquent, l’impossibilité totale de passer le niveau. Vous devez utiliser le bruit de Perlin, la séquence des nombres pseudo-aléatoires qui minimise la variation entre les points consécutifs tout en maintenant son caractère aléatoire globale. Cette séquence est souvent utilisée dans le jeu pour générer les textures ou le terrain.

La classe Java du bruit de Perlin vous est fournie, vous n’avez qu’à l’utiliser : ImprovedNoise.java.

Exemple d’utilisation, g2 est une instance de Graphics2D, h est la hauteur du terrain de jeu, w est sa largeur.

g2.setStroke(new BasicStroke(8, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
g2.setColor(Color.BLACK);
for (int i=0; i<w; i+=w/20) {
    int dy1 = (int)Math.round(h/2*ImprovedNoise.noise((i)/150.0, 0.0, 0.0));
    int dy2 = (int)Math.round(h/2*ImprovedNoise.noise((i+w/20)/150.0, 0.0, 0.0));
    g2.drawLine(i, h/2+dy1, i+w/20, h/2+dy2);
}

Liens utiles

Projet

Dans ce projet, vous allez réaliser une interface graphique assez simple en utilisant des mécanismes de parallélisme en Java. Le projet sert à la fois comme un entraînement dans la programmation des interfaces et comme une introduction dans la programmation de système parallèles.

Sujet

Le but du projet est de réaliser un clone de l’ancien jeu vidéo “The Incredible Machine” en Java en exploitant les aspects parallèles de l’interface. Le jeu est un puzzle basé sur la simulation physique primitive (mouvement, gravité, contact des objets rigides). Un niveau comporte plusieurs objets qui peuvent interagir entre eux. Certains objets ont une position fixe sur le “terrain de jeu”, d’autres sont déplaçable par l’utilisateur. Le but du jeu est de déplacer les objets afin de réaliser une action précise, par exemple faire rentrer le ballon dans le panier, faire envoler le ballon gonflé à l’hélium, faire allumer une lampe, etc. La liste des objets et de leurs interactions est donnée dans l’annexe de l’énoncé, mais vous être libres de définir les actions.

Format de fichier

  • identificateur du niveau (nombre entier) ;
  • action cible (nombre entier) ;
  • nombre d’objets disponibles à l’utilisateur (nombre entier) ;
  • liste des objets disponibles, séparée par un espace (chaque objet – une lettre d’alphabet latin, différencier majuscules et minuscules) ;
  • nombre d’objets positionnés dans le niveau (nombre entier) ;
  • liste des objets positionnés, séparée par un espace (chaque objet – une lettre identifiant l’objet suivi des coordonnées horizontale et verticale en pixels du centre de l’objet, suivi de sa rotation en degrés, tout séparé par virgules).

Attention, certains objets n’ont que les rotations prédéfinies : 0, 90, 180, 270 degrés. D’autre n’en ont pas du tout, mettre 0 dans ces cas-là. Si une liste des objets est vide, mettre 0 comme le nombre des objets.

Exemple

42
2
3
A d g
2
A,10,10,45 B,10,10,0

Choix à faire

  • les actions-cible de niveau (au moins trois) ;
  • numérotation des actions dans le fichier : chaque action-cible a un nombre entier pour l’identifier ;
  • codage des objets : chaque objet a une lettre qui le représente dans le fichier ;
  • réalisation des cordes et courroies : quels objets sont attachés, comment ils sont représentés, comment sont gérés les objets avec plusieurs points d’attache.

Ces choix doivent être reflétés dans vos rapports.

Travail à faire

  • interface graphique de base : fenêtre, terrain de jeu, liste des objets disponibles, timer, objectif de niveau, boutons, menus ;
  • éditeur de niveau avec support de manipulation directe, drag & drop des objets ;
  • régime de simulation et animation des objets indépendante ;
  • interactions des objets et modèle physique dans le régime de simulation, cf. annexe de l’énoncé ;
  • chargement des niveaux ;
  • écrans d’initialisation, de pause et de fin de niveau ;
  • (bonus) sauvegarde des niveaux et de l’état de jeu.

Vu le nombre élevé des objets à gérer, il est recommandé de commencer par un sous-ensemble des objets réduit qui permet toutefois de construire des niveaux raisonnables. Par exemple, les murs, le panier et le ballon de basket.

Rapport

Le rapport de projet de 1-2 pages doit contenir :

  • les fonctionnalités et les objets présents dans le programme ;
  • les choix de conception que vous avez fait (inutile de donner une liste complète de codage des objets dans le rapport, mais la mettre dans la documentation) ;
  • les principes d’organisation d’exécution parallèle que vous avez utilisés ;
  • l’organisation des classes de votre programme ;
  • description du méchanisme Drag&Drop utilisé ;
  • tous ce que vous considerez pertitent pour les autre étudiants.

Le rapport et le code doivent obligatoirement être envoyé au chargé de TP avant 21h00 le jeudi 12 mai. Adresse : zinenko [arobase] lri [point] fr. Sujet : [info340] NOM Prénom.

Joignez vos codes source au rapport de façon qu’on puisse les compiler. Une seule classe avec la méthode main pour tout le programme est autorisée.

Il est obligatoire d’utiliser le format PDF pour le rapport et l’archive ZIP pour les codes source.

Soutenance

La soutenance des projets aura lieu le vendredi 13 mai à 9h30.

Pendant la soutenance, vous aurez à faire une présentation du votre projet (4-5 diapos) et une démonstration de votre projet avant de répondre aux 1-2 questions. Le contenu de la présentation correspond au celui du rapport.

Seuls les étudiants ayant envoyé le rapport et le code avant la date limite seront admis pour la soutenance.

Éléments de la conception des programmes

Machines à états

Les automates finis, aussi appelés machines à états même s’il existe une différence formelle dans les mathématiques, sont des constructions abstraites qui peuvent “être” dans l’un de nombre fini d’états. Un automate fini déterministe est toujours dans un état et dans un seul état. Le fait de changer d’état d’automate est appelé une transition. Cette transition suit un événement extérieur à l’automate et peut être conditionnelle aux paramètres de cet événement. Les automates finis sont très souvent utilisés pour modéliser les interactions homme-machine complexes.

Par exemple, le double-click de souris peut être détecté par un automate à cinq états. Les descriptions des états ici correspondent aux actions nécessaires pour faire une transition vers l’état suivant et les conditions de cette action. Toute autre action fait la transition à l’état 1. L’état 5 est, dans ce cas, finale et signifie la présence de double-click.

  1. aucun bouton appuyé ;
  2. bouton gauche appuyé une fois, mais n’est pas encore relâché ;
  3. bouton gauche appuyé et relâché dans la période de 40 millisecondes ;
  4. bouton gauche appuyé, relâché et de nouveau appuyé moins de 40 millisecondes après ;
  5. bouton gauche appuyé, relâché, de nouveau appuyé et relâché dans moins de 80 millisecondes.

Les événements extérieurs peuvent être de nature variée : entrée de l’utilisateur, réseau, timer, transitions dans un autre automate. Les événements peuvent aussi provenir de plusieurs sources parallèles à condition qu’ils soient gérés d’une manière séquentielle par l’automate grâce au mécanismes de synchronisation.

Dans ce projet vous pouvez utiliser les machines à états pour créer des animations ou bien pour gérer les état des objets pendant l’interaction. Dans le premier cas, les état de la machine correspondent aux cadres d’animation (chargés depuis le fichier) et les transitions sont générées par le timer, par exemple chaque 300 millisecondes. Dans le second cas, les états sont les véritables états de l’objet, par exemple le moteur est à l’arrêt ou fonctionne, et les transitions se font en réponse à une interaction avec d’autre objets.

Patron de conception “Observateur”

Dans la programmation d’interfaces, la communication asynchrone entre les différents objets se fait souvent à l’aide du patron de conception (pattern) “Observateur”. Dans le cœur de ce parton, un objet qui change pendant l’exécution d’une manière asynchrone, par exemple en réponse à l’entrée de l’utilisateur. Au lieu de vérifier constamment si on objet a changé, tous les autres objets qui ont besoin de être au courant du fait de changement s’enregistrent comme observateurs de changement. L’objet changeable maintient une liste des observateurs enregistrés et, quand sa position change, notifie les observateurs de ce changement.

Généralement, on crée une interface Java, couramment appelée Listener, avec une ou plusieurs méthodes qui seront appelées en cas de changement. La classe des objets changeables contient une liste de listeners et des méthodes pour en ajouter d’autres ainsi qu’un code qui fait appel aux méthodes de tous les listeners après (ou immédiatement avant) le changement. La classe qui a besoin de répondre à ce changement réalise l’interface de listener et contient le code à exécuter dans les méthodes correspondantes.

interface ChangeListener {
    public void valueAboutToChange();
    public void valueChanged(Value newValue);
}

class ChangingObject {
    private ArrayList<ChangeListener> m_listeners;
    private Value m_value;

    public void attachChangeListener(ChangeListener listener) {
            m_listeners.append(listener);
    }

    public void setValue(Value newValue) {
            for (ChangeListener listener : m_listeners)
                    listener.valueAboutToChange();                  // Notifier avant le changement
            value = newValue;
            for (ChangeListener listener : m_listeners)
                    listener.valueChanged(newValue);                // Notifier après le changement
    }
}

class MyClass implements ChangeListener {
    public void valueAboutToChange() {
            // Faire quelque chose.
    }
    public void valueChanged(Value newVlaue) {
            // Faire quelque chose d'autre.
    }
}

La bibliothèque des interfaces Java est basée autour de Listener pour les événements d’entrée : KeyListener, MouseListener, MouseMoveListener, etc. Les classes d’interaction avec l’utilisateur doivent réaliser ces interfaces Java, l’appel des méthodes étant géré par la bibliothèque.

Comme certains interfaces Java Listener ont beaucoup de méthodes dont la plupart ne sera pas utilisé, la bibliothèque contient aussi des classes qui réalisent toutes ces méthodes sans aucune action à l’intérieur : KeyAdapter, MouseAdapter, etc. Dans ce cas, vous n’avez qu’à redéfinir les méthodes dont vous avez besoin. Attention, Java ne supporte pas l’héritage multiple, une classe qui hérite de KeyAdapter ne peut pas hériter de JPanel en même temps.

Vous pouvez aussi définir vos propres Listener pour vous classes et vous événements, notamment pour les transitions des machines à états.

Dessin, entrées et threads

Le dessin 2D en Java utilise des threads à l’intérieur même si vous n’en créez pas. En effet, AWT, la partie de la bibliothèque standard de Java chargée de rendu graphique, utilise un thread spécial pour la manipulation d’interface. Ce thread, indépendant du thread principale, permet de ne pas bloquer l’interface pendant l’exécution d’une opération lourde et ainsi éviter le “gel” de l’écran, irritant pour l’utilisateur.

Le thread de l’interface est appelé Event Dispatch Thread (EDT) ou le thread de gestion des événements. Les événements qu’il gère concerne les entrées et les sorties graphiques du programme : le mouvement de la souris, l’entrée sur clavier ou bien l’affichage. Comme c’est le système d’exploitation qui interagit avec les appareils d’entrée-sortie, les événements sont transmis du système d’exploitation au système runtime du Java où l’EDT les renvoie aux classes-destinataires.

La demande de la mise à jour de l’écran du programme est aussi un événement traité par l’EDT. Il est crée soit par le système d’exploitation lors d’un affichage ou d’un redimensionnement de fenêtre, soit par l’appel manuel de la fonction repaint(). L’EDT alterne la repeinture de l’écran et le traitement des autres événements de sorte que deux demandes de repeinture consécutives peuvent mener à une seule opération.

La gestion centralisée des entrées et des sorties permet éviter les accès concurrents aux ressources uniques. L’EDT assure l’accès séquentiel grâce aux techniques de synchronisation Java, mais sans garantie d’ordre d’accès. Notez aussi que les actions des Listener d’entrée ainsi que les fonctions paint() sont exécutées dans le contexte de l’EDT.

Foire aux questions