(svn r10581) [NoAI] -Add: added WrightAI, and even though it is an aircraft only AI, it is the first NoAI that actually builds something useful and that is where the name came from (wright is archaic English for craftsman or builder) noai
authortruelight
Sun, 15 Jul 2007 13:24:06 +0000
branchnoai
changeset 9667 053afa64f977
parent 9666 5765eb70736d
child 9668 6fe3d2cb9655
(svn r10581) [NoAI] -Add: added WrightAI, and even though it is an aircraft only AI, it is the first NoAI that actually builds something useful and that is where the name came from (wright is archaic English for craftsman or builder)
[NoAI] -Remove: removed SQNoAI, as it didn't do anything useful
bin/ai/SQNoAI/main.nut
bin/ai/wrightai/main.nut
--- a/bin/ai/SQNoAI/main.nut	Sun Jul 15 12:56:41 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/* Extend our own AI from AIController so we can access basic things. */
-class SQNoAI extends AIController {
-	base     = null;
-	cargo    = null;
-	company  = null;
-	industry = null;
-	map      = null;
-	town     = null;
-	stop     = false;
-
-	constructor() {
-		this.base = AIBase();
-		this.cargo = AICargo();
-		this.company = AICompany();
-		this.industry = AIIndustry();
-		this.map = AIMap();
-		this.town = AITown();
-	}
-
-	function Start();
-	function Stop();
-	function GameLoop();
-}
-
-function SQNoAI::Start()
-{
-	while (!this.stop) {
-		this.Sleep(1);
-		this.GameLoop();
-	}
-}
-
-/* Define the GameLoop. This is called every tick. */
-function SQNoAI::GameLoop()
-{
-	if (this.GetTick() == 1) {
-		if (!this.company.SetCompanyName("SQNoAI")) {
-			this.company.SetCompanyName("SQNoAI " + this.base.Rand());
-		}
-		print("Map size: " + this.map.GetMapSizeX() + " by " +
-				this.map.GetMapSizeY() + ", " + this.map.GetMapSize() + " tiles");
-	}
-
-	if (this.GetTick() < 10) {
-		local cargo_label = this.cargo.GetCargoLabel(this.GetTick());
-		print(cargo_label + " is " + (this.cargo.IsFreight(this.GetTick()) ? "" : "not ") +
-				"freight and the income is " + this.cargo.GetCargoIncome(20, 10, this.GetTick()) +
-				" per 20 tiles in 10 days");
-	}
-
-	if (this.GetTick() < this.industry.GetMaxIndustryID()) {
-		if (this.industry.IsValidIndustry(this.GetTick())) {
-			local industry_name = this.industry.GetName(this.GetTick());
-			local t = this.industry.GetLocation(this.GetTick());
-
-			print("Industry " + industry_name + " [" + this.GetTick() + " of " +
-					this.industry.GetIndustryCount() + "] at (" + this.map.GetTileX(t) +
-					", " + this.map.GetTileY(t));
-		}
-	}
-
-	if (this.GetTick() % 10 == 0) {
-		local company_name = this.company.GetCompanyName();
-		print(company_name + " has loaned " + this.company.GetLoanAmount());
-	}
-
-	if (this.GetTick() % 14 == 0) {
-		local level = (this.company.GetMaxLoanAmount() / this.company.GetLoanInterval()) + 1;
-		this.company.SetLoanAmount(this.base.RandRange(level) * this.company.GetLoanInterval());
-	}
-
-	if (this.GetTick() % 13 == 0) {
-		local town_id = this.base.RandRange(this.town.GetMaxTownID());
-		if (this.town.IsValidTown(town_id)) {
-			local town_name = this.town.GetName(town_id);
-			local t = this.town.GetLocation(town_id);
-
-			print("Town " + town_name + " [" + town_id + " of " + this.town.GetTownCount() +
-					"] at (" + this.map.GetTileX(t) + ", " + this.map.GetTileY(t) + ") with " +
-					this.town.GetPopulation(town_id) + " inhabitants");
-		}
-	}
-}
-
-function SQNoAI::Stop()
-{
-	this.stop = true;
-}
-
-class FSQNoAI extends AIFactory {
-	function GetAuthor()      { return "OpenTTD Dev Team"; }
-	function GetName()        { return "SQNoAI"; }
-	function GetDescription() { return "Rather simple AI that tests all the functions of the AI layer in Squirrel."; }
-	function GetVersion()     { return 1; }
-	function GetDate()        { return "2007-03-14"; }
-	function CreateInstance() { return "SQNoAI"; }
-}
-
-/* Tell the core we are an AI */
-iFSQNoAI <-FSQNoAI();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/ai/wrightai/main.nut	Sun Jul 15 13:24:06 2007 +0000
@@ -0,0 +1,331 @@
+
+class WrightAI extends AIController {
+	airport = null;
+	company = null;
+	map = null;
+	order = null;
+	station = null;
+	vehicle = null;
+
+	name = null;
+	towns_used = null;
+	route_1 = null;
+	route_2 = null;
+	distance_of_route = {};
+	vehicle_to_depot = {};
+	vehicle_depot_tile = {};
+	delay_build_airport_route = 1000;
+
+	function Start();
+	function Stop();
+
+	constructor() {
+		this.airport = AIAirport();
+		this.company = AICompany();
+		this.map = AIMap();
+		this.order = AIOrder();
+		this.station = AIStation();
+		this.vehicle = AIVehicle();
+
+		this.towns_used = AIList();
+		this.route_1 = AIList();
+		this.route_2 = AIList();
+	}
+}
+
+/**
+ * Check if we have enough money (via loan and on bank).
+ */
+function WrightAI::HasMoney(money)
+{
+	if (this.company.GetBankBalance() + (this.company.GetMaxLoanAmount() - this.company.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 (this.company.GetBankBalance() > money) return;
+
+	local loan = money - this.company.GetBankBalance() + this.company.GetLoanInterval() + this.company.GetLoanAmount();
+	loan = loan - loan % this.company.GetLoanInterval();
+	print(this.name + ": Need a loan to get " + money + ": " + loan);
+	this.company.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()
+{
+	/* Get enough money to work with */
+	this.GetMoney(150000);
+
+	print(this.name + ": [INFO] Trying to build an airport route");
+	local limit = 2500;
+	local tile_1 = -1;
+	local tile_2 = -1;
+
+	tile_1 = this.FindSuitableAirportSpot(0);
+	if (tile_1 < 0) return -1;
+	tile_2 = this.FindSuitableAirportSpot(tile_1);
+	/* XXX -- If tile_2 isn't found, tile_1 town is added to used list, but never really used */
+	if (tile_2 < 0) return -2;
+
+	/* Build the airports for real */
+	if (!this.airport.BuildAirport(tile_1, AIAirport.AT_LARGE)) {
+		print(this.name + ": [ERROR] Although the testing told us we could build 2 airports, it still failed on the first airport at tile " + tile_1 + ".");
+		/* XXX -- We should free the towns again for an other day */
+		return -3;
+	}
+	if (!this.airport.BuildAirport(tile_2, AIAirport.AT_LARGE)) {
+		print(this.name + ": [ERROR] Although the testing told us we could build 2 airports, it still failed on the second airport at tile " + tile_2 + ".");
+		this.airport.RemoveAirport(tile_1);
+		/* XXX -- We should free the towns again for an other day */
+		return -4;
+	}
+
+	local ret = this.BuildAircraft(tile_1, tile_2);
+	if (ret < 0) {
+		this.airport.RemoveAirport(tile_1);
+		this.airport.RemoveAirport(tile_2);
+		/* XXX -- We should free the towns again for an other day */
+		return ret;
+	}
+
+	print(this.name + ": [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 = this.airport.GetHangarOfAirport(tile_1);
+	local engine = this.vehicle.FindBestAircraftVehicle(0, 0);
+	if (!this.vehicle.IsValidEngine(engine)) {
+		print(this.name + ": [ERROR] Couldn't find a suitable engine");
+		return -5;
+	}
+	local vehicle = this.vehicle.BuildVehicle(hangar, engine);
+	if (!this.vehicle.IsValidVehicle(vehicle)) {
+		print(this.name + ": [ERROR] Couldn't build the aircraft");
+		return -6;
+	}
+	/* Send him on his way */
+	this.order.AppendOrder(vehicle, tile_1, AIOrder.AIOF_NONE);
+	this.order.AppendOrder(vehicle, tile_2, AIOrder.AIOF_NONE);
+	this.vehicle.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);
+
+	print(this.name + ": [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(center_tile)
+{
+	local town_list = AITownList();
+	/* Remove all the towns we already used */
+	town_list.RemoveList(this.towns_used);
+
+	town_list.Valuate(AITownListPopulation());
+	town_list.KeepAboveValue(500);
+	/* Keep the best 10, if we can't find 2 stations in there, just leave it anyway */
+	town_list.KeepTop(10);
+	town_list.Valuate(AIListRandomize());
+
+	/* 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 - this.map.GetTileIndex(15, 15), tile + this.map.GetTileIndex(15, 15));
+		list.Valuate(AITileListBuildableRectangle(6, 6));
+		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(AITileListDistanceSquareToTile(center_tile));
+			list.KeepAboveValue(625);
+		}
+		/* Sort on acceptance, remove places that don't have acceptance */
+		list.Valuate(AITileListCargoAcceptance(0, 6, 6, 4));
+		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 (!this.airport.BuildAirport(tile, AIAirport.AT_LARGE)) continue;
+				good_tile = tile;
+				break;
+			}
+
+			/* Did we found a place to build the airport on? */
+			if (good_tile == 0) continue;
+		}
+
+		print(this.name + ": [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;
+	}
+
+	print(this.name + ": [INFO] Couldn't find a suitable town to build an airport in");
+	return -1;
+}
+
+function WrightAI::ManageAirRoutes()
+{
+	local list = AIVehicleList();
+	list.Valuate(AIVehicleListAge());
+	/* Give the plane at least 2 years to make a difference */
+	list.KeepAboveValue(365 * 2);
+	list.Valuate(AIVehicleListProfitLastYear());
+
+	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 && this.vehicle.GetProfitThisYear(i) < 10000) {
+			/* Send the vehicle to depot if we didn't do so yet */
+			if (!this.vehicle_to_depot.rawin(i) || this.vehicle_to_depot.rawget(i) != true) {
+				print(this.name + ": [INFO] Sending " + i + " to depot as profit is: " + profit + " / " + this.vehicle.GetProfitThisYear(i));
+				this.vehicle.SendVehicleToDepot(i);
+				this.vehicle_to_depot.rawset(i, true);
+			}
+		}
+		/* Try to sell it over and over till it really is in the depot */
+		if (this.vehicle_to_depot.rawin(i) && this.vehicle_to_depot.rawget(i) == true) {
+			if (this.vehicle.SellVehicle(i)) {
+				print(this.name + ": [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 = AIStationVehicleList(this.station.GetStationID(this.route_1.GetValue(i)));
+				if (list2.Count() == 0) {
+					/* Remove the airports */
+					print(this.name + ": [INFO] Removing airports as nobody serves them anymore.");
+					this.airport.RemoveAirport(this.route_1.GetValue(i));
+					this.airport.RemoveAirport(this.route_2.GetValue(i));
+					this.route_1.RemoteItem(i);
+					this.route_2.RemoteItem(i);
+					/* XXX -- We should free the towns_used entries too */
+				}
+				this.vehicle_to_depot.rawdelete(i);
+			}
+		}
+	}
+
+	/* Don't try to add planes when we are short on cash */
+	if (!this.HasMoney(50000)) return;
+
+	list = AIStationList(AIStationList.STATION_AIRPORT);
+	list.Valuate(AIStationListCargoWaiting(0));
+	list.KeepAboveValue(250);
+
+	for (local i = list.Begin(); list.HasNext(); i = list.Next()) {
+		local list2 = AIStationVehicleList(i);
+		/* No vehicles going to this station, abort */
+		/* TODO -- Sell station */
+		if (list2.Count() == 0) 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(AIVehicleListAge());
+		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;
+
+		print(this.name + ": [INFO] Station " + i + " (" + this.station.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));
+	}
+}
+
+function WrightAI::Start()
+{
+	/* Sleep 1 tick, as we can't execute anything in tick 0 */
+	Sleep(1);
+
+	/* Give the boy a name */
+	if (!this.company.SetCompanyName("WrightAI")) {
+		local i = 2;
+		while (!this.company.SetCompanyName("WrightAI #" + i)) {
+			i++;
+		}
+	}
+	this.name = this.company.GetCompanyName();
+	print(this.name + ": Welcome to WrightAI. I will be building airports all day long.");
+
+	/* We start with almost no loan, and we take a loan when we want to build something */
+	this.company.SetLoanAmount(this.company.GetLoanInterval());
+
+	/* We need our local ticker, as GetTick() will skip ticks */
+	local ticker = 0;
+	/* Let's go on for ever */
+	while (true) {
+		ticker++;
+		/* Once in a while, with enough money, try to build something */
+		if (ticker % this.delay_build_airport_route == 0 && this.HasMoney(100000)) {
+			local ret = this.BuildAirportRoute();
+			if (ret == -1) {
+				/* No more route found, delay even more before trying to find an other */
+				this.delay_build_airport_route = 10000;
+			}
+		}
+		/* 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) {
+			this.company.SetLoanAmount(0);
+		}
+		/* Make sure we do not create infinite loops */
+		Sleep(1);
+	}
+}
+
+function WrightAI::Stop()
+{
+}
+
+class FWrightAI extends AIFactory {
+	function GetAuthor()      { return "OpenTTD Dev Team"; }
+	function GetName()        { return "WrightAI"; }
+	function GetDescription() { return "A simple AI that tries to beat you with only aircrafts"; }
+	function GetVersion()     { return 1; }
+	function GetDate()        { return "2007-07-15"; }
+	function CreateInstance() { return "WrightAI"; }
+}
+
+/* Tell the core we are an AI */
+iFWrightAI <- FWrightAI();