Creating AI documentation + help

Discuss the new AI features ("NoAI") introduced into OpenTTD 0.7, allowing you to implement custom AIs, and the new Game Scripts available in OpenTTD 1.2 and higher.

Moderator: OpenTTD Developers

Post Reply
User avatar
Redirect Left
Tycoon
Tycoon
Posts: 7238
Joined: 22 Jan 2005 19:31
Location: Wakefield, West Yorkshire

Creating AI documentation + help

Post by Redirect Left »

I'm trying to build a simple AI. However i'm struggling, as the wiki page basically leaves you with a non functioning (loads but does nothing) info.nut and a main.nut. You are given the code for that, then told "here is the API, get reading".
It never tries to slowly build you up to an AI from scratch, suggesting to copy others (with sutiable licences) - even though that'll be totally overwhelming for the majority of people looking at .nut code for the first time in such a huge amount.

the API page (this) and the page linking to it on the wiki make no note of what these versions correspond to, whether one is better for gamescripts or not, whether one is more geared towards AI, or whether I should simply use the highest number and thus assumably most recent.

So i'm basically clueless, as this seems very different to the only other languages i use frequently, PHP & Pawn.

I've tried looking at some other AIs, but they're way too complex to really start with and hard to learn from, too much in one go.

I was thinking of starting with a simple oil service, boat -> refinery, then trying to do train services with the goods produced to towns, but so far I can't even get a basic bit of code together, as it seems when the AI pages were wrote, they gave up halfway and left you with the API that isn't very layman friendly instead of taking you through it, I'm trying to add to the seemingly lack of AI developers, but the tools to do so are not noob friendly.
I currently just have the basic info/main.nut files the tutorial does, and totally lost really already.
Image
Need some good tested AI? - Unofficial AI Tester, list of good stuff & thread is here.
User avatar
Sylf
President
President
Posts: 957
Joined: 23 Nov 2010 21:25
Location: ::1

Re: Creating AI documentation + help

Post by Sylf »

I will take a crack at this. I make no guarantee at this point that I will produce a working AI at this point - I've written a very simple game script (so I've touched a bit of squirrel script), but never written an AI before.
I'm going to see if I can connect at least 1 oil rig to a refinery using ships.

Here is my assumption.
* The map is in temperate climate
* The map already has oil rigs (fresh temperate map has no oil rigs, so I've prepared a random temperate scenario for testing purpose)
* All refineries on the maps are close enough to the water, so they can be serviced by ships.

Here is my plan.
1. Find all oil rigs on the map
2. Find the oil rig that is producing most oil
3. Find the oil refinery that is closest to the oil rig from step 2
4. If the distance between the rig and refinery is less than 30, that's too close. Find the next closest refinery
5. Place a port next to the refinery
6. Place buoys between the refinery and the rig
7. Place a ship yard close to the route
8. Purchase ships, and make them work
9. If I still have enough motivation left to keep going, then work on connecting the rest of oil rigs to the refinery from the steps above.

I don't have any code written at this point.
I will make posts little by little, as I make some progress, over next several days.
If all goes well, someone can summarize my adventure, and submit the tutorial to the wiki.
Attachments
Scenario.scn
This is the scenario I'll be using while writing this test AI.
(109.52 KiB) Downloaded 101 times
Last edited by Sylf on 26 Aug 2017 03:35, edited 1 time in total.
User avatar
Sylf
President
President
Posts: 957
Joined: 23 Nov 2010 21:25
Location: ::1

Re: Creating AI documentation + help

Post by Sylf »

First thing first. Since my initial goal is to work with oil rigs, I want to get the list of all oil rigs on the map.
Oil rigs has 2 characteristics I can use within my script:
1. They produce oil
2. They have docks

So, I start out by finding the oil cargo within the game

Code: Select all

  local cargoes = AICargoList();
  local oil;
  foreach (cargo, value in cargoes) {
    if( AICargo.GetCargoLabel(cargo) == "OIL_") {
      oil = cargo;
      break;
    }
  }
Next, I find all industries that produce oil

Code: Select all

  local oilrigs = AIIndustryList_CargoProducing(oil);
I take that list, and find all industries in that list that has a dock

Code: Select all

  oilrigs.Valuate(AIIndustry.HasDock);
  oilrigs.KeepValue(1);
Next, I find the last month production, and select the rig that produced the most.

Code: Select all

  oilrigs.Valuate(AIIndustry.GetLastMonthProduction, oil);
  local myRig = oilrigs.Begin();
Let's mark this industry, so we get some visuals.

Code: Select all

  AISign.BuildSign(AIIndustry.GetLocation(myRig), "origin");
==============================================================

Now we move on to finding which refinery we'll want to use. The step-by-step concept is very similar to above.

Code: Select all

  local refineries = AIIndustryList_CargoAccepting(oil);
  refineries.Valuate(AIIndustry.GetDistanceManhattanToTile, AIIndustry.GetLocation(myRig));
  refineries.Sort(AIList.SORT_BY_VALUE, true);
  local myRef = refineries.Begin();
  AISign.BuildSign(AIIndustry.GetLocation(myRef), "destination");
==============================================================

Now, before I wrap up for the day, I want to see if I can build anything. For now, I'm going to hard code a tile location, and build a dock there.

Code: Select all

  AILog.Info(AITile.GetSlope(0x418E));  // 0x418E comes from Land Area Information tool within the game
  AILog.Info(AITile.IsCoastTile(0x418E));
  AIMarine.BuildDock(0x418E, AIStation.STATION_NEW);
There. We've identified a producing industry we want to use, and a processing industry we want to use. And we built a dock.
Tomorrow, I'll find a way to let the script determine the nearby coast tile where we can build a dock.
Right now, the main.nut looks like this: https://paste.openttdcoop.org/pfif4lzeo
User avatar
Zuu
OpenTTD Developer
OpenTTD Developer
Posts: 4553
Joined: 09 Jun 2003 18:21
Location: /home/sweden

Re: Creating AI documentation + help

Post by Zuu »

Redirect Left wrote:I'm trying to build a simple AI. However i'm struggling, as the wiki page basically leaves you with a non functioning (loads but does nothing) info.nut and a main.nut. You are given the code for that, then told "here is the API, get reading".
It never tries to slowly build you up to an AI from scratch, suggesting to copy others (with sutiable licences) - even though that'll be totally overwhelming for the majority of people looking at .nut code for the first time in such a huge amount.
You are right that the docs is mainly written on a level that they make sense to people who have learned to program already using some other language/platform, and then come to OpenTTD AI/GS and Squirrel 2.0. It doesn't mean it is impossible to learn to program while also writing an AI, but the debugging facilities is not on the same level as more mainstream languages. Because it takes time and resources to provide that and our community is much smaller than PHP for example. What we do have to offer however is the possibility to write code that control AI/GS in OpenTTD that may keep you motivated. And motivation is very important in order to succeed in programming where problems come in all different shapes and there is not always a guide that fit your case.

I hope I don't sound to negative, but my point is that you are right that we don't have a full guide on the level you may want, but either you have to write it yourself (its a wiki) or live with that there is no such guide and use the forum and IRC to get help.

I do recommend though to activate ai_developer mode as it will provide you with some extra debugging facilities that I and others have added to the game for some interactive debugging. Also, there are some useful wiki articles with pitfalls and things we've learned over the years of using squirrel.
Redirect Left wrote:the API page (this) and the page linking to it on the wiki make no note of what these versions correspond to, whether one is better for gamescripts or not, whether one is more geared towards AI, or whether I should simply use the highest number and thus assumably most recent.
https://noai.openttd.org/docs/ This one is for AI API
https://nogo.openttd.org/docs/ This one is for GS API

The version numbers is the corresponding OpenTTD Version that the API docs correspond to. In general you can assume that people have the last stable and code for that. Or if you want the latest features, use trunk. This will eventually become the next stable. What you pick depends of course what trunk has to offer for new things but also if you write it only for yourself or if you think it is important that you can share it with a large player base.
My OpenTTD contributions (AIs, Game Scripts, patches, OpenTTD Auto Updater, and some sprites)
Junctioneer (a traffic intersection simulator)
User avatar
Sylf
President
President
Posts: 957
Joined: 23 Nov 2010 21:25
Location: ::1

Re: Creating AI documentation + help

Post by Sylf »

OK. Today, I figured out how to build a station the proper way.
Also, I built buoys between the oil rig and the refinery.

Yesterday, I built a dock using a hard-coded tile location.
After reading through some of the API pages, I found this nifty class.

Code: Select all

  local accepting = AITileList_IndustryAccepting(industry, 4);
Of course, I can only build docks on coastal tiles, so let's see if there are any within this set of tiles.

Code: Select all

  accepting.Valuate(AITile.IsCoastTile);
  accepting.KeepValue(1);
Now the "accepting" is a list of coastal tiles that accept oil. I'm going to now iterate through these tiles, attempting to build a dock on that tile. I keep repeating until I succeed.

Code: Select all

  foreach(myTile, value in accepting) {
    local slope = AITile.GetSlope(myTile);
    // Keep trying to build a dock until it succeeds
    if (AIMarine.BuildDock(myTile, AIStation.STATION_NEW)) {
      dockLocation = myTile;
      break;
    }
  }
Save, and run the AI. It will build the dock right next to the refinery! Hooray!
==============================================================
Now that we have a real dock, it's time to start building buoys.
First, I'm going to find the distance in terms of X-Y coordinates.

Code: Select all

  local rigStation = AIIndustry.GetDockLocation(myRig);
  local distance = AIMap.DistanceMax(dockLocation, rigStation);
  AILog.Info("Two stations are " + distance + " tiles apart.");

  local xDistance = AIMap.GetTileX(rigStation) - AIMap.GetTileX(dockLocation);
  local yDistance = AIMap.GetTileY(rigStation) - AIMap.GetTileY(dockLocation);
  AILog.Info("X distance: " + xDistance);
  AILog.Info("Y distance: " + yDistance);
I want to build a buoy about every 20 tiles, as directed on this wiki page.

Code: Select all

  local numSegments = distance / 20;
  local xSegSize = xDistance / numSegments;
  local ySegSize = yDistance / numSegments;
  AILog.Info("X segment size: " + xSegSize);
  AILog.Info("Y segment size: " + ySegSize);
Now that we know how many buoys we need to build, and how far apart they should be in X and Y directions, we build the buoys!

Code: Select all

  local buoys = AIList();
  local xLoc = AIMap.GetTileX(dockLocation);
  local yLoc = AIMap.GetTileY(dockLocation);
  for (local i = 0; i < numSegments; i++) {
    xLoc = xLoc + xSegSize;
    yLoc = yLoc + ySegSize;
    local tile = AIMap.GetTileIndex(xLoc, yLoc);
    if (AIMarine.BuildBuoy(tile)) {
      buoys.AddItem(AIWaypoint.GetWaypointID(tile);, 0);
    }
  }
==============================================================
This code is now getting somewhat lengthy. I'm going to break some of the code to separate functions.
After the refactoring, the code now looks like this.
It works great with the given scenario I'm using! If there are other industries or land on the tile the script wanted to build a buoy, it would fail, and just move on. In a real AI, you would want to expand BuildBuoy() function, so it will keep attempting to build a buoy until it succeeds. And that also means that you will not want to assume that numSegments = distance / 20; logic isn't the best one to use, if you need to go around a massive peninsula to reach the destination. But I won't address that issue in this tutorial to keep the code absolutely simple.
Tomorrow, I'm going to work on buying some ships, and give them orders.
User avatar
Sylf
President
President
Posts: 957
Joined: 23 Nov 2010 21:25
Location: ::1

Re: Creating AI documentation + help

Post by Sylf »

Now that we have the docks and buoys built, it's time to build ships.
Of course, we must build a ship yard (depot) first.
We will build one close to the target oil rig. Here, we're going to use a similar logic we used for building the dock.

Code: Select all

  local depot = this.BuildDepot(AIIndustry.GetLocation(myRig));
.
.
function MyTest::BuildDepot(tile) {
  local xLoc = AIMap.GetTileX(tile);
  local yLoc = AIMap.GetTileY(tile);
  local myDock = false;

  xLoc = xLoc - 5;
  yLoc = yLoc - 5;
  if (xLoc < 0) xLoc = 0;
  if (yLoc < 0) yLoc = 0;

  for (local x = 0; x < 12; x++) {
    for (local y = 0; y < 12; y++) {
      local tile1 = AIMap.GetTileIndex(xLoc+x, yLoc+y);
      local tile2 = AIMap.GetTileIndex(xLoc+x+1, yLoc+y);
      if (AIMarine.BuildWaterDepot(tile1, tile2)) {
        myDock = tile1;
        break;
      }
    }
    if (myDock) {
      break;
    }
  }
  return myDock;
}
==============================================================
Now we have a dock. We will now build our first ship.

Code: Select all

  local ship = this.BuildShip(depot, myDock, myRig, buoys);
.
.
function MyTest::BuildShip(depot, dock, rig, buoys) {
  local engines = AIEngineList(AIVehicle.VT_WATER);
  engines.Valuate(AIEngine.CanRefitCargo, oil);
  engines.KeepValue(1);
  engines.Valuate(AIEngine.GetCapacity);
  local engine = engines.Begin();

  local ship = AIVehicle.BuildVehicle(depot, engine);

  AIVehicle.RefitVehicle(ship, oil);

  return ship;
}
==============================================================
We now need to give it orders. I'm going to add some more logic within the BuildShip() function to make this happen - this is why I passed in the dock (next to refinery) location, and list of all buoys to this function.
First, we will add an order to go to the rig, and full load any cargo.

Code: Select all

  local rigStation = AIIndustry.GetDockLocation(rig);
  AIOrder.InsertOrder(ship, 0, rigStation, AIOrder.OF_FULL_LOAD_ANY);
Next, we iterate through the list of buoys, and add them to the orders.

Code: Select all

  local counter = 1;
  foreach (buoy, value in buoys) {
    local location = AIWaypoint.GetLocation(buoy);
    AIOrder.InsertOrder(ship, counter, location, AIOrder.OF_NONE);
    counter++;
  }
And unload, and leave empty...

Code: Select all

  AIOrder.InsertOrder(ship, counter, dock, (AIOrder.OF_NO_LOAD + AIOrder.OF_UNLOAD));
  counter++;
And buoy orders for the return trip...

Code: Select all

  buoys.Sort(AIList.SORT_BY_ITEM, true);
  foreach (buoy, value in buoys) {
    local location = AIWaypoint.GetLocation(buoy);
    AIOrder.InsertOrder(ship, counter, location, AIOrder.OF_NONE);
    counter++;
  }
Finish this section by starting the ship!

Code: Select all

  AIVehicle.StartStopVehicle(ship);
==============================================================
Now, let's write a logic that will manage my fleet. I want to monitor the target oil rig, and make sure that it has enough ships to service it well. This is the while(true){} loop finally comes into play.

Code: Select all

  while (true) {
    this.ManageFleet();
    Sleep(72);
  }
And to implement the MagageFleet() function...

Code: Select all

function MyTest::ManageFleet() {
  foreach (rig, value in myRigs) {
    local dockLocation = AIIndustry.GetDockLocation(rig);
    local dock = AIStation.GetStationID(dockLocation);
    if (AIStation.HasCargoRating(dock, oil)) {
      // If a station rating falls below 57%, clone a ship
      if (AIStation.GetCargoRating(dock, oil) < 57) {
        this.CloneFleet(dock);
      }
    }
  }
}

function MyTest::CloneFleet(station) {
  local vehicles = AIVehicleList_Station(station);
  local vehicle = vehicles.Begin();
  local stationLocation = AIStation.GetLocation(station);

  local depots = AIDepotList(AITile.TRANSPORT_WATER);
  depots.Valuate(AITile.GetDistanceSquareToTile, stationLocation);
  depots.Sort(AIList.SORT_BY_VALUE, true);
  local depot = depots.Begin();

  local newShip = AIVehicle.CloneVehicle(depot, vehicle, true);
  AIVehicle.StartStopVehicle(newShip);
  Sleep(500);
}
==============================================================
At this point, my whole code looks like this.
You can run this AI, and watch the fleet grow over time.
Of course, this AI will not expand its network of transport after the start - this is what you can implement within the main while(true) loop as well.
Nor is the ManageFleet() function robust enough to deal with excess ships.
Nor is the script smart enough to deal with ships that cannoct reach the destination.
But there should be enough in this simple tutorial that gives you some ideas on how to get started with your own AI.
HGus
Engineer
Engineer
Posts: 121
Joined: 12 May 2013 22:28
Location: Argentina

Re: Creating AI documentation + help

Post by HGus »

Thank you very much Sylf :)
Post Reply

Return to “OpenTTD AIs and Game Scripts”

Who is online

Users browsing this forum: No registered users and 8 guests