Skip to content

Commit

Permalink
MiningSites now automatically upgrade their containers to links; bugf…
Browse files Browse the repository at this point in the history
…ixes
  • Loading branch information
bencbartlett committed May 8, 2018
1 parent 750be67 commit 8688d1c
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 131 deletions.
19 changes: 11 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added Grafana dashboard support for new stats (in /assets)
- Lots of automation improvements to the room planner! Now once you create a room plan at RCL 1, you can dynamically add and remove outposts without needing to open/close the planner.
- Road planning is now done with the RoadPlanner, which is instantiated from a room planner, and provides a much higher degree of automation:
- Road planning is continuously recalculated every 1000 ticks with a heuristic that encourages road merging at minimal expense to path length
- Road network is continuously recalculated every 1000 ticks with a heuristic that encourages road merging at minimal expense to path length
- Roads which become deprecated (no longer in the optimal road plan) are allowed to decay
- Road routing hints (white/white flags) no longer have any effect. (The new routing algorithm uses a variant of this idea to merge roads more intelligently.)
- UpgradeSites now automatically calculate their optimal location instead of needing to be placed manually
- MiningSites at outposts now automatically build container outputs without you needing to open and close the room planner for their colony
- UpgradeSites now automatically calculate input location and no longer need to be placed in room planner
- MiningSites at outposts now automatically build container outputs without you needing to reboot room planner
- MiningSites in rooms now automatically upgrade their container to a link if necessary
- Hauling directives and overlords for hauling large amounts of resources long distances (e.g. scavenging from abandoned storage)
- Finished integrating LogisticsRequestDirectives: these act as requestor or provider objects at a position
- Requestor: requests energy to be dropped at a positiion
- Provider: resources dropped at position or in a tombstone to be collected
- Finished coding LogisticsRequestDirectives: these flags act as requestor or provider targets and have a `store` property
- `provider == false`: requests energy to be dropped at a positiion
- `provider == true`: resources dropped at position or in a tombstone to be collected
- Preliminary contract module for making deals between players
- Added `Energetics` module, which will make high-level decisions based on energy distributions
- Colonies now have a `lowPowerMode` operational state, which scales back production of miners and transporters at RCL8 with full storage/terminal
- `Energetics` module, which will make high-level decisions based on energy distributions
- Colonies now have a `lowPowerMode` operational state, which scales back production of miners and transporters at RCL8 with full storage/terminal

### Changed
- Transporters now use a single-sided greedy selection at RCL<4, since stable matching only works well when the transporter carry is a significant fraction of the logisticsRequest target's capacity
Expand All @@ -41,6 +42,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Bugfixes with `TaskDrop`, `TaskGoTo`, `TaskGoToRoom`
- Even more bugfixes with `TaskDrop`
- Fixed bug (hopefully) with creeps not approaching to range 1 in `TaskAttack` and `TaskHeal`
- Fixed bugs where claimers and reservers would get stuck trying to sign a room that was perma-signed by Screeps as a future newbie zone
- Fixed a bug where upgradeSites could misidentify their input in some pathological room layouts

## Overmind [0.2.1] - 2018.3.22
### Added
Expand Down
16 changes: 6 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,24 @@

- See the [changelog](https://github.com/bencbartlett/Overmind/blob/master/CHANGELOG.md) for patch notes.
- Documentation is available in the [wiki](https://github.com/bencbartlett/Overmind/wiki).
- Join the discussion in the [#overmind](https://screeps.slack.com/messages/overmind) Slack channel!
- Development roadmap can be seen [here](https://github.com/bencbartlett/Overmind/projects/1).
- Find me in game [here](https://screeps.com/a/#!/profile/Muon).

---

## What is Screeps?
Screeps is an [MMO strategy game for programmers](https://screeps.com/). The core objective is to expand your colony to gather more resources. The game is played on a massive shared server which runs 24/7, even when you aren't actively playing. To control your units, you program their behavior in JavaScript or any other transpiled language. This is the AI I have been developing for Screeps, themed loosely around the [Zerg's swarm intelligence](http://starcraft.wikia.com/wiki/Overlord) from Starcraft. Creeps belong to [Colonies](https://github.com/bencbartlett/Overmind/blob/master/src/Colony.ts), which have several [Hive Clusters](https://github.com/bencbartlett/Overmind/blob/master/src/hiveClusters/HiveCluster.ts). Creep actions for a given process are orchestrated by [Overlords](https://github.com/bencbartlett/Overmind/blob/master/src/overlords/Overlord.ts). The colony [Overseer](https://github.com/bencbartlett/Overmind/blob/master/src/Overseer.ts) places [Directives](https://github.com/bencbartlett/Overmind/blob/master/src/directives/Directive.ts) to adapt to stimuli.

### We're on slack!
Found something you like, hate, or find confusing? Join the discussion on Slack in the [#overmind](https://screeps.slack.com/messages/overmind) channel!

### Find me in game! (username: Muon)
I've recently respawned to shard2 in the [`EXS4X` sector](https://screeps.com/a/#!/map/shard2?pos=5.826,44.939).

## Using Overmind as your AI
If you're new to Screeps, I wouldn't recommend using Overmind as your AI: most of the fun of the game is programming your own AI and watching your little ant farm run! However, I've tried to make the codebase readable and well-documented, so feel free to fork the project or use it as inspiration when writing your AI.

If you do want to use Overmind as-is, the [latest release](https://github.com/bencbartlett/Overmind/releases) should work right out of the box. However, if you find something broken, please [submit an issue](https://github.com/bencbartlett/Overmind/issues/new) and I'll try to fix it.

### Out of the box
If you just want to run Overmind without modification, you can copy the compiled `main.js` file attached to the [latest release](https://github.com/bencbartlett/Overmind/releases) into your script.
If you just want to run Overmind without modification, you can copy the compiled `main.js` file attached to the [latest release](https://github.com/bencbartlett/Overmind/releases) into your script. Please note that Overmind is not (yet) fully automated; refer to the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for how to run the bot.

### Full installation
### Installing from source
If you want to install the full codebase, download or clone the repository, then navigate to the Overmind root directory and run:

```npm install```
Expand All @@ -38,11 +34,11 @@ To compile and deploy the codebase, create a `screeps.json` file from the [examp
- Compile and deploy to private server: `npm run push-pserver`
- Compile without deploying: `rollup -c`

The deployment scripts are based on [`screeps-typescript-starter`](https://github.com/screepers/screeps-typescript-starter); for additional help, refer to their [GitBook](https://screepers.gitbooks.io/screeps-typescript-starter/getting-started/deploying.html) or [submit an issue](https://github.com/bencbartlett/Overmind/issues/new).
The deployment scripts are based on [`screeps-typescript-starter`](https://github.com/screepers/screeps-typescript-starter); for additional help, refer to their [GitBook](https://screepers.gitbooks.io/screeps-typescript-starter/getting-started/deploying.html) or [submit an issue](https://github.com/bencbartlett/Overmind/issues/new). Please note that while the latest release of Overmind should always be stable, the latest commit may contain unstable features.

# Design overview

Check out the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for in-depth explanations of parts of the design of the AI. (Click the image below to see a higher-resolution version.)
Check out the [Overmind wiki](https://github.com/bencbartlett/Overmind/wiki) for in-depth explanations of parts of the design of the AI. (Click the diagram below to see a higher-resolution version.)

![[AI structural schematic](/assets/AIdiagram.png)](https://raw.githubusercontent.com/bencbartlett/Overmind/master/assets/AIdiagram.png)

48 changes: 1 addition & 47 deletions src/Overmind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,6 @@ export default class Overmind implements IOvermind {
let outpostName = flag.pos.roomName;
this.colonyMap[outpostName] = colonyName; // Create an association between room and colony name
colonyOutposts[colonyName].push(outpostName);

// // TODO: handle observer logic
// let thisRoom = Game.rooms[roomName];
// if (thisRoom) {
// thisRoom.memory.colony = colonyName;
// } else {
// this.invisibleRooms.push(roomName); // register room as invisible to be handled by observer
// }
}
}

Expand Down Expand Up @@ -129,39 +121,6 @@ export default class Overmind implements IOvermind {
}
}

// private handleObservers(): void {
// // Shuffle list of invisible rooms to allow different ones to be observed each tick
// this.invisibleRooms = _.shuffle(this.invisibleRooms);
// // Generate a map of available observers
// let availableObservers: { [colonyName: string]: StructureObserver } = {};
// for (let colonyName in this.Colonies) {
// let colony = this.Colonies[colonyName];
// if (colony.observer) {
// availableObservers[colonyName] = colony.observer;
// }
// }
// // Loop until you run out of rooms to observe or observers
// while (this.invisibleRooms.length > 0 && _.size(availableObservers) > 0) {
// let roomName = this.invisibleRooms.shift();
// if (roomName) {
// let colonyName = this.colonyMap[roomName];
// if (availableObservers[colonyName]) {
// availableObservers[colonyName].observeRoom(roomName);
// delete availableObservers[colonyName];
// } else {
// let observerRooms = _.keys(availableObservers);
// let inRangeRoom = _.find(observerRooms,
// oRoomName => Game.map.getRoomLinearDistance(oRoomName, roomName!)
// <= OBSERVER_RANGE);
// if (inRangeRoom) {
// availableObservers[inRangeRoom].observeRoom(roomName);
// delete availableObservers[colonyName];
// }
// }
// }
// }
// }

/* Global instantiation of Overmind object; run once every global refresh */
build(): void {
this.cache.build();
Expand All @@ -179,12 +138,6 @@ export default class Overmind implements IOvermind {

/* Intialize everything in pre-init phase of main loop. Does not call colony.init(). */
init(): void {
// // The order in which these functions are called is important
// this.verifyMemory();
// this.registerColonies(); // 2: Initialize each colony. Build() is called in main.ts
// this.registerCreeps(); // 4: Wrap all the creeps and assign to respective colonies
// // this.buildColonies(); // 5: Build the colony, instantiating virtual components
// this.registerDirectives(); // 5: Wrap all the directives and assign to respective overlords
for (let colonyName in this.Colonies) {
let start = Game.cpu.getUsed();
this.Colonies[colonyName].init();
Expand Down Expand Up @@ -220,5 +173,6 @@ export default class Overmind implements IOvermind {
Stats.log(`cpu.usage.${colonyName}.visuals`, Game.cpu.getUsed() - start);
}
}

};

98 changes: 80 additions & 18 deletions src/hiveClusters/hiveCluster_miningSite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,24 @@ export class MiningSite extends HiveCluster {
this.registerOutputRequests();
}

/* Calculate where the output will be built for this site */
private calculateOutpotPos(): RoomPosition | undefined {
get outputPos(): RoomPosition | undefined {
if (this.output) {
return this.output.pos;
} else if (this.outputConstructionSite) {
return this.outputConstructionSite.pos;
} else {
if (!this._outputPos) {
this._outputPos = this.calculateContainerPos();
if (!this._outputPos) {
log.warning(`Mining site at ${this.pos.print}: cannot determine outputPos!`);
}
}
return this._outputPos;
}
}

/* Calculate where the container output will be built for this site */
private calculateContainerPos(): RoomPosition | undefined {
let originPos: RoomPosition | undefined = undefined;
if (this.colony.storage) {
originPos = this.colony.storage.pos;
Expand All @@ -132,41 +148,87 @@ export class MiningSite extends HiveCluster {
}
}

get outputPos(): RoomPosition | undefined {
if (this.output) {
return this.output.pos;
} else if (this.outputConstructionSite) {
return this.outputConstructionSite.pos;
} else {
if (!this._outputPos) {
this._outputPos = this.calculateOutpotPos();
if (!this._outputPos) {
log.warning(`Mining site at ${this.pos.print}: cannot determine outputPos!`);
/* Calculate where the link will be built */
private calculateLinkPos(): RoomPosition | undefined {
let originPos: RoomPosition | undefined = undefined;
if (this.colony.storage) {
originPos = this.colony.storage.pos;
} else if (this.colony.roomPlanner.storagePos) {
originPos = this.colony.roomPlanner.storagePos;
}
if (originPos) {
let path = Pathing.findShortestPath(this.pos, originPos).path;
for (let pos of path) {
if (this.source.pos.getRangeTo(pos) == 2) {
return pos;
}
}
return this._outputPos;
}
}

/* Build a container output at the optimal location */
private buildContainerIfMissing(): void {
private buildOutputIfNeeded(): void {
if (!this.output && !this.outputConstructionSite) {
let buildHere = this.outputPos;
if (buildHere) {
let result = buildHere.createConstructionSite(STRUCTURE_CONTAINER);
// Build a link if one is available
let structureType: StructureConstant = STRUCTURE_CONTAINER;
if (this.room == this.colony.room) {
let numLinks = this.colony.links.length +
_.filter(this.colony.constructionSites,
site => site.structureType == STRUCTURE_LINK).length;
let numLinksAllowed = CONTROLLER_STRUCTURES.link[this.colony.level];
if (numLinks < numLinksAllowed &&
this.colony.hatchery && this.colony.hatchery.link &&
this.colony.commandCenter && this.colony.commandCenter.link) {
structureType = STRUCTURE_LINK;
buildHere = this.calculateLinkPos()!; // link pos will definitely be defined if buildHere is defined
}
}
let result = buildHere.createConstructionSite(structureType);
if (result == OK) {
return;
} else {
log.warning(`Mining site at ${this.pos.print}: cannot build output! Result: ${result}`);
log.error(`Mining site at ${this.pos.print}: cannot build output! Result: ${result}`);
}
}
}
}

private destroyContainerIfNeeded(): void {
let storage = this.colony.storage;
let replaceContainerAboveDistance = 10;
// Possibly replace if you are in colony room, have a container output and are sufficiently far from storage
if (this.room == this.colony.room && this.output && this.output instanceof StructureContainer &&
storage && Pathing.distance(this.output.pos, storage.pos) > replaceContainerAboveDistance) {
let numLinks = this.colony.links.length +
_.filter(this.colony.constructionSites, s => s.structureType == STRUCTURE_LINK).length;
let numLinksAllowed = CONTROLLER_STRUCTURES.link[this.colony.level];
let miningSitesInRoom = _.filter(_.values(this.colony.miningSites), (site: MiningSite) =>
site.pos.roomName == this.colony.pos.roomName) as MiningSite[];
let fartherSites = _.filter(miningSitesInRoom, site =>
Pathing.distance(storage!.pos, site.pos) > Pathing.distance(storage!.pos, this.pos));
let everyFartherSiteHasLink = _.every(fartherSites, site => site.output instanceof StructureLink);
// Destroy the output if 1) more links can be built, 2) every farther site has a link and
// 3) hatchery and commandCenter both have links
if (numLinksAllowed - numLinks > 0 && everyFartherSiteHasLink &&
this.colony.hatchery && this.colony.hatchery.link &&
this.colony.commandCenter && this.colony.commandCenter.link) {
this.output.destroy();
}
}

};

/* Run tasks: make output construciton site if needed; build and maintain the output structure */
run(): void {
if (Game.time % 10 == 5) {
this.buildContainerIfMissing();
let rebuildOnTick = 5;
let rebuildFrequency = 10;
if (Game.time % rebuildFrequency == rebuildOnTick - 1) {
this.destroyContainerIfNeeded();
}
if (Game.time % rebuildFrequency == rebuildOnTick) {
this.buildOutputIfNeeded();
}
}

Expand Down
27 changes: 22 additions & 5 deletions src/hiveClusters/hiveCluster_upgradeSite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import {Mem} from '../memory';
import {Visualizer} from '../visuals/Visualizer';
import {log} from '../lib/logger/log';
import {WorkerSetup} from '../creepSetup/defaultSetups';
import {Stats} from '../stats/stats';

interface UpgradeSiteMemory {
input?: { pos: protoPos, tick: number };
stats: { downtime: number };
}

@profile
Expand Down Expand Up @@ -56,7 +58,9 @@ export class UpgradeSite extends HiveCluster {
this.energyPerTick = (_.sum(_.map(this.overlord.upgraders, upgrader => upgrader.getActiveBodyparts(WORK))) +
_.sum(_.map(_.filter(this.colony.getCreepsByRole(WorkerSetup.role),
worker => worker.pos.inRangeTo((this.input || this).pos, 2)),
worker => worker.getActiveBodyparts(WORK)))) * UPGRADE_CONTROLLER_POWER;
worker => worker.getActiveBodyparts(WORK))));
// Compute stats
this.stats();
}

get memory(): UpgradeSiteMemory {
Expand Down Expand Up @@ -159,21 +163,34 @@ export class UpgradeSite extends HiveCluster {
}
}

private stats() {
let defaults = {
downtime: 0,
};
if (!this.memory.stats) this.memory.stats = defaults;
_.defaults(this.memory.stats, defaults);
// Compute downtime
this.memory.stats.downtime = (this.memory.stats.downtime * (CREEP_LIFE_TIME - 1) +
(this.input ? +this.input.isEmpty : 0)) / CREEP_LIFE_TIME;
Stats.log(`colonies.${this.colony.name}.upgradeSite.downtime`, this.memory.stats.downtime);
}

run(): void {
if (Game.time % 25 == 7) {
this.buildContainerIfMissing();
}
}

visuals() {
let info = [];
if (this.controller.level != 8) {
let progress = `${Math.floor(this.controller.progress / 1000)}K`;
let progressTotal = `${Math.floor(this.controller.progressTotal / 1000)}K`;
let percent = `${Math.floor(100 * this.controller.progress / this.controller.progressTotal)}`;
let info = [
`Progress: ${progress}/${progressTotal} (${percent}%)`,
];
Visualizer.showInfo(info, this);
info.push(`Progress: ${progress}/${progressTotal} (${percent}%)`);

}
info.push(`Downtime: ${this.memory.stats.downtime.toPercent()}`);
Visualizer.showInfo(info, this);
}
}
2 changes: 1 addition & 1 deletion src/lib/logger/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export class Log {
out.push(color('WARNING', 'orange'));
break;
case LogLevels.ALERT:
out.push(color('ALERT', 'yellow'));
out.push(color('ALERT ', 'yellow'));
break;
case LogLevels.INFO:
out.push(color('INFO ', 'green'));
Expand Down
Loading

0 comments on commit 8688d1c

Please sign in to comment.