├── .gitattributes ├── src ├── RoomPlanner.js ├── constructionSiteEnhancer.js ├── structures │ ├── Controller.js │ ├── Tower.js │ ├── Wall.js │ ├── Rampart.js │ ├── Extension.js │ ├── Link.js │ ├── _base.js │ └── Spawn.js ├── resource.js ├── roles │ ├── ScoutHarvester.js │ ├── Scout.js │ ├── Claimer.js │ ├── RoadWorker.js │ ├── RemoteCourier.js │ ├── Courier.js │ ├── Miner.js │ ├── Gatherer.js │ ├── Builder.js │ ├── EmergencyHarvester.js │ ├── Wanderer.js │ ├── Upgrader.js │ ├── Reserver.js │ ├── RemoteHarvester.js │ ├── Bootstrapper.js │ ├── Distributor.js │ └── Base.js ├── utils │ ├── body-costs.js │ ├── structure-map.js │ ├── structure-manager.js │ ├── creep-manager.js │ ├── role-map.js │ ├── reporter.js │ └── constants.js ├── controller.js ├── main.js ├── game.js ├── source.js ├── room-position.js ├── flag.js ├── playground.js └── room.js ├── .flowconfig ├── .gitignore ├── webpack.config.js ├── Gruntfile.js ├── scripts ├── fetchTestCases.js └── sanity-check.js ├── package.json └── .eslintrc /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto !eol 2 | 3 | -------------------------------------------------------------------------------- /src/RoomPlanner.js: -------------------------------------------------------------------------------- 1 | export default class RoomPlanner { 2 | static planRoom(room) { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/node_modules/[^s].* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | 9 | [options] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | slave_*.log 4 | npm-debug.log 5 | token.json 6 | testcases.json 7 | .vscode/ 8 | -------------------------------------------------------------------------------- /src/constructionSiteEnhancer.js: -------------------------------------------------------------------------------- 1 | Object.assign(ConstructionSite.prototype, { 2 | needsEnergy() { 3 | return true; 4 | }, 5 | }); -------------------------------------------------------------------------------- /src/structures/Controller.js: -------------------------------------------------------------------------------- 1 | import { STRUCTURE_CONTROLLER } from '../utils/constants'; 2 | 3 | export default class Controller extends StructureController { 4 | static structureType = STRUCTURE_CONTROLLER; 5 | 6 | needsEnergy() { 7 | return true; 8 | } 9 | } -------------------------------------------------------------------------------- /src/structures/Tower.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | 3 | export default class Tower extends StructureTower { 4 | performRole() { 5 | if (this.room.hasHostileCreeps() && !this.isEmpty()) { 6 | this.attack(this.pos.findClosestByRange(this.room.getHostileCreeps())); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/resource.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { Resource } from 'screeps-globals'; 3 | import { RESOURCE_ENERGY } from './utils/constants'; 4 | 5 | Object.assign(Resource.prototype, { 6 | availableEnergy() { 7 | if (this.resourceType === RESOURCE_ENERGY) return this.amount; 8 | return 0; 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/structures/Wall.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | 3 | const REPAIR_TO = 10000000; // ten million 4 | const TOWER_REPAIR_TO = 1000000; // one million 5 | 6 | export default class Wall extends StructureWall { 7 | work() {} // Walls don't work... that's silly. 8 | 9 | needsRepaired() { 10 | return this.hits < REPAIR_TO && this.hits / this.hitsMax < 0.9; 11 | } 12 | 13 | needsTowerRepaired() { 14 | return this.hits < TOWER_REPAIR_TO; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/structures/Rampart.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | 3 | const REPAIR_TO = 10000000; // ten million 4 | const TOWER_REPAIR_TO = 1000000; // one million 5 | 6 | export default class Rampart extends StructureRampart { 7 | // Ramparts don't do any work yet. Maybe in the future they'll unlock for allies. 8 | work() {} 9 | 10 | needsRepaired() { 11 | return this.hits < REPAIR_TO && this.hits / this.hitsMax < 0.9; 12 | } 13 | 14 | needsTowerRepaired() { 15 | return this.hits < TOWER_REPAIR_TO; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/roles/ScoutHarvester.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class ScoutHarvester extends Base { 4 | performRole() { 5 | if (this.findUnvisitedScoutFlags().length > 0) { 6 | this.scout(); 7 | } else { 8 | const sourcesNeedingHarvesters = this.room.getSourcesNeedingHarvesters(); 9 | if (sourcesNeedingHarvesters.length > 0) { 10 | this.memory.role = 'harvester'; 11 | this.memory.oldRole = 'scoutharvester'; 12 | this.memory.source = sourcesNeedingHarvesters[0].id; 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/structures/Extension.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | 3 | export default class Extension extends StructureExtension { 4 | performRole() { 5 | const { x, y, roomName } = this.pos; 6 | const positionsToBuildAt = [ 7 | new RoomPosition(x - 1, y - 1, roomName), 8 | new RoomPosition(x - 1, y + 1, roomName), 9 | ] 10 | if (this.room.canBuildExtension()) { 11 | positionsToBuildAt.forEach(position => { 12 | if (!this.room.controller || position.getRangeTo(this.room.controller) > 2) { 13 | this.room.createConstructionSite(position.x, position.y, STRUCTURE_EXTENSION); 14 | } 15 | }); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/main', 5 | output: { 6 | path: path.resolve('dist/'), 7 | filename: 'main.js', 8 | libraryTarget: 'commonjs2', 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.js$/, 14 | loader: 'babel-loader', 15 | options: { 16 | presets: [ 17 | require.resolve('babel-preset-react'), // React preset is needed only for flow support. 18 | require.resolve('babel-preset-es2015'), 19 | require.resolve('babel-preset-stage-2'), 20 | ], 21 | }, 22 | }, 23 | ], 24 | }, 25 | mode: 'none', 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/body-costs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | export default { 3 | calculateCosts(bodyParts): number { 4 | let cost = 0; 5 | bodyParts.forEach((bodyPart) => { 6 | const part = typeof bodyPart === 'string' ? bodyPart : bodyPart.type; 7 | cost += BODYPART_COST[part]; 8 | }); 9 | 10 | return cost; 11 | }, 12 | 13 | costFor(creepName) { 14 | return this.calculateCosts(Game.creeps[creepName].body); 15 | }, 16 | 17 | perTickCostFor(creepName, distance) { 18 | return this.costFor(creepName) / (1500 - distance); 19 | }, 20 | 21 | perTickCostForCreeps() { 22 | return Object.keys(Game.creeps).reduce((acc, name) => { 23 | acc[name] = this.perTickCostFor(name); 24 | return acc; 25 | }, {}); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.loadNpmTasks('grunt-screeps'); 3 | grunt.loadNpmTasks('grunt-contrib-watch'); 4 | 5 | grunt.initConfig({ 6 | screeps: { 7 | options: { 8 | accountAlias: 'Hatyr', 9 | token: process.env.SCREEPS_TOKEN || require('./token.json'), 10 | branch: 'default', 11 | ptr: false 12 | }, 13 | dist: { 14 | files: [{ 15 | expand: true, 16 | cwd: 'dist/', 17 | src: ['**/*.{js,wasm}'], 18 | flatten: true, 19 | }] 20 | } 21 | }, 22 | watch: { 23 | scripts: { 24 | files: 'dist/**/*.js', 25 | tasks: ['screeps'], 26 | options: { 27 | interrupt: true, 28 | }, 29 | }, 30 | }, 31 | }); 32 | } -------------------------------------------------------------------------------- /src/controller.js: -------------------------------------------------------------------------------- 1 | Object.assign(StructureController.prototype, { 2 | placeFlags() { 3 | const buildAblePositions = [...this.pos.buildablePositionsAtRange(2)]; 4 | const dropSpot = buildAblePositions.find(pos => { 5 | return pos.x === this.pos.x || pos.y === this.pos.y; 6 | }) || buildAblePositions[0]; 7 | this.room.placeFlag(dropSpot, 'CONTROLLER_ENERGY_DROP'); 8 | buildAblePositions.splice(buildAblePositions.indexOf(dropSpot), 1); 9 | buildAblePositions.sort((positionA, positionB) => { 10 | const positionADistance = positionA.getRangeTo(dropSpot); 11 | const positionBDistance = positionB.getRangeTo(dropSpot); 12 | return positionADistance - positionBDistance; 13 | }); 14 | this.room.createBuildFlag(buildAblePositions[0], STRUCTURE_LINK); 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /src/roles/Scout.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class Scout extends Base { 4 | performRole() { 5 | if (this.findUnvisitedScoutFlags().length > 0) { 6 | if (this.room.getDismantleFlag()) { 7 | this.dismantleFlag(this.room.getDismantleFlag()); 8 | } else { 9 | this.scout(); 10 | } 11 | } else if (this.room.getConstructionSites().length && this.carry.energy > 0) { 12 | this.moveToAndBuild(this.pos.findClosestByRange(this.room.getConstructionSites())); 13 | } else if (this.carry.energy === 0) { 14 | const droppedEnergies = this.room.getDroppedEnergy(); 15 | if (droppedEnergies.length > 0) { 16 | this.takeEnergyFrom(droppedEnergies[0]); 17 | } 18 | } else { 19 | this.moveToAndUpgrade(this.room.controller); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scripts/fetchTestCases.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const { ScreepsAPI } = require('screeps-api'); 3 | 4 | const TEST_CASE_PATH = './testcases.json'; 5 | 6 | module.exports = function fetchTestCases() { 7 | if (!fs.existsSync(TEST_CASE_PATH)) { 8 | const api = new ScreepsAPI({ 9 | token: process.env.SCREEPS_TOKEN || require('./token.json'), 10 | protocol: 'https', 11 | hostname: 'screeps.com', 12 | port: 443, 13 | path: '/' // Do no include '/api', it will be added automatically 14 | }); 15 | 16 | return api.memory.get('serializer', 'shard2').then((memory) => { 17 | fs.writeFileSync(TEST_CASE_PATH, JSON.stringify(memory.data, null, 2), 'utf8'); 18 | return memory.data; 19 | }); 20 | } 21 | 22 | return Promise.resolve(JSON.parse(fs.readFileSync(TEST_CASE_PATH))); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/structure-map.js: -------------------------------------------------------------------------------- 1 | import Controller from '../structures/Controller'; 2 | import Extension from '../structures/Extension'; 3 | import Link from '../structures/Link'; 4 | import Spawn from '../structures/Spawn'; 5 | import Tower from '../structures/Tower'; 6 | import Wall from '../structures/Wall'; 7 | import Rampart from '../structures/Rampart'; 8 | 9 | const oldStructureMap = { 10 | [STRUCTURE_EXTENSION]: Extension, 11 | [STRUCTURE_LINK]: Link, 12 | [STRUCTURE_RAMPART]: Rampart, 13 | [STRUCTURE_SPAWN]: Spawn, 14 | [STRUCTURE_TOWER]: Tower, 15 | [STRUCTURE_WALL]: Wall, 16 | }; 17 | 18 | const newStructureList = [ 19 | Controller, 20 | ]; 21 | 22 | export default newStructureList.reduce((acc, StructureClass) => { 23 | acc[StructureClass.structureType] = StructureClass; 24 | return acc; 25 | }, oldStructureMap); 26 | -------------------------------------------------------------------------------- /src/utils/structure-manager.js: -------------------------------------------------------------------------------- 1 | import structureMap from '../utils/structure-map'; 2 | 3 | function enhanceStructure(structure) { 4 | const Constructor = structureMap[structure.structureType]; 5 | if (Constructor) { 6 | return new Constructor(structure.id); 7 | } 8 | return structure; 9 | } 10 | 11 | function convertStructures() { 12 | return Object.entries(Game.rooms).reduce((prevStructures, [roomName, room]) => { 13 | return [ 14 | ...prevStructures, 15 | ...room.find(FIND_STRUCTURES).map(enhanceStructure), 16 | ]; 17 | }, []); 18 | } 19 | 20 | class StructureManager { 21 | structures() { 22 | return convertStructures(); 23 | } 24 | 25 | enhanceStructure(structure) { 26 | return enhanceStructure(structure); 27 | } 28 | } 29 | 30 | const structureManager = new StructureManager(); 31 | export default structureManager; 32 | -------------------------------------------------------------------------------- /src/structures/Link.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | 3 | class Link extends StructureLink { 4 | performRole() { 5 | if (this.isControllerLink() || this.cooldown) return; 6 | this.room.getControllerLinks().find((controllerLink) => { 7 | if (controllerLink.energy < 700) { 8 | this.transferEnergy(controllerLink); 9 | return true; 10 | } 11 | }); 12 | } 13 | 14 | isSourceLink() { 15 | return !this.isControllerLink(); 16 | } 17 | 18 | isControllerLink() { 19 | return this.room.determineControllerLinkLocations().find(pos => { 20 | return pos.isEqualTo(this.pos); 21 | }); 22 | } 23 | 24 | needsEnergy() { 25 | const closestContainer = this.pos.findClosestByRange(this.room.getContainers()); 26 | if (this.pos.getRangeTo(closestContainer) > 2) { 27 | return false; 28 | } 29 | return this.energy < this.energyCapacity; 30 | } 31 | } 32 | 33 | export default Link; 34 | -------------------------------------------------------------------------------- /src/roles/Claimer.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import creepManager from '../utils/creep-manager'; 3 | 4 | export default class Claimer extends Base { 5 | static role = 'claimer' 6 | 7 | static createCreepFor(spawn) { 8 | const creepsWithRole = creepManager.creepsWithRole(this.role).length; 9 | const claimFlags = Game.claimFlags(); 10 | if (!creepsWithRole && claimFlags.length && spawn.room.energyCapacityAvailable >= 650) { 11 | return { 12 | memory: { 13 | role: this.role, 14 | }, 15 | body: [MOVE, CLAIM], 16 | } 17 | } 18 | 19 | return undefined; 20 | } 21 | 22 | performRole() { 23 | if (!Game.claimFlags().length) this.suicide(); 24 | const claimFlag = Game.claimFlags()[0]; 25 | if (claimFlag.pos.roomName !== this.pos.roomName) { 26 | return this.moveTo(claimFlag); 27 | } 28 | return this.moveToAndClaim(this.room.controller); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | // Order here is important. These modify global prototypes. 3 | import 'screeps-perf'; 4 | import game from './game'; 5 | import './source'; 6 | import './room'; 7 | import './structures/_base'; 8 | import './room-position'; 9 | import './flag'; 10 | import './constructionSiteEnhancer'; 11 | import './Resource'; 12 | import './controller'; 13 | import profiler from 'screeps-profiler'; 14 | import { Room } from 'screeps-globals'; 15 | // import { playground } from './playground'; 16 | import reporter from './utils/reporter'; 17 | 18 | profiler.enable(); 19 | console.log('Detected a reset'); 20 | 21 | export function loop() { 22 | if (Room.prototype.work && Game.cpuLimit > 100) { 23 | profiler.wrap(() => { 24 | reporter.wrap(() => { 25 | // playground(() => { 26 | game.setup(); 27 | Object.keys(Game.rooms).forEach(roomName => { 28 | Game.rooms[roomName].work(); 29 | }); 30 | // }); 31 | }); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/creep-manager.js: -------------------------------------------------------------------------------- 1 | import roleMap from '../utils/role-map'; 2 | 3 | function enhanceCreep(creep) { 4 | return new roleMap[creep.memory.role](creep); 5 | } 6 | 7 | function convertCreeps() { 8 | return Object.keys(Game.creeps).map(creepName => { 9 | const creep = Game.creeps[creepName]; 10 | 11 | if (creep) { 12 | return enhanceCreep(creep); 13 | } 14 | }).filter(Boolean); 15 | } 16 | 17 | class CreepManager { 18 | creeps() { 19 | return convertCreeps(); 20 | } 21 | 22 | creepsWithRole(role) { 23 | return this.creeps().filter(creep => { 24 | try { 25 | return creep && creep.memory.role === role; 26 | } catch(e) { 27 | return false; 28 | } 29 | }); 30 | } 31 | 32 | // Occasionally we find a creep that is not enhanced... so we enhance it. 33 | enhanceCreep(creep) { 34 | return enhanceCreep(creep); 35 | } 36 | } 37 | 38 | const creepManager = new CreepManager(); 39 | 40 | Creep.prototype.enhance = function enhance() { 41 | return creepManager.enhanceCreep(this); 42 | }; 43 | 44 | export default creepManager; 45 | -------------------------------------------------------------------------------- /src/roles/RoadWorker.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class RoadWorker extends Base { 4 | static role = 'roadworker' 5 | 6 | static createCreepFor(spawn) { 7 | if (spawn.room.hasDamagedRoads() && !spawn.room.getCreepsWithRole(this.role).length) { 8 | return { 9 | memory: { 10 | role: this.role, 11 | }, 12 | body: [ 13 | MOVE, CARRY, WORK 14 | ], 15 | }; 16 | } 17 | return undefined; 18 | } 19 | 20 | performRole() { 21 | if (this.carry.energy === 0) { 22 | const closestEnergySource = this.pos.findClosestByRange(this.room.getEnergyStockSources()); 23 | if (closestEnergySource) { 24 | this.takeEnergyFrom(closestEnergySource); 25 | } 26 | } else { 27 | const roads = [ 28 | ...this.room.getRoads(), 29 | ...this.room.getContainers(), 30 | ].filter(road => { 31 | return road.hits < road.hitsMax; 32 | }); 33 | if (roads.length) { 34 | const road = this.pos.findClosestByRange(roads); 35 | this.moveToAndRepair(road); 36 | } else { 37 | this.suicide(); 38 | } 39 | } 40 | } 41 | 42 | shouldBeRecycled() { 43 | return this.room.getDamagedRoads().length < 1; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/roles/RemoteCourier.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class RemoteCourier extends Base { 4 | performRole() { 5 | if (this.isFull()) { 6 | this.memory.task = 'dropoff'; 7 | } else if (this.isEmpty()) { 8 | this.memory.task = 'pickup'; 9 | } 10 | 11 | if (this.memory.task === 'pickup') { 12 | this.performPickup(); 13 | } else { 14 | this.performDropOff(); 15 | } 16 | } 17 | 18 | isFull() { 19 | return this.carry.energy === this.carryCapacity; 20 | } 21 | 22 | isEmpty() { 23 | return this.carry.energy === 0; 24 | } 25 | 26 | flag() { 27 | return Game.flags[this.memory.flag]; 28 | } 29 | 30 | performPickup() { 31 | if (!this.flag()) return this.suicide(); 32 | if (this.room.name !== this.flag().pos.roomName) { 33 | this.moveTo(this.flag()); 34 | } else { 35 | const target = this.room.getEnergySourcesThatNeedsStocked()[0]; 36 | this.takeEnergyFrom(target); 37 | } 38 | } 39 | 40 | performDropOff() { 41 | if (this.room !== this.getSpawn().room) { 42 | this.moveTo(this.getSpawn()); 43 | } else { 44 | const storage = this.room.getStorage(); 45 | if (storage) { 46 | this.deliverEnergyTo(storage); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/roles/Courier.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class Courier extends Base { 4 | performRole() { 5 | const potentialTargets = this.room.getStructuresNeedingEnergy(); 6 | let dumpTarget = this.pos.findClosestByRange(potentialTargets); 7 | 8 | if (this.carry.energy === this.carryCapacity) { 9 | this.memory.task = 'deliver'; 10 | } else if (!dumpTarget || this.carry.energy === 0) { 11 | this.memory.task = 'pickup'; 12 | } 13 | 14 | if (!dumpTarget) { 15 | dumpTarget = this.room.getControllerEnergyDropFlag(); 16 | } 17 | 18 | if (this.memory.task === 'pickup') { 19 | if (!this.memory.target) { 20 | const target = this.room.getEnergySourcesThatNeedsStocked()[0]; 21 | this.memory.target = target ? target.id : ''; 22 | } 23 | 24 | if (this.memory.target) { 25 | const target = Game.getObjectById(this.memory.target); 26 | let result; 27 | if (target) { 28 | result = this.takeEnergyFrom(target); 29 | } 30 | if (!target || result === 0) { 31 | this.memory.target = ''; 32 | } 33 | } else { 34 | this.deliverEnergyTo(dumpTarget); 35 | } 36 | } else { 37 | this.deliverEnergyTo(dumpTarget); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screeps", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gdborton/screeps.git" 12 | }, 13 | "author": "Gary Borton ", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/gdborton/screeps/issues" 17 | }, 18 | "homepage": "https://github.com/gdborton/screeps#readme", 19 | "private": true, 20 | "devDependencies": { 21 | "babel-core": "^6.5.2", 22 | "babel-eslint": "^6.0.2", 23 | "babel-loader": "^7.1.5", 24 | "babel-preset-es2015": "^6.5.0", 25 | "babel-preset-react": "^6.5.0", 26 | "babel-preset-stage-2": "^6.5.0", 27 | "eslint": "^2.2.0", 28 | "eslint-config-airbnb": "^6.0.0", 29 | "eslint-plugin-flowtype": "^2.1.0", 30 | "eslint-plugin-react": "^4.0.0", 31 | "grunt": "^1.0.3", 32 | "grunt-cli": "^1.3.1", 33 | "grunt-contrib-watch": "^1.1.0", 34 | "grunt-screeps": "^1.4.0", 35 | "screeps-api": "^1.7.2", 36 | "screeps-globals": "0.0.1", 37 | "screeps-perf": "0.0.1", 38 | "screeps-profiler": "^1.1.2", 39 | "webpack": "^4.17.1" 40 | }, 41 | "dependencies": { 42 | "expect": "^23.5.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/roles/Miner.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class extends Base { 4 | static role = 'miner'; 5 | 6 | static body = [ 7 | MOVE, 8 | WORK, WORK, WORK, WORK, WORK, 9 | ]; 10 | 11 | static createCreepFor(spawn) { 12 | const creeps = spawn.room.myCreeps(); 13 | const miners = spawn.room.getCreepsWithRole(this.role); 14 | const sources = spawn.room.getSources(); 15 | // if we're the only creep, don't build another one. 16 | if (miners === 1 && creeps === 1) { 17 | return undefined; 18 | } 19 | if (spawn.room.hasContainersConfigured() && miners.length < sources.length) { 20 | const source = sources.find((source) => { 21 | return !miners.find(miner => miner.memory.source === source.id); 22 | }); 23 | return { 24 | memory: { 25 | role: this.role, 26 | source: source.id, 27 | }, 28 | body: this.body, 29 | }; 30 | } 31 | return undefined; 32 | } 33 | 34 | performRole() { 35 | const source = this.targetSource(); 36 | const container = this.room.getContainers().find((container) => { 37 | return container.pos.getRangeTo(source) === 1; 38 | }); 39 | 40 | if (container && this.pos.getRangeTo(container) > 0) { 41 | return this.moveTo(container); 42 | } 43 | return this.moveToAndHarvest(source); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/roles/Gatherer.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import { MOVE, CARRY, RESOURCE_ENERGY } from '../utils/constants'; 3 | 4 | export default class Gatherer extends Base { 5 | static role = 'gatherer' 6 | 7 | performRole() { 8 | if (!this.isFull()) { 9 | let currentTarget = Game.getObjectById(this.memory.target); 10 | if (!currentTarget || currentTarget.totalUtilizedCapacity() < this.availableSpace()) { 11 | const targetContainer = this.room.getContainers().reduce((prev, container) => { 12 | if (!prev || container.totalUtilizedCapacity() > prev.totalUtilizedCapacity()) { 13 | return container; 14 | } 15 | return prev; 16 | }, undefined); 17 | 18 | this.memory.target = targetContainer.id; 19 | currentTarget = targetContainer; 20 | } 21 | return this.takeEnergyFrom(currentTarget); 22 | } else { 23 | const sourceLinks = this.room.getLinks().filter(link => link.needsEnergy()); 24 | if (sourceLinks.length) { 25 | return this.deliverEnergyTo(this.pos.findClosestByRange(sourceLinks)); 26 | } 27 | } 28 | } 29 | 30 | static createCreepFor(spawn) { 31 | if (spawn.room.hasLinksConfigured() && !spawn.room.getCreepsWithRole(this.role).length) { 32 | return { 33 | memory: { 34 | role: this.role, 35 | }, 36 | body: [MOVE, CARRY], 37 | }; 38 | } 39 | return undefined; 40 | } 41 | } -------------------------------------------------------------------------------- /src/roles/Builder.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class Builder extends Base { 4 | static role = 'builder' 5 | 6 | energySources() { 7 | return [ 8 | ...this.room.getControllerLinks(), 9 | ...[this.room.getStorage()], 10 | ].filter(Boolean); 11 | } 12 | 13 | performRole() { 14 | if (this.isEmpty()) { 15 | return this.gatherEnergy(); 16 | } 17 | this.attemptToUpgrade(); 18 | const constructionSites = this.room.getConstructionSites(); 19 | if (constructionSites.length) { 20 | const closestConstructionSite = this.pos.findClosestByRange(constructionSites); 21 | this.moveToAndBuild(closestConstructionSite); 22 | } else if (this.memory.target) { 23 | const target = Game.getObjectById(this.memory.target); 24 | if (target.hits < target.hitsMax) { 25 | this.moveToAndRepair(target); 26 | } else { 27 | this.memory.target = null; 28 | } 29 | } else { 30 | const damagedStructures = this.room.getStructures().sort((structureA, structureB) => { 31 | return (structureA.hits / structureA.hitsMax) - (structureB.hits / structureB.hitsMax); 32 | }); 33 | 34 | if (damagedStructures.length) { 35 | this.memory.target = damagedStructures[0].id; 36 | } 37 | } 38 | } 39 | 40 | shouldBeRecycled() { 41 | return this.room.getConstructionSites().length < 1; 42 | } 43 | 44 | needsEnergy() { 45 | return this.isEmpty(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/role-map.js: -------------------------------------------------------------------------------- 1 | import Base from '../roles/Base'; 2 | import Bootstrapper from '../roles/Bootstrapper'; 3 | import Builder from '../roles/Builder'; 4 | import Claimer from '../roles/Claimer'; 5 | import Courier from '../roles/Courier'; 6 | import Distributor from '../roles/Distributor'; 7 | import Gatherer from '../roles/Gatherer'; 8 | import EmergencyHarvester from '../roles/EmergencyHarvester'; 9 | import RemoteCourier from '../roles/RemoteCourier'; 10 | import RemoteHarvester from '../roles/RemoteHarvester'; 11 | import Reserver from '../roles/Reserver'; 12 | import RoadWorker from '../roles/RoadWorker'; 13 | import Scout from '../roles/Scout'; 14 | import ScoutHarvester from '../roles/ScoutHarvester'; 15 | import Upgrader from '../roles/Upgrader'; 16 | import Wanderer from '../roles/Wanderer'; 17 | import Miner from '../roles/Miner'; 18 | 19 | 20 | /** 21 | * These are run in order of importance. The first creep that says yes, gets 22 | * built. Generally emergency type Roles should be placed higher on the list. 23 | */ 24 | export const roleList = [ 25 | EmergencyHarvester, 26 | Miner, 27 | Distributor, 28 | Bootstrapper, 29 | Courier, 30 | Builder, 31 | Upgrader, 32 | Gatherer, 33 | RemoteCourier, 34 | RemoteHarvester, 35 | Reserver, 36 | Scout, 37 | ScoutHarvester, 38 | Claimer, 39 | RoadWorker, 40 | Wanderer, 41 | Base, 42 | ]; 43 | 44 | export default roleList.reduce((acc, Role) => { 45 | acc[Role.role] = Role; 46 | return acc; 47 | },{}); 48 | -------------------------------------------------------------------------------- /src/roles/EmergencyHarvester.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import Miner from './Miner'; 3 | import bodyCosts from '../utils/body-costs'; 4 | 5 | export default class EmergencyHarvester extends Base { 6 | static role = 'emergencyharvester' 7 | static body = [ 8 | MOVE, CARRY, MOVE, CARRY, WORK 9 | ]; 10 | 11 | static createCreepFor(spawn) { 12 | const miners = spawn.room.getCreepsWithRole(Miner.role).length; 13 | const emergencyHarvesters = spawn.room.getCreepsWithRole(this.role).length; 14 | const minerCost = bodyCosts.calculateCosts(Miner.body); 15 | const canAffordMiner = spawn.room.energyAvailable > minerCost; 16 | let target = 1; 17 | if (spawn.room.energyCapacityAvailable < minerCost) { 18 | target = spawn.pos.findClosestByRange(spawn.room.getSources()).freeEdges(); 19 | } 20 | if (emergencyHarvesters < target && ((miners < 1 && !canAffordMiner) || !spawn.room.hasContainersConfigured())) { 21 | return { 22 | memory: { 23 | role: this.role, 24 | }, 25 | body: this.body, 26 | } 27 | } 28 | return undefined; 29 | } 30 | 31 | performRole() { 32 | if (this.isEmpty()) { 33 | this.memory.task = 'harvest'; 34 | } else if (this.isFull()) { 35 | this.memory.task = 'spend'; 36 | } 37 | 38 | if (this.memory.task === 'spend') { 39 | return this.spendResources(); 40 | } 41 | 42 | return this.moveToAndHarvest(this.pos.findClosestByRange(this.room.getSources())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/roles/Wanderer.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import creepManager from '../utils/creep-manager'; 3 | 4 | // Wanderer wanders aimlessly in an attempt to generate a value for each room in the world. 5 | export default class Wanderer extends Base { 6 | static role = 'wanderer' 7 | 8 | static createCreepFor(spawn) { 9 | if (creepManager.creepsWithRole(this.role).length < 1) { 10 | return { 11 | memory: { 12 | role: this.role, 13 | }, 14 | body: [MOVE], 15 | } 16 | } 17 | return creepManager.creepsWithRole(this.role).length < 1; 18 | } 19 | 20 | performRole() { 21 | const target = this.aquireTarget(); 22 | const result = this.moveTo(target); 23 | if (result !== 0) { // couldn't move, doesn't matter the reason. 24 | this.memory.targetExit = undefined; 25 | } 26 | } 27 | 28 | aquireTarget() { 29 | if (!this.memory.targetExit || this.memory.targetExit.room !== this.room.name) { 30 | this.room.attemptReserve(); // we're in a new room, attempt to reserve. 31 | const targetExit = [...this.room.getUniqueExitPoints()].sort(() => { // return a random exit. 32 | return Math.floor(Math.random() * 3) - 1; 33 | })[0]; 34 | this.memory.targetExit = { 35 | room: this.room.name, 36 | x: targetExit.x, 37 | y: targetExit.y, 38 | }; 39 | } 40 | const targetExit = this.memory.targetExit; 41 | return new RoomPosition(targetExit.x, targetExit.y, targetExit.room); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /scripts/sanity-check.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | const { predictNextState } = require('../src/playground'); 3 | const fetchTestCases = require('./fetchTestCases'); 4 | 5 | fetchTestCases().then((testCaseMap) => { 6 | const testCases = Object.keys(testCaseMap).map((key) => { 7 | return { 8 | tickTime: parseInt(key), 9 | ...testCaseMap[key], 10 | }; 11 | }); 12 | 13 | testCases.forEach(({ state, intents, tickTime }, index) => { 14 | if (index === testCases.length - 1) return; 15 | const predictedNextState = predictNextState(state, intents); 16 | const actualNextState = testCaseMap[tickTime + 1].state; 17 | 18 | Object.entries(predictedNextState.gameObjects).forEach(([objectId, predictedGameObject]) => { 19 | const actualGameObject = actualNextState.gameObjects[objectId]; 20 | try { 21 | expect(predictedGameObject).toEqual(actualGameObject); 22 | } catch(e) { 23 | console.log(e.message); 24 | console.log('failed diff for', objectId); 25 | console.log('prev state', state.gameObjects[objectId]); 26 | console.log('new state', actualGameObject); 27 | console.log('tick time', tickTime); 28 | console.log('intents from', intents.filter(intent => intent.objectId === objectId)); 29 | const upon = intents 30 | .filter(intent => intent.targetId === objectId) 31 | .map((intent) => { 32 | return { 33 | ...intent, 34 | actor: state.gameObjects[intent.objectId], 35 | }; 36 | }); 37 | console.log('intents upon', JSON.stringify(upon, null, 2)); 38 | process.exit(); 39 | } 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/roles/Upgrader.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | import { MOVE, CARRY, WORK } from '../utils/constants'; 4 | 5 | export default class Upgrader extends Base { 6 | static role = 'upgrader' 7 | 8 | static createCreepFor(spawn) { 9 | if (spawn.room.getConstructionSites().length) return undefined; 10 | const creepsWithRole = spawn.room.getCreepsWithRole(this.role).length; 11 | let target = spawn.room.maxEnergyProducedPerTick() / 4; 12 | const storage = spawn.room.getStorage(); 13 | if (storage && storage.availableEnergy() < 100000) { 14 | target = 1; 15 | } 16 | if (creepsWithRole < target) { 17 | return { 18 | memory: { 19 | role: this.role, 20 | }, 21 | body: [ 22 | MOVE, 23 | CARRY, 24 | WORK, WORK, WORK, WORK 25 | ], 26 | }; 27 | } 28 | return undefined; 29 | } 30 | 31 | performRole() { 32 | const { controller } = this.room; 33 | if (controller) { 34 | const storage = this.room.getStorage(); 35 | const inRangeOfController = this.pos.inRangeTo(controller, 3); 36 | const nextToStorage = storage && this.pos.isNearTo(storage); 37 | if (inRangeOfController && nextToStorage) { 38 | if (this.isEmpty()) { 39 | return this.takeEnergyFrom(storage); 40 | } 41 | return this.upgradeController(controller); 42 | } 43 | return this.moveToAndUpgrade(controller); 44 | } 45 | } 46 | 47 | needsEnergy() { 48 | const { controller } = this.room; 49 | if (!(controller && controller.my)) return false; 50 | const storage = this.room.getStorage(); 51 | if (storage && this.pos.isNearTo(storage)) return false; 52 | return this.isEmpty() && this.pos.inRangeTo(controller, 3); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/roles/Reserver.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import creepManager from '../utils/creep-manager'; 3 | import { MOVE, CLAIM } from '../utils/constants'; 4 | 5 | export default class Reserver extends Base { 6 | static role = 'reserver' 7 | 8 | static createCreepFor(spawn) { 9 | return spawn.room.getReserveFlags().reduce((prev, flag) => { 10 | if (prev) return prev; 11 | if (flag.room && flag.room.controller && flag.room.controller.my) return prev; 12 | const creepForFlag = creepManager.creepsWithRole(this.role).find((creep) => { 13 | return creep.memory.targetFlag === flag.name; 14 | }); 15 | 16 | if (!creepForFlag) { 17 | return { 18 | memory: { 19 | role: this.role, 20 | targetFlag: flag.name, 21 | }, 22 | body: [MOVE, CLAIM] 23 | } 24 | } 25 | }, undefined); 26 | } 27 | 28 | performRole() { 29 | const flag = Game.flags[this.memory.targetFlag]; 30 | if (!flag) { 31 | return this.suicide(); 32 | } 33 | if (this.room.name !== flag.pos.roomName) { 34 | return this.moveTo(flag); 35 | } 36 | const myRooms = Object.entries(Game.rooms).filter(([_, room]) => room.controller && room.controller.my); 37 | const shouldClaim = myRooms.length < Game.gcl.level; 38 | if (shouldClaim) { 39 | return this.moveToAndClaim(this.room.controller); 40 | } 41 | return this.moveToAndReserve(this.room.controller); 42 | } 43 | 44 | moveToAndReserve(controller) { 45 | if (this.pos.getRangeTo(controller) > 1) { 46 | this.moveTo(controller); 47 | } else { 48 | this.reserveController(controller); 49 | } 50 | } 51 | 52 | moveToAndClaim(controller) { 53 | if (this.pos.getRangeTo(controller) > 1) { 54 | this.moveTo(controller); 55 | } else { 56 | this.claimController(controller); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/roles/RemoteHarvester.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | 3 | export default class RemoteHarvester extends Base { 4 | static role = 'remoteharvester' 5 | 6 | performRole() { 7 | const flag = Game.flags[this.memory.flag]; 8 | if (!flag) { 9 | this.memory.role = 'harvester'; 10 | return; 11 | } 12 | const targetRoomName = flag.pos.roomName; 13 | if (this.room.name !== targetRoomName) { 14 | return this.moveTo(flag); 15 | } else { 16 | if (!this.memory.source) { 17 | this.acquireTarget(); 18 | } 19 | const inRange = thing => this.pos.getRangeTo(thing) < 4; 20 | const constructionSites = this.room.getConstructionSites().filter(inRange); 21 | const buildAbility = this.body.reduce((prev, { type }) => { 22 | if (type === WORK) return prev + 5; 23 | return prev; 24 | }, 0); 25 | 26 | if (constructionSites.length && this.carry[RESOURCE_ENERGY] > buildAbility) { 27 | this.moveToAndBuild(constructionSites[0]); 28 | } else if (!this.isFull()) { 29 | return this.moveToAndHarvest(this.targetSource()); 30 | } else { 31 | return this.handleFull(); 32 | } 33 | } 34 | } 35 | 36 | handleFull() { 37 | const inRange = thing => this.pos.getRangeTo(thing) < 4; 38 | const container = this.room.getContainers().find(inRange); 39 | if (container) { 40 | if (container.needsRepaired()) { 41 | this.moveToAndRepair(container); // repair if needed. 42 | } else { 43 | if (!container.isFull()) { 44 | return this.deliverEnergyTo(container); 45 | } 46 | return this.drop(RESOURCE_ENERGY); 47 | } 48 | } 49 | } 50 | 51 | isFull() { 52 | return !(this.carry.energy < this.carryCapacity || this.carry.energy === 0); 53 | } 54 | 55 | acquireTarget() { 56 | this.memory.source = this.room.getSourcesNeedingHarvesters()[0].id; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "rules": { 5 | "newline-per-chained-call": 0, 6 | "arrow-body-style": 0 7 | }, 8 | "globals": { 9 | "BODYPART_COST": true, 10 | "CREEP_LIFE_TIME": true, 11 | "Game": true, 12 | "Creep": true, 13 | "LOOK_CREEPS": true, 14 | "StructureController": true, 15 | "StructureExtension": true, 16 | "StructureLink": true, 17 | "StructureRampart": true, 18 | "StructureSpawn": true, 19 | "StructureTower": true, 20 | "StructureWall": true, 21 | "Room": true, 22 | "RoomPosition": true, 23 | "Source": true, 24 | "Spawn": true, 25 | "Structure": true, 26 | "Flag": true, 27 | "MOVE": true, 28 | "WORK": true, 29 | "CARRY": true, 30 | "CLAIM": true, 31 | "ATTACK": true, 32 | "RANGED_ATTACK": true, 33 | "HEAL": true, 34 | "TOUGH": true, 35 | "Energy": true, 36 | "COLOR_YELLOW": true, 37 | "CONTROLLER_STRUCTURES": true, 38 | "FIND_CONSTRUCTION_SITES": true, 39 | "FIND_DROPPED_Resources": true, 40 | "FIND_EXIT": true, 41 | "FIND_FLAGS": true, 42 | "FIND_HOSTILE_CREEPS": true, 43 | "FIND_MINERALS": true, 44 | "FIND_MY_CREEPS": true, 45 | "FIND_MY_SPAWNS": true, 46 | "FIND_MY_STRUCTURES": true, 47 | "FIND_SOURCES": true, 48 | "FIND_STRUCTURES": true, 49 | "RESOURCE_ENERGY": true, 50 | "STRUCTURE_CONTAINER": true, 51 | "STRUCTURE_EXTENSION": true, 52 | "STRUCTURE_EXTRACTOR": true, 53 | "STRUCTURE_LINK": true, 54 | "STRUCTURE_OBSERVER": true, 55 | "STRUCTURE_RAMPART": true, 56 | "STRUCTURE_ROAD": true, 57 | "STRUCTURE_SPAWN": true, 58 | "STRUCTURE_STORAGE": true, 59 | "STRUCTURE_TERMINAL": true, 60 | "STRUCTURE_TOWER": true, 61 | "STRUCTURE_WALL": true, 62 | "Memory": true, 63 | "TOP": true, 64 | "TOP_RIGHT": true, 65 | "RIGHT": true, 66 | "BOTTOM_RIGHT": true, 67 | "BOTTOM": true, 68 | "BOTTOM_LEFT": true, 69 | "LEFT": true, 70 | "TOP_LEFT": true 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/roles/Bootstrapper.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import creepManager from '../utils/creep-manager'; 3 | 4 | export default class Bootstrapper extends Base { 5 | static role = 'bootstrapper' 6 | static createCreepFor(spawn) { 7 | const allBootstrappers = creepManager.creepsWithRole(this.role); 8 | const roomsWithoutSpawns = Game.roomArray().filter((room) => { 9 | return room.controller && room.controller.my && room.getSpawns().length === 0; 10 | }); 11 | const roomNeedingBootstrapper = roomsWithoutSpawns.find(roomWithoutSpawn => { 12 | const bootstrappersServingRoom = allBootstrappers.filter(bootstrapper => { 13 | return bootstrapper.memory.target === roomWithoutSpawn.name; 14 | }); 15 | let bootstrappersNeededForRoom = roomWithoutSpawn.controller.pos.findClosestByRange(roomWithoutSpawn.getSources()).freeEdges(); 16 | bootstrappersNeededForRoom = Math.min(2, bootstrappersNeededForRoom); 17 | return bootstrappersServingRoom.length < bootstrappersNeededForRoom; 18 | }); 19 | if (roomNeedingBootstrapper) { 20 | return { 21 | memory: { 22 | role: this.role, 23 | target: roomNeedingBootstrapper.name, 24 | }, 25 | body: [ 26 | WORK, WORK, WORK, 27 | MOVE, MOVE, MOVE, 28 | CARRY 29 | ], 30 | }; 31 | } 32 | } 33 | 34 | performRole() { 35 | const room = Game.rooms[this.memory.target]; 36 | const constructionSites = room.getConstructionSites(); 37 | const sources = room.getSources(); 38 | const hostileStructures = room.getHostileStructures(); 39 | 40 | if (hostileStructures.length) { 41 | return this.moveToAndDismantle(hostileStructures[0]); 42 | } 43 | if (this.isEmpty()) { 44 | this.memory.task = 'gather'; 45 | } else if (this.isFull()) { 46 | this.memory.task = 'build'; 47 | } 48 | if (this.memory.task === 'gather') { 49 | const targetSource = room.controller.pos.findClosestByRange(sources); 50 | return this.moveToAndHarvest(targetSource); 51 | } 52 | return this.moveToAndBuild(this.pos.findClosestByRange(constructionSites)); 53 | } 54 | } -------------------------------------------------------------------------------- /src/game.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import bodyCosts from './utils/body-costs'; 4 | import RoomPlanner from './RoomPlanner'; 5 | 6 | function getFlagsOfType(type) { 7 | return Game.flagArray().filter(flag => { 8 | return flag.name.toLowerCase().indexOf(type) !== -1; 9 | }); 10 | } 11 | 12 | let scoutFlags; 13 | const roomDistanceMap = {}; 14 | const enhancedGame = { 15 | roomPlanner() { 16 | return RoomPlanner; 17 | }, 18 | 19 | flagArray() { 20 | return Object.keys(Game.flags).map(flagName => { 21 | return Game.flags[flagName]; 22 | }); 23 | }, 24 | 25 | roomArray() { 26 | return Object.keys(Game.rooms).map(roomName => Game.rooms[roomName]); 27 | }, 28 | 29 | myRooms() { 30 | return Game.roomArray().filter(room => room.getControllerOwned()); 31 | }, 32 | 33 | clearScoutFlags() { 34 | Game.getScoutFlags().forEach(flag => { 35 | flag.remove(); 36 | }); 37 | }, 38 | 39 | clearAllFlags() { 40 | Game.flagArray().forEach(flag => { 41 | flag.remove(); 42 | }); 43 | }, 44 | 45 | getScoutFlags() { 46 | if (scoutFlags === undefined) { 47 | scoutFlags = getFlagsOfType('scout'); 48 | } 49 | 50 | return scoutFlags; 51 | }, 52 | 53 | dismantleFlags() { 54 | return getFlagsOfType('dismantle'); 55 | }, 56 | 57 | claimFlags() { 58 | return getFlagsOfType('claim'); 59 | }, 60 | 61 | activeRoom(roomName) { 62 | if(roomName) { 63 | Memory.activeRoom = roomName; 64 | } 65 | 66 | return Game.rooms[Memory.activeRoom]; 67 | }, 68 | 69 | getClosestOwnedRoomTo(targetRoomName) { 70 | if (!roomDistanceMap[targetRoomName]) { 71 | roomDistanceMap[targetRoomName] = Object.keys(Game.rooms).sort((roomNameA, roomNameB) => { 72 | const roomA = Game.rooms[roomNameA]; 73 | const roomB = Game.rooms[roomNameB]; 74 | return roomA.distanceToRoom(targetRoomName) - roomB.distanceToRoom(targetRoomName); 75 | })[0]; 76 | } 77 | return roomDistanceMap[targetRoomName]; 78 | }, 79 | 80 | clearConstructionSites() { 81 | Object.keys(Game.rooms).forEach(roomName => { 82 | const room = Game.rooms[roomName]; 83 | room.clearConstructionSites(); 84 | }); 85 | }, 86 | 87 | bodyCosts, 88 | }; 89 | 90 | export default { 91 | setup() { 92 | scoutFlags = undefined; 93 | Object.assign(Game, enhancedGame); 94 | }, 95 | }; 96 | -------------------------------------------------------------------------------- /src/source.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { Source } from 'screeps-globals'; 3 | 4 | Object.assign(Source.prototype, { 5 | // Finds and returns the number of open spots next to the source. 6 | freeEdges() { 7 | return this.pos.freeEdges(); 8 | }, 9 | 10 | needsHarvesters() { 11 | const harvesters = this.room.myCreeps(); 12 | let myHarvesters = 0; 13 | let workParts = 0; 14 | harvesters.forEach(harvester => { 15 | if (harvester.memory.source === this.id) { 16 | myHarvesters++; 17 | workParts = workParts + harvester.body.filter(bodyPart => { 18 | return bodyPart.type === 'work'; 19 | }).length; 20 | } 21 | }); 22 | 23 | return workParts < 5 && myHarvesters < this.freeEdges(); 24 | }, 25 | 26 | hasContainer() { 27 | return !!this.room.getContainers().find(container => container.pos.getRangeTo(this) === 1); 28 | }, 29 | 30 | hasTowerFlag() { 31 | return !!this.room.getTowerFlags().find(flag => { 32 | return flag.pos.getRangeTo(this.pos) < 3; 33 | }); 34 | }, 35 | 36 | getBuildablePositions() { 37 | const range2Positions = this.pos.buildablePositionsAtRange(2); 38 | const range1Positions = this.pos.buildablePositionsAtRange(); 39 | return range2Positions.filter(position => { 40 | return !!range1Positions.find(range1Position => { 41 | return range1Position.getRangeTo(position) === 1; 42 | }); 43 | }); 44 | }, 45 | 46 | placeFlags() { 47 | const buildablePositions = this.getBuildablePositions().sort((positionA, positionB) => { 48 | return this.pos.actualDistanceTo(positionA) - this.pos.actualDistanceTo(positionB); 49 | }); 50 | buildablePositions.pop(); // leave the last position as a walkway. 51 | 52 | buildablePositions.forEach((position, index) => { 53 | if (index === 0) { 54 | this.room.placeLinkFlag(position); 55 | } else if (index === 1 && this.isNearestToController()) { 56 | this.room.placeStorageFlag(position); 57 | } else { 58 | this.room.placeTowerFlag(position); 59 | } 60 | }); 61 | }, 62 | 63 | isNearestToController() { 64 | if (this._isNearestToController === undefined) { 65 | const sources = [...this.room.getSources()].sort((sourceA, sourceB) => { 66 | const sourceARange = sourceA.pos.getRangeTo(this.room.controller.pos); 67 | const sourceBRange = sourceB.pos.getRangeTo(this.room.controller.pos); 68 | return sourceARange - sourceBRange; 69 | }); 70 | this._isNearestToController = this === sources[0]; 71 | } 72 | 73 | return this._isNearestToController; 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /src/roles/Distributor.js: -------------------------------------------------------------------------------- 1 | import Base from './Base'; 2 | import { MOVE, CARRY } from '../utils/constants'; 3 | import Upgrader from './Upgrader'; 4 | import Miner from './Miner'; 5 | import Builder from './Builder'; 6 | 7 | export default class Distributor extends Base { 8 | static role = 'distributor' 9 | 10 | energySources() { 11 | return [ 12 | ...this.room.getControllerLinks(), 13 | ...[this.room.getStorage()], 14 | ...this.room.getDroppedEnergy(), 15 | ].filter(Boolean); 16 | } 17 | 18 | performRole() { 19 | const storage = this.room.getStorage(); 20 | 21 | const closestNeedingEnergy = (things) => { 22 | return things.reduce((prev, thing) => { 23 | if (thing.needsEnergy() && (!prev || this.pos.getRangeTo(thing) <= this.pos.getRangeTo(prev))) { 24 | return thing; 25 | } 26 | return prev; 27 | }, undefined); 28 | }; 29 | 30 | let target = closestNeedingEnergy(this.room.getSpawnStructures()); 31 | const targetTower = closestNeedingEnergy(this.room.getTowers()); 32 | const targetCreep = closestNeedingEnergy([ 33 | ...this.room.getCreepsWithRole(Upgrader.role), 34 | ...this.room.getCreepsWithRole(Builder.role), 35 | ]); 36 | 37 | target = target || targetTower || targetCreep; 38 | if (target) { 39 | if (this.isEmpty()) { 40 | return this.gatherEnergy(); 41 | } 42 | return this.deliverEnergyTo(target); 43 | } else { 44 | const storageLink = this.room.getControllerLinks()[0]; 45 | if (storage && storageLink) { 46 | if (!this.isEmpty()) { 47 | return this.deliverEnergyTo(storage); 48 | } else if (!storageLink.isEmpty()) { 49 | return this.takeEnergyFrom(storageLink); 50 | } 51 | return this.gatherEnergy(); 52 | } else { 53 | return this.gatherEnergy(); 54 | } 55 | } 56 | } 57 | 58 | static createCreepFor(spawn) { 59 | // walkDistance = 2 * (totalPathDistanceFromControllerToEachSource) 60 | // transitRate = carryCapacity / walkdistance * (distance / tick) 61 | // ^ basically a measure of how effective each distributor is. 62 | // transitRate should roughly equal the total production, most rooms 20/tick. 63 | const creepsWithRole = spawn.room.getCreepsWithRole(this.role); 64 | let target = 1; 65 | const body = [MOVE, CARRY, MOVE, CARRY, MOVE, CARRY]; 66 | if (spawn.room.hasLinksConfigured() && spawn.room.getStorage()) { 67 | target = 1; 68 | } else if (spawn.room.controller && spawn.room.getCreepsWithRole(Miner.role).length) { 69 | const walkDistance = spawn.room.getSources().reduce((prev, source) => { 70 | return prev + spawn.room.controller.pos.findOptimalPathTo(source).length; 71 | }, 0); 72 | 73 | const transitRate = 150 / walkDistance; 74 | target = Math.min(spawn.room.maxEnergyProducedPerTick() / transitRate, 5); 75 | } 76 | 77 | if (creepsWithRole.length < target) { 78 | return { 79 | memory: { 80 | role: this.role, 81 | }, 82 | // basically the cost of a spawn 83 | body, 84 | } 85 | } 86 | return undefined; 87 | } 88 | } -------------------------------------------------------------------------------- /src/structures/_base.js: -------------------------------------------------------------------------------- 1 | // Because structures have an inheritance chain (Gparent > parent > child), 2 | // it is difficult to properly extend their classes. 3 | 4 | /* @flow */ 5 | import { Structure } from 'screeps-globals'; 6 | import structureManager from '../utils/structure-manager'; 7 | 8 | Object.assign(Structure.prototype, { 9 | work() { 10 | if (this.performRole) { 11 | this.performRole(); 12 | } 13 | if (Game.time % 100 === 0) { 14 | this.buildAccessRoads(); 15 | } 16 | }, 17 | 18 | enhance() { 19 | return structureManager.enhanceStructure(this); 20 | }, 21 | 22 | isFull() { 23 | if (this.energyCapacity) { 24 | return this.energy === this.energyCapacity; 25 | } else if (this.storeCapacity) { 26 | return this.totalUtilizedCapacity() === this.storeCapacity; 27 | } 28 | return true; 29 | }, 30 | 31 | totalUtilizedCapacity() { 32 | if (this.store) { 33 | return Object.entries(this.store).reduce((acc, [key, val]) => { 34 | return acc + val; 35 | }, 0); 36 | } 37 | if (this.energy) return this.energy; 38 | }, 39 | 40 | availableEnergy() { 41 | if (this.store) { 42 | return this.store[RESOURCE_ENERGY]; 43 | } 44 | return this.energy || 0; 45 | }, 46 | 47 | needsRepaired() { 48 | return this.hits / this.hitsMax < 1; 49 | }, 50 | 51 | // Towers are great for repairing things quickly, but aren't effecient for energy. 52 | // Individual structures can override this to prevent waste. See walls. 53 | needsTowerRepaired() { 54 | return this.hits / this.hitsMax < 1; 55 | }, 56 | 57 | isEmpty() { 58 | if (this.energyCapacity) { 59 | return this.energy === 0; 60 | } else if (this.storeCapacity) { 61 | return this.store === 0; 62 | } 63 | 64 | return true; 65 | }, 66 | 67 | isSourceTower() { 68 | const sourcesNearby = this.room.getSources().filter(source => { 69 | return source.pos.getRangeTo(this) <= 2; 70 | }); 71 | 72 | return this.structureType === STRUCTURE_TOWER && sourcesNearby.length > 0; 73 | }, 74 | 75 | buildAccessRoads() { 76 | const top = new RoomPosition(this.pos.x, this.pos.y - 1, this.room.name); 77 | const left = new RoomPosition(this.pos.x - 1, this.pos.y, this.room.name); 78 | const right = new RoomPosition(this.pos.x + 1, this.pos.y, this.room.name); 79 | const bottom = new RoomPosition(this.pos.x, this.pos.y + 1, this.room.name); 80 | const positions = [top, left, right, bottom]; 81 | positions.forEach(position => { 82 | const terrain = position.lookFor('terrain'); 83 | if (terrain === 'swamp' && position.isOpen() && !position.hasRoad()) { 84 | this.room.createConstructionSite(position.x, position.y, STRUCTURE_ROAD); 85 | } 86 | }); 87 | }, 88 | 89 | needsEnergy() { 90 | if (!(this.store || this.energyCapacity)) return false; 91 | if (this.structureType === STRUCTURE_TERMINAL) return false; 92 | if (this.structureType === STRUCTURE_CONTAINER) return false; 93 | 94 | if (this.structureType === STRUCTURE_STORAGE) { 95 | return this.room.energyAvailable === this.room.energyCapacityAvailable; 96 | }; 97 | if (this.store) { 98 | return this.store[RESOURCE_ENERGY] < this.storeCapacity; 99 | } 100 | 101 | return this.energy < this.energyCapacity; 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /src/room-position.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { RoomPosition } from 'screeps-globals'; 3 | import creepManager from './utils/creep-manager'; 4 | 5 | Object.assign(RoomPosition.prototype, { 6 | identifier() { 7 | return `${this.roomName}x${this.x}y${this.y}`; 8 | }, 9 | 10 | actualDistanceTo(pos) { 11 | return Math.sqrt(Math.pow(this.x - pos.x, 2) + Math.pow(this.y - pos.y, 2)); 12 | }, 13 | 14 | freeEdges() { 15 | if (!(Memory.freeEdges && Memory.freeEdges[this.identifier()])) { 16 | Memory.freeEdges = Memory.freeEdges || {}; 17 | Memory.freeEdges[this.identifier()] = this.openPositionsAtRange().length; 18 | } 19 | 20 | return Memory.freeEdges[this.identifier()]; 21 | }, 22 | 23 | openPositionsAtRange(range = 1) { 24 | return this.buildablePositionsAtRange(range).filter(position => { 25 | return position.isOpen(); 26 | }); 27 | }, 28 | 29 | buildablePositionsAtRange(range = 1) { 30 | const room = Game.rooms[this.roomName]; 31 | const openPositions = []; 32 | const top = Math.max(this.y - range, 0); 33 | const bottom = Math.min(this.y + range, 49); 34 | const left = Math.max(this.x - range, 0); 35 | const right = Math.min(this.x + range, 49); 36 | const surroundings = room.lookAtArea(left, top, right, bottom); 37 | Object.keys(surroundings).forEach(x => { 38 | Object.keys(surroundings[x]).forEach(y => { 39 | const pos = new RoomPosition(+x, +y, this.roomName); // The + is for string -> number 40 | if (pos.getRangeTo(this) === range && pos.isBuildable()) { 41 | openPositions.push(pos); 42 | } 43 | }); 44 | }); 45 | return openPositions; 46 | }, 47 | 48 | isOpen() { 49 | return this.isBuildable() && !this.hasStructure() && !this.hasConstructionSite(); 50 | }, 51 | 52 | isBuildable() { 53 | const terrain = this.lookFor('terrain')[0]; 54 | const noBuildFlag = this.lookFor('flag').find(flag => flag.isNoBuildFlag()); 55 | return (terrain === 'swamp' || terrain === 'plain') && !noBuildFlag; 56 | }, 57 | 58 | hasConstructionSite() { 59 | if (this._hasConstructionSiteCalced === undefined) { 60 | this._hasConstructionSiteCalced = true; 61 | this._hasConstructionSite = this.lookFor('constructionSite').length > 0; 62 | } 63 | return this._hasConstructionSite; 64 | }, 65 | 66 | hasStructure() { 67 | if (this._hasStructureCalced === undefined) { 68 | this._hasStructureCalced = true; 69 | this._hasStructure = this.lookFor('structure').filter(structure => { 70 | return structure.structureType !== STRUCTURE_ROAD; 71 | }).length > 0; 72 | } 73 | return this._hasStructure; 74 | }, 75 | 76 | creep() { 77 | const creep = this.lookFor(LOOK_CREEPS)[0]; 78 | // Found creep will not be enhanced. 79 | if (creep) { 80 | return creepManager.enhanceCreep(creep); 81 | } 82 | return creep; 83 | }, 84 | 85 | hasRoad() { 86 | return this.lookFor('structure').filter(structure => { 87 | return structure.structureType === STRUCTURE_ROAD; 88 | }).length > 0; 89 | }, 90 | 91 | findOptimalPathTo(target) { 92 | const optimalPathOpts = { 93 | ignoreCreeps: true, 94 | ignoreRoads: true, 95 | ignoreDestructibleStructures: true, 96 | swampCost: 1, 97 | }; 98 | return this.findPathTo(target, optimalPathOpts); 99 | } 100 | }); 101 | -------------------------------------------------------------------------------- /src/utils/reporter.js: -------------------------------------------------------------------------------- 1 | function initializeReporter() { 2 | if (!Memory.reporter) { 3 | console.log('initializing reporter'); 4 | Memory.reporter = { 5 | runningSince: Game.time, 6 | rooms: { 7 | 8 | }, 9 | }; 10 | } 11 | } 12 | 13 | function gameRooms() { 14 | return Object.entries(Game.rooms).map(([name, room]) => room); 15 | } 16 | 17 | function updateReport() { 18 | gameRooms().forEach(room => { 19 | const eventLog = room.getEventLog(); 20 | let energyHarvested = 0; 21 | let energyOnRepair = 0; 22 | let energyOnUpgrade = 0; 23 | let energyOnBuild = 0; 24 | let currentStorage = room.find(FIND_MY_STRUCTURES).reduce((acc, structure) => { 25 | if (structure.store) { 26 | return acc + structure.store[RESOURCE_ENERGY]; 27 | } 28 | return acc; 29 | }, 0); 30 | const energyPotential = room.find(FIND_SOURCES).length * SOURCE_ENERGY_CAPACITY / ENERGY_REGEN_TIME; 31 | const energyOnDecay = room.find(FIND_DROPPED_RESOURCES).filter(resource => { 32 | return resource.resourceType === RESOURCE_ENERGY; 33 | }).length; 34 | const energyOnCreeps = room.find(FIND_MY_CREEPS).reduce((acc, creep) => { 35 | return acc + creep.body.reduce((bodyCost, bodyPart) => { 36 | return bodyCost + BODYPART_COST[bodyPart.type]; 37 | }, 0); 38 | }, 0) / CREEP_LIFE_TIME; 39 | eventLog.forEach(({ event, data }) => { 40 | const { amount, energySpent } = data || {}; 41 | if (event === EVENT_HARVEST) { 42 | energyHarvested += amount; 43 | } else if (event === EVENT_REPAIR) { 44 | energyOnRepair += energySpent; 45 | } else if (event === EVENT_UPGRADE_CONTROLLER) { 46 | energyOnUpgrade += energySpent; 47 | } else if (event === EVENT_BUILD) { 48 | energyOnBuild += energySpent; 49 | } 50 | }); 51 | Memory.reporter.rooms[room.name] = Memory.reporter.rooms[room.name] || { 52 | 'energy harvested': 0, 53 | 'energy on repair': 0, 54 | 'energy on upgrad': 0, 55 | 'energy on builds': 0, 56 | 'energy on decays': 0, 57 | 'energy on creeps': 0, 58 | 'current storages': currentStorage, 59 | 'energy storerate': 0, 60 | }; 61 | Memory.reporter.rooms[room.name]['energy harvested'] += energyHarvested; 62 | Memory.reporter.rooms[room.name]['energy on repair'] += energyOnRepair; 63 | Memory.reporter.rooms[room.name]['energy on upgrad'] += energyOnUpgrade; 64 | Memory.reporter.rooms[room.name]['energy on builds'] += energyOnBuild; 65 | Memory.reporter.rooms[room.name]['energy potential'] += energyPotential; 66 | Memory.reporter.rooms[room.name]['energy on decays'] += energyOnDecay; 67 | Memory.reporter.rooms[room.name]['energy on creeps'] += energyOnCreeps; 68 | Memory.reporter.rooms[room.name]['energy storerate'] += currentStorage - Memory.reporter.rooms[room.name]['current storages']; 69 | Memory.reporter.rooms[room.name]['current storages'] = currentStorage; 70 | Memory.reporter.rooms[room.name]['energy unharvest'] = Memory.reporter.rooms[room.name]['energy potential'] - Memory.reporter.rooms[room.name]['energy harvested']; 71 | }); 72 | } 73 | 74 | function report() { 75 | initializeReporter(); 76 | updateReport(); 77 | 78 | return function generateReport(reset) { 79 | if (reset) { 80 | Memory.reporter = undefined; 81 | initializeReporter(); 82 | updateReport(); 83 | } 84 | Object.entries(Memory.reporter.rooms).forEach(([roomName, roomValues]) => { 85 | const runningFor = (Game.time - Memory.reporter.runningSince + 1); 86 | console.log(`== ${roomName} == ${runningFor} ticks`); 87 | Object.entries(roomValues).forEach(([key, value]) => { 88 | if (key === 'current storages') { 89 | console.log(key, value); 90 | } else { 91 | console.log(key, value / runningFor); 92 | } 93 | }); 94 | }); 95 | }; 96 | } 97 | 98 | module.exports = { 99 | wrap(fn) { 100 | Game.report = report(); 101 | return fn(); 102 | }, 103 | } -------------------------------------------------------------------------------- /src/flag.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { Flag } from 'screeps-globals'; 3 | import creepManager from './utils/creep-manager'; 4 | 5 | const neutralStructures = [ 6 | STRUCTURE_ROAD, 7 | STRUCTURE_CONTAINER, 8 | ]; 9 | 10 | Object.assign(Flag.prototype, { 11 | work() { 12 | if (this.name.toLowerCase().indexOf('build') !== -1) { 13 | const parts = this.name.split('_'); 14 | if (parts.length < 2) return; // we're not going to find a target, just bail. 15 | const target = parts[1]; 16 | let shouldBuild = false; 17 | const ownedRoom = this.room.getControllerOwned(); 18 | const neutralStructure = neutralStructures.indexOf(target) !== -1; 19 | if (target && CONTROLLER_STRUCTURES[target] && (ownedRoom || neutralStructure)) { 20 | let max = 0; 21 | if (ownedRoom) { 22 | max = CONTROLLER_STRUCTURES[target][this.room.controller.level]; 23 | } else if (target === STRUCTURE_CONTAINER) { 24 | max = 5; 25 | } 26 | const current = this.room.find(target).length; 27 | shouldBuild = current < max; 28 | shouldBuild = shouldBuild && this.pos.isOpen(); 29 | } 30 | 31 | if (shouldBuild) { 32 | const result = this.room.createConstructionSite(this.pos.x, this.pos.y, target); 33 | if (result === 0) { 34 | this.remove(); 35 | } 36 | } 37 | } else if (this.name.toLowerCase() === 'rampart') { 38 | if (!this.pos.isOpen()) { 39 | const structure = this.pos.lookFor('structure')[0]; 40 | if (structure) { 41 | structure.destroy(); 42 | } 43 | } else { 44 | const result = this.room.createConstructionSite(this.pos.x, this.pos.y, STRUCTURE_RAMPART); 45 | if (result === 0) { 46 | this.remove(); 47 | } 48 | } 49 | } else if (this.isReserveFlag()) { 50 | this.performReserveFlagRole(); 51 | } else if (this.isClaimFlag()) { 52 | this.performClaimFlagRole(); 53 | } 54 | }, 55 | 56 | isReserveFlag() { 57 | return this.name.indexOf('reserve') === 0; 58 | }, 59 | 60 | isNoBuildFlag() { 61 | return this.name.indexOf('nobuild') !== -1; 62 | }, 63 | 64 | isClaimFlag() { 65 | return this.name.indexOf('claim') === 0; 66 | }, 67 | 68 | performClaimFlagRole() { 69 | if (!this.room.controller || this.room.controller.my || this.room.controller.owner || this.room.controller.reservation) { 70 | return this.remove(); 71 | } 72 | }, 73 | 74 | performReserveFlagRole() { 75 | const rate = 5; 76 | if (this.room && this.room.controller.my && this.room.getSpawns().length) return this.remove(); 77 | if (Game.time % rate === 0) { 78 | const room = Game.roomArray().find(potentialRoom => potentialRoom.name === this.pos.roomName); 79 | if (room) { 80 | const reservation = room.controller.reservation; 81 | const reservationTime = reservation && reservation.ticksToEnd || 0; 82 | this.memory.reservationTime = reservationTime; 83 | this.memory.sources = room.getSources().map(source => source.id); 84 | } 85 | } else { 86 | this.memory.reservationTime = Math.max(this.memory.reservationTime - rate, 0); 87 | } 88 | 89 | if (this.reservationTime() >= 4999) { 90 | this.memory.needsReserver = false; 91 | } else if (this.reservationTime() < 500) { 92 | this.memory.needsReserver = true; 93 | } 94 | }, 95 | 96 | reservationTime() { 97 | if (this.memory.reservationTime === undefined) { 98 | this.memory.reservationTime = 0; 99 | } 100 | 101 | return this.memory.reservationTime; 102 | }, 103 | 104 | needsReserver() { 105 | return this.isReserveFlag() && this.memory.needsReserver; 106 | }, 107 | 108 | needsRemoteCouriers() { 109 | const couriers = creepManager.creepsWithRole('remotecourier').filter(creep => { 110 | return creep.memory.flag === this.name; 111 | }); 112 | 113 | if (this.memory.sources) { 114 | return couriers.length < this.memory.sources.length; 115 | } 116 | 117 | return false; 118 | }, 119 | 120 | needsRemoteHarvesters() { 121 | const remoteHarvesters = creepManager.creepsWithRole('remoteharvester').filter(creep => { 122 | return creep.memory.flag === this.name; 123 | }); 124 | 125 | if (this.memory.sources) { 126 | return remoteHarvesters.length < this.memory.sources.length; 127 | } 128 | 129 | return false; 130 | }, 131 | }); 132 | -------------------------------------------------------------------------------- /src/structures/Spawn.js: -------------------------------------------------------------------------------- 1 | import './_base'; 2 | import { STRUCTURE_SPAWN } from '../utils/constants'; 3 | import bodyCosts from '../utils/body-costs'; 4 | import { roleList } from '../utils/role-map'; 5 | 6 | export default class Spawn extends StructureSpawn { 7 | static structureType = STRUCTURE_SPAWN; 8 | 9 | buildScout(availableEnergy) { 10 | const body = [MOVE, MOVE, MOVE, MOVE, MOVE, WORK, WORK, WORK, WORK, WORK]; 11 | let cost = bodyCosts.calculateCosts(body); 12 | while (cost < availableEnergy && body.length < 50) { 13 | body.push(MOVE); 14 | body.push(Game.dismantleFlags().length ? WORK : CARRY); 15 | cost = bodyCosts.calculateCosts(body); 16 | } 17 | while (cost > availableEnergy) { 18 | body.pop(); 19 | body.pop(); 20 | cost = bodyCosts.calculateCosts(body); 21 | } 22 | this.createCreep(body, undefined, { role: 'scout', spawn: this.name }); 23 | } 24 | 25 | buildRemoteCourier() { 26 | const target = this.room.getReserveFlagsNeedingRemoteCouriers()[0]; 27 | const body = []; 28 | while (body.length < 20) { 29 | body.push(MOVE); 30 | body.push(CARRY); 31 | } 32 | this.createCreep(body, undefined, { 33 | role: 'remotecourier', 34 | flag: target.name, 35 | spawn: this.name, 36 | }); 37 | } 38 | 39 | buildRemoteHarvester() { 40 | const target = this.room.getReserveFlagsNeedingRemoteHarvesters()[0]; 41 | const body = [ 42 | MOVE, MOVE, MOVE, MOVE, MOVE, MOVE, 43 | WORK, WORK, WORK, WORK, WORK, CARRY, 44 | ]; 45 | this.createCreep(body, undefined, { 46 | role: 'remoteharvester', 47 | spawn: this.name, 48 | flag: target.name, 49 | }); 50 | } 51 | 52 | buildScoutHarvester() { 53 | const body = [MOVE, MOVE, MOVE, MOVE, MOVE, WORK, WORK, WORK, WORK, WORK]; 54 | this.createCreep(body, undefined, { role: 'scoutharvester' }); 55 | } 56 | 57 | buildBuilder(availableEnergy) { 58 | const body = [MOVE, MOVE, WORK, CARRY]; 59 | let cost = bodyCosts.calculateCosts(body); 60 | 61 | while (cost < availableEnergy) { 62 | body.push(MOVE); 63 | body.push(CARRY); 64 | body.push(WORK); 65 | cost = bodyCosts.calculateCosts(body); 66 | } 67 | 68 | while (cost > availableEnergy || body.length > 50) { 69 | body.pop(); 70 | cost = bodyCosts.calculateCosts(body); 71 | } 72 | 73 | this.createCreep(body, undefined, { role: 'builder' }); 74 | } 75 | 76 | buildSourceTaker(availableEnergy) { 77 | const body = []; 78 | let cost = bodyCosts.calculateCosts(body); 79 | let toughParts = 0; 80 | while (toughParts < 10) { 81 | toughParts++; 82 | body.push(TOUGH, MOVE); 83 | } 84 | let rangedAttackParts = 0; 85 | while (cost < availableEnergy) { 86 | rangedAttackParts++; 87 | body.push(RANGED_ATTACK, MOVE); 88 | cost = bodyCosts.calculateCosts(body); 89 | } 90 | 91 | body.push(HEAL); 92 | 93 | while (cost > availableEnergy || body.length > 50) { 94 | body.pop(); 95 | cost = bodyCosts.calculateCosts(body); 96 | } 97 | 98 | this.createCreep(body, undefined, { role: 'sourcetaker' }); 99 | } 100 | 101 | work() { 102 | if (this.spawning) { 103 | return; 104 | } 105 | 106 | let creepToBuild = undefined; 107 | roleList.forEach((RoleClass) => { 108 | if (creepToBuild) return false; 109 | creepToBuild = RoleClass.createCreepFor(this); 110 | }); 111 | 112 | if (creepToBuild) { 113 | if (bodyCosts.calculateCosts(creepToBuild.body) <= this.availableEnergy()) { 114 | return this.createCreep(creepToBuild.body, creepToBuild.name, creepToBuild.memory); 115 | } else { 116 | this.extend(); 117 | return false; 118 | } 119 | } 120 | 121 | const availableEnergy = this.availableEnergy(); 122 | if (availableEnergy === this.maxEnergy()) { 123 | if (this.room.needsBuilders()) { 124 | this.buildBuilder(availableEnergy); 125 | } else { 126 | this.extend(); 127 | } 128 | } else { 129 | this.extend(); 130 | } 131 | } 132 | 133 | maxEnergy() { 134 | return this.room.energyCapacityAvailable; 135 | } 136 | 137 | needsRepaired() { 138 | return this.hits < this.hitsMax; 139 | } 140 | 141 | availableEnergy() { 142 | return this.room.energyAvailable; 143 | } 144 | 145 | extend() { 146 | if (this.room.canBuildExtension()) { 147 | this.room.createConstructionSite(this.pos.x - 1, this.pos.y - 1, STRUCTURE_EXTENSION); 148 | this.room.createConstructionSite(this.pos.x - 1, this.pos.y + 1, STRUCTURE_EXTENSION); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/roles/Base.js: -------------------------------------------------------------------------------- 1 | import bodyCosts from '../utils/body-costs'; 2 | import { Creep, Flag, Energy } from 'screeps-globals'; 3 | import profiler from 'screeps-profiler'; 4 | 5 | class Base extends Creep { 6 | constructor(creep) { 7 | super(creep.id); 8 | } 9 | 10 | work() { 11 | const creepFlag = Game.flags[this.name]; 12 | // move to creep flag if it is defined. 13 | if (creepFlag !== undefined) { 14 | if (this.pos.getRangeTo(creepFlag) === 0) { 15 | creepFlag.remove(); 16 | } else { 17 | this.moveTo(creepFlag); 18 | } 19 | } else if (this.shouldBeRecycled()) { 20 | this.recycle(); 21 | } else { 22 | this.performRole(); 23 | } 24 | } 25 | 26 | // prioritized list of places you can pull resources from. 27 | energySources() { 28 | return [ 29 | ...this.room.getLinks(), 30 | ...[this.room.getStorage()], 31 | ...this.room.getDroppedEnergy(), 32 | ...this.room.getContainers(), 33 | ]; 34 | } 35 | 36 | 37 | gatherEnergy() { 38 | const validEnergySources = this.energySources().filter(thing => { 39 | try { 40 | return thing && thing.availableEnergy() > this.availableSpace(); 41 | } catch(e) { 42 | console.log(thing, 'does not have an availableEnergy function'); 43 | throw e; 44 | } 45 | }); 46 | if (validEnergySources.length) { 47 | return this.takeEnergyFrom(this.pos.findClosestByRange(validEnergySources)); 48 | } 49 | } 50 | 51 | rankedEnergySpendTargets() { 52 | const targets = [ 53 | [...this.room.getExtensions(), ...this.room.getSpawns()], 54 | this.room.getConstructionSites(), 55 | ]; 56 | if (this.room.controller && this.room.controller.my) { 57 | targets.push([this.room.controller]); 58 | } 59 | return targets; 60 | } 61 | 62 | spendResources() { 63 | const spendTarget = this.rankedEnergySpendTargets().reduce((target, potentialTargets) => { 64 | if (target) return target; 65 | return this.pos.findClosestByRange(potentialTargets.filter(potentialTarget => { 66 | return potentialTarget.needsEnergy(); 67 | })); 68 | }, undefined); 69 | 70 | if (spendTarget) { 71 | return this.moveToAndSpendEnergyOn(spendTarget); 72 | } 73 | } 74 | 75 | needsRenewed() { 76 | return !this.shouldBeRecycled() && this.ticksToLive / CREEP_LIFE_TIME < 0.5; 77 | } 78 | 79 | attemptRenew() { 80 | this.room.getSpawns().forEach(spawn => { 81 | if (this.needsRenewed() && this.pos.getRangeTo(spawn) === 1 && !spawn.spawning) { 82 | spawn.renewCreep(this); 83 | } 84 | }); 85 | } 86 | 87 | moveToAndSpendEnergyOn(target) { 88 | if (target instanceof ConstructionSite) { 89 | return this.moveToAndBuild(target); 90 | } else if (target instanceof Structure || target instanceof Creep) { 91 | return this.deliverEnergyTo(target); 92 | } else if (target instanceof Source) { 93 | return this.deliverEnergyTo(target); 94 | } 95 | console.trace('unkown target'); 96 | } 97 | 98 | performRole() { 99 | console.log(`WHOA! ${this.role()} does not have a performRole implementation!!!`); // eslint-disable-line 100 | } 101 | 102 | role() { 103 | return this.memory.role; 104 | } 105 | 106 | targetSource() { 107 | return this.room.getSources().filter(source => { 108 | return this.memory.source === source.id; 109 | })[0]; 110 | } 111 | 112 | getSpawn() { 113 | const spawns = Object.keys(Game.spawns).map(spawnName => Game.spawns[spawnName]); 114 | const validSpawn = spawns.find(spawn => { 115 | return spawn.room === this.room; 116 | }); 117 | const spawn = validSpawn || Game.spawns[this.memory.spawn]; 118 | if (spawn) { 119 | return spawn.enhance(); 120 | } 121 | return spawn; 122 | } 123 | 124 | moveToThenDrop(target) { 125 | if (this.pos.getRangeTo(target) > 1) { 126 | this.moveTo(target); 127 | } else { 128 | this.drop(RESOURCE_ENERGY); 129 | } 130 | } 131 | 132 | moveToAndUpgrade(target) { 133 | if (this.pos.getRangeTo(target) > 1) { 134 | this.moveTo(this.room.controller); 135 | } else { 136 | this.attemptToUpgrade(); 137 | } 138 | } 139 | 140 | moveToAndClaim(target) { 141 | if (this.pos.getRangeTo(target) > 1) { 142 | return this.moveTo(target); 143 | } 144 | return this.claimController(target); 145 | } 146 | 147 | attemptToUpgrade() { 148 | if (this.pos.getRangeTo(this.room.controller) <= 2) { 149 | this.upgradeController(this.room.controller); 150 | } 151 | } 152 | 153 | moveToAndBuild(target) { 154 | const range = this.pos.getRangeTo(target); 155 | if (range > 1) { 156 | this.moveTo(target); 157 | } 158 | if (range <= 3) { 159 | this.build(target); 160 | } 161 | } 162 | 163 | hasVisitedFlag(flag) { 164 | const visitedFlags = this.memory.visitedFlags || []; 165 | return visitedFlags.indexOf(flag.name) !== -1; 166 | } 167 | 168 | findUnvisitedScoutFlags() { 169 | if (!this._unvisitedFlags) { 170 | const flags = Game.getScoutFlags(); 171 | this._unvisitedFlags = flags.filter((flag) => { 172 | return !this.hasVisitedFlag(flag); 173 | }); 174 | } 175 | return this._unvisitedFlags; 176 | } 177 | 178 | dismantleFlag(flag) { 179 | const structure = this.room.getStructureAt(flag.pos); 180 | if (structure) { 181 | this.moveToAndDismantle(structure); 182 | } else { 183 | flag.remove(); 184 | } 185 | } 186 | 187 | moveToAndDismantle(target) { 188 | if (this.pos.getRangeTo(target) === 1) { 189 | this.dismantle(target); 190 | } else { 191 | this.moveTo(target); 192 | } 193 | } 194 | 195 | scout() { 196 | const unvisitedFlags = this.findUnvisitedScoutFlags(); 197 | unvisitedFlags.sort((flagA, flagB) => { 198 | return parseInt(flagA.name, 10) - parseInt(flagB.name, 10); 199 | }); 200 | let targetFlag = unvisitedFlags[0]; 201 | if (this.pos.getRangeTo(targetFlag) === 0) { 202 | if (!this.memory.visitedFlags) { 203 | this.memory.visitedFlags = []; 204 | } 205 | this.memory.visitedFlags.push(targetFlag.name); 206 | targetFlag = unvisitedFlags[1]; 207 | } 208 | this.moveTo(targetFlag, { reusePath: 50 }); 209 | } 210 | 211 | moveToAndRepair(target) { 212 | const range = this.pos.getRangeTo(target); 213 | if (range > 1) { 214 | this.moveTo(target); 215 | } 216 | if (range <= 3) { 217 | this.repair(target); 218 | } 219 | } 220 | 221 | moveToAndHarvest(target) { 222 | if (this.pos.getRangeTo(target) > 1) { 223 | this.moveTo(target); 224 | } else { 225 | this.harvest(target); 226 | } 227 | } 228 | 229 | takeEnergyFrom(target) { 230 | const range = this.pos.getRangeTo(target); 231 | if (target instanceof Energy) { 232 | if (range > 1) { 233 | this.moveTo(target); 234 | } 235 | return this.pickup(target); 236 | } 237 | if (range > 1) { 238 | this.moveTo(target); 239 | } 240 | 241 | if (!target.transfer || target.structureType) { // eslint-disable-line 242 | return this.withdraw(target, RESOURCE_ENERGY); 243 | } 244 | 245 | return target.transfer(this, RESOURCE_ENERGY); 246 | } 247 | 248 | /** 249 | * Determine whether or not the supplied spawn should build this role. If it 250 | * should, generate and return the desired body, name and memory otherwise 251 | * return undefined. 252 | * 253 | * @param {Spawn} spawn The spawn that the role should generate a body for. 254 | */ 255 | static createCreepFor(spawn) { 256 | /** 257 | * This is the 258 | * This is currently defaulting to undefined to allow for incremental 259 | * implementation, eventually this should be marked as deprecated to enforce 260 | * the new role pattern. 261 | */ 262 | } 263 | 264 | isFull() { 265 | return this.totalCarryLoad() === this.carryCapacity; 266 | } 267 | 268 | isEmpty() { 269 | return !this.isFull() && this.totalCarryLoad() === 0; 270 | } 271 | 272 | availableSpace() { 273 | return this.carryCapacity - this.totalCarryLoad(); 274 | } 275 | 276 | totalCarryLoad() { 277 | return Object.entries(this.carry).reduce((acc, [key, val]) => { 278 | return acc + val; 279 | }, 0); 280 | } 281 | 282 | deliverEnergyTo(target) { 283 | const targetIsFlag = target instanceof Flag; 284 | if (targetIsFlag) { 285 | this.deliverEnergyToFlag(target); 286 | } else { 287 | const range = this.pos.getRangeTo(target); 288 | if (range <= 1) { 289 | this.transfer(target, RESOURCE_ENERGY); 290 | } else { 291 | this.moveTo(target); 292 | } 293 | } 294 | } 295 | 296 | deliverEnergyToFlag(flag) { 297 | const range = this.pos.getRangeTo(flag); 298 | if (range === 0) { 299 | this.drop(RESOURCE_ENERGY); 300 | } else { 301 | const blockingCreep = flag.pos.creep(); 302 | if (range === 1 && blockingCreep) { 303 | blockingCreep.unblockFlag(); 304 | } 305 | this.moveTo(flag); 306 | } 307 | } 308 | 309 | unblockFlag() { 310 | this.moveInRandomDirection(); 311 | } 312 | 313 | moveInRandomDirection() { 314 | const directions = [TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, LEFT, TOP_LEFT]; 315 | this.move(Math.floor(Math.random(directions.length) * directions.length)); 316 | } 317 | 318 | cost() { 319 | return bodyCosts.calculateCosts(this.body); 320 | } 321 | 322 | shouldBeRecycled() { 323 | return false; 324 | } 325 | 326 | recycle() { 327 | const spawn = this.getSpawn(); 328 | if (this.pos.getRangeTo(spawn) > 1) { 329 | this.moveTo(spawn); 330 | } else { 331 | spawn.recycleCreep(this); 332 | } 333 | } 334 | } 335 | 336 | profiler.registerClass(Base, 'CreepBase'); 337 | 338 | export default Base; 339 | -------------------------------------------------------------------------------- /src/playground.js: -------------------------------------------------------------------------------- 1 | const expect = require('expect'); 2 | 3 | const constants = require('./utils/constants'); 4 | 5 | const TICK_END = 'TICK_END'; 6 | const CREEP = 'CREEP'; 7 | const SOURCE = 'SOURCE'; 8 | const WITHDRAW = 'WITHDRAW'; 9 | const PICKUP = 'PICKUP'; 10 | const HARVEST = 'HARVEST'; 11 | const UPGRADE_CONTROLLER = 'UPGRADE_CONTROLLER'; 12 | const DROP = 'DROP'; 13 | const TRANSFER = 'TRANSFER'; 14 | const REPAIR = 'REPAIR'; 15 | 16 | function serializeRoom() { 17 | return { 18 | terrain: [], 19 | }; 20 | } 21 | 22 | function serializeRooms() { 23 | return Object.keys(Game.rooms).reduce((acc, roomName) => { 24 | return { 25 | ...acc, 26 | [roomName]: serializeRoom(Game.rooms[roomName]), 27 | }; 28 | }, {}); 29 | } 30 | 31 | function serializeCreep(creep) { 32 | return { 33 | memory: creep.memory, 34 | x: creep.pos.x, 35 | y: creep.pos.y, 36 | carry: creep.carry, 37 | body: creep.body, 38 | type: CREEP, 39 | }; 40 | } 41 | 42 | function serializeSource(source) { 43 | return { 44 | energy: source.energy, 45 | ticksToRegeneration: source.ticksToRegeneration, 46 | type: SOURCE, 47 | room: source.pos.roomName, 48 | x: source.pos.x, 49 | y: source.pos.y, 50 | }; 51 | } 52 | 53 | function serializeCreeps() { 54 | return Object.entries(Game.creeps).reduce((acc, [creepName, creep]) => { 55 | return { 56 | ...acc, 57 | [creep.id]: serializeCreep(creep), 58 | }; 59 | }, {}); 60 | } 61 | 62 | function serializeSources() { 63 | return Object.entries(Game.rooms).reduce((acc, [roomName, room]) => { 64 | return { 65 | ...acc, 66 | ...room.find(FIND_SOURCES).reduce((innerAc, source) => { 67 | return { 68 | ...innerAc, 69 | [source.id]: serializeSource(source), 70 | }; 71 | }, {}), 72 | }; 73 | }, {}); 74 | } 75 | 76 | function serializeGameObjects() { 77 | return { 78 | ...serializeCreeps(), 79 | ...serializeSources(), 80 | }; 81 | } 82 | 83 | function serializeGame() { 84 | return { 85 | rooms: serializeRooms(), 86 | gameObjects: serializeGameObjects(), 87 | }; 88 | } 89 | 90 | function predictRooms(rooms, intent) { 91 | return rooms; 92 | } 93 | 94 | const leftMovements = { 95 | [constants.TOP_LEFT]: constants.TOP_LEFT, 96 | [constants.BOTTOM_LEFT]: constants.BOTTOM_LEFT, 97 | [constants.LEFT]: constants.LEFT, 98 | }; 99 | 100 | const rightMovements = { 101 | [constants.TOP_RIGHT]: constants.TOP_RIGHT, 102 | [constants.BOTTOM_RIGHT]: constants.BOTTOM_RIGHT, 103 | [constants.RIGHT]: constants.RIGHT, 104 | }; 105 | 106 | const topMovements = { 107 | [constants.TOP_LEFT]: constants.TOP_LEFT, 108 | [constants.TOP_RIGHT]: constants.TOP_RIGHT, 109 | [constants.TOP]: constants.TOP, 110 | }; 111 | 112 | const bottomMovements = { 113 | [constants.BOTTOM_LEFT]: constants.BOTTOM_LEFT, 114 | [constants.BOTTOM_RIGHT]: constants.BOTTOM_RIGHT, 115 | [constants.BOTTOM]: constants.BOTTOM, 116 | }; 117 | 118 | function maxCarry(body) { 119 | return body.reduce((acc, bodyPart) => { 120 | if (bodyPart.type === constants.CARRY) { 121 | return acc + constants.CARRY_CAPACITY; 122 | } 123 | return acc; 124 | }, 0); 125 | } 126 | 127 | function maxPower(body) { 128 | return body.reduce((acc, bodyPart) => { 129 | if (bodyPart.type === constants.WORK) { 130 | return acc + constants.HARVEST_POWER; 131 | } 132 | 133 | return acc; 134 | }, 0); 135 | } 136 | 137 | function predictGameObjects(gameObjects, intent) { 138 | switch(intent.type) { 139 | case TICK_END: { 140 | return Object.entries(gameObjects).reduce((acc, [id, gameObject]) => { 141 | let newGameObject = gameObject; 142 | if (gameObject.type === SOURCE && gameObject.ticksToRegeneration !== undefined) { 143 | const newTTR = gameObject.ticksToRegeneration > 1 ? gameObject.ticksToRegeneration - 1 : 0; 144 | newGameObject = { 145 | ...gameObject, 146 | ticksToRegeneration: newTTR, 147 | }; 148 | } 149 | return { 150 | ...acc, 151 | [id]: newGameObject, 152 | }; 153 | }, {}); 154 | } 155 | case HARVEST: { 156 | const target = gameObjects[intent.targetId]; 157 | const actor = gameObjects[intent.objectId]; 158 | const actorMaxCarry = maxCarry(actor.body); 159 | const actorCurrentCarry = Object.values(actor.carry).reduce((acc, val) => acc + val); 160 | const actorAvailableCarry = actorMaxCarry - actorCurrentCarry; 161 | const maxTransfer = Math.min(maxPower(actor.body), actorAvailableCarry, target.energy); 162 | return { 163 | ...gameObjects, 164 | [intent.objectId]: { 165 | ...gameObjects[intent.objectId], 166 | carry: { 167 | energy: actor.carry.energy + maxTransfer, 168 | }, 169 | }, 170 | [intent.targetId]: { 171 | ...gameObjects[intent.targetId], 172 | energy: Math.max(target.energy - maxPower(actor.body), 0), 173 | }, 174 | }; 175 | } 176 | case REPAIR: { 177 | return { 178 | ...gameObjects, 179 | [intent.objectId]: { 180 | ...gameObjects[intent.objectId], 181 | carry: { 182 | energy: actor.carry.energy - 1, 183 | }, 184 | }, 185 | }; 186 | } 187 | case TRANSFER: { 188 | return { 189 | ...gameObjects, 190 | [intent.objectId]: { 191 | ...gameObjects[intent.objectId], 192 | carry: { 193 | energy: 0, 194 | }, 195 | } 196 | }; 197 | } 198 | case constants.MOVE: { 199 | let x = gameObjects[intent.objectId].x; 200 | let y = gameObjects[intent.objectId].y; 201 | if (leftMovements[intent.direction]) x--; 202 | if (rightMovements[intent.direction]) x++; 203 | if (topMovements[intent.direction]) y--; 204 | if (bottomMovements[intent.direction]) y++; 205 | return { 206 | ...gameObjects, 207 | [intent.objectId]: { 208 | ...gameObjects[intent.objectId], 209 | x, 210 | y, 211 | } 212 | }; 213 | } 214 | default: 215 | return gameObjects; 216 | } 217 | } 218 | 219 | function predictNextState(serializedState, intents) { 220 | return intents.reduce((acc, intent) => { 221 | return { 222 | rooms: predictRooms(acc.rooms, intent), 223 | gameObjects: predictGameObjects(acc.gameObjects, intent), 224 | }; 225 | }, serializedState); 226 | } 227 | 228 | function addIntent(intent) { 229 | Memory.serializer[Game.time].intents.push(intent); 230 | } 231 | 232 | function extendPrototypes() { 233 | if (!Creep.prototype._serializerExtended) { 234 | Creep.prototype._serializerExtended = true; 235 | const originalMove = Creep.prototype.move; 236 | Creep.prototype.move = function move(direction) { 237 | const result = originalMove.apply(this, arguments); 238 | if (result === 0) { 239 | addIntent({ 240 | objectId: this.id, 241 | type: constants.MOVE, 242 | direction, 243 | }); 244 | } 245 | return result; 246 | } 247 | 248 | const originalHarvest = Creep.prototype.harvest; 249 | Creep.prototype.harvest = function harvest(target) { 250 | const result = originalHarvest.apply(this, arguments); 251 | if (result === 0) { 252 | addIntent({ 253 | objectId: this.id, 254 | targetId: target.id, 255 | type: HARVEST, 256 | }); 257 | } 258 | return result; 259 | }; 260 | 261 | const originalPickup = Creep.prototype.pickup; 262 | Creep.prototype.pickup = function pickup(target, resourceType, amount) { 263 | const result = originalPickup.apply(this, arguments); 264 | if (result === 0) { 265 | addIntent({ 266 | type: PICKUP, 267 | objectId: this.id, 268 | targetId: target.id, 269 | resourceType, 270 | amount, 271 | }); 272 | } 273 | return result; 274 | }; 275 | 276 | const originalWithdraw = Creep.prototype.withdraw; 277 | Creep.prototype.withdraw = function withdraw(target, resourceType, amount) { 278 | const result = originalWithdraw.apply(this, arguments); 279 | if (result === 0) { 280 | addIntent({ 281 | type: WITHDRAW, 282 | objectId: this.id, 283 | targetId: target.id, 284 | resourceType, 285 | amount, 286 | }); 287 | } 288 | return result; 289 | }; 290 | 291 | const originalUpgradeController = Creep.prototype.upgradeController; 292 | Creep.prototype.upgradeController = function upgradeController(target) { 293 | const result = originalUpgradeController.apply(this, arguments); 294 | if (result === 0) { 295 | addIntent({ 296 | type: UPGRADE_CONTROLLER, 297 | objectId: this.id, 298 | targetId: target.id, 299 | }); 300 | } 301 | return result; 302 | }; 303 | 304 | const originalTransfer = Creep.prototype.transfer; 305 | Creep.prototype.transfer = function transfer(target, resourceType, amount) { 306 | const result = originalTransfer.apply(this, arguments); 307 | if (result === 0) { 308 | addIntent({ 309 | type: TRANSFER, 310 | objectId: this.id, 311 | targetId: target.id, 312 | amount, 313 | resourceType, 314 | }); 315 | } 316 | return result; 317 | } 318 | 319 | const originalRepair = Creep.prototype.repair; 320 | Creep.prototype.repair = function repair(target) { 321 | const result = originalRepair.apply(this, arguments); 322 | addIntent({ 323 | type: REPAIR, 324 | objectId: this.id, 325 | targetId: target.id, 326 | }); 327 | return result; 328 | } 329 | 330 | const originalDrop = Creep.prototype.drop; 331 | Creep.prototype.drop = function drop(resourceType, amount) { 332 | const result = originalDrop.apply(this, arguments); 333 | if (result === 0) { 334 | addIntent({ 335 | type: DROP, 336 | objectId: this.id, 337 | resourceType, 338 | amount, 339 | }); 340 | } 341 | return result; 342 | } 343 | } 344 | } 345 | 346 | function playground(loop) { 347 | extendPrototypes(); 348 | if (!Memory.serializer) Memory.serializer = {}; 349 | Memory.serializer[Game.time] = { 350 | state: serializeGame(), 351 | intents: [] 352 | }; 353 | const loopResult = loop(); 354 | Memory.serializer[Game.time].intents.push({ 355 | type: TICK_END, 356 | }); 357 | 358 | Object.keys(Memory.serializer).forEach((tickString) => { 359 | const tick = parseInt(tickString, 10); 360 | if (tick < Game.time - 10) { 361 | delete Memory.serializer[tickString]; 362 | } 363 | }); 364 | 365 | return loopResult; 366 | } 367 | 368 | module.exports = { 369 | playground, 370 | predictNextState, 371 | }; -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | OK: 0, 3 | ERR_NOT_OWNER: -1, 4 | ERR_NO_PATH: -2, 5 | ERR_NAME_EXISTS: -3, 6 | ERR_BUSY: -4, 7 | ERR_NOT_FOUND: -5, 8 | ERR_NOT_ENOUGH_ENERGY: -6, 9 | ERR_NOT_ENOUGH_RESOURCES: -6, 10 | ERR_INVALID_TARGET: -7, 11 | ERR_FULL: -8, 12 | ERR_NOT_IN_RANGE: -9, 13 | ERR_INVALID_ARGS: -10, 14 | ERR_TIRED: -11, 15 | ERR_NO_BODYPART: -12, 16 | ERR_NOT_ENOUGH_EXTENSIONS: -6, 17 | ERR_RCL_NOT_ENOUGH: -14, 18 | ERR_GCL_NOT_ENOUGH: -15, 19 | 20 | FIND_EXIT_TOP: 1, 21 | FIND_EXIT_RIGHT: 3, 22 | FIND_EXIT_BOTTOM: 5, 23 | FIND_EXIT_LEFT: 7, 24 | FIND_EXIT: 10, 25 | FIND_CREEPS: 101, 26 | FIND_MY_CREEPS: 102, 27 | FIND_HOSTILE_CREEPS: 103, 28 | FIND_SOURCES_ACTIVE: 104, 29 | FIND_SOURCES: 105, 30 | FIND_DROPPED_ENERGY: -106, 31 | FIND_DROPPED_RESOURCES: 106, 32 | FIND_STRUCTURES: 107, 33 | FIND_MY_STRUCTURES: 108, 34 | FIND_HOSTILE_STRUCTURES: 109, 35 | FIND_FLAGS: 110, 36 | FIND_CONSTRUCTION_SITES: 111, 37 | FIND_MY_SPAWNS: 112, 38 | FIND_HOSTILE_SPAWNS: 113, 39 | FIND_MY_CONSTRUCTION_SITES: 114, 40 | FIND_HOSTILE_CONSTRUCTION_SITES: 115, 41 | FIND_MINERALS: 116, 42 | FIND_NUKES: 117, 43 | FIND_TOMBSTONES: 118, 44 | 45 | TOP: 1, 46 | TOP_RIGHT: 2, 47 | RIGHT: 3, 48 | BOTTOM_RIGHT: 4, 49 | BOTTOM: 5, 50 | BOTTOM_LEFT: 6, 51 | LEFT: 7, 52 | TOP_LEFT: 8, 53 | 54 | COLOR_RED: 1, 55 | COLOR_PURPLE: 2, 56 | COLOR_BLUE: 3, 57 | COLOR_CYAN: 4, 58 | COLOR_GREEN: 5, 59 | COLOR_YELLOW: 6, 60 | COLOR_ORANGE: 7, 61 | COLOR_BROWN: 8, 62 | COLOR_GREY: 9, 63 | COLOR_WHITE: 10, 64 | 65 | LOOK_CREEPS: "creep", 66 | LOOK_ENERGY: "energy", 67 | LOOK_RESOURCES: "resource", 68 | LOOK_SOURCES: "source", 69 | LOOK_MINERALS: "mineral", 70 | LOOK_STRUCTURES: "structure", 71 | LOOK_FLAGS: "flag", 72 | LOOK_CONSTRUCTION_SITES: "constructionSite", 73 | LOOK_NUKES: "nuke", 74 | LOOK_TERRAIN: "terrain", 75 | LOOK_TOMBSTONES: "tombstone", 76 | 77 | OBSTACLE_OBJECT_TYPES: ["spawn", "creep", "source", "mineral", "controller", "constructedWall", "extension", "link", "storage", "tower", "observer", "powerSpawn", "powerBank", "lab", "terminal", "nuker"], 78 | 79 | MOVE: "move", 80 | WORK: "work", 81 | CARRY: "carry", 82 | ATTACK: "attack", 83 | RANGED_ATTACK: "ranged_attack", 84 | TOUGH: "tough", 85 | HEAL: "heal", 86 | CLAIM: "claim", 87 | 88 | BODYPART_COST: { 89 | "move": 50, 90 | "work": 100, 91 | "attack": 80, 92 | "carry": 50, 93 | "heal": 250, 94 | "ranged_attack": 150, 95 | "tough": 10, 96 | "claim": 600 97 | }, 98 | 99 | // WORLD_WIDTH and WORLD_HEIGHT constants are deprecated, please use Game.map.getWorldSize() instead 100 | WORLD_WIDTH: 202, 101 | WORLD_HEIGHT: 202, 102 | 103 | CREEP_LIFE_TIME: 1500, 104 | CREEP_CLAIM_LIFE_TIME: 600, 105 | CREEP_CORPSE_RATE: 0.2, 106 | CREEP_PART_MAX_ENERGY: 125, 107 | 108 | CARRY_CAPACITY: 50, 109 | HARVEST_POWER: 2, 110 | HARVEST_MINERAL_POWER: 1, 111 | REPAIR_POWER: 100, 112 | DISMANTLE_POWER: 50, 113 | BUILD_POWER: 5, 114 | ATTACK_POWER: 30, 115 | UPGRADE_CONTROLLER_POWER: 1, 116 | RANGED_ATTACK_POWER: 10, 117 | HEAL_POWER: 12, 118 | RANGED_HEAL_POWER: 4, 119 | REPAIR_COST: 0.01, 120 | DISMANTLE_COST: 0.005, 121 | 122 | RAMPART_DECAY_AMOUNT: 300, 123 | RAMPART_DECAY_TIME: 100, 124 | RAMPART_HITS: 1, 125 | RAMPART_HITS_MAX: { 2: 300000, 3: 1000000, 4: 3000000, 5: 10000000, 6: 30000000, 7: 100000000, 8: 300000000 }, 126 | 127 | ENERGY_REGEN_TIME: 300, 128 | ENERGY_DECAY: 1000, 129 | 130 | SPAWN_HITS: 5000, 131 | SPAWN_ENERGY_START: 300, 132 | SPAWN_ENERGY_CAPACITY: 300, 133 | CREEP_SPAWN_TIME: 3, 134 | SPAWN_RENEW_RATIO: 1.2, 135 | 136 | SOURCE_ENERGY_CAPACITY: 3000, 137 | SOURCE_ENERGY_NEUTRAL_CAPACITY: 1500, 138 | SOURCE_ENERGY_KEEPER_CAPACITY: 4000, 139 | 140 | WALL_HITS: 1, 141 | WALL_HITS_MAX: 300000000, 142 | 143 | EXTENSION_HITS: 1000, 144 | EXTENSION_ENERGY_CAPACITY: { 0: 50, 1: 50, 2: 50, 3: 50, 4: 50, 5: 50, 6: 50, 7: 100, 8: 200 }, 145 | 146 | ROAD_HITS: 5000, 147 | ROAD_WEAROUT: 1, 148 | ROAD_DECAY_AMOUNT: 100, 149 | ROAD_DECAY_TIME: 1000, 150 | 151 | LINK_HITS: 1000, 152 | LINK_HITS_MAX: 1000, 153 | LINK_CAPACITY: 800, 154 | LINK_COOLDOWN: 1, 155 | LINK_LOSS_RATIO: 0.03, 156 | 157 | STORAGE_CAPACITY: 1000000, 158 | STORAGE_HITS: 10000, 159 | 160 | STRUCTURE_SPAWN: "spawn", 161 | STRUCTURE_EXTENSION: "extension", 162 | STRUCTURE_ROAD: "road", 163 | STRUCTURE_WALL: "constructedWall", 164 | STRUCTURE_RAMPART: "rampart", 165 | STRUCTURE_KEEPER_LAIR: "keeperLair", 166 | STRUCTURE_PORTAL: "portal", 167 | STRUCTURE_CONTROLLER: "controller", 168 | STRUCTURE_LINK: "link", 169 | STRUCTURE_STORAGE: "storage", 170 | STRUCTURE_TOWER: "tower", 171 | STRUCTURE_OBSERVER: "observer", 172 | STRUCTURE_POWER_BANK: "powerBank", 173 | STRUCTURE_POWER_SPAWN: "powerSpawn", 174 | STRUCTURE_EXTRACTOR: "extractor", 175 | STRUCTURE_LAB: "lab", 176 | STRUCTURE_TERMINAL: "terminal", 177 | STRUCTURE_CONTAINER: "container", 178 | STRUCTURE_NUKER: "nuker", 179 | 180 | CONSTRUCTION_COST: { 181 | "spawn": 15000, 182 | "extension": 3000, 183 | "road": 300, 184 | "constructedWall": 1, 185 | "rampart": 1, 186 | "link": 5000, 187 | "storage": 30000, 188 | "tower": 5000, 189 | "observer": 8000, 190 | "powerSpawn": 100000, 191 | "extractor": 5000, 192 | "lab": 50000, 193 | "terminal": 100000, 194 | "container": 5000, 195 | "nuker": 100000 196 | }, 197 | CONSTRUCTION_COST_ROAD_SWAMP_RATIO: 5, 198 | CONSTRUCTION_COST_ROAD_WALL_RATIO: 150, 199 | 200 | CONTROLLER_LEVELS: { 1: 200, 2: 45000, 3: 135000, 4: 405000, 5: 1215000, 6: 3645000, 7: 10935000 }, 201 | CONTROLLER_STRUCTURES: { 202 | "spawn": { 0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 2, 8: 3 }, 203 | "extension": { 0: 0, 1: 0, 2: 5, 3: 10, 4: 20, 5: 30, 6: 40, 7: 50, 8: 60 }, 204 | "link": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 2, 6: 3, 7: 4, 8: 6 }, 205 | "road": { 0: 2500, 1: 2500, 2: 2500, 3: 2500, 4: 2500, 5: 2500, 6: 2500, 7: 2500, 8: 2500 }, 206 | "constructedWall": { 1: 0, 2: 2500, 3: 2500, 4: 2500, 5: 2500, 6: 2500, 7: 2500, 8: 2500 }, 207 | "rampart": { 1: 0, 2: 2500, 3: 2500, 4: 2500, 5: 2500, 6: 2500, 7: 2500, 8: 2500 }, 208 | "storage": { 1: 0, 2: 0, 3: 0, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1 }, 209 | "tower": { 1: 0, 2: 0, 3: 1, 4: 1, 5: 2, 6: 2, 7: 3, 8: 6 }, 210 | "observer": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 1 }, 211 | "powerSpawn": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 1 }, 212 | "extractor": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1, 7: 1, 8: 1 }, 213 | "terminal": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 1, 7: 1, 8: 1 }, 214 | "lab": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 3, 7: 6, 8: 10 }, 215 | "container": { 0: 5, 1: 5, 2: 5, 3: 5, 4: 5, 5: 5, 6: 5, 7: 5, 8: 5 }, 216 | "nuker": { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 1 } 217 | }, 218 | CONTROLLER_DOWNGRADE: { 1: 20000, 2: 5000, 3: 10000, 4: 20000, 5: 40000, 6: 60000, 7: 100000, 8: 150000 }, 219 | CONTROLLER_DOWNGRADE_RESTORE: 100, 220 | CONTROLLER_DOWNGRADE_SAFEMODE_THRESHOLD: 5000, 221 | CONTROLLER_CLAIM_DOWNGRADE: 300, 222 | CONTROLLER_RESERVE: 1, 223 | CONTROLLER_RESERVE_MAX: 5000, 224 | CONTROLLER_MAX_UPGRADE_PER_TICK: 15, 225 | CONTROLLER_ATTACK_BLOCKED_UPGRADE: 1000, 226 | CONTROLLER_NUKE_BLOCKED_UPGRADE: 200, 227 | 228 | SAFE_MODE_DURATION: 20000, 229 | SAFE_MODE_COOLDOWN: 50000, 230 | SAFE_MODE_COST: 1000, 231 | 232 | TOWER_HITS: 3000, 233 | TOWER_CAPACITY: 1000, 234 | TOWER_ENERGY_COST: 10, 235 | TOWER_POWER_ATTACK: 600, 236 | TOWER_POWER_HEAL: 400, 237 | TOWER_POWER_REPAIR: 800, 238 | TOWER_OPTIMAL_RANGE: 5, 239 | TOWER_FALLOFF_RANGE: 20, 240 | TOWER_FALLOFF: 0.75, 241 | 242 | OBSERVER_HITS: 500, 243 | OBSERVER_RANGE: 10, 244 | 245 | POWER_BANK_HITS: 2000000, 246 | POWER_BANK_CAPACITY_MAX: 5000, 247 | POWER_BANK_CAPACITY_MIN: 500, 248 | POWER_BANK_CAPACITY_CRIT: 0.3, 249 | POWER_BANK_DECAY: 5000, 250 | POWER_BANK_HIT_BACK: 0.5, 251 | 252 | POWER_SPAWN_HITS: 5000, 253 | POWER_SPAWN_ENERGY_CAPACITY: 5000, 254 | POWER_SPAWN_POWER_CAPACITY: 100, 255 | POWER_SPAWN_ENERGY_RATIO: 50, 256 | 257 | EXTRACTOR_HITS: 500, 258 | EXTRACTOR_COOLDOWN: 5, 259 | 260 | LAB_HITS: 500, 261 | LAB_MINERAL_CAPACITY: 3000, 262 | LAB_ENERGY_CAPACITY: 2000, 263 | LAB_BOOST_ENERGY: 20, 264 | LAB_BOOST_MINERAL: 30, 265 | // The LAB_COOLDOWN constant is deprecated, please use REACTION_TIME instead 266 | LAB_COOLDOWN: 10, 267 | LAB_REACTION_AMOUNT: 5, 268 | LAB_UNBOOST_ENERGY: 0, 269 | LAB_UNBOOST_MINERAL: 15, 270 | 271 | GCL_POW: 2.4, 272 | GCL_MULTIPLY: 1000000, 273 | GCL_NOVICE: 3, 274 | 275 | MODE_SIMULATION: null, 276 | MODE_WORLD: null, 277 | 278 | TERRAIN_MASK_WALL: 1, 279 | TERRAIN_MASK_SWAMP: 2, 280 | TERRAIN_MASK_LAVA: 4, 281 | 282 | MAX_CONSTRUCTION_SITES: 100, 283 | MAX_CREEP_SIZE: 50, 284 | 285 | MINERAL_REGEN_TIME: 50000, 286 | MINERAL_MIN_AMOUNT: { 287 | "H": 35000, 288 | "O": 35000, 289 | "L": 35000, 290 | "K": 35000, 291 | "Z": 35000, 292 | "U": 35000, 293 | "X": 35000 294 | }, 295 | MINERAL_RANDOM_FACTOR: 2, 296 | 297 | MINERAL_DENSITY: { 298 | 1: 15000, 299 | 2: 35000, 300 | 3: 70000, 301 | 4: 100000 302 | }, 303 | MINERAL_DENSITY_PROBABILITY: { 304 | 1: 0.1, 305 | 2: 0.5, 306 | 3: 0.9, 307 | 4: 1.0 308 | }, 309 | MINERAL_DENSITY_CHANGE: 0.05, 310 | 311 | DENSITY_LOW: 1, 312 | DENSITY_MODERATE: 2, 313 | DENSITY_HIGH: 3, 314 | DENSITY_ULTRA: 4, 315 | 316 | TERMINAL_CAPACITY: 300000, 317 | TERMINAL_HITS: 3000, 318 | TERMINAL_SEND_COST: 0.1, 319 | TERMINAL_MIN_SEND: 100, 320 | TERMINAL_COOLDOWN: 10, 321 | 322 | CONTAINER_HITS: 250000, 323 | CONTAINER_CAPACITY: 2000, 324 | CONTAINER_DECAY: 5000, 325 | CONTAINER_DECAY_TIME: 100, 326 | CONTAINER_DECAY_TIME_OWNED: 500, 327 | 328 | NUKER_HITS: 1000, 329 | NUKER_COOLDOWN: 100000, 330 | NUKER_ENERGY_CAPACITY: 300000, 331 | NUKER_GHODIUM_CAPACITY: 5000, 332 | NUKE_LAND_TIME: 50000, 333 | NUKE_RANGE: 10, 334 | NUKE_DAMAGE: { 335 | 0: 10000000, 336 | 2: 5000000 337 | }, 338 | 339 | TOMBSTONE_DECAY_PER_PART: 5, 340 | 341 | PORTAL_DECAY: 30000, 342 | 343 | ORDER_SELL: "sell", 344 | ORDER_BUY: "buy", 345 | 346 | MARKET_FEE: 0.05, 347 | 348 | FLAGS_LIMIT: 10000, 349 | 350 | SUBSCRIPTION_TOKEN: "token", 351 | 352 | RESOURCE_ENERGY: "energy", 353 | RESOURCE_POWER: "power", 354 | 355 | RESOURCE_HYDROGEN: "H", 356 | RESOURCE_OXYGEN: "O", 357 | RESOURCE_UTRIUM: "U", 358 | RESOURCE_LEMERGIUM: "L", 359 | RESOURCE_KEANIUM: "K", 360 | RESOURCE_ZYNTHIUM: "Z", 361 | RESOURCE_CATALYST: "X", 362 | RESOURCE_GHODIUM: "G", 363 | 364 | RESOURCE_HYDROXIDE: "OH", 365 | RESOURCE_ZYNTHIUM_KEANITE: "ZK", 366 | RESOURCE_UTRIUM_LEMERGITE: "UL", 367 | 368 | RESOURCE_UTRIUM_HYDRIDE: "UH", 369 | RESOURCE_UTRIUM_OXIDE: "UO", 370 | RESOURCE_KEANIUM_HYDRIDE: "KH", 371 | RESOURCE_KEANIUM_OXIDE: "KO", 372 | RESOURCE_LEMERGIUM_HYDRIDE: "LH", 373 | RESOURCE_LEMERGIUM_OXIDE: "LO", 374 | RESOURCE_ZYNTHIUM_HYDRIDE: "ZH", 375 | RESOURCE_ZYNTHIUM_OXIDE: "ZO", 376 | RESOURCE_GHODIUM_HYDRIDE: "GH", 377 | RESOURCE_GHODIUM_OXIDE: "GO", 378 | 379 | RESOURCE_UTRIUM_ACID: "UH2O", 380 | RESOURCE_UTRIUM_ALKALIDE: "UHO2", 381 | RESOURCE_KEANIUM_ACID: "KH2O", 382 | RESOURCE_KEANIUM_ALKALIDE: "KHO2", 383 | RESOURCE_LEMERGIUM_ACID: "LH2O", 384 | RESOURCE_LEMERGIUM_ALKALIDE: "LHO2", 385 | RESOURCE_ZYNTHIUM_ACID: "ZH2O", 386 | RESOURCE_ZYNTHIUM_ALKALIDE: "ZHO2", 387 | RESOURCE_GHODIUM_ACID: "GH2O", 388 | RESOURCE_GHODIUM_ALKALIDE: "GHO2", 389 | 390 | RESOURCE_CATALYZED_UTRIUM_ACID: "XUH2O", 391 | RESOURCE_CATALYZED_UTRIUM_ALKALIDE: "XUHO2", 392 | RESOURCE_CATALYZED_KEANIUM_ACID: "XKH2O", 393 | RESOURCE_CATALYZED_KEANIUM_ALKALIDE: "XKHO2", 394 | RESOURCE_CATALYZED_LEMERGIUM_ACID: "XLH2O", 395 | RESOURCE_CATALYZED_LEMERGIUM_ALKALIDE: "XLHO2", 396 | RESOURCE_CATALYZED_ZYNTHIUM_ACID: "XZH2O", 397 | RESOURCE_CATALYZED_ZYNTHIUM_ALKALIDE: "XZHO2", 398 | RESOURCE_CATALYZED_GHODIUM_ACID: "XGH2O", 399 | RESOURCE_CATALYZED_GHODIUM_ALKALIDE: "XGHO2", 400 | 401 | REACTIONS: { 402 | H: { 403 | O: "OH", 404 | L: "LH", 405 | K: "KH", 406 | U: "UH", 407 | Z: "ZH", 408 | G: "GH" 409 | }, 410 | O: { 411 | H: "OH", 412 | L: "LO", 413 | K: "KO", 414 | U: "UO", 415 | Z: "ZO", 416 | G: "GO" 417 | }, 418 | Z: { 419 | K: "ZK", 420 | H: "ZH", 421 | O: "ZO" 422 | }, 423 | L: { 424 | U: "UL", 425 | H: "LH", 426 | O: "LO" 427 | }, 428 | K: { 429 | Z: "ZK", 430 | H: "KH", 431 | O: "KO" 432 | }, 433 | G: { 434 | H: "GH", 435 | O: "GO" 436 | }, 437 | U: { 438 | L: "UL", 439 | H: "UH", 440 | O: "UO" 441 | }, 442 | OH: { 443 | UH: "UH2O", 444 | UO: "UHO2", 445 | ZH: "ZH2O", 446 | ZO: "ZHO2", 447 | KH: "KH2O", 448 | KO: "KHO2", 449 | LH: "LH2O", 450 | LO: "LHO2", 451 | GH: "GH2O", 452 | GO: "GHO2" 453 | }, 454 | X: { 455 | UH2O: "XUH2O", 456 | UHO2: "XUHO2", 457 | LH2O: "XLH2O", 458 | LHO2: "XLHO2", 459 | KH2O: "XKH2O", 460 | KHO2: "XKHO2", 461 | ZH2O: "XZH2O", 462 | ZHO2: "XZHO2", 463 | GH2O: "XGH2O", 464 | GHO2: "XGHO2" 465 | }, 466 | ZK: { 467 | UL: "G" 468 | }, 469 | UL: { 470 | ZK: "G" 471 | }, 472 | LH: { 473 | OH: "LH2O" 474 | }, 475 | ZH: { 476 | OH: "ZH2O" 477 | }, 478 | GH: { 479 | OH: "GH2O" 480 | }, 481 | KH: { 482 | OH: "KH2O" 483 | }, 484 | UH: { 485 | OH: "UH2O" 486 | }, 487 | LO: { 488 | OH: "LHO2" 489 | }, 490 | ZO: { 491 | OH: "ZHO2" 492 | }, 493 | KO: { 494 | OH: "KHO2" 495 | }, 496 | UO: { 497 | OH: "UHO2" 498 | }, 499 | GO: { 500 | OH: "GHO2" 501 | }, 502 | LH2O: { 503 | X: "XLH2O" 504 | }, 505 | KH2O: { 506 | X: "XKH2O" 507 | }, 508 | ZH2O: { 509 | X: "XZH2O" 510 | }, 511 | UH2O: { 512 | X: "XUH2O" 513 | }, 514 | GH2O: { 515 | X: "XGH2O" 516 | }, 517 | LHO2: { 518 | X: "XLHO2" 519 | }, 520 | UHO2: { 521 | X: "XUHO2" 522 | }, 523 | KHO2: { 524 | X: "XKHO2" 525 | }, 526 | ZHO2: { 527 | X: "XZHO2" 528 | }, 529 | GHO2: { 530 | X: "XGHO2" 531 | } 532 | }, 533 | 534 | BOOSTS: { 535 | work: { 536 | UO: { 537 | harvest: 3 538 | }, 539 | UHO2: { 540 | harvest: 5 541 | }, 542 | XUHO2: { 543 | harvest: 7 544 | }, 545 | LH: { 546 | build: 1.5, 547 | repair: 1.5 548 | }, 549 | LH2O: { 550 | build: 1.8, 551 | repair: 1.8 552 | }, 553 | XLH2O: { 554 | build: 2, 555 | repair: 2 556 | }, 557 | ZH: { 558 | dismantle: 2 559 | }, 560 | ZH2O: { 561 | dismantle: 3 562 | }, 563 | XZH2O: { 564 | dismantle: 4 565 | }, 566 | GH: { 567 | upgradeController: 1.5 568 | }, 569 | GH2O: { 570 | upgradeController: 1.8 571 | }, 572 | XGH2O: { 573 | upgradeController: 2 574 | } 575 | }, 576 | attack: { 577 | UH: { 578 | attack: 2 579 | }, 580 | UH2O: { 581 | attack: 3 582 | }, 583 | XUH2O: { 584 | attack: 4 585 | } 586 | }, 587 | ranged_attack: { 588 | KO: { 589 | rangedAttack: 2, 590 | rangedMassAttack: 2 591 | }, 592 | KHO2: { 593 | rangedAttack: 3, 594 | rangedMassAttack: 3 595 | }, 596 | XKHO2: { 597 | rangedAttack: 4, 598 | rangedMassAttack: 4 599 | } 600 | }, 601 | heal: { 602 | LO: { 603 | heal: 2, 604 | rangedHeal: 2 605 | }, 606 | LHO2: { 607 | heal: 3, 608 | rangedHeal: 3 609 | }, 610 | XLHO2: { 611 | heal: 4, 612 | rangedHeal: 4 613 | } 614 | }, 615 | carry: { 616 | KH: { 617 | capacity: 2 618 | }, 619 | KH2O: { 620 | capacity: 3 621 | }, 622 | XKH2O: { 623 | capacity: 4 624 | } 625 | }, 626 | move: { 627 | ZO: { 628 | fatigue: 2 629 | }, 630 | ZHO2: { 631 | fatigue: 3 632 | }, 633 | XZHO2: { 634 | fatigue: 4 635 | } 636 | }, 637 | tough: { 638 | GO: { 639 | damage: .7 640 | }, 641 | GHO2: { 642 | damage: .5 643 | }, 644 | XGHO2: { 645 | damage: .3 646 | } 647 | } 648 | }, 649 | 650 | REACTION_TIME: { 651 | OH: 20, 652 | ZK: 5, 653 | UL: 5, 654 | G: 5, 655 | UH: 10, 656 | UH2O: 5, 657 | XUH2O: 60, 658 | UO: 10, 659 | UHO2: 5, 660 | XUHO2: 60, 661 | KH: 10, 662 | KH2O: 5, 663 | XKH2O: 60, 664 | KO: 10, 665 | KHO2: 5, 666 | XKHO2: 60, 667 | LH: 15, 668 | LH2O: 10, 669 | XLH2O: 65, 670 | LO: 10, 671 | LHO2: 5, 672 | XLHO2: 60, 673 | ZH: 20, 674 | ZH2O: 40, 675 | XZH2O: 160, 676 | ZO: 10, 677 | ZHO2: 5, 678 | XZHO2: 60, 679 | GH: 10, 680 | GH2O: 15, 681 | XGH2O: 80, 682 | GO: 10, 683 | GHO2: 30, 684 | XGHO2: 150, 685 | }, 686 | 687 | PORTAL_UNSTABLE: 10 * 24 * 3600 * 1000, 688 | PORTAL_MIN_TIMEOUT: 12 * 24 * 3600 * 1000, 689 | PORTAL_MAX_TIMEOUT: 22 * 24 * 3600 * 1000, 690 | 691 | POWER_BANK_RESPAWN_TIME: 50000, 692 | 693 | INVADERS_ENERGY_GOAL: 100000, 694 | 695 | SYSTEM_USERNAME: 'Screeps', 696 | 697 | // SIGN_NOVICE_AREA and SIGN_RESPAWN_AREA constants are deprecated, please use SIGN_PLANNED_AREA instead 698 | SIGN_NOVICE_AREA: 'A new Novice or Respawn Area is being planned somewhere in this sector. Please make sure all important rooms are reserved.', 699 | SIGN_RESPAWN_AREA: 'A new Novice or Respawn Area is being planned somewhere in this sector. Please make sure all important rooms are reserved.', 700 | SIGN_PLANNED_AREA: 'A new Novice or Respawn Area is being planned somewhere in this sector. Please make sure all important rooms are reserved.', 701 | 702 | EVENT_ATTACK: 1, 703 | EVENT_OBJECT_DESTROYED: 2, 704 | EVENT_ATTACK_CONTROLLER: 3, 705 | EVENT_BUILD: 4, 706 | EVENT_HARVEST: 5, 707 | EVENT_HEAL: 6, 708 | EVENT_REPAIR: 7, 709 | EVENT_RESERVE_CONTROLLER: 8, 710 | EVENT_UPGRADE_CONTROLLER: 9, 711 | EVENT_EXIT: 10, 712 | 713 | EVENT_ATTACK_TYPE_MELEE: 1, 714 | EVENT_ATTACK_TYPE_RANGED: 2, 715 | EVENT_ATTACK_TYPE_RANGED_MASS: 3, 716 | EVENT_ATTACK_TYPE_DISMANTLE: 4, 717 | EVENT_ATTACK_TYPE_HIT_BACK: 5, 718 | EVENT_ATTACK_TYPE_NUKE: 6, 719 | 720 | EVENT_HEAL_TYPE_MELEE: 1, 721 | EVENT_HEAL_TYPE_RANGED: 2, 722 | }; 723 | 724 | constants['BODYPARTS_ALL'] = [ 725 | constants.MOVE, 726 | constants.WORK, 727 | constants.CARRY, 728 | constants.ATTACK, 729 | constants.RANGED_ATTACK, 730 | constants.TOUGH, 731 | constants.HEAL, 732 | constants.CLAIM 733 | ]; 734 | 735 | constants['RESOURCES_ALL'] = [ 736 | constants.RESOURCE_ENERGY, 737 | constants.RESOURCE_POWER, 738 | 739 | constants.RESOURCE_HYDROGEN, 740 | constants.RESOURCE_OXYGEN, 741 | constants.RESOURCE_UTRIUM, 742 | constants.RESOURCE_KEANIUM, 743 | constants.RESOURCE_LEMERGIUM, 744 | constants.RESOURCE_ZYNTHIUM, 745 | constants.RESOURCE_CATALYST, 746 | constants.RESOURCE_GHODIUM, 747 | 748 | constants.RESOURCE_HYDROXIDE, 749 | constants.RESOURCE_ZYNTHIUM_KEANITE, 750 | constants.RESOURCE_UTRIUM_LEMERGITE, 751 | 752 | constants.RESOURCE_UTRIUM_HYDRIDE, 753 | constants.RESOURCE_UTRIUM_OXIDE, 754 | constants.RESOURCE_KEANIUM_HYDRIDE, 755 | constants.RESOURCE_KEANIUM_OXIDE, 756 | constants.RESOURCE_LEMERGIUM_HYDRIDE, 757 | constants.RESOURCE_LEMERGIUM_OXIDE, 758 | constants.RESOURCE_ZYNTHIUM_HYDRIDE, 759 | constants.RESOURCE_ZYNTHIUM_OXIDE, 760 | constants.RESOURCE_GHODIUM_HYDRIDE, 761 | constants.RESOURCE_GHODIUM_OXIDE, 762 | 763 | constants.RESOURCE_UTRIUM_ACID, 764 | constants.RESOURCE_UTRIUM_ALKALIDE, 765 | constants.RESOURCE_KEANIUM_ACID, 766 | constants.RESOURCE_KEANIUM_ALKALIDE, 767 | constants.RESOURCE_LEMERGIUM_ACID, 768 | constants.RESOURCE_LEMERGIUM_ALKALIDE, 769 | constants.RESOURCE_ZYNTHIUM_ACID, 770 | constants.RESOURCE_ZYNTHIUM_ALKALIDE, 771 | constants.RESOURCE_GHODIUM_ACID, 772 | constants.RESOURCE_GHODIUM_ALKALIDE, 773 | 774 | constants.RESOURCE_CATALYZED_UTRIUM_ACID, 775 | constants.RESOURCE_CATALYZED_UTRIUM_ALKALIDE, 776 | constants.RESOURCE_CATALYZED_KEANIUM_ACID, 777 | constants.RESOURCE_CATALYZED_KEANIUM_ALKALIDE, 778 | constants.RESOURCE_CATALYZED_LEMERGIUM_ACID, 779 | constants.RESOURCE_CATALYZED_LEMERGIUM_ALKALIDE, 780 | constants.RESOURCE_CATALYZED_ZYNTHIUM_ACID, 781 | constants.RESOURCE_CATALYZED_ZYNTHIUM_ALKALIDE, 782 | constants.RESOURCE_CATALYZED_GHODIUM_ACID, 783 | constants.RESOURCE_CATALYZED_GHODIUM_ALKALIDE 784 | ]; 785 | 786 | constants['COLORS_ALL'] = [ 787 | constants.COLOR_RED, 788 | constants.COLOR_PURPLE, 789 | constants.COLOR_BLUE, 790 | constants.COLOR_CYAN, 791 | constants.COLOR_GREEN, 792 | constants.COLOR_YELLOW, 793 | constants.COLOR_ORANGE, 794 | constants.COLOR_BROWN, 795 | constants.COLOR_GREY, 796 | constants.COLOR_WHITE, 797 | ]; 798 | 799 | module.exports = constants; -------------------------------------------------------------------------------- /src/room.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import { Room } from 'screeps-globals'; 3 | import creepManager from './utils/creep-manager'; 4 | import structureManager from './utils/structure-manager'; 5 | import { LOOK_STRUCTURES, STRUCTURE_LINK } from './utils/constants'; 6 | 7 | const MIN_RESERVE_LEVEL = 6; 8 | 9 | //#region Old way of writing roles 10 | function getAllScoutHarvesters() { 11 | return creepManager.creeps().filter(creep => { 12 | return creep.memory.role === 'scoutharvester' || creep.memory.oldRole === 'scoutharvester'; 13 | }); 14 | } 15 | 16 | function getAllScouts() { 17 | return creepManager.creepsWithRole('scout'); 18 | } 19 | 20 | function getAllAttackers() { 21 | return creepManager.creepsWithRole('attacker'); 22 | } 23 | //#endregion 24 | 25 | const cardinality = { 26 | N: -1, 27 | S: 1, 28 | W: -1, 29 | E: 1, 30 | }; 31 | 32 | function coordValue(roomName, regex) { 33 | const xString = regex.exec(roomName)[1]; 34 | const modifier = cardinality[xString.substr(0, 1)]; 35 | const value = xString.substr(1); 36 | return modifier * value; 37 | } 38 | 39 | function xValueFromRoomName(roomName) { 40 | return coordValue(roomName, /([WE]\d+)/); 41 | } 42 | 43 | function yValueFromRoomName(roomName) { 44 | return coordValue(roomName, /([NS]\d+)/); 45 | } 46 | 47 | Object.assign(Room.prototype, { 48 | work() { 49 | this.getMyStructures().forEach((structure) => { 50 | structure.work(); 51 | }); 52 | 53 | this.myCreeps().forEach((creep) => { 54 | creep.work(); 55 | }); 56 | 57 | this.getFlags().forEach((flag) => { 58 | flag.work(); 59 | }); 60 | 61 | this.determineSourceLinkSites().forEach(this.placeLinkFlag, this); 62 | this.determineSourceSpawnLocations().forEach(this.placeSpawnFlag, this); 63 | this.determineContainerSites().forEach(this.placeContainerFlag, this); 64 | this.determineTowerSites().forEach(this.placeTowerFlag, this); 65 | 66 | if (this.getSpawns().length) { 67 | this.determineRoadSites().forEach(this.placeRoadFlag, this); 68 | this.determineControllerLinkLocations().forEach(this.placeLinkFlag, this); 69 | } 70 | }, 71 | 72 | hasDirectExitTo(roomName) { 73 | const targetX = xValueFromRoomName(roomName); 74 | const targetY = yValueFromRoomName(roomName); 75 | const x = this.getXCoord(); 76 | const y = this.getYCoord(); 77 | if (this.distanceToRoom(roomName) > 1) { 78 | return null; 79 | } 80 | 81 | if (x < targetX) { 82 | return this.hasEastExit(); 83 | } else if (x > targetX) { 84 | return this.hasWestExit(); 85 | } else if (y < targetY) { 86 | return this.hasSouthExit(); 87 | } 88 | 89 | return this.hasNorthExit(); 90 | }, 91 | 92 | hasEastExit() { 93 | return !!this.getUniqueExitPoints().find(exitPos => exitPos.x === 49); 94 | }, 95 | 96 | hasWestExit() { 97 | return !!this.getUniqueExitPoints().find(exitPos => exitPos.x === 0); 98 | }, 99 | 100 | hasNorthExit() { 101 | return !!this.getUniqueExitPoints().find(exitPos => exitPos.y === 0); 102 | }, 103 | 104 | hasSouthExit() { 105 | return !!this.getUniqueExitPoints().find(exitPos => exitPos.y === 49); 106 | }, 107 | 108 | hasHostileCreeps() { 109 | return this.getHostileCreeps().length > 0; 110 | }, 111 | 112 | // roomName needs to be a string because we may not have access to the room object on Game.rooms. 113 | distanceToRoom(roomName) { 114 | const xDistance = this.getXCoord() - xValueFromRoomName(roomName); 115 | const yDistance = this.getYCoord() - yValueFromRoomName(roomName); 116 | const distance = xDistance * xDistance + yDistance * yDistance; 117 | return Math.sqrt(distance); 118 | }, 119 | 120 | isClosestToRoom(roomName) { 121 | return Game.getClosestOwnedRoomTo(roomName); 122 | }, 123 | 124 | getXCoord() { 125 | return xValueFromRoomName(this.name); 126 | }, 127 | 128 | getYCoord() { 129 | return yValueFromRoomName(this.name); 130 | }, 131 | 132 | needsAttackers() { 133 | return getAllAttackers().length < 2; 134 | }, 135 | 136 | placeStructures() { 137 | if (this.needsObserver()) { 138 | this.buildObserver(); 139 | } 140 | 141 | if (this.needsExtractor()) { 142 | this.buildExtractor(); 143 | } 144 | 145 | if (this.needsTerminal()) { 146 | this.buildTerminal(); 147 | } 148 | }, 149 | 150 | buildRoadAt(x, y) { 151 | this._buildRoadAtCalls = this._buildRoadAtCalls || 0; 152 | if (this._buildRoadAtCalls < 5 && this.getConstructionSites().length < 5) { 153 | if (this.createConstructionSite(x, y, STRUCTURE_ROAD) === 0) { 154 | this._buildRoadAtCalls++; 155 | } 156 | } 157 | }, 158 | 159 | getHostileCreeps() { 160 | return this.find(FIND_HOSTILE_CREEPS); 161 | }, 162 | 163 | needsObserver() { 164 | return this.controller.level >= 8 && !this.getObserver(); 165 | }, 166 | 167 | needsExtractor() { 168 | return this.controller.level >= 6 && !this.getExtractor(); 169 | }, 170 | 171 | needsTerminal() { 172 | return this.controller.level >= 6 && !this.getTerminal(); 173 | }, 174 | 175 | buildObserver() { 176 | const spawn = this.getSpawns()[0]; 177 | if (spawn) { 178 | this.createConstructionSite(spawn.pos.x + 1, spawn.pos.y + 1, STRUCTURE_OBSERVER); 179 | } 180 | }, 181 | 182 | buildExtractor() { 183 | this.getMineralSites().forEach(mineral => { 184 | this.createConstructionSite(mineral.pos.x, mineral.pos.y, STRUCTURE_EXTRACTOR); 185 | }); 186 | }, 187 | 188 | buildTerminal() { 189 | const spawn = this.getSpawns[0]; 190 | if (spawn) { 191 | this.createConstructionSite(spawn.pos.x - 2, spawn.pos.y + 2, STRUCTURE_TERMINAL); 192 | } 193 | }, 194 | 195 | needsUpgraders() { 196 | const hasFreeEdges = this.upgraderCount() < this.controller.pos.freeEdges(); 197 | return hasFreeEdges && 198 | this.upgraderWorkParts() < this.maxEnergyProducedPerTick() && 199 | !this.getConstructionSites().length; 200 | }, 201 | 202 | clearConstructionSites() { 203 | this.getConstructionSites().forEach(constructionSite => { 204 | constructionSite.remove(); 205 | }); 206 | }, 207 | 208 | needsBuilders() { 209 | return this.builderCount() < 1 && this.getConstructionSites().length > 0; 210 | }, 211 | 212 | damagedBuildings() { 213 | return this.getStructures().filter(structure => { 214 | return structure.structureType !== STRUCTURE_ROAD && structure.needsRepaired(); 215 | }); 216 | }, 217 | 218 | getStorage() { 219 | if (!this._storageCalc) { 220 | this._storageCalc = true; 221 | this._storage = this.getMyStructures().filter(structure => { 222 | return structure.structureType === STRUCTURE_STORAGE; 223 | })[0]; 224 | } 225 | return this._storage; 226 | }, 227 | 228 | hasTowers() { 229 | return this.getTowers().length > 0; 230 | }, 231 | 232 | getTowers() { 233 | if (!this._towers) { 234 | this._towers = this.getMyStructures().filter(structure => { 235 | return structure.structureType === STRUCTURE_TOWER; 236 | }); 237 | } 238 | return this._towers; 239 | }, 240 | 241 | getContainers() { 242 | if (!this._containers) { 243 | this._containers = this.getStructures().filter(structure => { 244 | return structure.structureType === STRUCTURE_CONTAINER; 245 | }); 246 | } 247 | return this._containers; 248 | }, 249 | 250 | getObserver() { 251 | if (!this._observerCalc) { 252 | this._observerCalc = true; 253 | this._observer = this.getMyStructures().filter(structure => { 254 | return structure.structureType === STRUCTURE_OBSERVER; 255 | })[0]; 256 | } 257 | 258 | return this._observer; 259 | }, 260 | 261 | getMineralSites() { 262 | if (!this._minerals) { 263 | this._minerals = this.find(FIND_MINERALS); 264 | } 265 | return this._minerals; 266 | }, 267 | 268 | getExtractor() { 269 | if (!this._extractorCalc) { 270 | this._extractorCalc = true; 271 | this._extractor = this.getMyStructures().filter(structure => { 272 | return structure.structureType === STRUCTURE_EXTRACTOR; 273 | })[0]; 274 | } 275 | 276 | return this._extractor; 277 | }, 278 | 279 | getTerminal() { 280 | if (!this._terminalCalc) { 281 | this._termainalCalc = true; 282 | this._terminal = this.getMyStructures().filter(structure => { 283 | return structure.structureType === STRUCTURE_TERMINAL; 284 | })[0]; 285 | } 286 | return this._terminal; 287 | }, 288 | 289 | placeFlags() { 290 | if (this.controller) { 291 | this.controller.placeFlags(); 292 | } 293 | this.placeConstructionFlags(); 294 | }, 295 | 296 | placeConstructionFlags() { 297 | this.placeWallFlags(); 298 | }, 299 | 300 | placeStorageFlag(pos) { 301 | this.createBuildFlag(pos, STRUCTURE_STORAGE); 302 | }, 303 | 304 | placeLinkFlag(pos) { 305 | this.createBuildFlag(pos, STRUCTURE_LINK); 306 | }, 307 | 308 | placeRoadFlag(pos) { 309 | this.createBuildFlag(pos, STRUCTURE_ROAD); 310 | }, 311 | 312 | placeSpawnFlag(pos) { 313 | this.createBuildFlag(pos, STRUCTURE_SPAWN); 314 | }, 315 | 316 | placeTowerFlag(pos) { 317 | this.createBuildFlag(pos, STRUCTURE_TOWER); 318 | }, 319 | 320 | placeContainerFlag(pos) { 321 | this.createBuildFlag(pos, STRUCTURE_CONTAINER); 322 | }, 323 | 324 | placeWallFlags() { 325 | if (this.hasTowers()) { 326 | const exits = this.getExits(); 327 | exits.forEach(exitPos => { 328 | const potentialSpots = exitPos.openPositionsAtRange(2); 329 | const realSpots = potentialSpots.filter(potentialSpot => { 330 | let shouldBuild = true; 331 | exits.forEach(exit => { 332 | if (exit.getRangeTo(potentialSpot) < 2) { 333 | shouldBuild = false; 334 | } 335 | }); 336 | return shouldBuild; 337 | }); 338 | realSpots.forEach(realSpot => { 339 | this.createBuildFlag(realSpot, STRUCTURE_WALL); 340 | }); 341 | }); 342 | } 343 | }, 344 | 345 | createBuildFlag(pos, structureType) { 346 | const existingStructures = [ 347 | ...pos.lookFor(LOOK_STRUCTURES), 348 | ...pos.lookFor(LOOK_CONSTRUCTION_SITES) 349 | ]; 350 | const alreadyHasStructure = existingStructures.find(structure => { 351 | return structure.structureType === structureType || structureType === STRUCTURE_ROAD; 352 | }); 353 | 354 | if (alreadyHasStructure) { 355 | return undefined; 356 | } else if (existingStructures.length === 0 || (existingStructures.length === 1 && existingStructures[0].structureType === STRUCTURE_ROAD)) { 357 | return this.placeFlag(pos, `BUILD_${structureType}`); 358 | } 359 | 360 | return this.placeFlag(pos, `here?_${structureType}`); 361 | }, 362 | 363 | placeFlag(pos, name) { 364 | this.createFlag(pos, `${name}_${this.name}_x${pos.x}_y${pos.y}`); 365 | }, 366 | 367 | getTowerFlags() { 368 | return this.getFlags().filter(flag => { 369 | return flag.name.indexOf(STRUCTURE_TOWER) !== -1; 370 | }); 371 | }, 372 | 373 | getLinks() { 374 | if (!this._links) { 375 | this._links = this.getMyStructures().filter(structure => { 376 | return structure.structureType === STRUCTURE_LINK; 377 | }); 378 | } 379 | 380 | return this._links; 381 | }, 382 | 383 | upgraderWorkParts() { 384 | if (!this._upgraderWorkParts) { 385 | let parts = this.getUpgraders(); 386 | parts = parts.map(upgrader => { 387 | return upgrader.body.filter(bodyPart => { 388 | return bodyPart.type === WORK; 389 | }).length; 390 | }); 391 | 392 | if (parts.length) { 393 | this._upgraderWorkParts = parts.reduce((a, b) => { return a + b; }); 394 | } else { 395 | this._upgraderWorkParts = 0; 396 | } 397 | } 398 | 399 | return this._upgraderWorkParts; 400 | }, 401 | 402 | maxEnergyProducedPerTick() { 403 | return this.sourceCount() * 10; 404 | }, 405 | 406 | sourceCount() { 407 | return this.getSources().length; 408 | }, 409 | 410 | getMyStructures() { 411 | if (!this._myStructures) { 412 | const structures = this.getStructures(); 413 | this._myStructures = structures.filter(structure => structure.my); 414 | } 415 | 416 | return this._myStructures; 417 | }, 418 | 419 | getStructures() { 420 | if (!this._structures) { 421 | const structures = structureManager.structures(); 422 | this._structures = structures.filter(structure => structure.room === this); 423 | } 424 | return this._structures; 425 | }, 426 | 427 | getRoads() { 428 | if (!this._roads) { 429 | this._roads = this.getStructures().filter(structure => { 430 | return structure.structureType === STRUCTURE_ROAD; 431 | }); 432 | } 433 | 434 | return this._roads; 435 | }, 436 | 437 | getDamagedRoads() { 438 | if (!this._damagedRoads) { 439 | this._damagedRoads = this.getRoads().filter(road => { 440 | return road.structureType === STRUCTURE_ROAD && road.hits / road.hitsMax < 0.5; 441 | }); 442 | } 443 | 444 | return this._damagedRoads; 445 | }, 446 | 447 | hasDamagedRoads() { 448 | return this.getDamagedRoads().length > 0; 449 | }, 450 | 451 | placeReserveFlag() { 452 | this.placeFlag(this.getCenterPosition(), 'reserve'); 453 | }, 454 | 455 | getClaimFlags() { 456 | return this.getFlags().filter(flag => { 457 | return flag.name.includes('claim'); 458 | }); 459 | }, 460 | 461 | shouldClaim() { 462 | const claimFlags = this.getClaimFlags(); 463 | return claimFlags.length === 0 && this.controller && !this.controller.owner && !this.controller.reservation; 464 | }, 465 | 466 | placeClaimFlag() { 467 | this.placeFlag(this.getCenterPosition(), 'claim'); 468 | }, 469 | 470 | attemptReserve() { 471 | if (this.shouldClaim()) { 472 | return this.placeClaimFlag(); 473 | } else if (this.shouldReserve()) { 474 | return this.placeReserveFlag(); 475 | } 476 | }, 477 | 478 | shouldReserve() { 479 | const hasController = this.controller; 480 | const ownNearbyRoom = Game.myRooms().find(room => { 481 | const isNextTo = room.distanceToRoom(this.name) === 1; 482 | return isNextTo && 483 | room.ableToReserve() && 484 | this.hasDirectExitTo(room.name); 485 | }); 486 | 487 | return hasController && !this.getSpawns().length && !!ownNearbyRoom; 488 | }, 489 | 490 | getCreepsWithRole(role) { 491 | if (!this._creepsWithRole) { 492 | this._creepsWithRole = this.myCreeps().reduce((acc, creep) => { 493 | if (!acc[creep.memory.role]) acc[creep.memory.role] = []; 494 | acc[creep.memory.role].push(creep); 495 | return acc; 496 | }, {}); 497 | } 498 | return this._creepsWithRole[role] || []; 499 | }, 500 | 501 | getReservers() { 502 | return creepManager.creepsWithRole('reserver').filter(creep => creep.memory.room === this.name); 503 | }, 504 | 505 | getReserverCount() { 506 | return this.getReservers().length; 507 | }, 508 | 509 | ableToReserve() { 510 | return this.getControllerOwned() && this.controller.level >= MIN_RESERVE_LEVEL; 511 | }, 512 | 513 | getReserveFlags() { 514 | return Game.flagArray().filter(flag => { 515 | return flag.isReserveFlag() && this.distanceToRoom(flag.pos.roomName) === 1; 516 | }); 517 | }, 518 | 519 | getReserveFlagsNeedingReservers() { 520 | return this.getReserveFlags().filter(flag => flag.needsReserver()); 521 | }, 522 | 523 | getReserveFlagsNeedingRemoteCouriers() { 524 | return this.getReserveFlags().filter(flag => flag.needsRemoteCouriers()); 525 | }, 526 | 527 | getReserveFlagsNeedingRemoteHarvesters() { 528 | return this.getReserveFlags().filter(flag => flag.needsRemoteHarvesters()); 529 | }, 530 | 531 | needsRemoteHarvesters() { 532 | return this.getReserveFlagsNeedingRemoteHarvesters().length > 0; 533 | }, 534 | 535 | needsRemoteCouriers() { 536 | return this.getReserveFlagsNeedingRemoteCouriers().length > 0; 537 | }, 538 | 539 | needsReservers() { 540 | const meetsLevel = this.controller.level >= MIN_RESERVE_LEVEL; 541 | const flagsNeedingReservers = this.getReserveFlagsNeedingReservers(); 542 | return meetsLevel && flagsNeedingReservers.length && this.getReserverCount() < 1; 543 | }, 544 | 545 | needsCouriers() { 546 | if (this.hasLinksConfigured()) return false; 547 | if (this.courierCount() === 1 && this.getCouriers()[0].ticksToLive < 70) { 548 | return true; 549 | } 550 | 551 | const storage = this.getStorage(); 552 | if (!storage) { 553 | return this.courierCount() < 2; 554 | } else if (storage.store.energy > 500000) { 555 | return this.courierCount() < Math.floor(storage.store.energy / 200000); 556 | } 557 | 558 | return this.courierCount() < 1; 559 | }, 560 | 561 | getExits() { 562 | if (!this._exits) { 563 | this._exits = this.find(FIND_EXIT); 564 | } 565 | 566 | return this._exits; 567 | }, 568 | 569 | getUniqueExitPoints() { 570 | if (!this._uniqueExitPoints) { 571 | const exitCoords = this.getExits(); 572 | this._uniqueExitPoints = exitCoords.filter((coord, index) => { 573 | if (index === 0) { 574 | return true; 575 | } 576 | 577 | const prevCoord = exitCoords[index - 1]; 578 | return !(Math.abs(coord.x - prevCoord.x) < 2) || !(Math.abs(coord.y - prevCoord.y) < 2); 579 | }); 580 | } 581 | 582 | return this._uniqueExitPoints; 583 | }, 584 | 585 | getFlags() { 586 | return this.find(FIND_FLAGS).filter(flag => { 587 | return flag.room === this; 588 | }); 589 | }, 590 | 591 | getControllerEnergyDropFlag() { 592 | return this.getFlags().filter(flag => { 593 | return flag.name.indexOf('CONTROLLER_ENERGY_DROP') !== -1; 594 | })[0]; 595 | }, 596 | 597 | courierCount() { 598 | return this.getCouriers().length; 599 | }, 600 | 601 | getCouriers() { 602 | if (!this._couriers) { 603 | this._couriers = this.myCreeps().filter((creep) => { 604 | return creep.memory.role === 'courier'; 605 | }); 606 | } 607 | 608 | return this._couriers; 609 | }, 610 | 611 | myCreeps() { 612 | if (!this._myCreeps) { 613 | this._myCreeps = creepManager.creeps().filter(creep => creep.room === this); 614 | } 615 | 616 | return this._myCreeps; 617 | }, 618 | 619 | builderCount() { 620 | return this.getBuilders().length; 621 | }, 622 | 623 | getBuilders() { 624 | if (!this._builders) { 625 | this._builders = this.myCreeps().filter((creep) => { 626 | return creep.memory.role === 'builder'; 627 | }); 628 | } 629 | 630 | return this._builders; 631 | }, 632 | 633 | upgraderCount() { 634 | return this.getUpgraders().length; 635 | }, 636 | 637 | getUpgraders() { 638 | if (!this._upgraders) { 639 | this._upgraders = this.myCreeps().filter(creep => { 640 | return creep.memory.role === 'upgrader'; 641 | }); 642 | } 643 | return this._upgraders; 644 | }, 645 | 646 | getConstructionSites() { 647 | return this.find(FIND_CONSTRUCTION_SITES); 648 | }, 649 | 650 | getSources() { 651 | if (!this._sources) { 652 | this._sources = this.find(FIND_SOURCES); 653 | } 654 | 655 | return this._sources; 656 | }, 657 | 658 | getEnergySourceStructures() { 659 | return this.getMyStructures().filter(structure => { 660 | return structure.energy; 661 | }); 662 | }, 663 | 664 | droppedControllerEnergy() { 665 | if (!this._droppedControllerEnergy) { 666 | const dumpFlag = this.getControllerEnergyDropFlag(); 667 | this._droppedControllerEnergy = this.find(FIND_DROPPED_RESOURCES).filter(energy => { 668 | return energy.pos.getRangeTo(dumpFlag) === 0; 669 | })[0]; 670 | } 671 | 672 | return this._droppedControllerEnergy; 673 | }, 674 | 675 | getEnergyStockSources() { 676 | if (!this._energyStockSources) { 677 | const droppedControllerEnergy = this.droppedControllerEnergy(); 678 | this._energyStockSources = this.getEnergySourceStructures(); 679 | if (droppedControllerEnergy) { 680 | this._energyStockSources.unshift(droppedControllerEnergy); 681 | } 682 | } 683 | 684 | return this._energyStockSources; 685 | }, 686 | 687 | getSpawns() { 688 | return this.find(FIND_MY_SPAWNS); 689 | }, 690 | 691 | canBuildExtension() { 692 | if (this._canBuildExtensions === undefined) { 693 | const maxExtensions = CONTROLLER_STRUCTURES[STRUCTURE_EXTENSION][this.controller.level] || 0; 694 | this._canBuildExtensions = this.getExtensions().length < maxExtensions; 695 | } 696 | return this._canBuildExtensions; 697 | }, 698 | 699 | getExtensions() { 700 | if (!this._extensions) { 701 | this._extensions = this.getMyStructures().filter(structure => { 702 | return structure.structureType === STRUCTURE_EXTENSION; 703 | }); 704 | } 705 | 706 | return this._extensions; 707 | }, 708 | 709 | getHostileStructures() { 710 | return this.getStructures().filter(structure => structure.owner && !structure.my); 711 | }, 712 | 713 | courierTargets() { 714 | return this.getCouriers().filter(creep => { 715 | return creep.memory.role === 'courier' && !!creep.memory.target; 716 | }).map(courier => { 717 | return courier.memory.target; 718 | }); 719 | }, 720 | 721 | getDroppedEnergy() { 722 | return this.find(FIND_DROPPED_RESOURCES).sort((energyA, energyB) => { 723 | return energyB.energy - energyA.energy; 724 | }); 725 | }, 726 | 727 | getEnergyThatNeedsPickedUp() { 728 | const targets = this.courierTargets(); 729 | const dumpFlag = this.getControllerEnergyDropFlag(); 730 | 731 | return this.getDroppedEnergy().filter(energy => { 732 | const targeted = targets.indexOf(energy.id) !== -1; 733 | const inRange = energy.pos.getRangeTo(this.getCenterPosition()) < 23; 734 | return !targeted && inRange && energy.pos.getRangeTo(dumpFlag) !== 0; 735 | }); 736 | }, 737 | 738 | getCenterPosition() { 739 | return new RoomPosition(25, 25, this.name); 740 | }, 741 | 742 | getControllerOwned() { 743 | return this.controller && this.controller.my; 744 | }, 745 | 746 | getDismantleFlag() { 747 | return Game.dismantleFlags().filter((flag) => { 748 | return flag.room === this; 749 | })[0]; 750 | }, 751 | 752 | getStructureAt(roomPosition) { 753 | return this.getStructures().filter((structure) => { 754 | return structure.pos.getRangeTo(roomPosition) === 0; 755 | })[0]; 756 | }, 757 | 758 | hasScoutFlag() { 759 | return Game.getScoutFlags().filter((flag) => { 760 | return flag.room === this; 761 | }).length > 0; 762 | }, 763 | 764 | needsScouts() { 765 | let desiredValue = 2; 766 | if (Game.dismantleFlags().length > 0) { 767 | desiredValue = 4; 768 | } 769 | return this.hasScoutFlag() && getAllScouts().length < desiredValue; 770 | }, 771 | 772 | needsScoutHarvesters() { 773 | let desiredValue = 2; 774 | if (Game.dismantleFlags().length > 0) { 775 | desiredValue = 0; 776 | } 777 | return this.hasScoutFlag() && getAllScoutHarvesters().length < desiredValue; 778 | }, 779 | 780 | getSpawnStructures() { 781 | return [ 782 | ...this.getExtensions(), 783 | ...this.getSpawns(), 784 | ] 785 | }, 786 | 787 | getStructuresNeedingEnergy() { 788 | if (!this._structuresNeedingEnergyDelivery) { 789 | this._structuresNeedingEnergyDelivery = this.getMyStructures().filter(structure => { 790 | return structure.needsEnergy(); 791 | }); 792 | } 793 | return this._structuresNeedingEnergyDelivery; 794 | }, 795 | 796 | getEnergySourcesThatNeedsStocked() { 797 | if (this.getEnergyThatNeedsPickedUp().length) { 798 | return this.getEnergyThatNeedsPickedUp(); 799 | } else if (this.getContainers().length) { 800 | return this.getContainers().filter(container => !container.isEmpty()); 801 | } else if (this.getStorage() && !this.getStorage().isEmpty()) { 802 | return [this.getStorage()]; 803 | } else if (this.hasTowers()) { 804 | // All towers that aren't empty are a source of energy 805 | return this.getTowers().filter(tower => { 806 | return !tower.isEmpty(); 807 | }); 808 | } 809 | 810 | return []; 811 | }, 812 | 813 | determineBuildSites() { 814 | const containerSites = this.determineContainerSites(); 815 | 816 | return { 817 | containerSites, 818 | linkSites: this.determineLinkSites(), 819 | towerSites: this.determineTowerSites(), 820 | storageSite: this.determineStorageSite(), 821 | }; 822 | }, 823 | 824 | determineContainerSites() { 825 | if (!this._containerSites) { 826 | const sources = this.getSources(); 827 | this._containerSites = sources.map((source) => { 828 | let target = this.controller; 829 | if (sources.length > 1) { 830 | target = sources.find(potentialTarget => potentialTarget !== source); 831 | } 832 | return this.mapPathToPosition(source.pos.findOptimalPathTo(target))[0]; 833 | }).filter(Boolean); 834 | } 835 | 836 | return this._containerSites; 837 | }, 838 | 839 | mapPathToPosition(path) { 840 | return path.map(pathPos => { 841 | return new RoomPosition(pathPos.x, pathPos.y, this.name); 842 | }); 843 | }, 844 | 845 | findPositionsInPathToNearestSpawn(target, onlyTerrain) { 846 | return this.mapPathToPosition(this.findPathToNearestSpawn(target, onlyTerrain)); 847 | }, 848 | 849 | hasLinksConfigured() { 850 | return this.getLinks().length >= 2; 851 | }, 852 | 853 | determineLinkSites() { 854 | return [ 855 | ...this.determineSourceLinkSites(), 856 | ...this.determineControllerLinkLocations(), 857 | ]; 858 | }, 859 | 860 | hasContainersConfigured() { 861 | return this.getContainers().length === this.getSources().length; 862 | }, 863 | 864 | findPathToNearestSpawn(target) { 865 | let nearestPath; 866 | this.getSpawns().forEach((spawn) => { 867 | const path = target.pos.findOptimalPathTo(spawn); 868 | if (!nearestPath || nearestPath.length > path.length) { 869 | nearestPath = path; 870 | } 871 | }); 872 | 873 | return nearestPath; 874 | }, 875 | 876 | determineTransferPostions() { 877 | 878 | }, 879 | 880 | determineStorageSite() { 881 | if (!this.controller) return undefined; 882 | const controllerPathPositions = this.findPositionsInPathToNearestSpawn(this.controller, true); 883 | const transferPosition = controllerPathPositions[2]; 884 | return transferPosition.buildablePositionsAtRange(1).filter((position) => { 885 | return !controllerPathPositions.find(pathPos => pathPos.isEqualTo(position)); 886 | })[0]; 887 | }, 888 | 889 | determineControllerLinkLocations() { 890 | if (!this._locations) { 891 | this._locations = []; 892 | if (this.controller) { 893 | const pathPositions = this.findPositionsInPathToNearestSpawn(this.controller, true); 894 | if (pathPositions.length > 3) { 895 | this._locations = [pathPositions[3]]; 896 | } 897 | } 898 | } 899 | return this._locations; 900 | }, 901 | 902 | getControllerLinks() { 903 | return this.determineControllerLinkLocations().map((controllerLinkPos) => { 904 | const structures = controllerLinkPos.lookFor(LOOK_STRUCTURES); 905 | return structures.find(structure => structure.structureType === STRUCTURE_LINK); 906 | }).filter(Boolean); 907 | }, 908 | 909 | killAllCreeps() { 910 | this.myCreeps().forEach(creep => creep.suicide()); 911 | }, 912 | 913 | determineSourceLinkSites() { 914 | const sources = this.getSources(); 915 | if (!(this.controller && this.controller.my)) return []; 916 | 917 | return sources.map((source) => { 918 | let target = this.controller; 919 | if (sources.length > 1) { 920 | target = sources.find(potentialTarget => potentialTarget !== source); 921 | } 922 | const pathPositions = this.mapPathToPosition(source.pos.findOptimalPathTo(target)); 923 | const transferLocation = pathPositions[1]; 924 | return transferLocation.buildablePositionsAtRange(1).filter(position => { 925 | return !pathPositions.find(pathPos => pathPos.isEqualTo(position)); 926 | })[0]; 927 | }); 928 | }, 929 | 930 | determineSourceSpawnLocations() { 931 | const sources = this.getSources(); 932 | if (!(this.controller && this.controller.my)) return []; 933 | 934 | return sources.map((source) => { 935 | let target = this.controller; 936 | if (sources.length > 1) { 937 | target = sources.find(potentialTarget => potentialTarget !== source); 938 | } 939 | const pathPositions = this.mapPathToPosition(source.pos.findOptimalPathTo(target)); 940 | const transferLocation = pathPositions[1]; 941 | return transferLocation.buildablePositionsAtRange(1).filter(position => { 942 | return !pathPositions.find(pathPos => pathPos.isEqualTo(position)); 943 | })[1]; 944 | }); 945 | }, 946 | 947 | determineTowerSites() { 948 | const sources = this.getSources(); 949 | if (!(this.controller && this.controller.my)) return []; 950 | 951 | return sources.map((source) => { 952 | let target = this.controller; 953 | if (sources.length > 1) { 954 | target = sources.find(potentialTarget => potentialTarget !== source); 955 | } 956 | const pathPositions = this.mapPathToPosition(source.pos.findOptimalPathTo(target)); 957 | const transferLocation = pathPositions[1]; 958 | return transferLocation.buildablePositionsAtRange(1).filter(position => { 959 | return !pathPositions.find(pathPos => pathPos.isEqualTo(position)); 960 | })[2]; 961 | }); 962 | }, 963 | 964 | determineRoadSites() { 965 | if (this.controller && this.controller.my) { 966 | const closestSource = this.controller.pos.findClosestByRange(this.getSources()); 967 | const pathToClosestSource = this.controller.pos.findOptimalPathTo(closestSource); 968 | const path = [ 969 | ...this.getSources().reduce((acc, source) => { 970 | const optimalPath = source.pos.findOptimalPathTo(closestSource); 971 | return [ 972 | ...acc, 973 | ...optimalPath.slice(0, optimalPath.length - 1), 974 | ]; 975 | }, []), 976 | ...pathToClosestSource.slice(0, pathToClosestSource.length - 1), 977 | ]; 978 | return this.mapPathToPosition(path); 979 | } 980 | 981 | return []; 982 | }, 983 | }); 984 | --------------------------------------------------------------------------------