1
0
forked from 0ad/0ad

petra: improve performance of docks positioning (was really slow) + some cleanup

This was SVN commit r17705.
This commit is contained in:
mimo 2016-01-24 20:43:27 +00:00
parent 7c6f35b2c0
commit 4a58c7f8b6
5 changed files with 147 additions and 130 deletions

View File

@ -1,6 +1,6 @@
{
"name": "Petra Bot",
"description": "Petra is the default 0AD AI bot. Please report issues to Wildfire Games (see the link in the main menu).\n\nThe AI has a malus-bonus on resource stockpiling (either gathering rate or trade gain) varying from 0.5 for Sandbox to 1.6 for Very Hard (Medium = 1.0). In addition, the Sandbox level does not expand nor attack.",
"description": "Petra is the default 0AD AI bot. Please report issues to Wildfire Games (see the link in the main menu).\n\nThe AI has a bonus/penalty on resource stockpiling (either gathering rate or trade gain) varying from 0.5 for Sandbox to 1.6 for Very Hard (Medium = 1.0). In addition, the Sandbox level does not expand nor attack.",
"moduleName" : "PETRA",
"constructor": "PetraBot",
"useShared": true

View File

@ -207,48 +207,6 @@ m.createFrontierMap = function(gameState)
return map;
};
// return a measure of the proximity to our frontier (including our allies)
// 0=inside, 1=less than 24m, 2= less than 48m, 3= less than 64m, 4=less than 96m, 5=above 96m
m.getFrontierProximity = function(gameState, j)
{
var territoryMap = gameState.ai.HQ.territoryMap;
var borderMap = gameState.ai.HQ.borderMap;
const around = [ [-0.7,0.7], [0,1], [0.7,0.7], [1,0], [0.7,-0.7], [0,-1], [-0.7,-0.7], [-1,0] ];
var width = territoryMap.width;
var step = Math.round(24 / territoryMap.cellSize);
if (gameState.isPlayerAlly(territoryMap.getOwnerIndex(j)))
return 0;
var ix = j%width;
var iz = Math.floor(j/width);
var best = 5;
for (let a of around)
{
for (let i = 1; i < 5; ++i)
{
let jx = ix + Math.round(i*step*a[0]);
if (jx < 0 || jx >= width)
continue;
var jz = iz + Math.round(i*step*a[1]);
if (jz < 0 || jz >= width)
continue;
if (borderMap.map[jx+width*jz] > 1)
continue;
if (gameState.isPlayerAlly(territoryMap.getOwnerIndex(jx+width*jz)))
{
best = Math.min(best, i);
break;
}
}
if (best === 1)
break;
}
return best;
};
m.debugMap = function(gameState, map)
{
var width = map.width;

View File

@ -588,7 +588,9 @@ m.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
{
if (!gameState.ai.HQ.navalRegions[sea])
continue;
queues.dock.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_dock", { "land": [base.accessIndex], "sea": sea }));
let wantedLand = {};
wantedLand[base.accessIndex] = true;
queues.dock.addPlan(new m.ConstructionPlan(gameState, "structures/{civ}_dock", { "land": wantedLand, "sea": sea }));
dockStarted = true;
break;
}
@ -612,16 +614,12 @@ m.NavalManager.prototype.buildNavalStructures = function(gameState, queues)
{
if (gameState.countEntitiesAndQueuedByType(naval, true) < 1 && gameState.ai.HQ.canBuild(gameState, naval))
{
let land = [];
let wantedLand = {};
for (let base of gameState.ai.HQ.baseManagers)
{
if (!base.anchor)
continue;
if (land.indexOf(base.accessIndex) === -1)
land.push(base.accessIndex);
}
if (base.anchor)
wantedLand[base.accessIndex] = true;
let sea = docks.toEntityArray()[0].getMetadata(PlayerID, "sea");
queues.militaryBuilding.addPlan(new m.ConstructionPlan(gameState, naval, { "land": land, "sea": sea }));
queues.militaryBuilding.addPlan(new m.ConstructionPlan(gameState, naval, { "land": wantedLand, "sea": sea }));
break;
}
}

View File

@ -362,7 +362,6 @@ m.ConstructionPlan.prototype.findGoodPosition = function(gameState)
m.ConstructionPlan.prototype.findDockPosition = function(gameState)
{
var template = this.template;
var territoryMap = gameState.ai.HQ.territoryMap;
var obstructions = m.createObstructionMap(gameState, 0, template);
@ -378,11 +377,17 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
var width = gameState.ai.HQ.territoryMap.width;
var cellSize = gameState.ai.HQ.territoryMap.cellSize;
var nbShips = gameState.ai.HQ.navalManager.transportShips.length;
var proxyAccess;
if (this.metadata.proximity)
proxyAccess = gameState.ai.accessibility.getAccessValue(this.metadata.proximity);
var nbShips = gameState.ai.HQ.navalManager.transportShips.length;
var wantedLand = this.metadata && this.metadata.land ? this.metadata.land : null;
var wantedSea = this.metadata && this.metadata.sea ? this.metadata.sea : null;
var proxyAccess = this.metadata && this.metadata.proximity ? gameState.ai.accessibility.getAccessValue(this.metadata.proximity) : null;
if (nbShips === 0 && proxyAccess && proxyAccess > 1)
{
wantedLand = {};
wantedLand[proxyAccess] = true;
}
var dropsiteTypes = template.resourceDropsiteTypes();
var radius = Math.ceil(template.obstructionRadius() / obstructions.cellSize);
var halfSize = 0; // used for dock angle
@ -407,40 +412,33 @@ m.ConstructionPlan.prototype.findDockPosition = function(gameState)
const maxWater = 16;
for (let j = 0; j < territoryMap.length; ++j)
{
if (!this.isDockLocation(gameState, j, halfDepth, wantedLand, wantedSea))
continue;
let dist;
if (!proxyAccess)
{
// if not in our (or allied) territory, we do not want it too far to be able to defend it
dist = this.getFrontierProximity(gameState, j);
if (dist > 4)
continue;
}
let i = territoryMap.getNonObstructedTile(j, radius, obstructions);
if (i < 0)
continue;
let landAccess = this.getLandAccess(gameState, i, radius+1, obstructions.width);
if (landAccess.size == 0)
if (wantedSea && navalPassMap[i] !== wantedSea)
continue;
if (this.metadata)
{
if (this.metadata.land && !landAccess.has(+this.metadata.land))
continue;
if (this.metadata.sea && navalPassMap[i] != +this.metadata.sea)
continue;
if (nbShips === 0 && proxyAccess && proxyAccess > 1 && !landAccess.has(proxyAccess))
continue;
}
let dist;
let res = Math.min(maxRes, this.getResourcesAround(gameState, j, 80));
let res = dropsiteTypes ? Math.min(maxRes, this.getResourcesAround(gameState, dropsiteTypes, j, 80)) : maxRes;
let pos = [cellSize * (j%width+0.5), cellSize * (Math.floor(j/width)+0.5)];
if (this.metadata.proximity)
if (proxyAccess)
{
// if proximity is given, we look for the nearest point
dist = API3.SquareVectorDistance(this.metadata.proximity, pos);
dist = Math.sqrt(dist) + 20 * (maxRes - res);
}
else
{
// if not in our (or allied) territory, we do not want it too far to be able to defend it
dist = m.getFrontierProximity(gameState, j);
if (dist > 4)
continue;
dist += 0.6 * (maxRes - res);
}
// Add a penalty if on the map border as ship movement will be difficult
if (gameState.ai.HQ.borderMap.map[j] > 0)
dist += 2;
@ -625,43 +623,94 @@ m.ConstructionPlan.prototype.checkDockPlacement = function(gameState, x, z, half
return {"land": land, "water": water};
};
// get the list of all the land access from this position
m.ConstructionPlan.prototype.getLandAccess = function(gameState, i, radius, w)
/**
* fast check if we can build a dock: returns false if nearest land is farther than the dock dimension
* if the (object) wantedLand is given, this nearest land should have one of these accessibility
* if wantedSea is given, this tile should be inside this sea
*/
const around = [ [ 1.0, 0.0], [ 0.87, 0.50], [ 0.50, 0.87], [ 0.0, 1.0], [-0.50, 0.87], [-0.87, 0.50],
[-1.0, 0.0], [-0.87,-0.50], [-0.50,-0.87], [ 0.0,-1.0], [ 0.50,-0.87], [ 0.87,-0.50] ];
m.ConstructionPlan.prototype.isDockLocation = function(gameState, j, dimension, wantedLand, wantedSea)
{
var access = new Set();
var landPassMap = gameState.ai.accessibility.landPassMap;
var kx = i % w;
var ky = Math.floor(i / w);
var land;
for (let dy = 0; dy <= radius; ++dy)
var width = gameState.ai.HQ.territoryMap.width;
var cellSize = gameState.ai.HQ.territoryMap.cellSize;
var dist = dimension + 2*cellSize;
var x = (j%width + 0.5) * cellSize;
var z = (Math.floor(j/width) + 0.5) * cellSize;
for (let a of around)
{
let dxmax = radius - dy;
let xp = kx + (ky + dy)*w;
let xm = kx + (ky - dy)*w;
for (let dx = -dxmax; dx <= dxmax; ++dx)
let pos = gameState.ai.accessibility.gamePosToMapPos([x + dist*a[0], z + dist*a[1]]);
if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width)
continue;
if (pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
continue;
let k = pos[0] + pos[1]*gameState.ai.accessibility.width;
let landPass = gameState.ai.accessibility.landPassMap[k];
if (landPass < 2 || (wantedLand && !wantedLand[landPass]))
continue;
pos = gameState.ai.accessibility.gamePosToMapPos([x - dist*a[0], z - dist*a[1]]);
if (pos[0] < 0 || pos[0] >= gameState.ai.accessibility.width)
continue;
if (pos[1] < 0 || pos[1] >= gameState.ai.accessibility.height)
continue;
k = pos[0] + pos[1]*gameState.ai.accessibility.width;
if (wantedSea && gameState.ai.accessibility.navalPassMap[k] !== wantedSea)
continue;
else if (!wantedSea && gameState.ai.accessibility.navalPassMap[k] < 2)
continue;
return true;
}
return false;
};
/**
* return a measure of the proximity to our frontier (including our allies)
* 0=inside, 1=less than 24m, 2= less than 48m, 3= less than 72m, 4=less than 96m, 5=above 96m
*/
m.ConstructionPlan.prototype.getFrontierProximity = function(gameState, j)
{
var territoryMap = gameState.ai.HQ.territoryMap;
if (gameState.isPlayerAlly(territoryMap.getOwnerIndex(j)))
return 0;
var borderMap = gameState.ai.HQ.borderMap;
var width = territoryMap.width;
var step = Math.round(24 / territoryMap.cellSize);
var ix = j % width;
var iz = Math.floor(j / width);
var best = 5;
for (let a of around)
{
for (let i = 1; i < 5; ++i)
{
if (kx + dx < 0 || kx + dx >= w)
let jx = ix + Math.round(i*step*a[0]);
if (jx < 0 || jx >= width)
continue;
if (ky + dy >= 0 && ky + dy < w)
let jz = iz + Math.round(i*step*a[1]);
if (jz < 0 || jz >= width)
continue;
if (borderMap.map[jx+width*jz] > 1)
continue;
if (gameState.isPlayerAlly(territoryMap.getOwnerIndex(jx+width*jz)))
{
land = landPassMap[xp + dx];
if (land > 1 && !access.has(land))
access.add(land);
}
if (ky - dy >= 0 && ky - dy < w)
{
land = landPassMap[xm + dx];
if (land > 1 && !access.has(land))
access.add(land);
best = Math.min(best, i);
break;
}
}
if (best === 1)
break;
}
return access;
return best;
};
// get the sum of the resources (except food) around, inside a given radius
// resources have a weight (1 if dist=0 and 0 if dist=size) doubled for wood
m.ConstructionPlan.prototype.getResourcesAround = function(gameState, i, radius)
m.ConstructionPlan.prototype.getResourcesAround = function(gameState, types, i, radius)
{
let resourceMaps = gameState.sharedScript.resourceMaps;
let w = resourceMaps.wood.width;
@ -671,9 +720,9 @@ m.ConstructionPlan.prototype.getResourcesAround = function(gameState, i, radius)
let iy = Math.floor(i / w);
let total = 0;
let nbcell = 0;
for (let k in resourceMaps)
for (let k of types)
{
if (k === "food")
if (k === "food" || !resourceMaps[k])
continue;
let weigh0 = (k === "wood") ? 2 : 1;
for (let dy = 0; dy <= size; ++dy)

View File

@ -395,10 +395,12 @@ m.Worker.prototype.startGathering = function(gameState)
continue;
if ((supply = findSupply(this.ent, base.dropsiteSupplies[resource].nearby)))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position());
return true;
if (navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position()))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
return true;
}
}
}
if (resource === "food") // --> for food, try to gather from fields if any, otherwise build one if any
@ -409,17 +411,21 @@ m.Worker.prototype.startGathering = function(gameState)
continue;
if ((supply = this.gatherNearestField(gameState, base.ID)))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position());
return true;
if (navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position()))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
return true;
}
}
if ((supply = this.buildAnyField(gameState, base.ID)))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position());
return true;
if (navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position()))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
return true;
}
}
}
}
@ -429,10 +435,12 @@ m.Worker.prototype.startGathering = function(gameState)
continue;
if ((supply = findSupply(this.ent, base.dropsiteSupplies[resource].medium)))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position());
return true;
if (navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position()))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
return true;
}
}
}
// Okay so we haven't found any appropriate dropsite anywhere.
@ -442,17 +450,19 @@ m.Worker.prototype.startGathering = function(gameState)
return false;
if (foundation.resourceDropsiteTypes() && foundation.resourceDropsiteTypes().indexOf(resource) !== -1)
{
if (foundation.getMetadata(PlayerID, "base") !== self.baseID)
self.ent.setMetadata(PlayerID, "base", foundation.getMetadata(PlayerID, "base"));
self.ent.setMetadata(PlayerID, "target-foundation", foundation.id());
let foundationAccess = foundation.getMetadata(PlayerID, "access");
if (!foundationAccess)
{
foundationAccess = gameState.ai.accessibility.getAccessValue(foundation.position());
foundation.setMetadata(PlayerID, "access", foundationAccess);
}
navalManager.requireTransport(gameState, self.ent, access, foundationAccess, foundation.position());
return true;
if (navalManager.requireTransport(gameState, self.ent, access, foundationAccess, foundation.position()))
{
if (foundation.getMetadata(PlayerID, "base") !== self.baseID)
self.ent.setMetadata(PlayerID, "base", foundation.getMetadata(PlayerID, "base"));
self.ent.setMetadata(PlayerID, "target-foundation", foundation.id());
return true;
}
}
return false;
});
@ -487,10 +497,12 @@ m.Worker.prototype.startGathering = function(gameState)
continue;
if ((supply = findSupply(this.ent, base.dropsiteSupplies[resource].faraway)))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position());
return true;
if (navalManager.requireTransport(gameState, this.ent, access, base.accessIndex, supply.position()))
{
if (base.ID !== this.baseID)
this.ent.setMetadata(PlayerID, "base", base.ID);
return true;
}
}
}