Skip to content

Commit

Permalink
better roadPlanner determinsim; tweaks to workers and expansionPlanner
Browse files Browse the repository at this point in the history
  • Loading branch information
bencbartlett committed Feb 1, 2019
1 parent 0df04d9 commit da466af
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 48 deletions.
24 changes: 22 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ All notable changes to this project will be documented in this file. The format
- New `overload` stat tracks the rolling average of ticks where the hatchery is idle, wants to spawn something, but is unable to because it is being loaded
- RoadPlanner improvements:
- New routing algorithm allows for tunnel placement, although this will be relatively rare due to very high maintenance costs
- Changes to `Colony.destinations` ensures more determinism when recomputing road networks; you should notice a decrease in roads which become deprecated as levels grow or outposts are added
- `RoadPlanner.roadCoverage` property tracks paving completion throughout a colony; transporter bodies will now use this stat rather than colony level to determine when to switch between setups with 1:1 and 2:1 carry:move ratios
- `CombatIntel.isEdgeDancing` uses new info tracked in `RoomIntel` to determine if a likely tower drain attack is occurring; towers will adjust their firing patterns accordingly.
- New information tracked with `RoomIntel`:
- (Approximate) harvesting data and rolling averages of energy/tick over 10k, 100k, and 1M ticks
- Casualty data, with effective energy costs and rolling average of cost/tick over 10k, 100k, 1M ticks
- Creep occupancy data over the last 25 ticks (computed only in owned rooms)
- Safety data, tracking consecutive safe, unsafe ticks and rolling average of safety over last 1k and 10k ticks
- `CombatIntel.isEdgeDancing` uses creep occupancy data tracked in `RoomIntel` to determine if a likely tower drain attack is occurring; towers will adjust their firing patterns accordingly.


### Changed
Expand All @@ -34,6 +40,7 @@ All notable changes to this project will be documented in this file. The format
- Workers will upgrade controllers sooner at higher levels and will spawn when a downgrade is imminent
- Non-stationary managers have fewer move parts in bunker-type colonies
- Reservers allow for a lower reservation buffer and will use the cached reservation info from `RoomIntel` if vision is unavailable
- Queens now spawn with 1:1 move:carry ratios until a storage is built


### Fixed
Expand Down Expand Up @@ -202,6 +209,8 @@ Important notes as of this release:
- Deprecated `DirectiveLogisticsRequest`
- Removed `lodash.minBy` dependencies to reduce compiled codebase size



## Overmind [0.4.1] - 2018.6.15

This patch makes Abathur a little smarter in which reactions he chooses and fixes some bugs accidentally introduced by changes in the last release.
Expand All @@ -220,6 +229,7 @@ This patch makes Abathur a little smarter in which reactions he chooses and fixe
- Fixed a bug in LogisticsNetwork where predicted carry amounts could exceed carry capacity



## Overmind [0.4.0]: "require('more-minerals')" - 2018.6.14

*"We require more minerals."* Well finally, we now have them! This long-overdue release (the largest update to date by additions/deletions) adds fully automatic mineral mining, processing, trading, and boosting capabilities to Overmind!
Expand Down Expand Up @@ -297,7 +307,10 @@ Finally, we now have a [feature request](https://github.com/bencbartlett/Overmin
### Removed
- Removed all contents from `src/deprecated`



## Overmind [0.3.1] - 2018.5.12

### Added
- Workers sign controllers at low RCL

Expand All @@ -308,6 +321,7 @@ Finally, we now have a [feature request](https://github.com/bencbartlett/Overmin
- Removed `LabMineralType` directive as it is no longer relevant



## Overmind [0.3.0]: "Back to base-ics" - 2018.5.9

This release adds a ton of new automation features to base planning, partially overhauling the old RoomPlanner. Once a room plan is set at RCL1, the RoomPlanner never needs to be reopened. Road networks are now continuously re-computed by the new `RoadPlanner` module, allowing you to dynamically add and remove outposts. UpgradeSites now determine their own optimal placements, and UpgradeSites and MiningSites now automatically build links when appropriate.
Expand Down Expand Up @@ -366,7 +380,9 @@ The terminal network has been improved as well, and now tracks transfers between
- Fixed a bug where upgradeSites could misidentify their input in some pathological room layouts



## Overmind [0.2.1] - 2018.3.22

### Added
- Memory stat collection and `User` variable (#3 - thanks CoolFeather2!)
- Brief setup instructions for dashboard
Expand All @@ -381,6 +397,7 @@ The terminal network has been improved as well, and now tracks transfers between
- Moved changelog to root



## Overmind [0.2.0]: "Logistics Logic" - 2018.3.15

This release completely overhauls the logistics system in Overmind, replacing haulers, suppliers, and mineralSuppliers, which functioned on a rigid rules-based system, with a more flexible universal "transport" creep. `LogisticsGroup`s employ a stable matching algorithm to efficiently assign resource collection and supply tasks to transporters, seeking to maximize aggregate change in resources per tick.
Expand All @@ -406,8 +423,11 @@ This release completely overhauls the logistics system in Overmind, replacing ha
release of the Task system)



## Overmind [0.1.0]: "GL HF" - 2018.3.2
This release was initially deployed on 2018.3.2 but was re-versioned on 2018.3.15.

(This release was initially deployed on 2018.3.2 but was re-versioned on 2018.3.15.)

### Added
- Initial pre-release of Overmind after 190 commits and about 80,000 additions.

Expand Down
2 changes: 1 addition & 1 deletion src/Colony.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export class Colony {
repairables: Structure[]; // | Repairable structures, discounting barriers and roads
rechargeables: rechargeObjectType[]; // | Things that can be recharged from
// obstacles: RoomPosition[]; // | List of other obstacles, e.g. immobile creeps
destinations: RoomPosition[];
destinations: { pos: RoomPosition, order: number }[];
// Hive clusters
hiveClusters: HiveCluster[]; // List of all hive clusters
commandCenter: CommandCenter | undefined; // Component with logic for non-spawning structures
Expand Down
2 changes: 1 addition & 1 deletion src/directives/resource/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class DirectiveExtract extends Directive {
constructor(flag: Flag) {
super(flag);
if (this.colony) {
this.colony.destinations.push(this.pos);
this.colony.destinations.push({pos: this.pos, order: this.memory.created || Game.time});
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/directives/resource/harvest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class DirectiveHarvest extends Directive {
super(flag);
if (this.colony) {
this.colony.miningSites[this.name] = this;
this.colony.destinations.push(this.pos);
this.colony.destinations.push({pos: this.pos, order: this.memory.created || Game.time});
}
_.defaultsDeep(this.memory, defaultDirectiveHarvestMemory);
}
Expand Down
2 changes: 1 addition & 1 deletion src/hiveClusters/hatchery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class Hatchery extends HiveCluster {
super(colony, headSpawn, 'hatchery');
// Register structure components
this.memory = Mem.wrap(this.colony.memory, 'hatchery', HatcheryMemoryDefaults, true);
if (this.colony.layout == 'twoPart') this.colony.destinations.push(this.pos);
if (this.colony.layout == 'twoPart') this.colony.destinations.push({pos: this.pos, order: -1});
this.spawns = colony.spawns;
this.availableSpawns = _.filter(this.spawns, spawn => !spawn.spawning);
this.extensions = colony.extensions;
Expand Down
2 changes: 1 addition & 1 deletion src/hiveClusters/sporeCrawler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class SporeCrawler extends HiveCluster {
// }

private preventRampartDecay() {
if (this.colony.level <= 4 && this.towers.length > 0) {
if (this.colony.level < 7 && this.towers.length > 0) {
// expensive to check all rampart hits; only run in intermediate RCL
let dyingRamparts = _.filter(this.room.ramparts, rampart =>
rampart.hits < WorkerOverlord.settings.barrierHits.critical
Expand Down
2 changes: 1 addition & 1 deletion src/hiveClusters/upgradeSite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class UpgradeSite extends HiveCluster {
}
return this.calculateBatteryPos() || log.alert(`Upgrade site at ${this.pos.print}: no batteryPos!`);
});
if (this.batteryPos) this.colony.destinations.push(this.batteryPos);
if (this.batteryPos) this.colony.destinations.push({pos: this.batteryPos, order: 0});
// Register link
$.set(this, 'link', () => this.pos.findClosestByLimitedRange(colony.availableLinks, 3));
this.colony.linkNetwork.claimLink(this.link);
Expand Down
26 changes: 16 additions & 10 deletions src/overlords/core/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,14 @@ export class WorkerOverlord extends Overlord {
barrier => barrier.hits < WorkerOverlord.settings.barrierHits.critical), 10);
// Generate a list of structures needing repairing (different from fortifying except in critical case)
this.repairStructures = $.structures(this, 'repairStructures', () =>
_.filter(this.colony.repairables, function (structure) {
_.filter(this.colony.repairables, structure => {
if (structure.structureType == STRUCTURE_CONTAINER) {
return structure.hits < 0.5 * structure.hitsMax;
// only repair containers in owned room
if (structure.pos.roomName == this.colony.name) {
return structure.hits < 0.5 * structure.hitsMax;
} else {
return false;
}
} else {
return structure.hits < structure.hitsMax;
}
Expand Down Expand Up @@ -137,20 +142,21 @@ export class WorkerOverlord extends Overlord {
} else {
numWorkers = $.number(this, 'numWorkers', () => {
// At higher levels, spawn workers based on construction and repair that needs to be done
const MAX_WORKERS = 3; // Maximum number of workers to spawn
let constructionTicks = _.sum(this.constructionSites,
site => site.progressTotal - site.progress) / BUILD_POWER;
const MAX_WORKERS = 5; // Maximum number of workers to spawn
let buildTicks = _.sum(this.constructionSites,
site => Math.max(site.progressTotal - site.progress, 0)) / BUILD_POWER;
let repairTicks = _.sum(this.repairStructures,
structure => structure.hitsMax - structure.hits) / REPAIR_POWER;
let paveTicks = _.sum(this.colony.rooms, room => this.colony.roadLogistics.energyToRepave(room));
let paveTicks = _.sum(this.colony.rooms,
room => this.colony.roadLogistics.energyToRepave(room)) / 1; // repairCost=1
let fortifyTicks = 0;
if (this.colony.assets.energy > WorkerOverlord.settings.fortifyDutyThreshold) {
fortifyTicks = 0.25 * _.sum(this.fortifyBarriers,
barrier => WorkerOverlord.settings.barrierHits[this.colony.level]
- barrier.hits) / REPAIR_POWER;
fortifyTicks = 0.25 * _.sum(this.fortifyBarriers, barrier =>
Math.max(0, WorkerOverlord.settings.barrierHits[this.colony.level]
- barrier.hits)) / REPAIR_POWER;
}
// max constructionTicks for private server manually setting progress
let numWorkers = Math.ceil(2 * (Math.max(constructionTicks, 0) + repairTicks + fortifyTicks) /
let numWorkers = Math.ceil(2 * (5 * buildTicks + repairTicks + paveTicks + fortifyTicks) /
(workPartsPerWorker * CREEP_LIFE_TIME));
numWorkers = Math.min(numWorkers, MAX_WORKERS);
if (this.colony.controller.ticksToDowngrade <= (this.colony.level >= 4 ? 10000 : 2000)) {
Expand Down
67 changes: 40 additions & 27 deletions src/roomPlanner/RoadPlanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Colony, getAllColonies} from '../Colony';
import {MatrixTypes, Pathing} from '../movement/Pathing';
import {profile} from '../profiler/decorator';
import {$} from '../caching/GlobalCache';
import {getCacheExpiration} from '../utilities/utils';
import {getCacheExpiration, onPublicServer} from '../utilities/utils';

export interface RoadPlannerMemory {
roadLookup: { [roomName: string]: { [roadCoordName: string]: boolean } };
Expand All @@ -22,6 +22,11 @@ export interface RoadPlannerMemory {
}
}

const PLAIN_COST = 3;
const SWAMP_COST = 4;
const WALL_COST = 15 * PLAIN_COST;
const EXISTING_PATH_COST = PLAIN_COST - 1;

let memoryDefaults: RoadPlannerMemory = {
roadLookup : {},
roadCoverage : 0.0,
Expand All @@ -38,10 +43,9 @@ export class RoadPlanner {
costMatrices: { [roomName: string]: CostMatrix };

static settings = {
encourageRoadMerging : true, // will reduce cost of some tiles in existing paths to encourage merging
tileCostReductionInterval : 10, // spatial frequency of tile cost reduction
recalculateRoadNetworkInterval: 1000, // recalculate road networks every (this many) ticks
recomputeCoverageInterval : 500, // recompute coverage to each destination this often
encourageRoadMerging : true,
recalculateRoadNetworkInterval: onPublicServer() ? 3000 : 1000, // recalculate road networks this often
recomputeCoverageInterval : onPublicServer() ? 1000 : 500, // recompute coverage to each destination this often
buildRoadsAtRCL : 4,
};

Expand All @@ -65,12 +69,11 @@ export class RoadPlanner {

private recomputeRoadCoverages(storagePos: RoomPosition) {
// Compute coverage for each path
let destinations = _.sortBy(this.colony.destinations, pos => pos.getMultiRoomRangeTo(storagePos));
for (let destination of destinations) {
let destName = destination.name;
for (let destination of this.colony.destinations) {
let destName = destination.pos.name;
if (!this.memory.roadCoverages[destName] || Game.time > this.memory.roadCoverages[destName].exp) {
log.debug(`Recomputing road coverage from ${storagePos.print} to ${destination.print}...`);
let roadCoverage = this.computeRoadCoverage(storagePos, destination);
log.debug(`Recomputing road coverage from ${storagePos.print} to ${destination.pos.print}...`);
let roadCoverage = this.computeRoadCoverage(storagePos, destination.pos);
if (roadCoverage != undefined) {
// Set expiration to be longer if road is nearly complete
let expiration = roadCoverage.roadCount / roadCoverage.length >= 0.75
Expand All @@ -82,6 +85,18 @@ export class RoadPlanner {
exp : expiration
};
log.debug(`Coverage: ${JSON.stringify(roadCoverage)}`);
} else {
if (this.memory.roadCoverages[destName]) {
// if you already have some data, use it for a little while
this.memory.roadCoverages[destName].exp += 200;
} else {
// otherwise put in a placeholder
this.memory.roadCoverages[destName] = {
roadCount: 0,
length : 1,
exp : Game.time + 100
};
}
}
}
}
Expand Down Expand Up @@ -133,10 +148,10 @@ export class RoadPlanner {
private buildRoadNetwork(storagePos: RoomPosition, obstacles: RoomPosition[]): void {
this.costMatrices = {};
this.roadPositions = [];
let destinations = _.sortBy(this.colony.destinations, pos => pos.getMultiRoomRangeTo(storagePos));
let destinations = _.sortBy(this.colony.destinations, destination => destination.order);
// Connect commandCenter to each destination in colony
for (let pos of destinations) {
this.planRoad(storagePos, pos, obstacles);
for (let destination of destinations) {
this.planRoad(storagePos, destination.pos, obstacles);
}
this.formatRoadPositions();
}
Expand All @@ -155,19 +170,17 @@ export class RoadPlanner {
let matrix = new PathFinder.CostMatrix();
const terrain = Game.map.getRoomTerrain(roomName);

const ROAD_PLANNER_WALL_COST = 50;

for (let y = 0 + 1; y < 50 - 1; ++y) {
for (let x = 0 + 1; x < 50 - 1; ++x) {
switch (terrain.get(x, y)) {
case TERRAIN_MASK_SWAMP:
matrix.set(x, y, 1);
matrix.set(x, y, SWAMP_COST);
break;
case TERRAIN_MASK_WALL:
matrix.set(x, y, ROAD_PLANNER_WALL_COST);
matrix.set(x, y, WALL_COST);
break;
default: // plain
matrix.set(x, y, 1);
matrix.set(x, y, PLAIN_COST);
break;
}
}
Expand Down Expand Up @@ -210,25 +223,25 @@ export class RoadPlanner {
if (Pathing.shouldAvoid(roomName) && roomName != origin.roomName && roomName != destination.roomName) {
return false;
}
return this.generateRoadPlanningCostMatrix(roomName, obstacles);
if (!this.costMatrices[roomName]) {
this.costMatrices[roomName] = this.generateRoadPlanningCostMatrix(roomName, obstacles);
}
return this.costMatrices[roomName];
};

let ret = PathFinder.search(origin, {pos: destination, range: 1}, {roomCallback: callback});
let ret = PathFinder.search(origin, {pos: destination, range: 1}, {roomCallback: callback, maxOps: 40000});

if (ret.incomplete) {
log.warning(`Roadplanner for ${this.colony.print}: could not plan road path!`);
return;
}

// Set every n-th tile of a planned path to be cost 1 to encourage road overlap for future pathing
// Reduce the cost of planned paths to encourage road overlap for future pathing
if (RoadPlanner.settings.encourageRoadMerging) {
let interval = RoadPlanner.settings.tileCostReductionInterval;
for (let i of _.range(ret.path.length)) {
if (i % interval == interval - 1) {
let pos = ret.path[i];
if (this.costMatrices[pos.roomName] && !pos.isEdge) {
this.costMatrices[pos.roomName].set(pos.x, pos.y, 0x01);
}
let pos = ret.path[i];
if (i % 2 == 0 && this.costMatrices[pos.roomName] && !pos.isEdge) {
this.costMatrices[pos.roomName].set(pos.x, pos.y, EXISTING_PATH_COST);
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions src/strategy/ExpansionPlanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ExpansionPlanner {
return false;
}

// compute possible outposts
// compute possible outposts (includes host room)
let possibleOutposts = Cartographer.findRoomsInRange(room.name, 2);

// find source positions
Expand Down Expand Up @@ -109,10 +109,11 @@ export class ExpansionPlanner {
break;
}
if (verbose) log.info(msg + ret.path.length);
roomScore += energyPerSource / ret.path.length;
let offset = 25; // prevents over-sensitivity to very close sources
roomScore += energyPerSource / (ret.path.length + offset);
}
if (valid) {
outpostScores[roomName] = roomScore;
outpostScores[roomName] = Math.floor(roomScore);
}
}

Expand All @@ -122,6 +123,7 @@ export class ExpansionPlanner {
let roomsByScore = _.sortBy(_.keys(outpostScores), roomName => -1 * outpostScores[roomName]);
for (let roomName of roomsByScore) {
if (sourceCount > Colony.settings.remoteSourcesByLevel[8]) break;
let factor = roomName == room.name ? 2 : 1; // weight owned room scores more heavily
totalScore += outpostScores[roomName];
sourceCount += outpostSourcePositions[roomName].length;
}
Expand All @@ -136,6 +138,7 @@ export class ExpansionPlanner {
outposts : outpostScores,
};
}

return true;
}

Expand Down

0 comments on commit da466af

Please sign in to comment.