Browse Source

Finish refactoring / documentation. Finally, xo is happy!

David Franke 1 year ago
parent
commit
d82f014bf1
10 changed files with 895 additions and 526 deletions
  1. 2
    1
      package.json
  2. 1
    1
      src/manager.spawn.js
  3. 26
    19
      src/process.strategy.expand.js
  4. 4
    4
      src/role.builder.remote.js
  5. 4
    8
      src/role.helper.js
  6. 1
    1
      src/role.transporter.js
  7. 576
    408
      src/room-planner.js
  8. 3
    0
      src/snippets.js
  9. 57
    5
      src/stats.js
  10. 221
    79
      src/utilities.js

+ 2
- 1
package.json View File

@@ -39,7 +39,8 @@
39 39
 			"Memory"
40 40
 		],
41 41
 		"ignores": [
42
-			"**/screeps-profiler.js"
42
+			"**/screeps-profiler.js",
43
+			"**/snippets.js"
43 44
 		]
44 45
 	}
45 46
 }

+ 1
- 1
src/manager.spawn.js View File

@@ -524,7 +524,7 @@ Room.prototype.addBuilderSpawnOptions = function () {
524 524
 
525 525
 	// There are a lot of ramparts in planned rooms, spawn builders appropriately.
526 526
 	if (this.roomPlanner && this.roomPlanner.memory.locations && this.controller && this.controller.my && this.controller.level >= 4) {
527
-		maxWorkParts += _.size(this.roomPlanner.memory.locations.rampart || []) / 10;
527
+		maxWorkParts += _.size(this.roomPlanner.memory.locations.rampart) / 10;
528 528
 	}
529 529
 
530 530
 	// Add more repairers if we have a lot of energy to spare.

+ 26
- 19
src/process.strategy.expand.js View File

@@ -53,25 +53,7 @@ ExpandProcess.prototype.run = function () {
53 53
 	Memory.hivemind.canExpand = false;
54 54
 	if (!memory.expand.currentTarget && canExpand) {
55 55
 		Memory.hivemind.canExpand = true;
56
-		// Choose a room to expand to.
57
-		// @todo Handle cases where expansion to a target is not reasonable, like it being taken by somebody else, path not being safe, etc.
58
-		let bestTarget = null;
59
-		for (const info of _.values(memory.roomList)) {
60
-			if (!info.expansionScore || info.expansionScore <= 0) continue;
61
-			if (bestTarget && bestTarget.expansionScore >= info.expansionScore) continue;
62
-
63
-			// Don't try to expand to a room that can't be reached safely.
64
-			const bestSpawn = this.findClosestSpawn(info.roomName);
65
-			if (!bestSpawn) continue;
66
-
67
-			info.spawnRoom = bestSpawn;
68
-
69
-			bestTarget = info;
70
-		}
71
-
72
-		if (bestTarget) {
73
-			this.startExpansion(bestTarget);
74
-		}
56
+		this.chooseNewExpansionTarget();
75 57
 	}
76 58
 
77 59
 	if (memory.expand.currentTarget) {
@@ -117,6 +99,31 @@ ExpandProcess.prototype.run = function () {
117 99
 	}
118 100
 };
119 101
 
102
+/**
103
+ * Chooses a new target room to expand to.
104
+ */
105
+ExpandProcess.prototype.chooseNewExpansionTarget = function () {
106
+	// Choose a room to expand to.
107
+	// @todo Handle cases where expansion to a target is not reasonable, like it being taken by somebody else, path not being safe, etc.
108
+	let bestTarget = null;
109
+	for (const info of _.values(Memory.strategy.roomList)) {
110
+		if (!info.expansionScore || info.expansionScore <= 0) continue;
111
+		if (bestTarget && bestTarget.expansionScore >= info.expansionScore) continue;
112
+
113
+		// Don't try to expand to a room that can't be reached safely.
114
+		const bestSpawn = this.findClosestSpawn(info.roomName);
115
+		if (!bestSpawn) continue;
116
+
117
+		info.spawnRoom = bestSpawn;
118
+
119
+		bestTarget = info;
120
+	}
121
+
122
+	if (bestTarget) {
123
+		this.startExpansion(bestTarget);
124
+	}
125
+};
126
+
120 127
 /**
121 128
  * Starts expanding to a given room.
122 129
  *

+ 4
- 4
src/role.builder.remote.js View File

@@ -25,13 +25,13 @@ const roleRemoteBuilder = {
25 25
 			this.setBuilderState(true);
26 26
 		}
27 27
 
28
-		if (creep.memory.building) {
29
-			this.performRemoteBuild();
28
+		if (creep.memory.upgrading) {
29
+			this.performControllerUpgrade();
30 30
 			return;
31 31
 		}
32 32
 
33
-		if (creep.memory.upgrading) {
34
-			this.performControllerUpgrade();
33
+		if (creep.memory.building) {
34
+			this.performRemoteBuild();
35 35
 			return;
36 36
 		}
37 37
 

+ 4
- 8
src/role.helper.js View File

@@ -119,10 +119,7 @@ Creep.prototype.performHelperGather = function () {
119 119
 	for (const lab of labs) {
120 120
 		if (lab.energy + this.carryCapacity > lab.energyCapacity) continue;
121 121
 
122
-		let target = terminal;
123
-		if (storage && (storage.store[RESOURCE_ENERGY] || 0) > 0) {
124
-			target = storage;
125
-		}
122
+		const target = this.room.getBestStorageSource(RESOURCE_ENERGY);
126 123
 
127 124
 		if (this.pos.getRangeTo(target) > 1) {
128 125
 			this.moveToRange(target, 1);
@@ -178,10 +175,7 @@ Creep.prototype.performHelperLabGather = function () {
178 175
 				this.moveToRange(target, 1);
179 176
 			}
180 177
 			else {
181
-				let amount = Math.min(diff, this.carryCapacity - _.sum(this.carry));
182
-				amount = Math.min(amount, target.store[resourceType]);
183
-
184
-				if (!target.store[resourceType]) {
178
+				if (!target || !target.store[resourceType]) {
185 179
 					// Something went wrong, we don't actually have enough of this stuff.
186 180
 					// Delete any boost orders using this resource.
187 181
 					this.room.boostManager.memory.creepsToBoost = _.filter(
@@ -192,6 +186,8 @@ Creep.prototype.performHelperLabGather = function () {
192 186
 					return true;
193 187
 				}
194 188
 
189
+				let amount = Math.min(diff, this.carryCapacity - _.sum(this.carry));
190
+				amount = Math.min(amount, target.store[resourceType]);
195 191
 				this.withdraw(target, resourceType, amount);
196 192
 			}
197 193
 

+ 1
- 1
src/role.transporter.js View File

@@ -1342,7 +1342,7 @@ Creep.prototype.ensureValidDeliveryTarget = function () {
1342 1342
 	if ((creep.carry[resourceType] || 0) <= 0) return false;
1343 1343
 
1344 1344
 	if (typeof creep.memory.deliverTarget === 'string') {
1345
-		return this.ensureValidDeliveryTargetObject(Game.getObjectById(creep.memory.deliverTarget));
1345
+		return this.ensureValidDeliveryTargetObject(Game.getObjectById(creep.memory.deliverTarget), resourceType);
1346 1346
 	}
1347 1347
 
1348 1348
 	if (creep.memory.deliverTarget.type === 'bay') {

+ 576
- 408
src/room-planner.js
File diff suppressed because it is too large
View File


+ 3
- 0
src/snippets.js View File

@@ -12,3 +12,6 @@ JSON.stringify(_.sortBy(Memory.strategy.roomList, 'expansionScore'));
12 12
 
13 13
 // Re-run room planner for a room.
14 14
 Memory.rooms.E49S51.roomPlanner.plannerVersion = 0;
15
+
16
+// Find out which processes use a lot of CPU
17
+JSON.stringify(_.sortBy(_.map(Memory.hivemind.process, (a, b) => {a.name = b; return a}), a => -a.cpu));

+ 57
- 5
src/stats.js View File

@@ -2,6 +2,14 @@
2 2
 
3 3
 const stats = {
4 4
 
5
+	/**
6
+	 * Initializes harvest stat memory if not available.
7
+	 *
8
+	 * @param {string} source
9
+	 *   Name of the room the harvesting operation belongs to.
10
+	 * @param {string} target
11
+	 *   Encoded room position of the target source.
12
+	 */
5 13
 	initRemoteHarvestMemory(source, target) {
6 14
 		const memory = Memory.rooms[source];
7 15
 
@@ -20,6 +28,14 @@ const stats = {
20 28
 		}
21 29
 	},
22 30
 
31
+	/**
32
+	 * Resets stats for remote harvesting for a source.
33
+	 *
34
+	 * @param {string} source
35
+	 *   Name of the room the harvesting operation belongs to.
36
+	 * @param {string} target
37
+	 *   Encoded room position of the target source.
38
+	 */
23 39
 	clearRemoteHarvestStats(source, target) {
24 40
 		if (!Memory.rooms[source]) return;
25 41
 
@@ -32,6 +48,16 @@ const stats = {
32 48
 		memory.remoteHarvesting[target].revenue = 0;
33 49
 	},
34 50
 
51
+	/**
52
+	 * Adds energy cost to remote harvest stats for a source.
53
+	 *
54
+	 * @param {string} source
55
+	 *   Name of the room the harvesting operation belongs to.
56
+	 * @param {string} target
57
+	 *   Encoded room position of the target source.
58
+	 * @param {number} cost
59
+	 *   Amount of energy spent.
60
+	 */
35 61
 	addRemoteHarvestCost(source, target, cost) {
36 62
 		if (!Memory.rooms[source]) return;
37 63
 
@@ -41,6 +67,16 @@ const stats = {
41 67
 		memory.remoteHarvesting[target].creepCost += cost;
42 68
 	},
43 69
 
70
+	/**
71
+	 * Adds defense related energy cost to remote harvest stats for a source.
72
+	 *
73
+	 * @param {string} source
74
+	 *   Name of the room the harvesting operation belongs to.
75
+	 * @param {string} target
76
+	 *   Encoded room position of the target source.
77
+	 * @param {number} cost
78
+	 *   Amount of energy spent.
79
+	 */
44 80
 	addRemoteHarvestDefenseCost(source, target, cost) {
45 81
 		if (!Memory.rooms[source]) return;
46 82
 
@@ -52,6 +88,11 @@ const stats = {
52 88
 
53 89
 	/**
54 90
 	 * Saves a new stat value for long term history tracking.
91
+	 *
92
+	 * @param {string} key
93
+	 *   Identifier this information should be stored under.
94
+	 * @param {number} value
95
+	 *   Most current value to save.
55 96
 	 */
56 97
 	recordStat(key, value) {
57 98
 		if (!Memory.history) {
@@ -67,6 +108,13 @@ const stats = {
67 108
 
68 109
 	/**
69 110
 	 * Recursively saves new data in long term history.
111
+	 *
112
+	 * @param {object} memory
113
+	 *   The object to store history data.
114
+	 * @param {number} multiplier
115
+	 *   Interval we are currently concerned with.
116
+	 * @param {number} value
117
+	 *   Value to save.
70 118
 	 */
71 119
 	saveStatValue(memory, multiplier, value) {
72 120
 		const increment = 10;
@@ -79,11 +127,7 @@ const stats = {
79 127
 		}
80 128
 
81 129
 		if (memory[multiplier].currentValues.length >= increment) {
82
-			let avg = 0;
83
-			for (const i in memory[multiplier].currentValues) {
84
-				avg += memory[multiplier].currentValues[i];
85
-			}
86
-
130
+			let avg = _.sum(memory[multiplier].currentValues);
87 131
 			avg /= memory[multiplier].currentValues.length;
88 132
 
89 133
 			stats.saveStatValue(memory, multiplier * increment, avg);
@@ -97,6 +141,14 @@ const stats = {
97 141
 
98 142
 	/**
99 143
 	 * Retrieves long term history data.
144
+	 *
145
+	 * @param {string} key
146
+	 *   Identifier of the information to retreive.
147
+	 * @param {number} interval
148
+	 *   Interval for which to retreive the data. Needs to be a power of 10.
149
+	 *
150
+	 * @return {number}
151
+	 *   Average value of the given stat during the requested interval.
100 152
 	 */
101 153
 	getStat(key, interval) {
102 154
 		// @todo Allow intervals that are not directly stored, like 300.

+ 221
- 79
src/utilities.js View File

@@ -1,33 +1,42 @@
1 1
 'use strict';
2 2
 
3 3
 /* global hivemind PathFinder Room RoomPosition TERRAIN_MASK_WALL
4
-BODYPART_COST TOUGH ATTACK RANGED_ATTACK HEAL */
4
+BODYPART_COST TOUGH ATTACK RANGED_ATTACK HEAL MAX_CREEP_SIZE */
5 5
 
6 6
 const utilities = {
7 7
 
8 8
 	/**
9 9
 	 * Dynamically determines the username of the current user.
10
+	 *
11
+	 * @return {string}
12
+	 *   The determined user name.
10 13
 	 */
11 14
 	getUsername() {
12
-		for (const i in Game.spawns) {
13
-			return Game.spawns[i].owner.username;
14
-		}
15
+		return _.first(Game.spawns).owner.username;
15 16
 	},
16 17
 
18
+	/**
19
+	 * Calculates and stores paths for remote harvesting.
20
+	 *
21
+	 * @param {Room} room
22
+	 *   Source room for the harvestint operation
23
+	 * @param {RoomPosition} sourcePos
24
+	 *   Position of the source to harvest.
25
+	 */
17 26
 	precalculatePaths(room, sourcePos) {
18 27
 		if (Game.cpu.getUsed() > Game.cpu.tickLimit * 0.5) return;
19 28
 
20
-		const flagPosition = utilities.encodePosition(sourcePos);
29
+		const sourceLocation = utilities.encodePosition(sourcePos);
21 30
 
22 31
 		if (!room.memory.remoteHarvesting) {
23 32
 			room.memory.remoteHarvesting = {};
24 33
 		}
25 34
 
26
-		if (!room.memory.remoteHarvesting[flagPosition]) {
27
-			room.memory.remoteHarvesting[flagPosition] = {};
35
+		if (!room.memory.remoteHarvesting[sourceLocation]) {
36
+			room.memory.remoteHarvesting[sourceLocation] = {};
28 37
 		}
29 38
 
30
-		const harvestMemory = room.memory.remoteHarvesting[flagPosition];
39
+		const harvestMemory = room.memory.remoteHarvesting[sourceLocation];
31 40
 
32 41
 		if (harvestMemory.cachedPath && Game.time - harvestMemory.cachedPath.lastCalculated < 500) {
33 42
 			// No need to recalculate path.
@@ -57,6 +66,21 @@ const utilities = {
57 66
 		}
58 67
 	},
59 68
 
69
+	/**
70
+	 * Finds a path using PathFinder.search.
71
+	 *
72
+	 * @param {RoomPosition} startPosition
73
+	 *   Position to start the search from.
74
+	 * @param {object} endPosition
75
+	 *   Position or Positions or object with information about path target.
76
+	 * @param {boolean} allowDanger
77
+	 *   If true, allow traversing unsafe rooms.
78
+	 * @param {object} addOptions
79
+	 *   Options to add to pathfinder options.
80
+	 *
81
+	 * @return {object}
82
+	 *   Result of the pathfinding operation.
83
+	 */
60 84
 	getPath(startPosition, endPosition, allowDanger, addOptions) {
61 85
 		const options = {
62 86
 			plainCost: 2,
@@ -64,8 +88,6 @@ const utilities = {
64 88
 			maxOps: 10000, // The default 2000 can be too little even at a distance of only 2 rooms.
65 89
 
66 90
 			roomCallback: roomName => {
67
-				const room = Game.rooms[roomName];
68
-
69 91
 				// If a room is considered inaccessible, don't look for paths through it.
70 92
 				if (!allowDanger && hivemind.roomIntel(roomName).isOwned()) {
71 93
 					if (!addOptions || !addOptions.whiteListRooms || addOptions.whiteListRooms.indexOf(roomName) === -1) {
@@ -98,9 +120,9 @@ const utilities = {
98 120
 		};
99 121
 
100 122
 		if (addOptions) {
101
-			for (const key in addOptions) {
102
-				options[key] = addOptions[key];
103
-			}
123
+			_.each(addOptions, (value, key) => {
124
+				options[key] = value;
125
+			});
104 126
 		}
105 127
 
106 128
 		return PathFinder.search(startPosition, endPosition, options);
@@ -109,6 +131,19 @@ const utilities = {
109 131
 	costMatrixCache: {},
110 132
 	costMatrixCacheAge: Game.time,
111 133
 
134
+	/**
135
+	 * Gets the pathfinding cost matrix for a room.
136
+	 *
137
+	 * @param {string} roomName
138
+	 *   Name of the room.
139
+	 * @param {object} options
140
+	 *   Further options regarding the matrix. Can have the following keys:
141
+	 *   - `singleRoom`: If true, return a matrix for creeps that cannot leave
142
+	 *     the room.
143
+	 *
144
+	 * @return {PathFinder.CostMatrix}
145
+	 *   The requested cost matrix.
146
+	 */
112 147
 	getCostMatrix(roomName, options) {
113 148
 		// Clear cost matrix cache from time to time.
114 149
 		if (utilities.costMatrixCacheAge < Game.time - 500) {
@@ -135,19 +170,7 @@ const utilities = {
135 170
 			cacheKey += ':singleRoom';
136 171
 
137 172
 			if (!utilities.costMatrixCache[cacheKey]) {
138
-				matrix = matrix.clone();
139
-				const terrain = new Room.Terrain(roomName);
140
-				for (let x = 0; x < 50; x++) {
141
-					for (let y = 0; y < 50; y++) {
142
-						if (x === 0 || y === 0 || x === 49 || y === 49) {
143
-							if (terrain.get(x, y) !== TERRAIN_MASK_WALL) {
144
-								matrix.set(x, y, 50);
145
-							}
146
-						}
147
-					}
148
-				}
149
-
150
-				utilities.costMatrixCache[cacheKey] = matrix;
173
+				utilities.costMatrixCache[cacheKey] = this.generateSingleRoomCostMatrix(matrix, roomName);
151 174
 			}
152 175
 		}
153 176
 
@@ -156,42 +179,117 @@ const utilities = {
156 179
 		return matrix;
157 180
 	},
158 181
 
159
-	getClosest(creep, targets) {
160
-		if (targets.length > 0) {
161
-			const target = creep.pos.findClosestByRange(targets);
162
-			if (target) {
163
-				return target.id;
182
+	/**
183
+	 * Generates a derivative cost matrix that highly discourages room exits.
184
+	 *
185
+	 * @param {PathFinder.CostMatrix} matrix
186
+	 *   The matrix to use as a base.
187
+	 * @param {string} roomName
188
+	 *   Name of the room this matrix represents.
189
+	 *
190
+	 * @return {PathFinder.CostMatrix}
191
+	 *   The modified cost matrix.
192
+	 */
193
+	generateSingleRoomCostMatrix(matrix, roomName) {
194
+		const newMatrix = matrix.clone();
195
+		const terrain = new Room.Terrain(roomName);
196
+		for (let x = 0; x < 50; x++) {
197
+			for (let y = 0; y < 50; y++) {
198
+				if ((x === 0 || y === 0 || x === 49 || y === 49) && terrain.get(x, y) !== TERRAIN_MASK_WALL) {
199
+					newMatrix.set(x, y, 50);
200
+				}
164 201
 			}
165 202
 		}
166 203
 
167
-		return null;
204
+		return newMatrix;
168 205
 	},
169 206
 
207
+	/**
208
+	 * Returns closest target to a room object.
209
+	 *
210
+	 * @param {RoomObject} roomObject
211
+	 *   The room object the search originates from.
212
+	 * @param {RoomObject[]} targets
213
+	 *   A list of room objects to check.
214
+	 *
215
+	 * @return {RoomObject}
216
+	 *   The closest target.
217
+	 */
218
+	getClosest(roomObject, targets) {
219
+		if (targets.length > 0) {
220
+			const target = roomObject.pos.findClosestByRange(targets);
221
+			return target && target.id;
222
+		}
223
+	},
224
+
225
+	/**
226
+	 * Gets most highly rated option from a list.
227
+	 *
228
+	 * @param {Array} options
229
+	 *   List of options, each option should at least contain the keys `priority`
230
+	 *   and `weight`.
231
+	 *
232
+	 * @return {object}
233
+	 *   The object with the highest priority and weight (within that priority).
234
+	 */
170 235
 	getBestOption(options) {
171 236
 		let best = null;
172 237
 
173
-		for (const i in options) {
174
-			if (!best || options[i].priority > best.priority || (options[i].priority == best.priority && options[i].weight > best.weight)) {
175
-				best = options[i];
238
+		for (const option of options) {
239
+			if (!best || option.priority > best.priority || (option.priority === best.priority && option.weight > best.weight)) {
240
+				best = option;
176 241
 			}
177 242
 		}
178 243
 
179 244
 		return best;
180 245
 	},
181 246
 
247
+	/**
248
+	 * Calculates how much a creep cost to spawn.
249
+	 * @todo Move into Creep.prototype.
250
+	 *
251
+	 * @param {Creep} creep
252
+	 *   The creep in question.
253
+	 *
254
+	 * @return {number}
255
+	 *   Energy cost for this creep.
256
+	 */
182 257
 	getBodyCost(creep) {
183 258
 		let cost = 0;
184
-		for (const i in creep.body) {
185
-			cost += BODYPART_COST[creep.body[i].type];
259
+		for (const part of creep.body) {
260
+			cost += BODYPART_COST[part.type];
186 261
 		}
187 262
 
188 263
 		return cost;
189 264
 	},
190 265
 
266
+	/**
267
+	 * Get part counts for this creep.
268
+	 * @todo Move into Creep.prototype.
269
+	 *
270
+	 * @param {Creep} creep
271
+	 *   The creep in question.
272
+	 *
273
+	 * @return {object}
274
+	 *   Amount of parts of each type in the creep's body.
275
+	 */
191 276
 	getBodyParts(creep) {
192 277
 		return creep.memory.body;
193 278
 	},
194 279
 
280
+	/**
281
+	 * Dynamically generates a creep body for spawning.
282
+	 *
283
+	 * @param {object} weights
284
+	 *   Weights specifying how to distribute the different body part types.
285
+	 * @param {number} maxCost
286
+	 *   Maximum cost for the creep.
287
+	 * @param {object} maxParts
288
+	 *   Maximum number of parts of certain types to use.
289
+	 *
290
+	 * @return {string[]}
291
+	 *   List of parts that make up the requested creep.
292
+	 */
195 293
 	generateCreepBody(weights, maxCost, maxParts) {
196 294
 		const newParts = {};
197 295
 		let size = 0;
@@ -202,7 +300,7 @@ const utilities = {
202 300
 		}
203 301
 
204 302
 		// Generate initial body containing at least one of each part.
205
-		for (const part in weights) {
303
+		for (const part of _.keys(weights)) {
206 304
 			newParts[part] = 1;
207 305
 			size++;
208 306
 			cost += BODYPART_COST[part];
@@ -213,27 +311,29 @@ const utilities = {
213 311
 		}
214 312
 
215 313
 		let done = false;
216
-		while (!done && size < 50) {
314
+		while (!done && size < MAX_CREEP_SIZE) {
217 315
 			done = true;
218
-			for (const part in BODYPART_COST) {
316
+			_.each(weights, (weight, part) => {
219 317
 				const currentWeight = newParts[part] / size;
220
-				if (currentWeight <= weights[part] && cost + BODYPART_COST[part] <= maxCost) {
221
-					if (!maxParts || !maxParts[part] || newParts[part] < maxParts[part]) {
222
-						done = false;
223
-						newParts[part]++;
224
-						size++;
225
-						cost += BODYPART_COST[part];
226
-						if (size >= 50) {
227
-							break;
228
-						}
229
-					}
230
-					else {
231
-						// Limit for this bodypart has been reached, so stop adding.
232
-						done = true;
233
-						break;
234
-					}
318
+				if (currentWeight > weight) return;
319
+				if (cost + BODYPART_COST[part] > maxCost) return;
320
+
321
+				if (maxParts && maxParts[part] && newParts[part] >= maxParts[part]) {
322
+					// Limit for this bodypart has been reached, so stop adding.
323
+					done = true;
324
+					return false;
235 325
 				}
236
-			}
326
+
327
+				done = false;
328
+				newParts[part]++;
329
+				size++;
330
+				cost += BODYPART_COST[part];
331
+
332
+				if (size >= MAX_CREEP_SIZE) {
333
+					// Maximum creep size reached, stop adding parts.
334
+					return false;
335
+				}
336
+			});
237 337
 		}
238 338
 
239 339
 		// Chain the generated configuration into an array of body parts.
@@ -262,8 +362,7 @@ const utilities = {
262 362
 
263 363
 		// Add military parts last to keep fighting effeciency.
264 364
 		const lastParts = [RANGED_ATTACK, ATTACK, HEAL];
265
-		for (const p in lastParts) {
266
-			const part = lastParts[p];
365
+		for (const part of lastParts) {
267 366
 			for (let i = 0; i < newParts[part] || 0; i++) {
268 367
 				body.push(part);
269 368
 			}
@@ -274,6 +373,13 @@ const utilities = {
274 373
 
275 374
 	/**
276 375
 	 * Serializes a position for storing it in memory.
376
+	 * @todo Move to RoomPosition.prototype.
377
+	 *
378
+	 * @param {RoomPosition} position
379
+	 *   The position to encode.
380
+	 *
381
+	 * @return {string}
382
+	 *   The encoded position.
277 383
 	 */
278 384
 	encodePosition(position) {
279 385
 		if (!position) return;
@@ -283,6 +389,13 @@ const utilities = {
283 389
 
284 390
 	/**
285 391
 	 * Creates a RoomPosition object from serialized data.
392
+	 * @todo Move to RoomPosition as static function.
393
+	 *
394
+	 * @param {string} position
395
+	 *   The encoded position.
396
+	 *
397
+	 * @return {RoomPosition}
398
+	 *   The original room position.
286 399
 	 */
287 400
 	decodePosition(position) {
288 401
 		if (!position) return;
@@ -296,40 +409,50 @@ const utilities = {
296 409
 
297 410
 	/**
298 411
 	 * Serializes an array of RoomPosition objects for storing in memory.
412
+	 *
413
+	 * @param {RoomPosition[]} path
414
+	 *   A list of positions to encode.
415
+	 *
416
+	 * @return {string[]}
417
+	 *   The encoded path.
299 418
 	 */
300 419
 	serializePositionPath(path) {
301
-		const result = [];
302
-		for (const i in path) {
303
-			result.push(utilities.encodePosition(path[i]));
304
-		}
305
-
306
-		return result;
420
+		return _.map(path, utilities.encodePosition);
307 421
 	},
308 422
 
309 423
 	/**
310 424
 	 * Deserializes a serialized path into an array of RoomPosition objects.
425
+	 *
426
+	 * @param {string[]} path
427
+	 *   A list of positions to decode.
428
+	 *
429
+	 * @return {RoomPosition[]}
430
+	 *   The decoded path.
311 431
 	 */
312 432
 	deserializePositionPath(path) {
313
-		const result = [];
314
-		for (const i in path) {
315
-			result.push(utilities.decodePosition(path[i]));
316
-		}
317
-
318
-		return result;
433
+		return _.map(path, utilities.decodePosition);
319 434
 	},
320 435
 
321 436
 	/**
322
-	 * Generates a Van der Corput sequence for the given number of digits and base.
437
+	 * Generates a Van der Corput sequence.
438
+	 *
439
+	 * @param {number} power
440
+	 *   Number of "digits" relative to base to generate a sequence for.
441
+	 * @param {number} base
442
+	 *   Base for the sequence. Detemines spacing of the sequence.
443
+	 *
444
+	 * @return {number[]}
445
+	 *   The generated sequence, containing all numbers from 1 to base^power.
323 446
 	 */
324
-	generateEvenSequence(numDigits, base) {
447
+	generateEvenSequence(power, base) {
325 448
 		const numbers = [];
326 449
 		const digits = [];
327
-		for (let i = 0; i < numDigits; i++) {
450
+		for (let i = 0; i < power; i++) {
328 451
 			digits[i] = 0;
329 452
 		}
330 453
 
331 454
 		function increase(digit) {
332
-			if (digit >= numDigits) return;
455
+			if (digit >= power) return;
333 456
 
334 457
 			digits[digit]++;
335 458
 			if (digits[digit] >= base) {
@@ -340,7 +463,7 @@ const utilities = {
340 463
 
341 464
 		function getNumber() {
342 465
 			let sum = 0;
343
-			for (let i = 0; i < numDigits; i++) {
466
+			for (let i = 0; i < power; i++) {
344 467
 				sum *= base;
345 468
 				sum += digits[i];
346 469
 			}
@@ -362,7 +485,17 @@ const utilities = {
362 485
 	},
363 486
 
364 487
 	/**
365
-	 * Choose whether a calculation should currently be executed based on priorities.
488
+	 * Choose whether an operation should currently run based on priorities.
489
+	 *
490
+	 * @param {number} offset
491
+	 *   Offset to add to time, so not all operations get throttled on the same tick.
492
+	 * @param {number} minBucket
493
+	 *   Minimum amount of bucket needed for this operation to run.
494
+	 * @param {number} maxBucket
495
+	 *   Amount of bucket at which this operation should always run.
496
+	 *
497
+	 * @return {boolean}
498
+	 *   True if the operation is allowed to run.
366 499
 	 */
367 500
 	throttle(offset, minBucket, maxBucket) {
368 501
 		utilities.initThrottleMemory();
@@ -383,6 +516,12 @@ const utilities = {
383 516
 		return true;
384 517
 	},
385 518
 
519
+	/**
520
+	 * Gets a new offset for a throttled operation.
521
+	 *
522
+	 * @return {number}
523
+	 *   Offset to store for a throttled operation.
524
+	 */
386 525
 	getThrottleOffset() {
387 526
 		utilities.initThrottleMemory();
388 527
 
@@ -394,6 +533,9 @@ const utilities = {
394 533
 		return Memory.throttleInfo.currentOffset;
395 534
 	},
396 535
 
536
+	/**
537
+	 * Initializes memory with general throttling information.
538
+	 */
397 539
 	initThrottleMemory() {
398 540
 		if (!Memory.throttleInfo) {
399 541
 			Memory.throttleInfo = {
@@ -412,9 +554,9 @@ const utilities = {
412 554
 			const max = sequence[0];
413 555
 			Memory.throttleInfo.max = max;
414 556
 
415
-			for (const i in sequence) {
416
-				Memory.throttleInfo.numbers[sequence[i]] = 1 - (i / max);
417
-			}
557
+			_.each(sequence, (number, index) => {
558
+				Memory.throttleInfo.numbers[number] = 1 - (index / max);
559
+			});
418 560
 
419 561
 			Memory.throttleInfo.numbers[0] = 1;
420 562
 		}