forked from 0ad/0ad
Jubal
a6eccb1290
- Gather weights change over time - Workers are occasionally moved (about once in every 400 turns, at random, needs to be improved) - Workers don't go and try hunting fish and snarl up the engine. This was SVN commit r10318.
147 lines
4.6 KiB
JavaScript
147 lines
4.6 KiB
JavaScript
/*
|
|
* This is a primitive initial attempt at an AI player.
|
|
* The design isn't great and maybe the whole thing should be rewritten -
|
|
* the aim here is just to have something that basically works, and to
|
|
* learn more about what's really needed for a decent AI design.
|
|
*
|
|
* The basic idea is we have a collection of independent modules
|
|
* (EconomyManager, etc) which produce a list of plans.
|
|
* The modules are mostly stateless - each turn they look at the current
|
|
* world state, and produce some plans that will improve the state.
|
|
* E.g. if there's too few worker units, they'll do a plan to train
|
|
* another one. Plans are discarded after the current turn, if they
|
|
* haven't been executed.
|
|
*
|
|
* Plans are grouped into a small number of PlanGroups, and for each
|
|
* group we try to execute the highest-priority plans.
|
|
* If some plan groups need more resources to execute their highest-priority
|
|
* plan, we'll distribute any unallocated resources to that group's
|
|
* escrow account. Eventually they'll accumulate enough to afford their plan.
|
|
* (The purpose is to ensure resources are shared fairly between all the
|
|
* plan groups - none of them should be starved even if they're trying to
|
|
* execute a really expensive plan.)
|
|
*/
|
|
|
|
/*
|
|
* Lots of things we should fix:
|
|
*
|
|
* * Find entities with no assigned role, and give them something to do
|
|
* * Keep some units back for defence
|
|
* * Consistent terminology (type vs template etc)
|
|
* * ...
|
|
*
|
|
*/
|
|
|
|
|
|
function JuBotAI(settings)
|
|
{
|
|
// warn("Constructing JuBotAI for player "+settings.player);
|
|
|
|
BaseAI.call(this, settings);
|
|
|
|
this.turn = 0;
|
|
|
|
this.modules = [
|
|
new EconomyManager(),
|
|
new MilitaryAttackManager(),
|
|
];
|
|
|
|
this.planGroups = {
|
|
economyPersonnel: new PlanGroup(),
|
|
economyConstruction: new PlanGroup(),
|
|
militaryPersonnel: new PlanGroup(),
|
|
};
|
|
}
|
|
|
|
JuBotAI.prototype = new BaseAI();
|
|
|
|
JuBotAI.prototype.ShareResources = function(remainingResources, unaffordablePlans)
|
|
{
|
|
// Share our remaining resources among the plangroups that need
|
|
// to accumulate more resources, in proportion to their priorities
|
|
|
|
for each (var type in remainingResources.types)
|
|
{
|
|
// Skip resource types where we don't have any spare
|
|
if (remainingResources[type] <= 0)
|
|
continue;
|
|
|
|
// Find the plans that require some of this resource type,
|
|
// and the sum of their priorities
|
|
var ps = [];
|
|
var sumPriority = 0;
|
|
for each (var p in unaffordablePlans)
|
|
{
|
|
if (p.plan.getCost()[type] > p.group.getEscrow()[type])
|
|
{
|
|
ps.push(p);
|
|
sumPriority += p.priority;
|
|
}
|
|
}
|
|
|
|
// Avoid divisions-by-zero
|
|
if (!sumPriority)
|
|
continue;
|
|
|
|
// Share resources by priority, clamped to the amount the plan actually needs
|
|
for each (var p in ps)
|
|
{
|
|
var amount = Math.floor(remainingResources[type] * p.priority / sumPriority);
|
|
var max = p.plan.getCost()[type] - p.group.getEscrow()[type];
|
|
p.group.getEscrow()[type] += Math.min(max, amount);
|
|
}
|
|
}
|
|
};
|
|
|
|
JuBotAI.prototype.OnUpdate = function()
|
|
{
|
|
// Run the update every n turns, offset depending on player ID to balance the load
|
|
if ((this.turn + this.player) % 4 == 0)
|
|
{
|
|
var gameState = new GameState(this);
|
|
|
|
// Find the resources we have this turn that haven't already
|
|
// been allocated to an escrow account.
|
|
// (We need to do this before executing any plans, because those will
|
|
// distort the escrow figures.)
|
|
var remainingResources = gameState.getResources();
|
|
for each (var planGroup in this.planGroups)
|
|
remainingResources.subtract(planGroup.getEscrow());
|
|
|
|
Engine.ProfileStart("plan setup");
|
|
|
|
// Compute plans from each module
|
|
for each (var module in this.modules)
|
|
module.update(gameState, this.planGroups);
|
|
|
|
// print(uneval(this.planGroups)+"\n");
|
|
|
|
Engine.ProfileStop();
|
|
Engine.ProfileStart("plan execute");
|
|
|
|
// Execute as many plans as possible, and keep a record of
|
|
// which ones we can't afford yet
|
|
var unaffordablePlans = [];
|
|
for each (var planGroup in this.planGroups)
|
|
{
|
|
var plan = planGroup.executePlans(gameState, this.modules);
|
|
if (plan)
|
|
unaffordablePlans.push({"group": planGroup, "priority": plan.priority, "plan": plan.plan});
|
|
}
|
|
|
|
Engine.ProfileStop();
|
|
|
|
this.ShareResources(remainingResources, unaffordablePlans);
|
|
|
|
// print(uneval(this.planGroups)+"\n");
|
|
|
|
// Reset the temporary plan data
|
|
for each (var planGroup in this.planGroups)
|
|
planGroup.resetPlans();
|
|
}
|
|
// if (this.turn == 0){
|
|
// this.chat("Good morning. Please prepare for annihilation. Jubal apologises for any inconvenience likely to be caused by your imminent demise.");
|
|
// }
|
|
this.turn++;
|
|
};
|