TD du samedi 29 avril

Composé par Dominique Rossin (Ecole Polytechnique)

1. Introduction

Dans ce TD, nous allons revoir les bases de la programmation avec des objets et de l'héritage en simulant la circulation automobile sur un tronçon d'autoroute. L'idée est la suivante: On se place sur un bout d'autoroute que l'on représentera par un tableau ou chaque case représente environ 3 mètres.

Dans une première partie, nous construirons les différentes classes nécessaires pour la représentation de l'autoroute, des véhicules. Dans un second temps nous nous intéresserons à écrire une interface graphique permettant de régler certains paramètres de la simulation.

2. Faites vos classes

A vos marques

Nous allons essayer d'écrire le programme le plus évolutif possible. Pour cela, nous allons structurer les classes en une hiérarchie permettant de futurs ajouts. Par exemple, il existe différents types de conducteurs et il serait normal de pouvoir aisément rajouter un nouveau comportement. De même, il devrait être possible dans votre programme de rajouter des types de véhicules allant de la mobilette au semi-remorque en passant par le tracteur, fléau bien connu de nos autoroutes ...

Prêt

  1. Créez une classe Vehicule dans un fichier Vehicule.java permettant de stocker pour chaque véhicule: Munir cette classe de son constructeur permettant d'initialiser ces paramètres. On fera attention de respecter l'ordre des paramètres (voie,x,vLimite,route) ci-dessus. On supposera que vitesse=vLimite.
  2. Créez une classe Autoroute dans un fichier Autoroute.java permettant de représenter la route. Cette classe contiendra pour l'instant un tableau bidimensionnel de Vehicule. Munir cette classe d'un constructeur prenant en paramètre les tailles du tableau. Le premier paramètre sera le nombre de voies, et le second le nombre de cases de l'autoroute. Ce constructeur initialisera le tableau.
  3. Y ajouter aussi une méthode int longueur() et int nVoies() qui renvoient la longueur et le nombre de voies de l'autoroute.
  4. Munir votre classe Autoroute d'une méthode Vehicule existeVehicule(int nVoie, int x) qui renvoie un référence sur le véhicule présent sur l'autoroute sur la voie numéro nVoie et à la distance x ou null si aucun véhicule n'est présent. Si les coordonnées sont en dehors des limites alors on renverra aussi null
  5. Munir votre classe Autoroute d'une méthode void enleveVehicule(Vehicule auto) et void ajouteVehicule(Vehicule auto) qui respectivement enlève et ajoute un véhicule à une certaine position de l'autoroute. La position étant celle lue dans l'objet de type Vehicule passé en paramètre. Afin de pallier à certains problèmes pour la suite, votre méthode enleveVehicule(Vehicule auto) ne fera rien si la case de l'autoroute ou est censée être le véhicule est vide. La méthode ajouteVehicule(Vehicule auto) ajoutera le véhicule à sa position si cette position est valide (à l'intérieur de l'autoroute) et ne fera rien sinon. Ces deux fonctions ne font rien si la case demandée est à l'extérieur de l'autoroute. Vous pouvez tester votre programme à l'aide de la fonction main suivante:
        public static void main(String args[]) {
    	Autoroute A62 = new Autoroute(3,1000);
    	Vehicule auto = new Vehicule(0,100,0.5,A62);
    	A62.ajouteVehicule(auto);
    	System.out.println(A62.existeVehicule(0,100));
    	A62.enleveVehicule(auto);
    	System.out.println(A62.existeVehicule(0,100));
    	A62.enleveVehicule(auto);
        }
    
    
    Vous devriez obtenir un résultat semblable au suivant:
    Vehicule@757aef
    null
    
    
  6. Afin d'améliorer l'affichage, redéfinissez la méthode public String toString() de la classe Vehicule de manière à renvoyer pour un véhicule donné la chaîne suivante: "Voiture en (nVoie, distance)". Vous pouvez tester à nouveau votre programme et vous devriez obtenir:
    Voiture en (0,100.0)
    null
    

Partez

Le fonctionnement du programme sera le suivant. A chaque pas de temps le programme examine la liste des Vehicule sur l'autoroute et appelle la méthode avance de chaque voiture rencontrée. Dans un second temps le programme reparcours la liste des voitures et les affiche à leur place.

  1. Créez dans la classe Autoroute une méthode ListeV listeVehicules() qui renvoie la liste des véhicules sur l'autoroute. Pour cela vous modifierez la classe Liste vue en cours et en TD et que l'on retrouve ici. pour obtenir une classe ListeV de listes qui contiennent des Vehicule plutot que des entiers.
    public static void main(String args[]) {
    	Autoroute  A62 = new Autoroute(3,1000);
    	A62.ajouteVehicule(new Vehicule(0,100,0.5,A62));
    	A62.ajouteVehicule(new Vehicule(1,200,0.7,A62));
    	A62.ajouteVehicule(new Vehicule(0,A62.longueur()-1,0.7,A62));
    	System.out.println(A62.listeVehicules());
    }
    
    Vous devriez obtenir le résultat suivant:
    Voiture en (0,100.0) -> Voiture en (0,999.0) -> Voiture en (1,200.0)
    
    Notez que la classe Liste redéfinit la méthode toString().
  2. Créez dans la classe Vehicule une méthode statique static Vehicule vehiculeAleatoire(Autoroute ar) prenant en paramètre une autoroute et renvoyant un nouvel objet de type véhicule de coordonnées (voieAleatoire,0) et de vitesse aléatoire comprise entre 0.5 et 0.8. Cette méthode servira plus tard a tirer au hasard différents type de conducteurs (prudents, dangereux, ...) Cette méthode ne placera pas le Vehicule sur la route. (On rappelle que Math.random() renvoie un nombre aléatoire dans l'intervalle [0..1[
  3. Ecrivez dans la classe Vehicule une méthode avance() qui augmente la position x de la voiture selon sa vitesse.
    x = x + v
    
    Attention, lors de cette incrémentation la voiture peut changer de case du tableau. N'oubliez pas de mettre à jour celui-ci. Par exemple, si la voiture était en position (0,32.8) et que la vitesse est 0.3 alors après incrémentation la voiture passe en position 33.1. Il faut donc enlever la référence vers la voiture de la case (32,0) du tableau et mettre la voiture dans la case (33,0). On ne s'occupera pas des problèmes de collision éventuels. Vous pouvez maintenant tester votre programme à l'aide de la fonction main suivante:
    public static void main(String args[]) {
    	Autoroute A62 = new Autoroute(3,1000);
    	A62.ajouteVehicule(new Vehicule(0,100,0.5,A62));
    	Vehicule auto1 = Vehicule.vehiculeAleatoire(A62);
    	A62.ajouteVehicule(auto1);
    	System.out.println(A62.listeVehicules());
    	auto1.avance(); auto1.avance(); auto1.avance(); auto1.avance();
    	Vehicule auto2 = new Vehicule(0,A62.longueur()-1,0.5,A62);
    	A62.ajouteVehicule(auto2);
    	System.out.println(A62.listeVehicules());
    	auto2.avance(); auto2.avance(); auto2.avance(); auto2.avance();
    	System.out.println(A62.listeVehicules());
    }
    
    Vous devriez obtenir un résultat similaire au suivant (avec le même nombre de voitures à chaque fois):
    Voiture en (0,0.0) -> Voiture en (0,100.0)
    Voiture en (0,2.4338413336906934) -. Voiture en (0,100.0) -. Voiture en (0,999.0)
    Voiture en (0,2.4338413336906934)-> Voiture en (0,100.0)
    
    Vous pouvez maintenant tester votre programme en utilisant la classe suivante. (Cliquez sur le bouton droit sur le lien et sauvegardez le fichier dans le même répertoire que vos classes puis compilez l'ensemble des fichiers et lancez la classe Test). Ce programme met aléatoirement des voitures au début de la route, avec une vitesse aléatoire. Vous devez obtenir le résultat:
    voiture
    Remarquez que lors des collisions, un des véhicules disparait.
  4. Nous allons maintenant créer des véhicules un peu plus intelligents. Pour celà, créez une classe VehiculeAttentif dans un fichier du même nom héritant de la classe Vehicule. Munir cette classe de son constructeur par défaut et qui apelle le constructeur de la classe Vehicule.
  5. Munir la classe VehiculeAttentif des méthodes suivantes: Vous pouvez maintenant tester votre programme en modifiant la méthode VehiculeAleatoire de manière à ne créer que des VehiculeAttentif avec les même paramètres de vitesse et de position que précédement. Laissez tourner suffisament longtemps votre programme, vous verrez apparaître un phénomène bien connu, le Horner. Remarquez que des véhicules peuvent rester bloqués un grand moment. Pour éviter ce phénomène, dans la méthode aFondAFond, on pourra vérifier que la vitesse soit au moins de 0.05.
  6. On va maintenant créer une sous-classe à VehiculeAttentif, nommée VehiculeRapide (dans un fichier du meme nom). Cette classe représente des véhicules qui peuvent en plus dépasser les autres véhicules. Leur comportement est le suivant: Si un véhicule est présent devant lui alors le véhicule va vérifier s'il ne peut pas déboiter sur sa gauche ou sur sa droite. S'il ne peut pas alors il ralentit. Remarquez que vous pouvez réaliser ce comportement en redéfinissant la méthode pasSiVite. Pour déboiter, vous pouvez tester si la case d'arrivée est libre mais vous pouvez aussi tester si la voie est libre sur une certaine distance devant en surchargeant par exemple la méthode dangerDevant() par la méthode boolean dangerDevant(int voie) qui regarde si un véhicule est présent dans les 20 cases devant la position du véhicule mais sur la voie numéro voie.
  7. Modifiez la méthode vehiculeAleatoire de manière a créer à la fois des voitures rapides aléatoires dont la vitesse limite est comprise entre 0.5 et 0.8 et des VehiculeAttentif.

4. Un peu de controle

Un petit cours d'interface graphique

Dans cette partie nous allons rajouter des composants à l'interface graphique permettant de régler le débit de véhicule, les vitesses des véhicules ...Cette partie s'adresse esssentiellement aux personnes intéressées par apprendre à réaliser ce type d'interface.

Nous allons donc regarder dans un premier temps le contenu du fichier Test qui contient les classes suivantes:

Ajout de nouveaux composants

Nous allons voir maintenant comment rajouter des composants graphiques. Nous allons commencer par un bouton pour remettre à zéro l'autoroute, soit enlever toutes les voitures circulant sur celle-ci.

Nous allons regrouper ces commandes dans un panneau que l'on apellera panneau de commande.

  1. Ecrire une classe PanneauCommande héritant de la classe JPanel. Munir cette classe d'un constructeur réalisant les opérations suivantes:
  2. Ajoutez dans le constructeur de la classe Fenetre la ligne suivante qui ajoute le panneau de commande dans la fenêtre. Cette ligne doit etre ajoutée avant la commande setVisible(true).
    getContentPane().add("South",new PanneauCommade());
    
    Vous pouvez tester votre programme. Vous devriez obtenir un résultat come ci-contre.
    Dessin avec bouton
    Malheureusement, lorsque vous appuyez sur le bouton, aucune action n'est réalisée. Il faut donc lier l'évènement appui sur le bouton à l'appel d'une fonction que l'on va écrire. Pour cela, il faut utiliser des écouteurs d'évènements.
  3. Dans le constructeur de la classe PanneauCommande, juste après la création du bouton, rajouter la ligne:
    bouton.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
           // Mettre le code a executer ici
        }
    });   
    
    Cette ligne crée une classe anonyme. Ce qu'il faut retenir c'est que lors de l'appui sur le bouton, la méthode actionPerformed est appelée. Il suffit donc d'y mettre le code à exécuter comme Fenetre.route.clear() et de mettre une méthode clear dans la classe Autoroute.
  4. Ajouter à votre interface un bouton permettant de quitter l'application.
    dessin3
  5. Nous allons maintenant ajouter un curseur permettant de régler le débit des voitures, ie la probabilité d'apparition des voitures sur la route. Ce débit est controlé par la variable static double debit=0.05 contenue dans la classe Fenetre. Nous utiliserons pour cela un JSlider. Nous voulons pouvoir régler ce débit entre 0 et 0.1. Dans le panneau de commande, ajoutez un JSlider en appelant le constructeur JSlider() qui crée un curseur pour des valeurs entre 0 et 100 et une valeur par défaut de 50. Testez votre programme.
  6. Nous allons maintenant lier le fait de changer le curseur de position avec le changement du débit. Malheureusement, dans le cas des JSlider le fait de changer le curseur de position n'est pas une action à proprement parler mais un changement d'état.
    	slider.addChangeListener(new ChangeListener() {
    		public void stateChanged(ChangeEvent e) {
    		   // Code a réaliser
    		   System.out.println((int) ((JSlider)e.getSource()).getValue());
    		}
    	    });
    

    dessin3