Algorithmique de la mobilité

↑ Accueil du cours


Surveillance de feux de forêts et canadairs autonomes

Dans cette séance, nous allons réaliser graduellement un scénario complexe impliquant des entités fixes et des entités mobiles. Les interactions se feront par envoi de messages. À tout moment, vous pouvez vous référer à la vidéo suivante pour visualiser le genre de résultat que vous êtes censés obtenir (à titre indicatif seulement).

Vidéo 1: Scénario de surveillance de feux de forêt avec canadairs

Préparation

Créez un projet en copiez-y les six classes données plus bas (Canadair, Fire, Lake, Sensor, Station et MainCanadair) ainsi que la classe WaypointNode que vous avez peut-être déjà (sinon ce lien).

Version de base

  1. Ajoutez une détection des feux dans la classe des capteurs de température (Sensor) en surchargeant onSensingIn(). Utilisez instanceof pour être sûr qu’il s’agit de feux et non pas d’une autre sorte de noeud. Lorsqu’un feu est détecté, le capteur doit se mettre en mode alerte (couleur rouge, setColor(Color.red) ).
  2. Vérifiez que ça fonctionne à l'aide de la souris.
  3. Ajoutez une détection des feux sur les canadairs (onSensingIn()). Lorsqu'un feu est détecté, le canadair l'éteint (méthode die() sur le feu).
  4. Vérifiez que ça fonctionne à l'aide de la souris.
  5. Faites en sorte qu'un capteur (Sensor) détecte s'il n'y a plus aucun feu dans son entourage et si c'est le cas, il se remet en mode normal. Vous pouvez maintenir la liste des feux actuellement captés par ce capteur via la liste sensedFires prévue à cet effet. Pour détecter qu'un feu a disparu, utilisez onSensingOut(), et pour supprimer la couleur actuelle, utiliser une couleur null.
  6. Vérifiez que ça fonctionne à l'aide de la souris (clic droit sur un feu pour le supprimer).
  7. Lorsque la station démarre, elle émet un message vide. Propagez ce message parmis les capteurs de sorte à construire et dessiner un arbre couvrant ayant la station de base pour racine (utilisez onMessage, utilisez parent, utilisez getCommonLinkWith, utilisez setWidth).
  8. Tant qu'un capteur est en mode alerte, il doit envoyer périodiquement ses coordonnées à son parent, qui les relaie à son parent, etc. jusqu'à la station de base (utilisez onClock, utilisez getLocation et des messages de type Point).
  9. Vérifiez que cela fonctionne en utilisant une autre couleur sur les capteurs intermédiaires.
  10. Si un capteur intermédiaire n'a pas reçu de message depuis 10 rounds, il repasse en couleur normale. (utilisez getTime() lors de la réception d'un message pour récupérer le numéro du round, et testez-le dans le onClock()).
  11. Vérifiez que tout fonctionne bien en ajoutant et supprimant des feux à la souris.
  12. Lorsque la station de base reçoit les coordonnées d'un capteur en alerte, elle doit les transmettre aux potentiels canadairs alentours (via sendAll). Ces derniers doivent se mettre en route vers le capteur (souvenez vous que Canadair hérite de WaypointNode, qui possède une méthode addDestination()...)
  13. Un Canadair n'a pas le droit de s'arrêter en vol, il ne peut s'arrêter que sur son emplacement de parking. Pour vous entrainer à piloter un canadair, faites d'abord un aller-retour depuis le parking vers le capteur en alerte, en ignorant le feu (utilisez onArrival(), testez alors si la cible courante était différente du parking, auquel cas reprenez l'emplacement de parking comme nouvelle cible.)

Améliorations

Les objectifs suivants admettent un certain degré de liberté.

Bonus (obligatoire si la séance n'est pas encore terminée)

Code source des classes

Classe Canadair

import io.jbotsim.core.Point;
import io.jbotsim.ui.icons.Icons;

public class Canadair extends WaypointNode {
    Point parking;

    public Canadair(){
        setIcon(Icons.CANADAIR);
        setIconSize(16);
        setCommunicationRange(120);
        setSensingRange(30);
    }

    @Override
    public void onStart() {
        parking = getLocation();
        super.setSpeed(2);
    }
}

Classe Fire

import java.util.ArrayList;
import java.util.Random;

import io.jbotsim.core.Node;
import io.jbotsim.ui.icons.Icons;

/* Gardez cette classe telle quelle */
public class Fire extends Node {
    static ArrayList<Fire> allFires = new ArrayList<Fire>();
    Random r=new Random();

    @Override
    public void onStart(){
        allFires.add(this);
        disableWireless();
        setIcon(Icons.FIRE);
    }

    @Override
    public void onClock(){
        if (Math.random() < 0.01 && allFires.size() < 100)
            propagate();
    }

    public void propagate(){
        double x = getX() + r.nextDouble() * 20 - 10;
        double y = getY() + r.nextDouble() * 20 - 10;
        for (Fire fire : allFires)
            if (fire.distance(x, y) < 10)
                return;
        getTopology().addNode(x, y, new Fire());
    }

    @Override
    public void die(){
        super.die();
        allFires.remove(this);
    }
}

Classe Lake

import io.jbotsim.core.Node;
import io.jbotsim.ui.icons.Icons;

public class Lake extends Node {

    public Lake(){
        setIcon(Icons.LAKE);
        setIconSize(40);
        disableWireless();
    }
}

Classe Sensor

import io.jbotsim.core.Node;
import io.jbotsim.ui.icons.Icons;

import java.util.ArrayList;
import java.util.List;

public class Sensor extends Node {
    Node parent = null;
    List<Fire> sensedFires = new ArrayList<>();

    @Override
    public void onStart() {
        setIcon(Icons.SENSOR);
        setIconSize(16);
        setCommunicationRange(120);
        setSensingRange(60);
    }
}

Classe Station

import io.jbotsim.core.Message;
import io.jbotsim.core.Node;
import io.jbotsim.ui.icons.Icons;

public class Station extends Node {

    public Station() {
        setIcon(Icons.STATION);
        setIconSize(25);
        setCommunicationRange(120);
    }

    @Override
    public void onStart() {
        sendAll(new Message());
    }
}

Classe MainCanadair

import io.jbotsim.core.*;
import io.jbotsim.ui.JViewer;
import io.jbotsim.core.DelayMessageEngine;

/**
 * Il n'est pas essentiel de comprendre ce qu'il y a dans cette classe.
 */
public class MainCanadair {
    public static void deploySensors(Topology topology){
        for (int i=0; i<6; i++)
            for (int j=0; j<4; j++)
                topology.addNode(i*100+180-(j%2)*30, j*100+100, new Sensor());
        topology.addNode(50, 400, new Station());
        for (Link link : topology.getLinks())
            link.setColor(Color.gray);
    }
    public static void main(String[] args) {
        Topology topology = new Topology(800,600);
        topology.setLinkResolver(new LinkResolver(){
            @Override
            public boolean isHeardBy(Node n1, Node n2) {
                if ((n1 instanceof Sensor && n2 instanceof Canadair) ||
                        (n2 instanceof Sensor && n1 instanceof Canadair))
                    return false;
                return (n1.isWirelessEnabled() && n2.isWirelessEnabled()
                        && n1.distance(n2) < n1.getCommunicationRange());
            }
        });
        topology.setMessageEngine(new DelayMessageEngine(topology, 10));
        deploySensors(topology);
        topology.addNode(50, 500, new Canadair());
        topology.addNode(100, 500, new Canadair());
        topology.addNode(50, 50, new Lake());
        topology.setTimeUnit(30);
        topology.setDefaultNodeModel(Fire.class);
        new JViewer(topology);
        topology.start();
    }
}