Teaching - 2i013

Enseignants


2i013 - Groupe 5 : Course de voiture

TME 2

Partie 0: Vecteur

Comme vu en cours, afin d'éviter les bugs et les ambiguités dans la classe Vecteur:

  • Déclarer les attributs comme public final
public class Vecteur{
public final double x,y;
...

et modifier les méthodes en conséquences.

!!!! Construire la classe Vecteur incluant les méthodes suivantes (la plupart ont normalement été développées à l'issue du TME1):

  • constructeur classique (x,y)
  • constructeur par différence new Vecteur(A,B) -> AB
public Vecteur(Vecteur a, Vecteur b){
// les coordonnées correspondent à B - A

}
  • produit scalaire
  • calcul de l'angle entre deux vecteurs A,B (avec le signe)
angle = sign\left( (A \wedge B)_z\right) \cos^{-1}\left(\frac{ A \cdot B }{\|A\|\|B\|}\right)

Attention, il faut vérifier que \frac{A \cdot B}{\|A\|\|B\|} est bien compris entre -1 et 1. Théoriquement c'est toujours le cas, mais il y a des problèmes avec les arrondis.

  • calcul de la norme d'un vecteur
  • normalisation d'un vecteur (en divisant un vecteur par sa norme, nous obtenons un vecteur de norme 1 qui doit être retourné)
  • addition(s)
    • création d'un nouveau vecteur résultant à partir de deux vecteurs
  • multiplication par un scalaire
    • création d'un nouveau vecteur résultant
  • rotation

Partie 1: Retour sur le TME 1: Circuit

Le but de cette partie est de boucler le TME numéro 1 tout en mettant en oeuvre l'architecture vue en cours 2 pour gagner du temps. Rappel de la philosophie:

  1. réfléchir à la spécification des objets de base (Circuit, Voiture, ...)
  2. définir une interface
  3. (souvent) définir une classe Factory (pour le moment basée sur des méthodes static) dédiée à la construction de l'objet
  4. dans le main, on utilise des type abstrait (interface) pour les variables et les factory pour la construction: le main est très lisible et évolutif

Faites la lecture de fichier mais développez le code dans le CircuitFactory

!!!! Circuit Boucler le TME 1 jusqu'à la sauvegarde du circuit dans un fichier image.

Vous utiliserez tout de même la nouvelle architecture pour gérer le Circuit:

  • interface Circuit
  • class CircuitImpl
  • class CircuitFactoryFromFile

Interface (vue en cours)

Rappel de cours:

 public interface Circuit {    
        public Terrain getTerrain(int i, int j);
        public Terrain getTerrain(Vecteur v);

        public Vecteur getPointDepart();
        public Vecteur getDirectionDepart();
        public Vecteur getDirectionArrivee();
        public int getWidth();
        public int getHeight();
        public ArrayList<Vecteur> getArrivees();

        public double getDist(int i, int j); // dijkstra

        // SALE et discutable, désactivé pour le moment
        // public Terrain[][] getMatrix();     
 }

CircuitImpl

Pas d'utilisation des fichiers:

  • on se contente de répondre au cahier des charges
  • on fait un constructeur simple prenant directement un tableau de Terrain

CircuitFactory

Dans un circuit, on considère pour l'instant que les valeurs suivants sont immuables:

  public final Vecteur dirDepart = new Vecteur(0,1);
  public final Vecteur dirArrivee = new Vecteur(0,1);

Vous pourrez donc intégrer le code ci-dessus dans la factory de circuit sous la forme de constante.

La factory compte une méthode static retournant un Circuit. Dans la pratique, on retourne l'instanciation d'un CircuitImpl créée à partir des outils de lecture de fichier et des constantes définies dans la classe.

Partie 2: Construire une voiture (et une factory) selon le principe vu en cours.

Commande

Vous commencerez en réalité par coder la classe Commande, contenant deux attributs (pour l'accélération et la rotation) et deux accesseurs.

La chose importante à garder en tête: les attributs d'accélération et de rotation sont compris entre -1 et 1. Ce sont des pourcentages liés au capacités de la voiture. Par exemple, si on accélère à fond (=1) avec une voiture possédant une capacité d'accélération de 0.05, la voiture va voir sa vitesse augmentée de 0.05.

Voiture

Le cahier des charges est défini dans une interface (vue en cours):

  public interface Voiture {
        // pour le pilotage
        public void drive(Commande c);
        public double getMaxTurn(); // cf juste après

        // pour l'observation
        public double getVitesse();
        public Vecteur getPosition();
        public Vecteur getDirection();
        public double getBraquage();
  }

VoitureImpl

Comme définie dans les transparents de cours, la voiture se compose de la liste d'attributs suivants:

// outils pour la gestion des limites de rotation en fonction de la vitesse
private double[] tabVitesse;    
private double[] tabTurn;  

// capacités   
private final double vmax, braquage;
private final double alpha_c, alpha_f, beta_f;

// état à l'instant t
private double vitesse;
private Vecteur position;
private Vecteur direction;

La méthode double getMaxTurn() retourne la COMMANDE de rotation maximale admissible en fonction de la vitesse actuelle de la voiture.

  • Tester la vitesse actuelle de la voiture par rapport à vmax * tabVitesse
  • Retourner la valeur associée dans tabTurn

Exemples:

  • j'ai une vmax de 0.9 et des tableaux définis par:
double[] tabVitesse     = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.};
double[] tabTurn        = {1.,  0.9, 0.75, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.05};
  • si la vitesse est inférieure à 0.1*vmax, je renvoie 1 i.e.: je peux donner une commande entre -1 et 1
  • si vitesse < 0.2*vmax alors cTurnMax=0.9
  • si la vitesse est de 0.53, je me retrouve dans la catégorie <0.6*vmax car 0.9*0.6=0.54. Ma limite est donc 0.4. Je peux donc donner une commande de rotation entre -0.4 et 0.4.

Concernant maintenant la méthode drive: vous utiliserez le canevas suivant (qui reprend les formules vues en cours)

        public void drive(Commande c){
                // VERIFICATIONS !!!
                // 1) Est ce que la rotation et l'accélération sont entre -1 et 1, sinon, throw new RuntimeException
                ...
                // 2) Est ce que la rotation demandée est compatible avec la vitesse actuelle, sinon, throw new RuntimeException
                ...

                // approche normale
                // 1.1) gestion du volant
                direction = direction.rotation(c.getTurn() * braquage); // modif de direction
                // Note: on remarque bien que l'on tourne d'un pourcentage des capacités de la voiture

                // 1.2) garanties, bornes...
                direction = direction.unitVec(); // renormalisation pour éviter les approximations

                // 2.1) gestion des frottements

                vitesse -= alpha_f;
                vitesse -= beta_f*vitesse;

                // 2.2) gestion de l'acceleration/freinage

                vitesse += c.getAcc() * alpha_c;

                vitesse = Math.max(0., vitesse); // pas de vitesse négative (= pas de marche arriere)
                vitesse = Math.min(vmax, vitesse); // pas de dépassement des capacités

                // 3) mise à jour de la position

                position = position.add(direction.fact(vitesse));
        }

VoitureFactory

Rappel sur les constantes à utiliser (aussi définies dans le cours):

  private double[] tabVitesse     = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.};
  private double[] tabTurn        = {1.,  0.9, 0.75, 0.6, 0.5, 0.4, 0.3, 0.2, 0.1, 0.05};

  private double vmax = 0.9;
  private double alpha_c = 0.005;
  private double braquage = 0.1;
  private double alpha_f = 0.0002;
  private double beta_f = 0.0005;

  private double vitesse = 0.; // vitesse initiale

Initialiser la position de la voiture n'est pas trivial: ça signifie que la méthode de construction de la voiture doit prendre en argument le Circuit.

ATTENTION: vous devez réfléchir à l'organisation entre la VoitureImpl et la VoitureFactory:

  • la VoitureImpl contient le modèle physique de la voiture
    • tous les attributs (alpha, beta, vmax...) sont des attributs de la voiture
    • le constructeur est très complexe (beaucoup d'arguments): ça vaut le coup d'utiliser le générateur automatique
    • les valeurs par défaut ne doivent pas être stockées dans la VoitureImpl
      • impossible de faire évoluer proprement le code
  • VoitureFactory est simplement là pour faire un appel au constructeur de VoitureImpl
    • la methode de construction peut être static (c'est plus simple dans un premier temps)
    • le type de retour de build est Voiture pour être sûr que le main manipulera toujours une abstraction (et garantir une mise à jour facile du code)
    • la factory connaît les valeurs par défaut (sous forme de constantes par exemple)
      • si on souhaite une autre voiture, on crée une autre factory

Partie 3: Programme principal

Le programme principal crée le circuit (via une factory), la voiture (également via une factory) et une liste de commandes arbitraire. Le terrain sera aussi converti en image et stocké dans une variable de type BufferedImage.

La liste de commande est crée à l'aide d'une boucle for: l'idée est de créer (au moins) 3 listes:

  • une liste de 100 accélérations sans rotations
  • une liste de 100 accélérations sans rotations concaténée avec une liste de 100 accélération/rotation à droite (à fond)
  • une liste de 100 accélérations sans rotations concaténée avec une liste de 100 accélération/rotation à gauche (à fond)

Ces listes doivent permettre de valider le comportement du modèle de voiture et vérifier la synchronisation entre le Circuit et l'image (cf problèmes de repère évoqués en cours 1).

Le programme principal utilise ensuite une boucle sur toutes les commandes. A chaque itération:

  • on applique une commande sur la voiture
  • on récupère la position de la voiture
  • on colorie dans l'image la case correspondant à la voiture

NB: pour l'instant, il n'y a pas d'interaction entre le circuit et la voiture, ça viendra plus tard.

Partie 4: Strategie liste de commande

Coder l'interface Strategy et l'implémentation StrategyListeCommande suivant les règles vues en cours.

Proposition: en fin de liste, la stratégie renverra null (puisqu'elle ne dispose plus de Commande)

Rappels de cours:

 public interface Strategy {   
        public Commande getCommande(); 
 }

L'idée consiste donc à stocker la liste de commande dans la stratégie et initialiser un compteur à 0.

Partie 5: Première simulation, modification de l'architecture

un objet simulation est initialisé avec:

  • une Voiture
  • un Circuit
  • une Strategy (ici une StrategyListeCommande)

La simulation fait les opérations suivantes:

  1. construire une image du circuit
  2. Tant que la strategie fournit des commandes
    1. pour chaque commande, l'appliquer sur la voiture
    2. récupérer la position de la voiture et colorier le pixel de l'image en jaune (par exemple)

Cette architecture est encore provisoire, elle sera modifiée lors de l'introduction de l'architecture MVC la semaine prochaine.