bin/ai/wrightai/main.nut
author truebrain
Sun, 24 Feb 2008 21:06:38 +0000
branchnoai
changeset 9750 71106199d143
parent 9747 7e8f23e8d0c8
child 9753 7209db94ad12
permissions -rw-r--r--
(svn r12232) [NoAI] -Fix: various of fixes for wrightai (mostly finished all XXX / TODO) (yorick)

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;

	function Start();
	function Stop();

	constructor() {
		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 (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();
	print(this.name + ": [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.AiportAvailable(AIAirport.AT_SMALL) ? AIAirport.AT_SMALL : AIAirport.AT_LARGE);

	/* Get enough money to work with */
	this.GetMoney(150000);

	print(this.name + ": [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)) {
		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 + ".");
		this.towns_used.RemoveValue(tile_1);
		this.towns_used.RemoveValue(tile_2);
		return -3;
	}
	if (!AIAirport.BuildAirport(tile_2, airport_type)) {
		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 + ".");
		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;
	}

	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 = 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(AIEngineListPrice());
	engine_list.KeepBelowValue(balance < 300000 ? 50000 : (balance < 1000000 ? 300000 : 1000000));

	engine_list.Valuate(AIEngineListCargoType())
	engine_list.KeepValue(0); // passengers

	engine_list.Valuate(AIEngineListCapacity())
	engine_list.KeepTop(1);

	engine = engine_list.Begin();

	if (!AIEngine.IsValidEngine(engine)) {
		print(this.name + ": [ERROR] Couldn't find a suitable engine");
		return -5;
	}
	local vehicle = AIVehicle.BuildVehicle(hangar, engine);
	if (!AIVehicle.IsValidVehicle(vehicle)) {
		print(this.name + ": [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);

	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(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(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 - AIMap.GetTileIndex(15, 15), tile + AIMap.GetTileIndex(15, 15));
		list.Valuate(AITileListBuildableRectangle(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(AITileListDistanceSquareToTile(center_tile));
			list.KeepAboveValue(625);
		}
		/* Sort on acceptance, remove places that don't have acceptance */
		list.Valuate(AITileListCargoAcceptance(0, 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)) 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 && 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) {
				print(this.name + ": [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)) {
				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 = 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(AIStationListCargoWaiting(0));
	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(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 + " (" + 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 */
	print(this.name + ": [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_CRASHED_VEHICLE:
				local ec = AIEventVehicleCrash.Convert(e);
				local v = ec.GetVehicleID();
				print(this.name + ": [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()
{
	/* Sleep 1 tick, as we can't execute anything in tick 0 */
	Sleep(1);

	/* Give the boy a name */
	if (!AICompany.SetCompanyName("WrightAI")) {
		local i = 2;
		while (!AICompany.SetCompanyName("WrightAI #" + i)) {
			i++;
		}
	}
	this.name = AICompany.GetCompanyName(AICompany.MY_COMPANY);
	/* Say hello to the user */
	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 */
	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 */
				this.ChangeName("FailedWrightAI");
				print(this.name + ": 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;
	}
}

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.1; }
	function GetDate()        { return "2008-02-24"; }
	function CreateInstance() { return "WrightAI"; }
}

/* Tell the core we are an AI */
iFWrightAI <- FWrightAI();