summaryrefslogtreecommitdiff
path: root/bin/ai/wrightai
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ai/wrightai')
-rw-r--r--bin/ai/wrightai/info.nut15
-rw-r--r--bin/ai/wrightai/main.nut387
2 files changed, 402 insertions, 0 deletions
diff --git a/bin/ai/wrightai/info.nut b/bin/ai/wrightai/info.nut
new file mode 100644
index 000000000..3cd7cfa90
--- /dev/null
+++ b/bin/ai/wrightai/info.nut
@@ -0,0 +1,15 @@
+/* $Id$ */
+
+class WrightAI extends AIInfo {
+ function GetAuthor() { return "OpenTTD NoAI Developers Team"; }
+ function GetName() { return "WrightAI"; }
+ function GetDescription() { return "A simple AI that tries to beat you with only aircrafts"; }
+ function GetVersion() { return 2; }
+ function GetDate() { return "2008-02-24"; }
+ function CreateInstance() { return "WrightAI"; }
+ function GetSettings() {
+ AddSetting({name = "min_town_size", description = "The minimal size of towns to work on", min_value = 100, max_value = 1000, easy_value = 500, medium_value = 400, hard_value = 300, custom_value = 500, flags = 0});
+ }
+}
+
+RegisterAI(WrightAI());
diff --git a/bin/ai/wrightai/main.nut b/bin/ai/wrightai/main.nut
new file mode 100644
index 000000000..53b387b2e
--- /dev/null
+++ b/bin/ai/wrightai/main.nut
@@ -0,0 +1,387 @@
+/* $Id$ */
+
+class WrightAI extends AIController {
+ name = null;
+ towns_used = null;
+ route_1 = null;
+ route_2 = null;
+ distance_of_route = {};
+ vehicle_to_depot = {};
+ delay_build_airport_route = 1000;
+ passenger_cargo_id = -1;
+
+ function Start();
+
+ constructor() {
+ this.towns_used = AIList();
+ this.route_1 = AIList();
+ this.route_2 = AIList();
+
+ local list = AICargoList();
+ for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
+ if (AICargo.HasCargoClass(i, AICargo.CC_PASSENGERS)) {
+ this.passenger_cargo_id = i;
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Check if we have enough money (via loan and on bank).
+ */
+function WrightAI::HasMoney(money)
+{
+ if (AICompany.GetBankBalance(AICompany.MY_COMPANY) + (AICompany.GetMaxLoanAmount() - AICompany.GetLoanAmount()) > money) return true;
+ return false;
+}
+
+/**
+ * Get the amount of money requested, loan if needed.
+ */
+function WrightAI::GetMoney(money)
+{
+ if (!this.HasMoney(money)) return;
+ if (AICompany.GetBankBalance(AICompany.MY_COMPANY) > money) return;
+
+ local loan = money - AICompany.GetBankBalance(AICompany.MY_COMPANY) + AICompany.GetLoanInterval() + AICompany.GetLoanAmount();
+ loan = loan - loan % AICompany.GetLoanInterval();
+ AILog.Info("Need a loan to get " + money + ": " + loan);
+ AICompany.SetLoanAmount(loan);
+}
+
+/**
+ * Build an airport route. Find 2 cities that are big enough and try to build airport in both cities.
+ * Then we can build an aircraft and make some money.
+ */
+function WrightAI::BuildAirportRoute()
+{
+ local airport_type = (AIAirport.AirportAvailable(AIAirport.AT_SMALL) ? AIAirport.AT_SMALL : AIAirport.AT_LARGE);
+
+ /* Get enough money to work with */
+ this.GetMoney(150000);
+
+ AILog.Info("Trying to build an airport route");
+
+ local tile_1 = this.FindSuitableAirportSpot(airport_type, 0);
+ if (tile_1 < 0) return -1;
+ local tile_2 = this.FindSuitableAirportSpot(airport_type, tile_1);
+ if (tile_2 < 0) {
+ this.towns_used.RemoveValue(tile_1);
+ return -2;
+ }
+
+ /* Build the airports for real */
+ if (!AIAirport.BuildAirport(tile_1, airport_type, true)) {
+ AILog.Error("Although the testing told us we could build 2 airports, it still failed on the first airport at tile " + tile_1 + ".");
+ this.towns_used.RemoveValue(tile_1);
+ this.towns_used.RemoveValue(tile_2);
+ return -3;
+ }
+ if (!AIAirport.BuildAirport(tile_2, airport_type, true)) {
+ AILog.Error("Although the testing told us we could build 2 airports, it still failed on the second airport at tile " + tile_2 + ".");
+ AIAirport.RemoveAirport(tile_1);
+ this.towns_used.RemoveValue(tile_1);
+ this.towns_used.RemoveValue(tile_2);
+ return -4;
+ }
+
+ local ret = this.BuildAircraft(tile_1, tile_2);
+ if (ret < 0) {
+ AIAirport.RemoveAirport(tile_1);
+ AIAirport.RemoveAirport(tile_2);
+ this.towns_used.RemoveValue(tile_1);
+ this.towns_used.RemoveValue(tile_2);
+ return ret;
+ }
+
+ AILog.Info("Done building a route");
+ return ret;
+}
+
+/**
+ * Build an aircraft with orders from tile_1 to tile_2.
+ * The best available aircraft of that time will be bought.
+ */
+function WrightAI::BuildAircraft(tile_1, tile_2)
+{
+ /* Build an aircraft */
+ local hangar = AIAirport.GetHangarOfAirport(tile_1);
+ local engine = null;
+
+ local engine_list = AIEngineList(AIVehicle.VEHICLE_AIR);
+
+ /* When bank balance < 300000, buy cheaper planes */
+ local balance = AICompany.GetBankBalance(AICompany.MY_COMPANY);
+ engine_list.Valuate(AIEngine.GetPrice);
+ engine_list.KeepBelowValue(balance < 300000 ? 50000 : (balance < 1000000 ? 300000 : 1000000));
+
+ engine_list.Valuate(AIEngine.GetCargoType);
+ engine_list.KeepValue(this.passenger_cargo_id);
+
+ engine_list.Valuate(AIEngine.GetCapacity);
+ engine_list.KeepTop(1);
+
+ engine = engine_list.Begin();
+
+ if (!AIEngine.IsValidEngine(engine)) {
+ AILog.Error("Couldn't find a suitable engine");
+ return -5;
+ }
+ local vehicle = AIVehicle.BuildVehicle(hangar, engine);
+ if (!AIVehicle.IsValidVehicle(vehicle)) {
+ AILog.Error("Couldn't build the aircraft");
+ return -6;
+ }
+ /* Send him on his way */
+ AIOrder.AppendOrder(vehicle, tile_1, AIOrder.AIOF_NONE);
+ AIOrder.AppendOrder(vehicle, tile_2, AIOrder.AIOF_NONE);
+ AIVehicle.StartStopVehicle(vehicle);
+ this.distance_of_route.rawset(vehicle, AIMap.DistanceManhattan(tile_1, tile_2));
+ this.route_1.AddItem(vehicle, tile_1);
+ this.route_2.AddItem(vehicle, tile_2);
+
+ AILog.Info("Done building an aircraft");
+
+ return 0;
+}
+
+/**
+ * Find a suitable spot for an airport, walking all towns hoping to find one.
+ * When a town is used, it is marked as such and not re-used.
+ */
+function WrightAI::FindSuitableAirportSpot(airport_type, center_tile)
+{
+ local airport_x, airport_y, airport_rad;
+
+ airport_x = AIAirport.GetAirportWidth(airport_type);
+ airport_y = AIAirport.GetAirportHeight(airport_type);
+ airport_rad = AIAirport.GetAirportCoverageRadius(airport_type);
+
+ local town_list = AITownList();
+ /* Remove all the towns we already used */
+ town_list.RemoveList(this.towns_used);
+
+ town_list.Valuate(AITown.GetPopulation);
+ town_list.KeepAboveValue(GetSetting("min_town_size"));
+ /* Keep the best 10, if we can't find 2 stations in there, just leave it anyway */
+ town_list.KeepTop(10);
+ town_list.Valuate(AIBase.RandItem);
+
+ /* Now find 2 suitable towns */
+ for (local town = town_list.Begin(); town_list.HasNext(); town = town_list.Next()) {
+ /* Don't make this a CPU hog */
+ Sleep(1);
+
+ local tile = AITown.GetLocation(town);
+
+ /* Create a 30x30 grid around the core of the town and see if we can find a spot for a small airport */
+ local list = AITileList();
+ /* XXX -- We assume we are more than 15 tiles away from the border! */
+ list.AddRectangle(tile - AIMap.GetTileIndex(15, 15), tile + AIMap.GetTileIndex(15, 15));
+ list.Valuate(AITile.IsBuildableRectangle, airport_x, airport_y);
+ list.KeepValue(1);
+ if (center_tile != 0) {
+ /* If we have a tile defined, we don't want to be within 25 tiles of this tile */
+ list.Valuate(AITile.GetDistanceSquareToTile, center_tile);
+ list.KeepAboveValue(625);
+ }
+ /* Sort on acceptance, remove places that don't have acceptance */
+ list.Valuate(AITile.GetCargoAcceptance, this.passenger_cargo_id, airport_x, airport_y, airport_rad);
+ list.RemoveBelowValue(10);
+
+ /* Couldn't find a suitable place for this town, skip to the next */
+ if (list.Count() == 0) continue;
+ /* Walk all the tiles and see if we can build the airport at all */
+ {
+ local test = AITestMode();
+ local good_tile = 0;
+
+ for (tile = list.Begin(); list.HasNext(); tile = list.Next()) {
+ Sleep(1);
+ if (!AIAirport.BuildAirport(tile, airport_type, true)) continue;
+ good_tile = tile;
+ break;
+ }
+
+ /* Did we found a place to build the airport on? */
+ if (good_tile == 0) continue;
+ }
+
+ AILog.Info("Found a good spot for an airport in town " + town + " at tile " + tile);
+
+ /* Make the town as used, so we don't use it again */
+ this.towns_used.AddItem(town, tile);
+
+ return tile;
+ }
+
+ AILog.Info("Couldn't find a suitable town to build an airport in");
+ return -1;
+}
+
+function WrightAI::ManageAirRoutes()
+{
+ local list = AIVehicleList();
+ list.Valuate(AIVehicle.GetAge);
+ /* Give the plane at least 2 years to make a difference */
+ list.KeepAboveValue(365 * 2);
+ list.Valuate(AIVehicle.GetProfitLastYear);
+
+ for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
+ local profit = list.GetValue(i);
+ /* Profit last year and this year bad? Let's sell the vehicle */
+ if (profit < 10000 && AIVehicle.GetProfitThisYear(i) < 10000) {
+ /* Send the vehicle to depot if we didn't do so yet */
+ if (!vehicle_to_depot.rawin(i) || vehicle_to_depot.rawget(i) != true) {
+ AILog.Info("Sending " + i + " to depot as profit is: " + profit + " / " + AIVehicle.GetProfitThisYear(i));
+ AIVehicle.SendVehicleToDepot(i);
+ vehicle_to_depot.rawset(i, true);
+ }
+ }
+ /* Try to sell it over and over till it really is in the depot */
+ if (vehicle_to_depot.rawin(i) && vehicle_to_depot.rawget(i) == true) {
+ if (AIVehicle.SellVehicle(i)) {
+ AILog.Info("Selling " + i + " as it finally is in a depot.");
+ /* Check if we are the last one serving those airports; else sell the airports */
+ local list2 = AIVehicleList_Station(AIStation.GetStationID(this.route_1.GetValue(i)));
+ if (list2.Count() == 0) this.SellAirports(i);
+ vehicle_to_depot.rawdelete(i);
+ }
+ }
+ }
+
+ /* Don't try to add planes when we are short on cash */
+ if (!this.HasMoney(50000)) return;
+
+ list = AIStationList(AIStation.STATION_AIRPORT);
+ list.Valuate(AIStation.GetCargoWaiting, this.passenger_cargo_id);
+ list.KeepAboveValue(250);
+
+ for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
+ local list2 = AIVehicleList_Station(i);
+ /* No vehicles going to this station, abort and sell */
+ if (list2.Count() == 0) {
+ this.SellAirports(i);
+ continue;
+ };
+
+ /* Find the first vehicle that is going to this station */
+ local v = list2.Begin();
+ local dist = this.distance_of_route.rawget(v);
+
+ list2.Valuate(AIVehicle.GetAge);
+ list2.KeepBelowValue(dist);
+ /* Do not build a new vehicle if we bought a new one in the last DISTANCE days */
+ if (list2.Count() != 0) continue;
+
+ AILog.Info("Station " + i + " (" + AIStation.GetLocation(i) + ") has too many cargo, adding a new vehicle for the route.");
+
+ /* Make sure we have enough money */
+ this.GetMoney(50000);
+
+ return this.BuildAircraft(this.route_1.GetValue(v), this.route_2.GetValue(v));
+ }
+}
+
+/**
+ * Sells the airports from route index i
+ * Removes towns from towns_used list too
+ */
+function WrightAI::SellAirports(i) {
+ /* Remove the airports */
+ AILog.Info("Removing airports as nobody serves them anymore.");
+ AIAirport.RemoveAirport(this.route_1.GetValue(i));
+ AIAirport.RemoveAirport(this.route_2.GetValue(i));
+ /* Free the towns_used entries */
+ this.towns_used.RemoveValue(this.route_1.GetValue(i));
+ this.towns_used.RemoveValue(this.route_2.GetValue(i));
+ /* Remove the route */
+ this.route_1.RemoveItem(i);
+ this.route_2.RemoveItem(i);
+}
+
+function WrightAI::HandleEvents()
+{
+ while (AIEventController.IsEventWaiting()) {
+ local e = AIEventController.GetNextEvent();
+ switch (e.GetEventType()) {
+ case AIEvent.AI_ET_VEHICLE_CRASHED: {
+ local ec = AIEventVehicleCrashed.Convert(e);
+ local v = ec.GetVehicleID();
+ AILog.Info("We have a crashed vehicle (" + v + "), buying a new one as replacement");
+ this.BuildAircraft(this.route_1.GetValue(v), this.route_2.GetValue(v));
+ this.route_1.RemoveItem(v);
+ this.route_2.RemoveItem(v);
+ } break;
+
+ default:
+ break;
+ }
+ }
+}
+
+function WrightAI::Start()
+{
+ if (this.passenger_cargo_id == -1) {
+ AILog.Error("WrightAI could not find the passenger cargo");
+ return;
+ }
+
+ /* Give the boy a name */
+ if (!AICompany.SetName("WrightAI")) {
+ local i = 2;
+ while (!AICompany.SetName("WrightAI #" + i)) {
+ i++;
+ }
+ }
+ this.name = AICompany.GetName(AICompany.MY_COMPANY);
+ /* Say hello to the user */
+ AILog.Info("Welcome to WrightAI. I will be building airports all day long.");
+ AILog.Info(" - Minimum Town Size: " + GetSetting("min_town_size"));
+
+ /* We start with almost no loan, and we take a loan when we want to build something */
+ AICompany.SetLoanAmount(AICompany.GetLoanInterval());
+
+ /* We need our local ticker, as GetTick() will skip ticks */
+ local ticker = 0;
+ /* Determine time we may sleep */
+ local sleepingtime = 100;
+ if (this.delay_build_airport_route < sleepingtime)
+ sleepingtime = this.delay_build_airport_route;
+
+ /* Let's go on for ever */
+ while (true) {
+ /* Once in a while, with enough money, try to build something */
+ if ((ticker % this.delay_build_airport_route == 0 || ticker == 0) && this.HasMoney(100000)) {
+ local ret = this.BuildAirportRoute();
+ if (ret == -1 && ticker != 0) {
+ /* No more route found, delay even more before trying to find an other */
+ this.delay_build_airport_route = 10000;
+ }
+ else if (ret < 0 && ticker == 0) {
+ /* The AI failed to build a first airport and is deemed */
+ AICompany.SetName("Failed " + this.name);
+ AILog.Error("Failed to build first airport route, now giving up building. Repaying loan. Have a nice day!");
+ AICompany.SetLoanAmount(0);
+ return;
+ }
+ }
+ /* Manage the routes once in a while */
+ if (ticker % 2000 == 0) {
+ this.ManageAirRoutes();
+ }
+ /* Try to get ride of our loan once in a while */
+ if (ticker % 5000 == 0) {
+ AICompany.SetLoanAmount(0);
+ }
+ /* Check for events once in a while */
+ if (ticker % 100 == 0) {
+ this.HandleEvents();
+ }
+ /* Make sure we do not create infinite loops */
+ Sleep(sleepingtime);
+ ticker += sleepingtime;
+ }
+}
+