Gestion d’une gare cachée (3)

Remplissage et vidange

. Par : Jean-Luc. URL : https://www.locoduino.org/spip.php?article157

Dans « Gestion d’une gare cachée (2) » nous avons posé les bases d’une conception objet pour notre gare cachée. Cette conception est fondée sur un graphe où chaque nœud est un élément de voie. Nous avons développé un algorithme de parcours exhaustif de ce graphe pour trouver, tout en déterminant la position des aiguilles, une voie de la gare cachée. Toutefois, il nous manque un algorithme pour trouver une voie libre et pour trouver une voie occupée. Nous allons maintenant ajouter ces deux algorithmes.

Le premier algorithme que nous allons ajouter est très proche de celui que nous avons développé dans le précédent article. Seul le critère de sélection de la voie en gare change. Au lieu de chercher une voie possédant l’identifiant voulu, nous allons chercher une voie libre. Le second algorithme recherchera aléatoirement une voie occupée dans la gare. Il nous faut donc ajouter une variable membre booléenne dans la classe VoieGare que nous appellerons libre. Cette variable sera initialisée à true car nous supposons qu’au démarrage du système la gare cachée est entièrement vide. La classe VoieGare devient donc :

/*
 * Classe pour les voies de gare
 */
class VoieGare : public Voie
{
  private:
    bool mLibre;
    
  public:
    VoieGare(byte identifiant) : Voie(identifiant) { mLibre = true; }
    virtual bool creeCheminVers(byte identifiant);
};

Il nous reste maintenant à écrire nos deux algorithmes.

Trouver une voie libre

Nous allons non seulement la trouver mais établir au passage les positions des aiguilles qui permettent d’y amener un train. Tout d’abord, il faut ajouter une fonction membre virtuelle dans la classe de base. Cette fonction membre, creeCheminVersLibre retourne un identifiant de voie, donc un byte [1]. Elle retournera un identifiant de voie si une voie libre est trouvée et son chemin établi, et l’identifiant spécial AUCUNE_VOIE si toutes les voies sont occupées.

/*
 * Classe de base des éléments de voie
 * 
 * Fonctions virtuelles de recherche de chemin et identifiant
 */
class Voie
{
  private:
    byte mIdentifiant;
 
  public:
    Voie(byte identifiant) { mIdentifiant = identifiant; }
    byte idVoie() { return mIdentifiant; }
    virtual bool creeCheminVers(byte identifiant) = 0;
    virtual byte creeCheminVersLibre() = 0;
};

Pour une voie de la gare, creeCheminVersLibre va simplement retourner l’identifiant de la voie si cette voie est libre, ou AUCUNE_VOIE si cette voie est occupée, mais nous allons également en profiter pour afficher l’identifiant de la voie si elle est trouvée libre afin de tester le système et également de passer mLibre à false puisqu’il s’agit maintenant d’occuper cette voie.

byte VoieGare::creeCheminVersLibre()
{
  if (mLibre) {
    Serial.print(F("Occupation de : "));
    afficheVoie(idVoie());
    Serial.println();
    mLibre = false;
    return idVoie();
  }
  else {
    return AUCUNE_VOIE;
  }
}

Un aiguillage demandera aux deux voies qui lui sont connectées, droite et déviée, si elle est libre. On commencera par la connexion droite. Si une voie libre est trouvée, les aiguilles sont positionnées en conséquence. C’est très similaire à la fonction creeCheminVers.

byte Aiguillage::creeCheminVersLibre()
{
  byte voie;
  if (mVoieDroite != NULL && mVoieDeviee != NULL) {
    /* Uniquement si correctement connecte     */
    /* Cherche la destination cote voie droite */
    voie = mVoieDroite->creeCheminVersLibre();
    if (voie != AUCUNE_VOIE) {
      /* Destination trouvee cote droit */
      afficheVoie(idVoie());
      Serial.println(F(": Droit"));
      return voie;
    }
    else {
      /* Cherche la destination cote voie deviee */
      voie = mVoieDeviee->creeCheminVersLibre();
      if (voie != AUCUNE_VOIE) {
        /* destination trouvee cote devie */
        afficheVoie(idVoie());
        Serial.println(F(": Devie"));
        return voie;
      }
      else {
        /* destination non trouvee */
        return AUCUNE_VOIE;
      }
    }
  }
}

Enfin, la voie d’entrée se contente de demander à la voie qui lui est connectée si elle elle libre, la demande se propageant le long du graphe jusqu’à une voie de gare.

byte VoieExtreme::creeCheminVersLibre()
{
  if (mVoie != NULL) {
    return mVoie->creeCheminVersLibre();
  }
}

Nous pouvons maintenant tester notre programme. Le test est simple, on ajoute des trains dans la gare jusqu’à ce qu’elle soit pleine. Pour cela nous ajoutons une fonction de test qui affiche en clair si une voie libre est trouvée.

void testLibre()
{
  if (voieEntree.creeCheminVersLibre() != AUCUNE_VOIE)
    Serial.println(F("TROUVE"));
  else
    Serial.println(F("NON TROUVE"));
}

Il faut également compléter la fonction d’affichage du nom d’une voie en clair. Nous n’y avions mis que les aiguillages, il faut maintenant afficher le nom des voies de la gare.

    case VOIE_GARE_0:
       Serial.print("Voie gare 0"); break;
    case VOIE_GARE_1:
       Serial.print("Voie gare 1"); break;
    case VOIE_GARE_2:
       Serial.print("Voie gare 2"); break;
    case VOIE_GARE_3:
       Serial.print("Voie gare 3"); break;

Enfin nous ajoutons la séquence de test à la fin de setup

  Serial.println("***** TEST de chemin vers libre");
  testLibre();    
  testLibre();    
  testLibre();    
  testLibre();    
  testLibre();    

Comme notre gare a quatre voies, les quatre premiers appels à testLibre trouveront une voie et le dernier échouera. On a bien le résultat escompté :

***** TEST de chemin vers libre
Occupation de : Voie gare 0
Aiguillage E0: Droit
TROUVE
Occupation de : Voie gare 3
Aiguillage E2: Droit
Aiguillage E1: Droit
Aiguillage E0: Devie
TROUVE
Occupation de : Voie gare 2
Aiguillage E2: Devie
Aiguillage E1: Droit
Aiguillage E0: Devie
TROUVE
Occupation de : Voie gare 1
Aiguillage E1: Devie
Aiguillage E0: Devie
TROUVE
NON TROUVE

Sélectionner un train

Pour extraire un train de la gare, nous pourrions procéder de la même manière en inversant le critère de sélection. Au lieu de chercher une voie libre, nous chercherions une voie occupée. Mais, l’ordre de recherche d’une voie étant toujours le même, cette façon de faire conduirait à une circulation uniforme.

Au lieu de cela, nous allons sélectionner une voie de gare occupée aléatoirement. Il nous manque pour cela un moyen de savoir si une voie est occupée. En effet, la variable membre libre est privée. Nous ajoutons une fonction membre occupee à la classe VoieGare qui retourne true si la voie est occupée.

bool occupee() { return ! mLibre; }

Et nous écrivons une fonction qui sélectionne aléatoirement une voie occupée. Il faut tout d’abord recenser les voies occupées. Le plus simple est de parcourir chacune des 4 voies de la gare et, si elle est occupée, ajouter son identifiant dans un tableau. Appelons ce tableau voiesOccupees. Ensuite, connaissant le nombre de voies occupées, nous tirons un nombre aléatoire entre 0 et ce nombre - 1 afin de sélectionner la voie occupée. Pour en savoir plus sur l’aléatoire, vous pouvez lire l’article « Comment gérer l’aléatoire ? » [2]. Le plus simple est également que les voies de la gare soient dans un tableau, nous en profitons pour mettre le nombre de voies dans une constante :

const int TAILLE_GARE = 4;

VoieGare voieGare[TAILLE_GARE] = {
   VoieGare(VOIE_GARE_0),
   VoieGare(VOIE_GARE_1),
   VoieGare(VOIE_GARE_2),
   VoieGare(VOIE_GARE_3)
};

Il faut également faire attention au fait que la gare peut être entièrement vide. Dans ce cas, le tableau des voies occupées sera vide et nous serons dans l’incapacité de retourner un identifiant de voie. Pour cela, nous ajoutons un identifiant spécial nomme AUCUNE_VOIE. Nous retournerons cet identifiant si aucune voie n’est occupée. Voici cette fonction que nous appellerons selectionneTrain :

/*
 * Sélectionne aléatoirement un des trains des voies occupées
 */
byte selectionneTrain()
{
  byte voiesOccupees[TAILLE_GARE];
  byte indexVoieOccupee = 0;
  /* Parcoure les voies de la gare */
  for (byte indexVoieGare = 0; indexVoieGare < TAILLE_GARE; indexVoieGare++) {
    if (voieGare[indexVoieGare].occupee()) {
      /* Une voie occupee est trouvée, on ajoute son identifiant à notre tableau */
      voiesOccupees[indexVoieOccupee++] = voieGare[indexVoieGare].idVoie();
    }
  }
  if (indexVoieOccupee > 0) {
    /* Au moins une voie occupée a été trouvée */
    /* Tire au hasard une voie occupée */
    byte voie = random(0, indexVoieOccupee);
    /* et retourne la voie */
    return voiesOccupees[voie];   
  }
  else {
    /* la gare est vide ! */
    return AUCUNE_VOIE;
  }
}

Cette fonction retournant l’identifiant de la voie de gare sur laquelle le train stationne, il suffit ensuite d’appeler creeCheminVers(...) avec comme argument cet identifiant pour manœuvrer les aiguilles et extraire ce train de la gare. Il manque seulement une fonction membre de la classe VoieGare permettant de libérer une voie de la gare une fois que le train est parti. Ajoutons la.

void libere()  { mLibre = true; }

Et, enfin, une fonction permettant de parcourir les voies de la gare et de libérer celle dont l’identifiant est passé en argument. Appelons cette fonction libereVoieGare.

void libereVoieGare(byte identifiant)
{
  for (byte indexVoieGare = 0; indexVoieGare < TAILLE_GARE; indexVoieGare++) {
    if (voieGare[indexVoieGare].idVoie() == identifiant) {
      /* La voie d'indentifiant correspondant est trouvée */
      voieGare[indexVoieGare].libere();
      /* Comme on a trouvé, on sort de la boucle */
      break;
    }
  } 
}

Il reste à tester tout cela. À la suite du test de remplissage que nous avons fait ci-dessous, ajoutons un test de vidage aléatoire.

  randomSeed(getSeed());
  Serial.println("***** TEST de vidange de la gare");
  while (true)
  {
    byte identifiant = selectionneTrain();
    if (identifiant != AUCUNE_VOIE) {
      Serial.print("Selectionne le train de : ");
      afficheVoie(identifiant);
      Serial.println();
      voieSortie.creeCheminVers(identifiant);
      libereVoieGare(identifiant);
    }
    else {
      break;    
    }
  }
  Serial.println("TERMINE");

Voici une des sorties possibles de ce test, une des sorties car la vidange étant aléatoire, une autre séquence peut être obtenue.

Selectionne le train de : Voie gare 1
Aiguillage S2: Devie
Aiguillage S1: Droit
Aiguillage S0: Devie
Selectionne le train de : Voie gare 2
Aiguillage S1: Devie
Aiguillage S0: Devie
Selectionne le train de : Voie gare 0
Aiguillage S2: Droit
Aiguillage S1: Droit
Aiguillage S0: Devie
Selectionne le train de : Voie gare 3
Aiguillage S0: Droit
TERMINE

Voici le sketch complet avec tous les tests :

Sketch de gare cachée
Tous les objets et fonctions ainsi que le test.

Nous avons maintenant toutes les structures de données et toutes les fonctions pour gérer notre gare cachée. Notez que c’est directement adaptable à n’importe quelle taille de gare, il suffit de créer les objets correspondant aux éléments de voies, de les connecter et de mettre à jour la constante TAILLE_GARE ainsi que les tests.

Le prochain article traitera des capteurs et des actionneurs que nous allons utiliser.

[1Ceci nous limite à 256 identifiants de voie ce qui est suffisant pour une gare cachée.

[2D’ailleurs, nous intégrons la fonction getSeed() décrite à la fin de cet article.