Détermination de chemin routier
Framework NonIA

Page principale NonIA

Développement

Jalons de développement
Suggestion de changements de l'API

Programmation de l'IA

Documentation API
Introduction
Fichier info.nut
Fichier main.nut
Principes de base
Détermination de chemin routier
Détermination de chemin ferroviaire
Sauver / Charger des données
Choses à savoir
Squirrel
Page principale
Erreurs courantes
Listes
Traiter les erreurs OTTD
Tramways
Bibliothèques de l'IA
Forum
FAQ du forum
Comportement de l'IA

IAs

TestAI
WrightAI
Convoy
Voir le forum
Voir BaNaNaS
Tous les articles de la catégorie NonIA

Contents

Avantages

Si vous avez essayez de construire par vous-mêmes une détermination de chemin routier, vous avez remarqué qu'il était difficile de le faire correctement. Principalement, les angles des fondations sont un problème. Utiliser la détermination de chemin depuis la bibliothèque résoudra ce problème pour vous, afin que vous n'ayez pas à faire le travail vous-mêmes.

Initialisation

Comme avec ce genre de bibliothèque, avant de l'utiliser, vous devez l'importer. Vous pouvez le faire en plaçant la ligne suivante au début de votre code.

import("pathfinder.road", "RoadPathFinder", 3);

Cela importe la détermination de chemin routier version 3 et l'appelle RoadPathFinder. Vous pouvez maintenant l'instancier comme toute autre classe:

local pathfinder = RoadPathFinder();

Maintenant, fixez pour quel type de route vous voulez trouver un chemin:

AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);

Avant de commencer à rechercher un chemin, vous voudrez peut-être positionner quelques paramètres. Ceux qui sont disponibles sont:

Nom Valeur par défaut Signification
cost.max_cost 2000000000 Le coût maximal pour une route.
cost.tile 100 Le coût pour une seule case.
cost.no_existing_road 40 Le coût ajouté à cost.tile si la route n'existe pas encore.
cost.turn 100 Le coût ajouté à cost.tile si la direction est modifiée.
cost.slope 200 Le coût supplémentaire si une case de route est en pente.
cost.bridge_per_tile 150 Le coût par case d'un nouveau pont; il est ajouté à cost.tile.
cost.tunnel_per_tile 120 Le coût par case d'un nouveau tunnel; il est ajouté à cost.tile.
cost.coast 20 Le coût supplémentaire d'une case côtière.
cost.max_bridge_length 10 La longueur maximale d'un pont qui peut être construite. Remarquez que tous les ponts existants seront explorés, quelle que soit leur longueur.
cost.max_tunnel_length 20 La longueur maximale d'un tunnel qui peut être construite. Remarquez que tous les tunnels existants seront explorés, quelle que soit leur longueur.

Pour positionner un paramètre, utilisez simplement:

pathfinder.cost.tile = 100;

Planifier un trajet

Donc, maintenant que nous savons comment créer une instance de détermination de chemin et paramétrer les fonctions de coût, il est temps de mettre cette connaissance en pratique. Planifions notre premier trajet. Tout d'abord, nous avons besoin d'une (ou plusieurs) case(s) de ressource et d'une (ou plusieurs) case(s) cible. Pour le moment, supposons que nous les connaissons déjà, car expliquer comment les obtenir dépasse le cadre de cette page. Disons donc, par exemple, que nous voulons construire une route entre case_a et case_b. Tout d'abord, nous appelons InitializePath sur notre objet de détermination de chemin:

pathfinder.InitializePath([case_a], [case_b]);

La détermination de chemin attend un tableau de cases de départ et un tableau de cases de destination; comme nous n'avons qu'une case de départ et une de destination, nous créons simplement deux tableaux avec un seul élément pour chacun. Maintenant, la détermination de chemin est prête à trouver un chemin.

local path = false;
while (path == false) {
  path = pathfinder.FindPath(100);
  AIController.Sleep(1);
}

Le paramètre que FindPath accepte est le nombre d'itérations qu'il doit exécuter avant de répondre. 100 convient bien. Ne donnez pas une trop grande valeur, car vous noterez des ralentissements dans OpenTTD. La ligne AIController.Sleep(1); est vraiment nécessaire. FindPath renvoie false s'il n'a pas terminé avec la détermination de chemin. Une fois fini, il renvoie soit le chemin trouvé, ou null pour indiquer qu'aucune route n'existe.

Construire la route planifiée

Supposons que la détermination de chemin a renvoyé un trajet, comment commencer à le construire? Pour démarrer, il est intéressant de savoir que FindPath renvoie une instance de la classe AyStar.Path. Pour voir la documentation, ouvrez bin/ai/library/graph/aystar/main.nut. Sommairement, il comporte trois fonctions: GetParent(), GetCost() et GetTile(). GetCost() N'est pas utile pour le moment, donc nous n'utiliserons que GetParent() et GetTile(). Pour construire une route trouvée par la détermination de chemin, nous appelons path.GetParent() jusqu'à ce qu'il renvoie null. Comme la détermination de chemin renvoie aussi des ponts et des tunnels, nous devons vérifier la distance entre le nœud actuel et le précédent. Si cette distance est supérieure à 1, nous devons construire un pont ou un tunnel.

while (path != null) {
  local par = path.GetParent();
  if (par != null) {
    local last_node = path.GetTile();
    if (AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) == 1 ) {
      if (!AIRoad.BuildRoad(path.GetTile(), par.GetTile())) {
        /* Une erreur est survenue lors de la construction d'une portion de route. À FAIRE: la gérer.
         * Notez que ce peut aussi être le cas si la route était déjà construite. */
      }
    } else {
      /* Construire un pont ou un tunnel. */
      if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) {
        /* Si c'était une case de route, la démolit d'abord. À faire pour contourner des bouts de route utilisés. */
        if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile());
        if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) {
          if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) {
            /* Une erreur s'est produite lors de la construction d'un tunnel. À FAIRE: la gérer. */
          }
        } else {
          local bridge_list = AIBridgeList_Length(AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1);
          bridge_list.Valuate(AIBridge.GetMaxSpeed);
          bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, false);
          if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile())) {
            /* Une erreur s'est produite lors de la construction d'un pont. À FAIRE: la gérer. */
          }
        }
      }
    }
  }
  path = par;
}

Ce code boucle tout le long de la route. Pour chaque élément, il vérifie s'il est déjà connecté à la case précédente par route ou via un pont. S'il ne l'est pas, il tente de construire une route. Remarquez que dans une vraie IA, vous vérifierez probablement si AIRoad.BuildRoad renvoie true, et si non, vous relancerez la détermination de chemin.

Note: n'utilisez pas parent comme nom de variable pour GetParent, c'est un mot-clé, donnant des résultats étranges.

Une IA exemple

import("pathfinder.road", "RoadPathFinder", 3);

class SampleAI extends AIController {
}

function SampleAI::Start()
{
  /* Obtient une liste de toutes les villes de la carte. */
  local townlist = AITownList();

  /* Trie la liste par population, les plus importantes en premier. */
  townlist.Valuate(AITown.GetPopulation);
  townlist.Sort(AIAbstractList.SORT_BY_VALUE, false);

  /* Choisit les deux villes avec la plus grande population. */
  local townid_a = townlist.Begin();
  local townid_b = townlist.Next();

  /* Affiche le nom des villes que nous allons essayer de relier. */
  AILog.Info("En train de relier " + AITown.GetName(townid_a) + " à " + AITown.GetName(townid_b));

  /* Dit à OpenTTD que nous voulons construire une route normale (pas une voie de tramway). */
  AIRoad.SetCurrentRoadType(AIRoad.ROADTYPE_ROAD);

  /* Crée une instance de détermination de chemin. */
  local pathfinder = RoadPathFinder();

  /* Positionne le coût d'un virage très haut. */
  pathfinder.cost.turn = 5000;

  /* Donne les cases de départ et de destination à la détermination de chemin. */
  pathfinder.InitializePath([AITown.GetLocation(townid_a)], [AITown.GetLocation(townid_b)]);

  /* Tente de trouver un trajet. */
  local path = false;
  while (path == false) {
    path = pathfinder.FindPath(100);
    this.Sleep(1);
  }

  if (path == null) {
    /* Pas de trajet trouvé. */
    AILog.Error("pathfinder.FindPath a renvoyé null");
  }

  /* Si un chemin a été trouvé, construit une route le long de celui-ci. */
  while (path != null) {
    local par = path.GetParent();
    if (par != null) {
      local last_node = path.GetTile();
      if (AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) == 1 ) {
        if (!AIRoad.BuildRoad(path.GetTile(), par.GetTile())) {
          /* Une erreur s'est produite en construisant une portion de route. À FAIRE: la gérer. 
           * Remarquez que ce peut être aussi le cas quand la route est déjà construite. */
        }
      } else {
        /* Construit un pont ou un tunnel. */
        if (!AIBridge.IsBridgeTile(path.GetTile()) && !AITunnel.IsTunnelTile(path.GetTile())) {
          /* S'il y avait une case de route, la démolit d'abord. Faire ainsi contourne une portion de voie utilisée. */
          if (AIRoad.IsRoadTile(path.GetTile())) AITile.DemolishTile(path.GetTile());
          if (AITunnel.GetOtherTunnelEnd(path.GetTile()) == par.GetTile()) {
            if (!AITunnel.BuildTunnel(AIVehicle.VT_ROAD, path.GetTile())) {
              /* Une erreur est survenue en construisant un tunnel. À FAIRE: la traiter. */
            }
          } else {
            local bridge_list = AIBridgeList_Length(AIMap.DistanceManhattan(path.GetTile(), par.GetTile()) + 1);
            bridge_list.Valuate(AIBridge.GetMaxSpeed);
            bridge_list.Sort(AIAbstractList.SORT_BY_VALUE, false);
            if (!AIBridge.BuildBridge(AIVehicle.VT_ROAD, bridge_list.Begin(), path.GetTile(), par.GetTile())) {
              /* Une erreur est survenue en construisant un pont. À FAIRE: la traiter. */
            }
          }
        }
      }
    }
    path = par;
  }
  AILog.Info("Terminé");
}

Créez un info.nut pour cette IA et essayez-la. Faites varier les différents coûts, par exemple en mettant pathfinder.cost.no_existing_road = pathfinder.cost.max_cost. Maintenant, la détermination de chemin ne trouvera que des routes sur des routes déjà existantes. Ce n'est pas très utile si vous voulez construire une nouvelle liaison, mais ce peut être pratique si vous voulez vérifier si une liaison précédemment construite est toujours OK.