Skip to content

Commit

Permalink
Creating theoretical outpost evaluator + console command to eval all …
Browse files Browse the repository at this point in the history
…current colony outposts
  • Loading branch information
Matthew Roy committed May 8, 2020
1 parent fac6124 commit c1cccf3
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 1 deletion.
34 changes: 34 additions & 0 deletions src/console/Console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {Colony, ColonyMemory, getAllColonies} from '../Colony';
import {Directive} from '../directives/Directive';
import {RoomIntel} from '../intel/RoomIntel';
import {Overlord} from '../overlords/Overlord';
import {ExpansionEvaluator} from '../strategy/ExpansionEvaluator';
import {EmpireAnalysis} from '../utilities/EmpireAnalysis';
import {alignedNewline, bullet} from '../utilities/stringConstants';
import {color, printRoomName, toColumns} from '../utilities/utils';
Expand Down Expand Up @@ -55,6 +56,7 @@ export class OvermindConsole {
global.setRoomUpgradeRate = this.setRoomUpgradeRate;
global.getEmpireMineralDistribution = this.getEmpireMineralDistribution;
global.listPortals = this.listPortals;
global.evaluateOutpostEfficiencies = this.evaluateOutpostEfficiencies;
}

// Help, information, and operational changes ======================================================================
Expand Down Expand Up @@ -103,6 +105,7 @@ export class OvermindConsole {
descr['setRoomUpgradeRate(room, upgradeRate)'] = 'changes the rate which a room upgrades at, default is 1';
descr['getEmpireMineralDistribution()'] = 'returns current census of colonies and mined sk room minerals';
descr['getPortals(rangeFromColonies)'] = 'returns active portals within colony range';
descr['evaluateOutpostEfficiencies()'] = 'prints all colony outposts efficiency';

// Console list
const descrMsg = toColumns(descr, {justify: true, padChar: '.'});
Expand Down Expand Up @@ -523,6 +526,37 @@ export class OvermindConsole {
return ret;
}

static evaluateOutpostEfficiencies(): string {
const colonies = getAllColonies();
const outpostEfficiencies: {[roomName: string]: number} = {};
let avgEnergyPerCPU = 0;

colonies.forEach(colony => {
if (colony.bunker) {
colony.outposts.forEach(outpost => {
const res = ExpansionEvaluator.computeTheoreticalMiningEfficiency(colony.bunker!.anchor, outpost);
if (typeof res === 'boolean') {
log.error(`Failed on outpost ${outpost.print}`);
} else {
outpostEfficiencies[outpost.name] = res;
avgEnergyPerCPU += res;
}
});
}
});

avgEnergyPerCPU = avgEnergyPerCPU/Object.keys(outpostEfficiencies).length;
let ret = `Suspect Outposts +25% below avg efficiency of ${avgEnergyPerCPU}: \n`;

for (const outpost in outpostEfficiencies) {
if (outpostEfficiencies[outpost] < avgEnergyPerCPU*0.75) {
ret += `${outpost} ${outpostEfficiencies[outpost]} \n`;
}
}

return ret;
}

// Memory management ===============================================================================================

static deepCleanMemory(): string {
Expand Down
5 changes: 5 additions & 0 deletions src/creepSetups/CreepSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ export class CreepSetup {
return body;
}

generateMaxedBody() {
// TODO hardcoded for our current cap with extensions missing
return this.generateBody(11100);
}

/**
* Returns the number of parts that a body will have if made from a given colony
*/
Expand Down
119 changes: 118 additions & 1 deletion src/strategy/ExpansionEvaluator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {log} from '../console/log';
import {RoomIntel} from '../intel/RoomIntel';
import {bodyCost} from '../creepSetups/CreepSetup';
import {CombatSetups, Setups} from '../creepSetups/setups';
import {RoomIntel, SourceInfo} from '../intel/RoomIntel';
import {Pathing} from '../movement/Pathing';
import {profile} from '../profiler/decorator';
import {BasePlanner} from '../roomPlanner/BasePlanner';
Expand All @@ -11,6 +13,7 @@ import {
ROOMTYPE_CROSSROAD,
ROOMTYPE_SOURCEKEEPER
} from '../utilities/Cartographer';
import set = Reflect.set;

export const EXPANSION_EVALUATION_FREQ = 500;
export const MIN_EXPANSION_DISTANCE = 2;
Expand Down Expand Up @@ -58,6 +61,120 @@ export class ExpansionEvaluator {
}
}


/**
* Returns the net energy income per cpu spent
* @param dropoffLocation point of origin
* @param room
* @param verbose
*/
static computeTheoreticalMiningEfficiency(dropoffLocation: RoomPosition, room: Room, verbose = false): boolean|number {
const roomName = room.name;
const roomType = Cartographer.roomType(roomName);
let cpuCost = 0;
let creepEnergyCost = 0;
let spawnTimeCost = 0;
const upkeepEnergyCost = 0; // todo later can factor in road damage from all creeps moving

const sourcePositions = RoomIntel.getSourceInfo(roomName);
if (sourcePositions == undefined) {
if (verbose) log.info(`No memory of outpost room: ${roomName}. Aborting score calculation!`);
return false;
}
// Compute Path length
// TODO have it track how many swamp/plain/tunnel
const sourcePathLengths: {[sourcePos: string]: number} = {};
for (const source of sourcePositions) {
if (!source.containerPos) {
log.info(`Can't find container position for source ${source} during efficiency calc`);
return false;
}
// TODO Need to factor in where roads would go
const path = Pathing.findShortestPath(dropoffLocation, source.containerPos,
{ignoreStructures: true, allowHostile: true});

if (path.incomplete) {
log.error(`Couldn't find path to source ${source} for mining efficiency calc`);
return false;
}

sourcePathLengths[source.pos.print] = path.path.length;
}

// Compute Energy Supply
const energyPerSource = roomType == ROOMTYPE_CONTROLLER ? SOURCE_ENERGY_CAPACITY : SOURCE_ENERGY_KEEPER_CAPACITY;

// Compute miner upkeep
for (const source of sourcePositions) {
const setup = roomType == ROOMTYPE_CONTROLLER ? Setups.drones.miners.standard.generateMaxedBody()
: Setups.drones.miners.sourceKeeper.generateMaxedBody();
const effectiveCreepUptime = (CREEP_LIFE_TIME - sourcePathLengths[source.pos.print]);
creepEnergyCost += bodyCost(setup)/effectiveCreepUptime;
spawnTimeCost += setup.length*CREEP_SPAWN_TIME/effectiveCreepUptime;
// Always harvesting, sometimes replacement is moving
cpuCost += 0.2 + 0.2*(1-effectiveCreepUptime/CREEP_LIFE_TIME);
}

// Compute reserver/skReaper upkeep
if (roomType == ROOMTYPE_CONTROLLER) {
const controller = RoomIntel.getControllerInfo(roomName);
if (!controller) {
log.error(`Expansion Efficiency Calc: Can't find controller for room ${roomName}`);
return false;
} else {
const setup = Setups.infestors.reserve.generateMaxedBody();
const controllerPath = Pathing.findShortestPath(dropoffLocation, controller.pos,
{ignoreStructures: true, allowHostile: true});
if (controllerPath.incomplete) {
log.error(`Couldn't find path to controller ${controller} for mining efficiency calc`);
return false;
}
const claimPower = _.filter(setup, (part: BodyPartConstant) => part == CLAIM).length;
const effectiveLifetimeReservationGeneration = (CREEP_CLAIM_LIFE_TIME - controllerPath.path.length) * claimPower;
creepEnergyCost += bodyCost(setup)/effectiveLifetimeReservationGeneration;
spawnTimeCost += setup.length*CREEP_SPAWN_TIME/effectiveLifetimeReservationGeneration;
cpuCost += 0.2 * CREEP_CLAIM_LIFE_TIME / effectiveLifetimeReservationGeneration;
}
} else if (roomType == ROOMTYPE_SOURCEKEEPER) {
// Handle SK
const setup = CombatSetups.zerglings.sourceKeeper.generateMaxedBody();
const skPath = Pathing.findPathToRoom(dropoffLocation, roomName,
{ignoreStructures: true, allowHostile: true});
if (skPath.incomplete) {
log.error(`Couldn't find path to sk room ${roomName} for mining efficiency calc`);
return false;
}
const effectiveCreepUptime = (CREEP_LIFE_TIME - skPath.path.length);
creepEnergyCost += bodyCost(setup)/effectiveCreepUptime;
spawnTimeCost += setup.length*CREEP_SPAWN_TIME/effectiveCreepUptime;
// Increased cost, always moving, frequent attack/move+heal intents, and during overlap 2nd creep moving to room
cpuCost += 0.2 + 0.15 + 0.2*(1-effectiveCreepUptime/CREEP_LIFE_TIME);
// TODO examine for accuracy Increased cost, frequent attack/move+heal intents
}

// Compute transporter upkeep
for (const source of sourcePositions) {
const setup = Setups.transporters.default.generateMaxedBody();
// Calculate amount of hauling each transporter provides in a lifetime
const transporterCarryParts = _.filter(setup, (part: BodyPartConstant) => part == CARRY).length;
const effectiveEnergyTransportedPerTick = transporterCarryParts * CARRY_CAPACITY
/ (2 * sourcePathLengths[source.pos.print]); // round trip
const transportersPerSource = energyPerSource/ENERGY_REGEN_TIME/effectiveEnergyTransportedPerTick;

creepEnergyCost += bodyCost(setup)*transportersPerSource/CREEP_LIFE_TIME;
spawnTimeCost += setup.length*CREEP_SPAWN_TIME*transportersPerSource/CREEP_LIFE_TIME;
cpuCost += 0.2 * transportersPerSource;
}

const netIncome = (energyPerSource*sourcePositions.length/ENERGY_REGEN_TIME)-creepEnergyCost;

let msg = `(Potential) Outpost ${room.print} type ${roomType} evaluated for colony at ${dropoffLocation.roomName} with per tick results \n`;
msg += `Income: ${energyPerSource*sourcePositions.length/ENERGY_REGEN_TIME} Net Income: ${netIncome} Net Energy per CPU: ${netIncome/cpuCost}\n`;
msg += `Creep Costs: Energy ${creepEnergyCost}, Spawn Time ${spawnTimeCost}, and CPU ${cpuCost} \n`;
log.alert(msg);
return netIncome/cpuCost;
}

// Compute the total score for a room
static computeExpansionData(room: Room, verbose = false): boolean {
if (verbose) log.info(`Computing score for ${room.print}...`);
Expand Down

0 comments on commit c1cccf3

Please sign in to comment.