Files
@ r10696:8dfe83e30d01
Branch filter:
Location: cpp/openttd-patchpack/source/bin/ai/wrightai/main.nut
r10696:8dfe83e30d01
12.2 KiB
text/plain
(svn r15027) -Merge: tomatos and bananas left to be, here is NoAI for all to see.
NoAI is an API (a framework) to build your own AIs in. See:
http://wiki.openttd.org/wiki/index.php/AI:Main_Page
With many thanks to:
- glx and Rubidium for their syncing, feedback and hard work
- Yexo for his feedback, patches, and AIs which tested the system very deep
- Morloth for his feedback and patches
- TJIP for hosting a challenge which kept NoAI on track
- All AI authors for testing our AI API, and all other people who helped in one way or another
-Remove: all old AIs and their cheats/hacks
NoAI is an API (a framework) to build your own AIs in. See:
http://wiki.openttd.org/wiki/index.php/AI:Main_Page
With many thanks to:
- glx and Rubidium for their syncing, feedback and hard work
- Yexo for his feedback, patches, and AIs which tested the system very deep
- Morloth for his feedback and patches
- TJIP for hosting a challenge which kept NoAI on track
- All AI authors for testing our AI API, and all other people who helped in one way or another
-Remove: all old AIs and their cheats/hacks
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 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;
}
}
|