diff --git a/src/config.js b/src/config.js index 915c663db..6ea8e3eec 100644 --- a/src/config.js +++ b/src/config.js @@ -7,11 +7,7 @@ global.brain = { global.roles = {}; global.profiler = {}; -try { - global.friends = require('friends'); // eslint-disable-line global-require -} catch (e) { - global.friends = []; -} +global.friends = ["Antagonist", "ceneezer"] global.config = { profiler: { @@ -46,8 +42,8 @@ global.config = { info: { signController: true, - signText: 'Fully automated open source NPC: http://tooangel.github.io/screeps/', - resignInterval: 500, + signText: 'XenoAmesss here. Nice to meat you!', + resignInterval: 5000, }, // Due to newly introduces via global variable caching this can be removed @@ -154,7 +150,7 @@ global.config = { }, pixel: { - enabled: false, + enabled: true, minBucketAfter: 2500, }, @@ -220,7 +216,7 @@ global.config = { myRoom: { underAttackMinAttackTimer: 50, - leastSpawnsToRebuildStructureSpawn: 1, + leastSpawnsToRebuildStructureSpawn: 2, }, room: { @@ -345,6 +341,11 @@ global.config = { }, maliciousNpcUsernames: ['Invader', 'Source Keeper'], + structureSpawn: { + leastStructureSpawnToDestroyStructureSpawn: 3, + otherRoomCreepComeHelpBuildStructureHelperCount: 5 + }, + }; try { diff --git a/src/main.js b/src/main.js index 995c601cb..10b41ac77 100644 --- a/src/main.js +++ b/src/main.js @@ -25,6 +25,7 @@ global.data.stats = global.data.stats || { memoryFree: 0, heapFree: 0, }; +global.maliciousNpcUsernames = ['Invader', 'Source Keeper']; console.log(`${Game.time} Script reload - Load: ${global.load} tickLimit: ${Game.cpu.tickLimit} limit: ${Game.cpu.limit} Bucket: ${Game.cpu.bucket}`); diff --git a/src/prototype_room_creepbuilder.js b/src/prototype_room_creepbuilder.js index f684534f0..e54bb87d5 100644 --- a/src/prototype_room_creepbuilder.js +++ b/src/prototype_room_creepbuilder.js @@ -75,7 +75,10 @@ Room.prototype.creepMem = function(role, targetId, targetRoom, level, base) { */ Room.prototype.getPriority = function(object) { const priority = config.priorityQueue; - const target = object.routing && object.routing.targetRoom; + let target = object.routing && object.routing.targetRoom; + if (target && target.name) { + target = target.name; + } const age = Game.time - (object.role.time || Game.time); const ageTerm = age / CREEP_LIFE_TIME * 20; if (target === this.name) { @@ -106,6 +109,8 @@ Room.prototype.spawnCheckForCreate = function() { creep.ttl = creep.ttl || config.creep.queueTtl; if (this.findSpawnsNotSpawning().length === 0) { creep.ttl--; + } else if (this.energyAvailable === this.energyCapacityAvailable) { + creep.ttl = 0; } return false; }; diff --git a/src/prototype_room_my.js b/src/prototype_room_my.js index ecc793a03..bfce05596 100644 --- a/src/prototype_room_my.js +++ b/src/prototype_room_my.js @@ -273,6 +273,25 @@ Room.prototype.getUniversalAmount = function() { return 1; }; +Room.prototype.getUpgraderAmount = function() { + if (!this.controller || !this.controller.my) { + return 0; + } + if (!this.storage) { + return 1; + } + if (!this.storage.my) { + return 0; + } + if (this.controller.level === 8) { + return 1; + } + if (this.storage.isLow()) { + return 1; + } + return 1 + Math.min(1, Math.floor(this.storage.store.energy / config.storage.lowValue / 20)); +}; + Room.prototype.handleAttackTimerWithoutHostiles = function() { this.memory.attackTimer = Math.max(this.memory.attackTimer - 5, 0); if (this.memory.attackTimer <= 0) { @@ -565,15 +584,16 @@ Room.prototype.executeRoomHandleHostiles = function() { }; Room.prototype.executeRoomCheckBasicCreeps = function() { - const amount = this.getUniversalAmount(); - this.checkRoleToSpawn('universal', amount); + const universalAmount = this.getUniversalAmount(); + this.checkRoleToSpawn('universal', universalAmount); this.checkAndSpawnSourcer(); if (this.controller.level >= 4 && this.storage && this.storage.my) { this.checkRoleToSpawn('storagefiller', 1, 'filler'); } - if (this.storage && this.storage.my && this.storage.store.energy > config.room.upgraderMinStorage && !this.memory.misplacedSpawn) { - this.checkRoleToSpawn('upgrader', 1, this.controller.id); + const upgraderAmount = this.getUpgraderAmount(); + if (this.storage && this.storage.my && this.storage.store.energy > config.room.upgraderMinStorage && !this.memory.misplacedSpawn && upgraderAmount > 0) { + this.checkRoleToSpawn('upgrader', upgraderAmount, this.controller.id); } }; diff --git a/src/role_carry.js b/src/role_carry.js index dfb42715c..a0790539a 100644 --- a/src/role_carry.js +++ b/src/role_carry.js @@ -416,8 +416,7 @@ roles.carry.action = function(creep) { // End of path, can't harvest, suicide (otherwise the sourcer gets stuck) if (!reverse && creep.body.filter((part) => part.type === WORK).length === 0) { // creep.log('Suiciding because end of path, no energy, do not want to get in the way of the sourcer (better recycle?)'); - creep.memory.killed = true; - creep.suicide(); + creep.moveRandom(); } return true; diff --git a/src/role_claimer.js b/src/role_claimer.js index 3034b6e8d..c392fcc12 100644 --- a/src/role_claimer.js +++ b/src/role_claimer.js @@ -15,14 +15,21 @@ roles.claimer.settings = { }; roles.claimer.action = function(creep) { - creep.creepLog('New claimer, in room, claiming'); - // TODO just added the targetId to the creep, I hope it works - // const returnCodeMove = creep.moveTo(creep.room.controller.pos); - // console.log(`Move returnCode ${returnCodeMove}`); - const returnCode = creep.claimController(creep.room.controller); - if (returnCode === OK) { - creep.creepLog('New claimer, in room, claimed'); - creep.suicide(); + let claimerActionCompleted = creep.memory.claimerActionCompleted; + if (!claimerActionCompleted) { + creep.creepLog('New claimer, in room, claiming'); + // TODO just added the targetId to the creep, I hope it works + // const returnCodeMove = creep.moveTo(creep.room.controller.pos); + // console.log(`Move returnCode ${returnCodeMove}`); + const returnCode = creep.claimController(creep.room.controller); + if (returnCode === OK) { + creep.creepLog('New claimer, in room, claimed'); + creep.memory.claimerActionCompleted = claimerActionCompleted = true; + } + } + if (claimerActionCompleted) { + creep.memory.role = 'scout'; + return roles.scout.action(creep); } return true; }; diff --git a/src/role_quester.js b/src/role_quester.js index afdc95797..57d987bf4 100644 --- a/src/role_quester.js +++ b/src/role_quester.js @@ -16,7 +16,7 @@ roles.quester.settings = { roles.quester.questLost = function(creep, quest, reason, value) { creep.log(`Quest lost cs: ${value} ${JSON.stringify(quest)}`); delete Memory.quests[creep.memory.level]; - creep.suicide(); + creep.memory.role = 'scout'; }; roles.quester.questWon = function(creep, quest) { @@ -33,7 +33,7 @@ roles.quester.questWon = function(creep, quest) { }; creep.room.terminal.send(RESOURCE_ENERGY, 100, quest.player.room, JSON.stringify(response)); delete Memory.quests[creep.memory.level]; - creep.suicide(); + creep.memory.role = 'scout'; }; roles.quester.handleBuildConstructionSite = function(creep, quest) { @@ -67,7 +67,7 @@ roles.quester.action = function(creep) { const quest = Memory.quests[creep.memory.level]; if (!quest) { creep.log(`Quest ${creep.memory.level} not found, suiciding`); - creep.suicide(); + creep.memory.role = 'scout'; return; } if (quest.quest === 'buildcs') { diff --git a/src/role_signer.js b/src/role_signer.js index 54a287e5e..f87267711 100644 --- a/src/role_signer.js +++ b/src/role_signer.js @@ -21,7 +21,7 @@ roles.signer.action = function(creep) { // creep.memory.routing = creep.memory.nextTarget.routing; // creep.memory.nextTarget = creep.memory.nextTarget.nextTarget; // } else { - creep.suicide(); + creep.memory.role = 'scout'; // } return true; } else { diff --git a/src/role_sourcer.js b/src/role_sourcer.js index 58d509278..d2e878b65 100644 --- a/src/role_sourcer.js +++ b/src/role_sourcer.js @@ -118,7 +118,7 @@ function harvest(creep) { if (returnCode === ERR_NO_BODYPART) { creep.room.checkRoleToSpawn('defender', 2, undefined, creep.room.name); creep.respawnMe(); - creep.suicide(); + creep.memory.role = 'scout'; return false; } diff --git a/src/role_upgrader.js b/src/role_upgrader.js index c4adff664..47a13619d 100644 --- a/src/role_upgrader.js +++ b/src/role_upgrader.js @@ -51,22 +51,41 @@ roles.upgrader.updateSettings = function(room) { }; }; -roles.upgrader.killPrevious = true; +roles.upgrader.killPrevious = false; roles.upgrader.boostActions = ['upgradeController']; -roles.upgrader.action = function(creep) { +roles.upgrader.actionUpgrade = function(creep) { creep.mySignController(); creep.spawnReplacement(1); if (!creep.room.controller.isAboutToDowngrade()) { if (creep.room.isUnderAttack()) { - return true; + return false; } if (creep.room.storage && creep.room.storage.isLow()) { - return true; + return false; + } + } + + const updateResult = creep.upgradeController(creep.room.controller); + const withdrawResult = creep.withdraw(creep.room.storage, RESOURCE_ENERGY); + if (OK !== withdrawResult) { + creep.getEnergyFromStorage(); + return false; + } + return updateResult === OK && withdrawResult === OK; +}; + +roles.upgrader.action = function(creep) { + const upgradeResult = roles.upgrader.actionUpgrade(creep); + if (upgradeResult) { + const roads = creep.pos.findInRangeStructures(FIND_STRUCTURES, 0, [STRUCTURE_ROAD]) + if (roads && roads.length) { + creep.moveRandom(); } } + return upgradeResult; +}; - creep.upgradeController(creep.room.controller); - creep.withdraw(creep.room.storage, RESOURCE_ENERGY); - return true; +roles.upgrader.preMove = function(creep) { + roles.upgrader.action(creep); }; diff --git a/src/screeps-profiler.js b/src/screeps-profiler.js new file mode 100644 index 000000000..5ea830888 --- /dev/null +++ b/src/screeps-profiler.js @@ -0,0 +1,350 @@ +'use strict'; + +let usedOnStart = 0; +let enabled = false; +let depth = 0; + +function AlreadyWrappedError() { + this.name = 'AlreadyWrappedError'; + this.message = 'Error attempted to double wrap a function.'; + this.stack = ((new Error())).stack; +} + +function setupProfiler() { + depth = 0; // reset depth, this needs to be done each tick. + Game.profiler = { + stream(duration, filter) { + setupMemory('stream', duration || 10, filter); + }, + email(duration, filter) { + setupMemory('email', duration || 100, filter); + }, + profile(duration, filter) { + setupMemory('profile', duration || 100, filter); + }, + background(filter) { + setupMemory('background', false, filter); + }, + restart() { + if (Profiler.isProfiling()) { + const filter = Memory.profiler.filter; + let duration = false; + if (!!Memory.profiler.disableTick) { + // Calculate the original duration, profile is enabled on the tick after the first call, + // so add 1. + duration = Memory.profiler.disableTick - Memory.profiler.enabledTick + 1; + } + const type = Memory.profiler.type; + setupMemory(type, duration, filter); + } + }, + reset: resetMemory, + output: Profiler.output, + }; + + overloadCPUCalc(); +} + +function setupMemory(profileType, duration, filter) { + resetMemory(); + const disableTick = Number.isInteger(duration) ? Game.time + duration : false; + if (!Memory.profiler) { + Memory.profiler = { + map: {}, + totalTime: 0, + enabledTick: Game.time + 1, + disableTick, + type: profileType, + filter, + }; + } +} + +function resetMemory() { + Memory.profiler = null; +} + +function overloadCPUCalc() { + if (Game.rooms.sim) { + usedOnStart = 0; // This needs to be reset, but only in the sim. + Game.cpu.getUsed = function getUsed() { + return performance.now() - usedOnStart; + }; + } +} + +function getFilter() { + return Memory.profiler.filter; +} + +const functionBlackList = [ + 'getUsed', // Let's avoid wrapping this... may lead to recursion issues and should be inexpensive. + 'constructor', // es6 class constructors need to be called with `new` +]; + +function wrapFunction(name, originalFunction) { + if (originalFunction.profilerWrapped) { throw new AlreadyWrappedError(); } + function wrappedFunction() { + if (Profiler.isProfiling()) { + const nameMatchesFilter = name === getFilter(); + const start = Game.cpu.getUsed(); + if (nameMatchesFilter) { + depth++; + } + const result = originalFunction.apply(this, arguments); + if (depth > 0 || !getFilter()) { + const end = Game.cpu.getUsed(); + Profiler.record(name, end - start); + } + if (nameMatchesFilter) { + depth--; + } + return result; + } + + return originalFunction.apply(this, arguments); + } + + wrappedFunction.profilerWrapped = true; + wrappedFunction.toString = () => + `// screeps-profiler wrapped function:\n${originalFunction.toString()}`; + + return wrappedFunction; +} + +function hookUpPrototypes() { + Profiler.prototypes.forEach(proto => { + profileObjectFunctions(proto.val, proto.name); + }); +} + +function profileObjectFunctions(object, label) { + const objectToWrap = object.prototype ? object.prototype : object; + + Object.getOwnPropertyNames(objectToWrap).forEach(functionName => { + const extendedLabel = `${label}.${functionName}`; + + const isBlackListed = functionBlackList.indexOf(functionName) !== -1; + if (isBlackListed) { + return; + } + + const descriptor = Object.getOwnPropertyDescriptor(objectToWrap, functionName); + if (!descriptor) { + return; + } + + const hasAccessor = descriptor.get || descriptor.set; + if (hasAccessor) { + const configurable = descriptor.configurable; + if (!configurable) { + return; + } + + const profileDescriptor = {}; + + if (descriptor.get) { + const extendedLabelGet = `${extendedLabel}:get`; + profileDescriptor.get = profileFunction(descriptor.get, extendedLabelGet); + } + + if (descriptor.set) { + const extendedLabelSet = `${extendedLabel}:set`; + profileDescriptor.set = profileFunction(descriptor.set, extendedLabelSet); + } + + Object.defineProperty(objectToWrap, functionName, profileDescriptor); + return; + } + + const isFunction = typeof descriptor.value === 'function'; + if (!isFunction) { + return; + } + const originalFunction = objectToWrap[functionName]; + objectToWrap[functionName] = profileFunction(originalFunction, extendedLabel); + }); + + return objectToWrap; +} + +function profileFunction(fn, functionName) { + const fnName = functionName || fn.name; + if (!fnName) { + console.log('Couldn\'t find a function name for - ', fn); + console.log('Will not profile this function.'); + return fn; + } + + return wrapFunction(fnName, fn); +} + +const Profiler = { + printProfile() { + console.log(Profiler.output()); + }, + + emailProfile() { + Game.notify(Profiler.output(1000)); + }, + + output(passedOutputLengthLimit) { + const outputLengthLimit = passedOutputLengthLimit || 1000; + if (!Memory.profiler || !Memory.profiler.enabledTick) { + return 'Profiler not active.'; + } + + const endTick = Math.min(Memory.profiler.disableTick || Game.time, Game.time); + const startTick = Memory.profiler.enabledTick + 1; + const elapsedTicks = endTick - startTick; + const header = 'calls\t\ttime\t\tavg\t\tfunction'; + const footer = [ + `Avg: ${(Memory.profiler.totalTime / elapsedTicks).toFixed(2)}`, + `Total: ${Memory.profiler.totalTime.toFixed(2)}`, + `Ticks: ${elapsedTicks}`, + ].join('\t'); + + const lines = [header]; + let currentLength = header.length + 1 + footer.length; + const allLines = Profiler.lines(); + let done = false; + while (!done && allLines.length) { + const line = allLines.shift(); + // each line added adds the line length plus a new line character. + if (currentLength + line.length + 1 < outputLengthLimit) { + lines.push(line); + currentLength += line.length + 1; + } else { + done = true; + } + } + lines.push(footer); + return lines.join('\n'); + }, + + lines() { + const stats = Object.keys(Memory.profiler.map).map(functionName => { + const functionCalls = Memory.profiler.map[functionName]; + return { + name: functionName, + calls: functionCalls.calls, + totalTime: functionCalls.time, + averageTime: functionCalls.time / functionCalls.calls, + }; + }).sort((val1, val2) => { + return val2.totalTime - val1.totalTime; + }); + + const lines = stats.map(data => { + return [ + data.calls, + data.totalTime.toFixed(1), + data.averageTime.toFixed(3), + data.name, + ].join('\t\t'); + }); + + return lines; + }, + + prototypes: [ + { name: 'Game', val: Game }, + { name: 'Room', val: Room }, + { name: 'Structure', val: Structure }, + { name: 'Spawn', val: Spawn }, + { name: 'Creep', val: Creep }, + { name: 'RoomPosition', val: RoomPosition }, + { name: 'Source', val: Source }, + { name: 'Flag', val: Flag }, + ], + + record(functionName, time) { + if (!Memory.profiler.map[functionName]) { + Memory.profiler.map[functionName] = { + time: 0, + calls: 0, + }; + } + Memory.profiler.map[functionName].calls++; + Memory.profiler.map[functionName].time += time; + }, + + endTick() { + if (Game.time >= Memory.profiler.enabledTick) { + const cpuUsed = Game.cpu.getUsed(); + Memory.profiler.totalTime += cpuUsed; + Profiler.report(); + } + }, + + report() { + if (Profiler.shouldPrint()) { + Profiler.printProfile(); + } else if (Profiler.shouldEmail()) { + Profiler.emailProfile(); + } + }, + + isProfiling() { + if (!enabled || !Memory.profiler) { + return false; + } + return !Memory.profiler.disableTick || Game.time <= Memory.profiler.disableTick; + }, + + type() { + return Memory.profiler.type; + }, + + shouldPrint() { + const streaming = Profiler.type() === 'stream'; + const profiling = Profiler.type() === 'profile'; + const onEndingTick = Memory.profiler.disableTick === Game.time; + return streaming || (profiling && onEndingTick); + }, + + shouldEmail() { + return Profiler.type() === 'email' && Memory.profiler.disableTick === Game.time; + }, +}; + +module.exports = { + wrap(callback) { + if (enabled) { + setupProfiler(); + } + + if (Profiler.isProfiling()) { + usedOnStart = Game.cpu.getUsed(); + + // Commented lines are part of an on going experiment to keep the profiler + // performant, and measure certain types of overhead. + + // var callbackStart = Game.cpu.getUsed(); + const returnVal = callback(); + // var callbackEnd = Game.cpu.getUsed(); + Profiler.endTick(); + // var end = Game.cpu.getUsed(); + + // var profilerTime = (end - start) - (callbackEnd - callbackStart); + // var callbackTime = callbackEnd - callbackStart; + // var unaccounted = end - profilerTime - callbackTime; + // console.log('total-', end, 'profiler-', profilerTime, 'callbacktime-', + // callbackTime, 'start-', start, 'unaccounted', unaccounted); + return returnVal; + } + + return callback(); + }, + + enable() { + enabled = true; + hookUpPrototypes(); + }, + + output: Profiler.output, + + registerObject: profileObjectFunctions, + registerFN: profileFunction, + registerClass: profileObjectFunctions, +}; diff --git a/src_ts/utils_creep_part.ts b/src_ts/utils_creep_part.ts new file mode 100644 index 000000000..437534a8a --- /dev/null +++ b/src_ts/utils_creep_part.ts @@ -0,0 +1,342 @@ +'use strict'; + +declare var MOVE: string; +declare var WORK: string; +declare var CARRY: string; +declare var ATTACK: string; +declare var RANGED_ATTACK: string; +declare var TOUGH: string; +declare var HEAL: string; +declare var CLAIM: string; + +/** + * Creep part data + * + * @class + */ +class CreepPartData { + + /** + * true if not enough energy + * + * @type {boolean} + */ + fail: boolean; + + /** + * cost of parts + * + * @type {int} + */ + cost: number; + + /** + * part as array + * + * @type {string[]} + */ + parts: string[]; + + /** + * the amount of parts + * + * @type {int} + */ + len: number; + + /** + * null + * + * @type {boolean|undefined} + */ + null: boolean | undefined; + + constructor() { + this.fail = false; + this.cost = 0; + this.parts = []; + this.len = 0; + } +} + +/** + * Sort body parts with the same order used in layout. Parts not in layout are last ones. + * + * @param {string[]} parts the parts array to sort. + * @param {CreepPartData} layout the base layout. + * @return {string[]} sorted array. + */ +const sortCreepParts = function ( + parts: string[], + layout: CreepPartData +) { + // 0. fill parts into part type count map and set + + const partTypeCountMap: Map = new Map( + [ + [TOUGH, 0], + [MOVE, 0], + [CARRY, 0], + [WORK, 0], + [CLAIM, 0], + [HEAL, 0], + [ATTACK, 0], + [RANGED_ATTACK, 0], + ] + ); + for (const part of parts) { + partTypeCountMap.set(part, partTypeCountMap.get(part) + 1); + } + + const uniquePartTypeSet: Set = new Set(); + for (const part of layout.parts) { + uniquePartTypeSet.add(part); + } + + const haveRangeAttack: boolean = partTypeCountMap.get(RANGED_ATTACK) > 0; + + // 1. pre-calculate buried parts + + const buriedParts: string[] = calculateBuriedParts( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 2. calculate all tough at the beginning. + const frontToughParts: string[] = calculateFrontToughParts( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 3. calculate move part 1 + const moveParts1: string[] = calculateMoveParts1( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 4. calculate move part 2 + const moveParts2: string[] = calculateMoveParts2( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 5. calculate center layout loop + const centerLayoutParts: string[] = calculateCenterLayoutParts( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 6. calculate additional parts (which only exist in parts but not in layout) + const additionalParts: string[] = calculateAdditionalParts( + layout, + partTypeCountMap, + uniquePartTypeSet, + haveRangeAttack + ); + + // 7. calculate additional parts (which only exist in parts but not in layout) + + const resultParts: string[] = []; + + resultParts.push(...frontToughParts); + resultParts.push(...moveParts1); + resultParts.push(...centerLayoutParts); + resultParts.push(...additionalParts); + resultParts.push(...moveParts2); + resultParts.push(...buriedParts); + + return resultParts + +}; + +/** + * calculateBuriedParts + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateBuriedParts + */ +function calculateBuriedParts( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + const buriedParts = []; + for (const part of uniquePartTypeSet) { + switch (part) { + case MOVE: + case HEAL: + case RANGED_ATTACK: + case TOUGH: + continue; + default: + } + const partCount: number = partTypeCountMap.get(part); + if (partCount > 0) { + buriedParts.push(part); + partTypeCountMap.set(part, partCount - 1); + } + } + const movePartsTotal: number = partTypeCountMap.get(MOVE); + if (movePartsTotal > 0) { + buriedParts.push(MOVE); + partTypeCountMap.set(MOVE, movePartsTotal - 1); + } + const healPartsTotal: number = partTypeCountMap.get(HEAL); + if (healPartsTotal > 0) { + buriedParts.push( + ...Array(healPartsTotal).fill(HEAL) + ); + partTypeCountMap.set(HEAL, 0); + } + const rangedAttackPartsTotal: number = partTypeCountMap.get(RANGED_ATTACK); + if (rangedAttackPartsTotal > 0) { + buriedParts.push(RANGED_ATTACK); + partTypeCountMap.set(RANGED_ATTACK, rangedAttackPartsTotal - 1); + } + return buriedParts; +} + +/** + * calculateFrontToughParts + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateFrontToughParts + */ +function calculateFrontToughParts( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + const frontToughParts = Array(partTypeCountMap.get(TOUGH)).fill(TOUGH); + partTypeCountMap.set(TOUGH, 0); + return frontToughParts; +} + +/** + * calculateMoveParts1 + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateMoveParts1 + */ +function calculateMoveParts1( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + const movePartsTotal: number = partTypeCountMap.get(MOVE); + const moveParts1MoveCount: number = movePartsTotal % 2 === 0 ? movePartsTotal / 2 : (movePartsTotal + 1) / 2; + const moveParts1 = Array(moveParts1MoveCount).fill(MOVE); + partTypeCountMap.set(MOVE, movePartsTotal - moveParts1MoveCount); + return moveParts1; +} + +/** + * calculateMoveParts2 + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateMoveParts2 + */ +function calculateMoveParts2( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + let moveParts2: string[]; + if (haveRangeAttack) { + moveParts2 = []; + } else { + const moveParts2MoveCount: number = partTypeCountMap.get(MOVE); + moveParts2 = Array(moveParts2MoveCount).fill(MOVE); + partTypeCountMap.set(MOVE, 0); + } + return moveParts2; +} + +/** + * calculateCenterLayoutParts + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateCenterLayoutParts + */ +function calculateCenterLayoutParts( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + const centerLayoutParts = []; + let stillLooping: boolean = true; + while (stillLooping) { + stillLooping = false; + for (const part of layout.parts) { + if (!haveRangeAttack && part === MOVE) { + continue; + } + const partCount: number = partTypeCountMap.get(part); + if (partCount > 0) { + centerLayoutParts.push(part); + stillLooping = true; + partTypeCountMap.set(part, partCount - 1); + } + } + } + return centerLayoutParts; +} + +/** + * calculateAdditionalParts + * + * @param {CreepPartData} layout + * @param {Map} partTypeCountMap + * @param {Set} uniquePartTypeSet + * @param {boolean} haveRangeAttack + * @return {string[]} calculateAdditionalParts + */ +function calculateAdditionalParts( + layout: CreepPartData, + partTypeCountMap: Map, + uniquePartTypeSet: Set, + haveRangeAttack: boolean +) { + const additionalParts = []; + for (const entry of partTypeCountMap.entries()) { + if (entry[1] > 0) { + additionalParts.push( + ...Array(entry[1]).fill(entry[0]) + ); + entry[1] = 0; + } + } + return additionalParts; +} + +export {CreepPartData, sortCreepParts} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..5b48d9a63 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "sourceMap": true, + "compilerOptions": { + "target": "ES6", + "module": "Node16", + "sourceMap": false, + "outDir": "src/" + } +}