diff --git a/binaries/data/mods/public/simulation/ai/jubot/data.json b/binaries/data/mods/public/simulation/ai/jubot/data.json index b0449c89a5..43a8c8a6d2 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/data.json +++ b/binaries/data/mods/public/simulation/ai/jubot/data.json @@ -1,5 +1,5 @@ { "name": "JuBot", "description": "Jubal's improved version of the 0AD TestBot.", - "constructor": "TestBotAI" + "constructor": "JuBotAI" } diff --git a/binaries/data/mods/public/simulation/ai/jubot/jubot.js b/binaries/data/mods/public/simulation/ai/jubot/jubot.js new file mode 100644 index 0000000000..a5e4847b76 --- /dev/null +++ b/binaries/data/mods/public/simulation/ai/jubot/jubot.js @@ -0,0 +1,146 @@ +/* + * 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); + 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 annhilation. Jubal apologises for any inconvenience likely to be caused by your imminent demise."); + } + this.turn++; +}; diff --git a/binaries/data/mods/public/simulation/ai/jubot/military.js b/binaries/data/mods/public/simulation/ai/jubot/military.js index 879e3a12b2..8338cf17e3 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/military.js +++ b/binaries/data/mods/public/simulation/ai/jubot/military.js @@ -298,7 +298,7 @@ var MilitaryAttackManager = Class({ trainDefenderSquad: function(gameState, planGroups) { var pendingdefense = gameState.getOwnEntitiesWithRole("defenders"); - //TestBotAI.prototype.chat("Number of defenders is" + pendingdefense.length); + //JuBotAI.prototype.chat("Number of defenders is" + pendingdefense.length); if (pendingdefense.length < this.defsquadmin && gameState.displayCiv() == "iber"){ planGroups.economyPersonnel.addPlan(122, new UnitTrainingPlan(gameState, @@ -310,21 +310,21 @@ var MilitaryAttackManager = Class({ new UnitTrainingPlan(gameState, "units/{civ}_infantry_spearman_b", 3, { "role": "defenders" }) ); - //TestBotAI.prototype.chat("Training defenders"); + //JuBotAI.prototype.chat("Training defenders"); } else if (pendingdefense.length < this.defsquad && gameState.displayCiv() == "iber"){ planGroups.economyPersonnel.addPlan(110, new UnitTrainingPlan(gameState, "units/{civ}_infantry_swordsman_b", 3, { "role": "defenders" }) ); - //TestBotAI.prototype.chat("Training defenders"); + //JuBotAI.prototype.chat("Training defenders"); } else if (pendingdefense.length < this.defsquad){ planGroups.economyPersonnel.addPlan(110, new UnitTrainingPlan(gameState, "units/{civ}_infantry_spearman_b", 3, { "role": "defenders" }) ); - //TestBotAI.prototype.chat("Training defenders"); + //JuBotAI.prototype.chat("Training defenders"); } }, @@ -648,25 +648,25 @@ var MilitaryAttackManager = Class({ if (whatnext > 0.85){ this.killstrat = 0; // Regular "train a few guys and go kill stuff" type attack. - //TestBotAI.prototype.chat("Regular attack (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Regular attack (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); } else if (whatnext > 0.55) { this.killstrat = 2; - //TestBotAI.prototype.chat("Cavalry raid (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Cavalry raid (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); // Cavalry raid } else if (whatnext > 0.2) { this.killstrat = 3; - //TestBotAI.prototype.chat("3 pronged assault (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("3 pronged assault (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); // 3 prong } else { this.killstrat = 1; - //TestBotAI.prototype.chat("Full assault (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Full assault (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); //Full Assault! } } @@ -732,25 +732,25 @@ var MilitaryAttackManager = Class({ if (whatnext > 0.8){ this.killstrat = 0; // Regular "train a few guys and go kill stuff" type attack. - //TestBotAI.prototype.chat("Regular attack (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Regular attack (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); } else if (whatnext > 0.5) { this.killstrat = 2; - //TestBotAI.prototype.chat("Cavalry raid (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Cavalry raid (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); // Cavalry raid } else if (whatnext > 0.3) { this.killstrat = 3; - //TestBotAI.prototype.chat("3 pronged assault (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("3 pronged assault (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); // 3 prong } else { this.killstrat = 1; - //TestBotAI.prototype.chat("Full assault (" + gameState.displayCiv() + ")"); - //TestBotAI.prototype.chat(whatnext); + //JuBotAI.prototype.chat("Full assault (" + gameState.displayCiv() + ")"); + //JuBotAI.prototype.chat(whatnext); //Full Assault! } } diff --git a/binaries/data/mods/public/simulation/ai/jubot/plan-building.js b/binaries/data/mods/public/simulation/ai/jubot/plan-building.js index d1fa8b75de..541d6e8249 100644 --- a/binaries/data/mods/public/simulation/ai/jubot/plan-building.js +++ b/binaries/data/mods/public/simulation/ai/jubot/plan-building.js @@ -191,14 +191,14 @@ var BuildingConstructionPlan = Class({ { var v = friendlyTiles[i]; var foe = enemyTiles[i]; - //TestBotAI.prototype.chat(v); - //TestBotAI.prototype.chat(i); - //TestBotAI.prototype.chat(foe); + //JuBotAI.prototype.chat(v); + //JuBotAI.prototype.chat(i); + //JuBotAI.prototype.chat(foe); if (v >= bestVal) { bestVal = v; bestIdx = i; - //TestBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + "."); + //JuBotAI.prototype.chat("BestVal is " + bestVal + ", and bestIdx is " + bestIdx + "."); } } }