Exemplo n.º 1
0
//
// Here it is.
function find(start, options) {

	// Sanity check
	if (!(start instanceof WorldPosition)) {
		throw new Error('Must use WorldPosition in pathfinder');
	}

	// Build a high-level plan of rooms to search
	let targets = options.targets ? options.targets : [ options.target ];
	targetFlee = options.flee;
	targetRange = options.range;
	let maxCost = options.maxCost || 1000;
	if (!options.flee) {
		roomPlan = planPath(start, targets, parents, options);
		if (!roomPlan) {
			return;
		}
	} else {
		if (targets.length !== 1) {
			throw new Error('Can\'t flee or range from multiple targets');
		}
		roomPlan = [ {
			targets: targets,
			position: start,
			roomName: start.getRoomName(),
		} ];
	}

	// Initialize heap and open/close list
	heap = new Heap(roomPlan.length * 2500);
	openClosed = new OpenClosed(roomPlan.length * 2500);
	if (parents.length < roomPlan.length * 2500) {
		parents = new Uint16Array(roomPlan.length * 2500);
	}

	// Initialize cost functions
	currentCostContextId = -1;
	heuristicContextId = -1;
	avoidCreeps = Boolean(options.avoidCreeps);
	entityBits = (
		((options.ignoreCreeps || avoidCreeps) ? 0 : EntityData.FRIENDLY_CREEP | EntityData.HOSTILE_CREEP) |
		((options.ignoreHostileStructures || options.destructive) ? 0 : EntityData.HOSTILE_STRUCTURE)
	);
	if (options.weightRatio <= 0.2) {
		roadCost = 2;
		plainCost = 1;
		swampCost = 1;
	} else if (options.weightRatio > 1) {
		roadCost = 1;
		plainCost = 2;
		swampCost = 10;
	} else {
		roadCost = 2;
		plainCost = 1;
		swampCost = 5;
	}
	if (options.roadCost) {
		roadCost = options.roadCost;
	}
	if (options.swampCost) {
		swampCost = options.swampCost;
	}
	if (options.plainCost) {
		plainCost = options.plainCost;
	}
	terrainDataInstances = new Array(roomPlan.length);
	entityDataInstances = new Array(roomPlan.length);
	dangerDataInstances = new Array(roomPlan.length);
	destructionDataInstances = new Array(roomPlan.length);
	target = undefined;
	roomPlan.forEach(function(plan, ii) {
		let pos = plan.position || (plan.heuristic instanceof WorldLine ? plan.heuristic.pos : plan.heuristic);
		if (plan.targets) {
			if (plan.targets.length === 1) {
				target = plan.targets[0];
			} else {
				target = plan.targets;
			}
		}
		let roomId = Math.floor(pos.xx / 50) * kWorldSize * 2 + Math.floor(pos.yy / 50);
		rooms[ii] = roomId;
		reverseRooms[roomId] = ii + 1;
		terrainDataInstances[ii] = TerrainData.factory(plan.roomName);
		if (options.safe) {
			dangerDataInstances[ii] = DangerData.factory(plan.roomName);
		}
		if (ii === roomPlan.length - 1) {
			// Don't bother with extra data if it's not the current room
			let room = Game.rooms[plan.roomName];
			if (room) {
				if (entityBits) {
					entityDataInstances[ii] = EntityData.factory(room, options.ignoreTask);
				}
				if (options.destructive) {
					destructionDataInstances[ii] = DestructionData.factory(room);
				}
			}
		}
	});

	// Other stuff
	var opsRemaining;
	if (options.maxOps) {
		opsRemaining = options.maxOps;
	} else {
		opsRemaining = Math.min(1000, heuristic(start.xx, start.yy) * 200);
	}
	opsRemaining = options.maxOps || 1000;
	var minNodeCost = Infinity;
	var minNode;
	heuristicWeight = Math.min(roadCost, plainCost, swampCost) * 1.2;

	// Make sure destination tiles are walkable. This also inializes `indexFromPos` via `look`
	let alteredData = [];
	!options.skipWalkable && !options.flee && target && (Array.isArray(target) ? target : [ target ]).forEach(function(target) {
		if (look(target.xx, target.yy) === Infinity) {
			let xx = target.xx % 50, yy = target.yy % 50;
			let tmp = terrainData.look(xx, yy);
			if (tmp === TerrainData.WALL) {
				alteredData.push([ terrainData, xx, yy, tmp ]);
				terrainData.poke(xx, yy, TerrainData.PLAIN);
			}
			if (entityData) {
				tmp = entityData.look(xx, yy);
				if (tmp & entityBits) {
					alteredData.push([ entityData, xx, yy, tmp ]);
					entityData.poke(xx, yy, 0);
				}
			}
		}
	});

	// Mark unwalkable tiles
	if (options.avoid) {
		options.avoid.forEach(function(pos) {
			let tmp = look(pos.xx, pos.yy);
			alteredData.push([ terrainData, pos.xx % 50, pos.yy % 50, tmp ]);
			terrainData.poke(pos.xx % 50, pos.yy % 50, TerrainData.WALL);
		});
	}

	// First iteration of A* done upfront w/ no JPS
	var index = indexFromPos(start);
	openClosed.close(index);
	// heap.add(index, heuristic(start.xx, start.yy)); (uncomment for basic A*)
	for (var dir = 1; dir <= 8; ++dir) {
		var neighbor = start.getPositionInDirection(dir);

		// Check for portal nodes on start
		if (start.xx % 50 === 49) {
			if (neighbor.xx % 50 === 0 && start.yy !== neighbor.yy) {
				continue;
			}
		} else if (start.xx % 50 === 0) {
			if (neighbor.xx % 50 === 49 && start.yy !== neighbor.yy) {
				continue;
			}
		} else if (start.yy % 50 === 49) {
			if (neighbor.yy % 50 === 0 && start.xx !== neighbor.xx) {
				continue;
			}
		} else if (start.yy % 50 === 0) {
			if (neighbor.yy % 50 === 49 && start.xx !== neighbor.xx) {
				continue;
			}
		}

		// Add all neighbors to heap
		var cost = look(neighbor.xx, neighbor.yy);
		if (cost === Infinity) {
			continue;
		}
		var neighborIndex = indexFromPos(neighbor);
		heap.add(neighborIndex, cost + heuristic(neighbor.xx, neighbor.yy));
		openClosed.open(neighborIndex);
		parents[neighborIndex] = index;
	}

	// Begin JPS A*
	let lastRoomIndex = undefined;
	while (heap.length() && --opsRemaining) {
		++global.pf;

		// Pull cheapest open node off the (c)heap
		var index = heap.min();
		var fcost = heap.minCost();
		if (fcost > maxCost) {
			break;
		}

		// Close this node
		heap.remove()
		openClosed.close(index);

		// Calculate costs
		var pos = posFromIndex(index);
		var hcost = heuristic(pos.xx, pos.yy);
		var gcost = fcost - hcost;
		var cost = look(pos.xx, pos.yy);
		// console.log(String(pos), 'g('+ gcost+ ') + h('+ hcost+ ') = f('+ fcost+ ')');

		// New room? Throw away the heap
		let roomIndex = Math.floor(index / (50 * 50));
		if (lastRoomIndex === undefined) {
			lastRoomIndex = roomIndex;
		} else if (lastRoomIndex > roomIndex) {
			heap.release();
			heap = new Heap(roomPlan.length * 2500);
			lastRoomIndex = roomIndex;
		}

		// Reached destination?
		if (checkDestination(pos.xx, pos.yy, true)) {
			minNode = index;
			minNodeCost = 0;
			break;
		} else if (hcost < minNodeCost) {
			minNodeCost = hcost;
			minNode = index;
		}

		// Check for special room portal rules
		let borderNeighbors;
		let parent = posFromIndex(parents[index]);
		let dx = pos.xx > parent.xx ? 1 : (pos.xx < parent.xx ? -1 : 0);
		let dy = pos.yy > parent.yy ? 1 : (pos.yy < parent.yy ? -1 : 0);
		if (pos.xx % 50 === 0) {
			if (dx === -1) {
				borderNeighbors = [ new WorldPosition(pos.xx - 1, pos.yy) ];
			} else if (dx === 1) {
				borderNeighbors = [
					new WorldPosition(pos.xx + 1, pos.yy - 1),
					new WorldPosition(pos.xx + 1, pos.yy),
					new WorldPosition(pos.xx + 1, pos.yy + 1),
				];
			}
		} else if (pos.xx % 50 === 49) {
			if (dx === 1) {
				borderNeighbors = [ new WorldPosition(pos.xx + 1, pos.yy) ];
			} else if (dx === -1) {
				borderNeighbors = [
					new WorldPosition(pos.xx - 1, pos.yy - 1),
					new WorldPosition(pos.xx - 1, pos.yy),
					new WorldPosition(pos.xx - 1, pos.yy + 1),
				];
			}
		} else if (pos.yy % 50 === 0) {
			if (dy === -1) {
				borderNeighbors = [ new WorldPosition(pos.xx, pos.yy - 1) ];
			} else if (dy === 1) {
				borderNeighbors = [
					new WorldPosition(pos.xx - 1, pos.yy + 1),
					new WorldPosition(pos.xx, pos.yy + 1),
					new WorldPosition(pos.xx + 1, pos.yy + 1),
				];
			}
		} else if (pos.yy % 50 === 49) {
			if (dy === 1) {
				borderNeighbors = [ new WorldPosition(pos.xx, pos.yy + 1) ];
			} else if (dy === -1) {
				borderNeighbors = [
					new WorldPosition(pos.xx - 1, pos.yy - 1),
					new WorldPosition(pos.xx, pos.yy - 1),
					new WorldPosition(pos.xx + 1, pos.yy - 1),
				];
			}
		}
		if (borderNeighbors) {
			for (let neighbor of borderNeighbors) {
				let ncost = look(neighbor.xx, neighbor.yy);
				if (ncost === Infinity) {
					continue;
				}
				let neighborIndex = indexFromPos(neighbor);
				let isOpen = openClosed.get(neighborIndex);
				if (isOpen === false) {
					continue;
				}
				let fcost = gcost + ncost + heuristic(neighbor.xx, neighbor.yy);
				if (isOpen === true) {
					if (heap.costs[neighborIndex] > fcost) {
						heap.update(neighborIndex, fcost);
						parents[neighborIndex] = index;
					}
				} else {
					openClosed.open(neighborIndex);
					heap.add(neighborIndex, fcost);
					parents[neighborIndex] = index;
				}
			}
			continue;
		}

		// Regular search
		if (false) {

			// Basic A*
			for (var dir = 1; dir <= 8; ++dir) {
				var neighbor = pos.getPositionInDirection(dir);
				var acost = look(neighbor.xx, neighbor.yy);
				if (acost === Infinity) {
					continue;
				}
				var neighborIndex = indexFromPos(neighbor);
				let isOpen = openClosed.get(neighborIndex);
				if (isOpen === false) {
					continue;
				}
				let ncost = gcost + acost + heuristic(neighbor.xx, neighbor.yy);
				if (isOpen === true) {
					if (heap.costs[neighborIndex] > ncost) {
						heap.update(neighborIndex, ncost);
						parents[neighborIndex] = index;
					}
				} else {
					openClosed.open(neighborIndex);
					heap.add(neighborIndex, ncost);
					parents[neighborIndex] = index;
				}
			}
		} else {

			// JPS
			var parentGCost = fcost - heuristic(pos.xx, pos.yy);
			var tmp;
			if (dx !== 0) {
				if (dy !== 0) {
					tmp = look(pos.xx, pos.yy + dy);
					if (tmp !== Infinity) {
						jumpNeighbor(pos, index, pos.xx, pos.yy + dy, parentGCost, cost, tmp);
					}
					tmp = look(pos.xx + dx, pos.yy);
					if (tmp !== Infinity) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy, parentGCost, cost, tmp);
					}
					tmp = look(pos.xx + dx, pos.yy + dy);
					if (tmp !== Infinity) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy + dy, parentGCost, cost, tmp);
					}
					if (look(pos.xx - dx, pos.yy) !== cost) {
						jumpNeighbor(pos, index, pos.xx - dx, pos.yy + dy, parentGCost, cost, look(pos.xx - dx, pos.yy + dy));
					}
					if (look(pos.xx, pos.yy - dy) !== cost) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy - dy, parentGCost, cost, look(pos.xx + dx, pos.yy - dy));
					}
				} else {
					tmp = look(pos.xx + dx, pos.yy);
					if (tmp !== Infinity) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy, parentGCost, cost, tmp);
					}
					if (look(pos.xx, pos.yy + 1) !== cost) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy + 1, parentGCost, cost, look(pos.xx + dx, pos.yy + 1));
					}
					if (look(pos.xx, pos.yy - 1) !== cost) {
						jumpNeighbor(pos, index, pos.xx + dx, pos.yy - 1, parentGCost, cost, look(pos.xx + dx, pos.yy - 1));
					}
				}
			} else {
				tmp = look(pos.xx, pos.yy + dy);
				if (tmp !== Infinity) {
					jumpNeighbor(pos, index, pos.xx, pos.yy + dy, parentGCost, cost, tmp);
				}
				if (look(pos.xx + 1, pos.yy) !== cost) {
					jumpNeighbor(pos, index, pos.xx + 1, pos.yy + dy, parentGCost, cost, look(pos.xx + 1, pos.yy + dy));
				}
				if (look(pos.xx - 1, pos.yy) !== cost) {
					jumpNeighbor(pos, index, pos.xx - 1, pos.yy + dy, parentGCost, cost, look(pos.xx - 1, pos.yy + dy));
				}
			}
		}
	}

	// Reconstruct path from A* graph
	let ret;
	if (minNode && (minNodeCost === 0 || !options.strict)) {
		let index = minNode;
		let pos = posFromIndex(index);
		let ii = 0;
		let path = [], roads = [], swamps = [];
		while (!pos.isEqualTo(start)) {
			path.push(pos);
			checkRoadAndSwamp(pos, ii++, roads, swamps);
			index = parents[index];
			var next = posFromIndex(index);
			if (!next.isNearTo(pos)) {
				var dir = pos.getDirectionTo(next);
				do {
					pos = pos.getPositionInDirection(dir);
					path.push(pos);
					checkRoadAndSwamp(pos, ii++, roads, swamps);
				} while (!pos.isNearTo(next));
			}
			pos = next;
		}
		ret = new PathInfo(path, swamps, roads);
		ret.reverse();
	} else {
		ret = false;
	}

	// Cleanup
	alteredData.forEach(function(data) {
		data[0].poke(data[1], data[2], data[3]);
	});
	for (let ii = 0; ii < roomPlan.length; ++ii) {
		reverseRooms[rooms[ii]] = 0;
	}

	// TODO: This is a hack which clears out the reverse room lookup table every time you search for a
	// path. This shouldn't be needed because the above loop clears it out, but very rarely it doesn't
	// work and causes weird errors on the next path search. I have no idea why!
	reverseRooms = new Uint8Array(Math.pow(kWorldSize * 2, 2) + 1);

	heap.release();
	openClosed.release();
	return ret;
}
Exemplo n.º 2
0
function plan(start, targets, parents, options) {

	// Init state
	heap = new Heap(2500);
	openClosed = new OpenClosed(2500);
	indexSize = 0;
	indexMap = {};
	reverseIndexMap = new Map();
	let maxCost = options.maxCost || 1000;

	// Start node must be visible
	if (TerrainData.factory(start.getRoomName()) === undefined) {
		return;
	}

	// Initial A* node
	heap.add(indexFromEntity(start), heuristic(start, targets));

	// Do it
	let minNode;
	let minCost = Infinity;
	search: while (heap.length()) {

		// Pull a node off the heap
		let index = heap.min();
		let entity = entityFromIndex(index);
		let gcost = heap.minCost(); // not yet gcost, but will be adjusted below
		heap.remove();
		if (gcost > maxCost) {
			continue;
		}
		let hcost = heuristic(entity, targets);
		openClosed.close(index);

		// Convert whatever is on the heap to a WorldPosition
		let roomName;
		let entryPosition;
		if (entity instanceof WorldPosition) {
			// The first and last iteration will be a WorldPosition
			gcost -= hcost;
			roomName = entity.getRoomName();
			entryPosition = entity;
			for (let target of targets) {
				if (target === entity) {
					// Found a target
					minNode = index;
					break search;
				}
			}
		} else {
			// If it's a RoomPortal we need to calculate where we left the last room from now
			gcost -= hcost - 1;
			let neighbor = entity.neighbor();
			if (!neighbor) {
				// Try to explore room portals when incomplete map data is encountered
				if (minCost > hcost) {
					minCost = hcost;
					minNode = index;
				}
				continue;
			}
			roomName = neighbor.roomName;
			entryPosition = moveToNextRoom(calculateExitPosition(entity, entityFromIndex(parents[index]), targets));

			// This injects the entry position into the parent chain
			let entryPositionIndex = indexFromEntity(entryPosition);
			parents[entryPositionIndex] = index;
			index = entryPositionIndex;
		}

		// If any of the targets are in this room we can add them to the heap
		for (let target of targets) {
			if (target.getRoomName() === roomName) {
				let targetIndex = indexFromEntity(target);
				if (visit(targetIndex, gcost + entryPosition.getRangeTo(target))) { // heuristic() is 0 since these are all targets
					parents[targetIndex] = index;
				}
			}
		}

		// Now add all the room portals to the heap
		let terrainData = TerrainData.factory(roomName);
		for (let edge of terrainData.edges) {
			for (let portal of edge) {
				let neighbor = portal.neighbor();
				if (neighbor) {
					openClosed.close(indexFromEntity(neighbor));
				}
				let portalIndex = indexFromEntity(portal);
				let exitCost;
				if (entryPosition instanceof WorldLine) {
					// Exit cost should be the same for the whole exit line
					exitCost = portal.edge.getRangeTo(entryPosition.pos);
				} else {
					exitCost = portal.edge.getRangeTo(entryPosition);
				}
				if (terrainData.controller !== undefined || terrainData.sources.length !== 0) {
					exitCost = Math.floor(exitCost / kHighwayDiscount);
				}
				let fcost = gcost + exitCost + heuristic(portal, targets);
				if (visit(portalIndex, fcost)) {
					parents[portalIndex] = index;
				}
			}
		}
	}

	// Reconstruct plan
	let rooms;
	let entity = entityFromIndex(minNode);
	let lastPosition;
	let hoffset = 0;
	if (entity instanceof RoomPortal) { // incomplete path due to unknown terrain
		let entry = entityFromIndex(parents[minNode]);
		let entryPosition = calculateExitPosition(entity, entry, targets);
		lastPosition = entryPosition;
		if (options.strict) {
			return;
		}
		rooms = [ {
			portal: entity,
			heuristic: entryPosition,
			hoffset: 0,
			roomName: entry.getRoomName(),
		} ];
	} else {
		lastPosition = entity;
		rooms = [ {
			targets: targets.filter(function(target) {
				return target.getRoomName() === entity.getRoomName();
			}),
			position: entity,
			roomName: entity.getRoomName(),
		} ];
	}
	let index = parents[minNode];
	entity = entityFromIndex(index);
	while (entity !== start) {
		index = parents[index];
		let portal = entityFromIndex(index);
		let lastPortal = portal.neighbor();
		if (lastPosition instanceof WorldPosition) {
			hoffset += lastPortal.edge.getRangeTo(lastPosition) + 1;
		} else if (lastPosition instanceof WorldLine) {
			hoffset += lastPortal.edge.getRangeTo(lastPosition.pos) + 1;
		} else {
			throw new Error('Rogue RoomPortal?');
		}
		lastPosition = moveToNextRoom(entity);
		rooms.push({
			portal: portal,
			heuristic: lastPosition,
			hoffset: hoffset,
			roomName: portal.roomName,
		});
		index = parents[index];
		entity = entityFromIndex(index);
	}

	// Cleanup
	heap.release();
	openClosed.release();
	indexMap = reverseIndexMap = undefined;
	return rooms;
}