Basic pathfinding implementations

This commit is contained in:
Anuken 2018-08-31 10:15:08 -04:00
parent 6570b411d1
commit 1ce3b40b4e
4 changed files with 382 additions and 22 deletions

View file

@ -9,6 +9,7 @@ import io.anuke.mindustry.content.blocks.StorageBlocks;
import io.anuke.mindustry.game.Team;
import io.anuke.mindustry.type.AmmoType;
import io.anuke.mindustry.type.Item;
import io.anuke.mindustry.type.Recipe;
import io.anuke.mindustry.world.Block;
import io.anuke.mindustry.world.Edges;
import io.anuke.mindustry.world.Tile;
@ -44,7 +45,7 @@ public class FortressGenerator{
void gen(){
gen.setBlock(enemyX, enemyY, StorageBlocks.core, team);
float difficultyScl = Mathf.clamp(gen.sector.difficulty / 25f + Mathf.range(1f/2f), 0f, 0.9999f);
float difficultyScl = Mathf.clamp(gen.sector.difficulty / 20f + gen.random.range(1f/2f), 0f, 0.9999f);
Array<Block> turrets = find(b -> (b instanceof ItemTurret && accepts(((ItemTurret) b).getAmmoTypes(), Items.copper) || b instanceof PowerTurret));
Array<Block> drills = find(b -> b instanceof Drill && !b.consumes.has(ConsumePower.class));
@ -62,42 +63,49 @@ public class FortressGenerator{
}
Item item = gen.drillItem(x, y, drill);
Block generator = gens.get(gen.random.random(0, gens.size-1));
if(item != null && item == Items.copper && gen.canPlace(x, y, drill)){
if(item != null && item == Items.copper && gen.canPlace(x, y, drill) && !gen.random.chance(0.5)){
gen.setBlock(x, y, drill, team);
}else if(gen.canPlace(x, y, generator) && gen.random.chance(0.01)){
gen.setBlock(x, y, generator, team);
}
}
}
Turret turret = (Turret) turrets.first();
//Turret turret = (Turret) turrets.first();
//place down turrets
for(int x = 0; x < gen.width; x++){
for(int y = 0; y < gen.height; y++){
if(Vector2.dst(x, y, enemyX, enemyY) > coreDst + 4 || !gen.canPlace(x, y, turret)){
continue;
}
for(Block block : turrets){
Turret turret = (Turret)block;
if(Vector2.dst(x, y, enemyX, enemyY) > coreDst + 4 || !gen.canPlace(x, y, turret) || !gen.random.chance(0.5)){
continue;
}
boolean found = false;
boolean found = false;
for(GridPoint2 point : Edges.getEdges(turret.size)){
Tile tile = gen.tile(x + point.x, y + point.y);
for(GridPoint2 point : Edges.getEdges(turret.size)){
Tile tile = gen.tile(x + point.x, y + point.y);
if(tile != null){
tile = tile.target();
if(tile != null){
tile = tile.target();
if(turret instanceof PowerTurret && tile.target().block() instanceof PowerGenerator){
found = true;
break;
}else if(turret instanceof ItemTurret && tile.block() instanceof Drill && accepts(((ItemTurret) turret).getAmmoTypes(), gen.drillItem(tile.x, tile.y, (Drill) tile.block()))){
found = true;
break;
if(turret instanceof PowerTurret && tile.target().block() instanceof PowerGenerator){
found = true;
break;
}else if(turret instanceof ItemTurret && tile.block() instanceof Drill && accepts(((ItemTurret) turret).getAmmoTypes(), gen.drillItem(tile.x, tile.y, (Drill) tile.block()))){
found = true;
break;
}
}
}
}
if(found){
gen.setBlock(x, y, turret, team);
if(found){
gen.setBlock(x, y, turret, team);
break;
}
}
}
}
@ -125,7 +133,6 @@ public class FortressGenerator{
}
}
}
}
boolean accepts(AmmoType[] types, Item item){
@ -140,7 +147,7 @@ public class FortressGenerator{
Array<Block> find(Predicate<Block> pred){
Array<Block> out = new Array<>();
for(Block block : Block.all()){
if(pred.evaluate(block)){
if(pred.evaluate(block) && Recipe.getByResult(block) != null){
out.add(block);
}
}

View file

@ -0,0 +1,264 @@
package io.anuke.mindustry.maps.generation.pathfinding;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.BinaryHeap;
import com.badlogic.gdx.utils.IntMap;
import io.anuke.mindustry.content.fx.Fx;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.core.Effects;
import io.anuke.ucore.function.Consumer;
import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.Geometry;
import io.anuke.ucore.util.Mathf;
//TODO
public class AStarPathFinder extends TilePathfinder{
IntMap<NodeRecord> records = new IntMap<>();
BinaryHeap<NodeRecord> openList;
NodeRecord current;
private int searchId;
private Tile end;
private static final byte UNVISITED = 0;
private static final byte OPEN = 1;
private static final byte CLOSED = 2;
private static final boolean debug = false;
public AStarPathFinder(Tile[][] tiles) {
super(tiles);
this.openList = new BinaryHeap<>();
}
@Override
public void search(Tile start, Predicate<Tile> result, Array<Tile> out){
}
public boolean searchNodePath(Tile startNode, Tile endNode, Array<Tile> outPath) {
this.end = endNode;
// Perform AStar
boolean found = search(startNode, endNode);
if (found) {
// Create a path made of nodes
generateNodePath(startNode, outPath);
}
return found;
}
protected boolean search(Tile startNode, Tile endNode) {
initSearch(startNode, endNode);
// Iterate through processing each node
do {
// Retrieve the node with smallest estimated total cost from the open list
current = openList.pop();
current.category = CLOSED;
// Terminate if we reached the goal node
if (current.node == endNode) return true;
visitChildren(endNode);
} while (openList.size > 0);
// We've run out of nodes without finding the goal, so there's no solution
return false;
}
/*
public boolean search(PathFinderRequest<Tile> request, long timeToRun) {
long lastTime = TimeUtils.nanoTime();
// We have to initialize the search if the status has just changed
if (request.statusChanged) {
initSearch(request.startNode, request.endNode);
request.statusChanged = false;
}
// Iterate through processing each node
do {
// Check the available time
long currentTime = TimeUtils.nanoTime();
timeToRun -= currentTime - lastTime;
if (timeToRun <= PathFinderQueue.TIME_TOLERANCE) return false;
// Retrieve the node with smallest estimated total cost from the open list
current = openList.pop();
current.category = CLOSED;
// Terminate if we reached the goal node; we've found a path.
if (current.node == request.endNode) {
request.pathFound = true;
generateNodePath(request.startNode, request.resultPath);
return true;
}
// Visit current node's children
visitChildren(request.endNode);
// Store the current time
lastTime = currentTime;
} while (openList.size > 0);
// The open list is empty and we've not found a path.
request.pathFound = false;
return true;
}*/
protected void initSearch(Tile startNode, Tile endNode) {
// Increment the search id
if (++searchId < 0) searchId = 1;
// Initialize the open list
openList.clear();
// Initialize the record for the start node and add it to the open list
NodeRecord startRecord = getNodeRecord(startNode);
startRecord.node = startNode;
//startRecord.connection = null;
startRecord.costSoFar = 0;
addToOpenList(startRecord, estimate(startNode, endNode));
current = null;
}
protected void visitChildren(Tile endNode) {
if(debug) Effects.effect(Fx.spawn, current.node.worldx(), current.node.worldy());
nodes(current.node, node -> {
float addCost = estimate(current.node, node);
float nodeCost = current.costSoFar + addCost;
float nodeHeuristic;
NodeRecord nodeRecord = getNodeRecord(node);
if (nodeRecord.category == CLOSED) { // The node is closed
// If we didn't find a shorter route, skip
if (nodeRecord.costSoFar <= nodeCost){
return;
}
// We can use the node's old cost values to calculate its heuristic
// without calling the possibly expensive heuristic function
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
} else if (nodeRecord.category == OPEN) { // The node is open
//If our route is no better, then skip
if (nodeRecord.costSoFar <= nodeCost){
return;
}
// Remove it from the open list (it will be re-added with the new cost)
openList.remove(nodeRecord);
// We can use the node's old cost values to calculate its heuristic
// without calling the possibly expensive heuristic function
nodeHeuristic = nodeRecord.getEstimatedTotalCost() - nodeRecord.costSoFar;
} else { // the node is unvisited
// We'll need to calculate the heuristic value using the function,
// since we don't have a node record with a previously calculated value
nodeHeuristic = estimate(node, endNode);
}
// Update node record's cost and connection
nodeRecord.costSoFar = nodeCost;
nodeRecord.from = current.node;
// Add it to the open list with the estimated total cost
addToOpenList(nodeRecord, nodeCost + nodeHeuristic);
});
}
protected void nodes(Tile current, Consumer<Tile> cons){
if(obstacle(current)) return;
for(int i = 0; i < 4; i ++){
Tile n = current.getNearby(i);
if(!obstacle(n)) cons.accept(n);
}
}
protected Tile rel(Tile tile, int i){
return tile.getNearby(Geometry.d8[Mathf.mod(i, 8)]);
}
protected boolean obstacle(Tile tile){
return tile == null || (tile.solid() && end.target() != tile && tile.target() != end);
}
protected float estimate(Tile tile, Tile other){
return Math.abs(tile.worldx() - other.worldx()) + Math.abs(tile.worldy() - other.worldy());
// (tile.occluded ? tilesize : 0) + (other.occluded ? tilesize : 0);
}
protected void generateNodePath(Tile startNode, Array<Tile> outPath) {
// Work back along the path, accumulating nodes
// outPath.clear();
while (current.from != null) {
outPath.add(current.node);
current = records.get(indexOf(current.from));
}
outPath.add(startNode);
// Reverse the path
outPath.reverse();
}
protected void addToOpenList(NodeRecord nodeRecord, float estimatedTotalCost) {
openList.add(nodeRecord, estimatedTotalCost);
nodeRecord.category = OPEN;
}
protected NodeRecord getNodeRecord(Tile node) {
if(!records.containsKey(indexOf(node))){
NodeRecord record = new NodeRecord();
record.node = node;
record.searchId = searchId;
records.put(indexOf(node), record);
return record;
}else{
NodeRecord record = records.get(indexOf(node));
if(record.searchId != searchId){
record.category = UNVISITED;
record.searchId = searchId;
}
return record;
}
}
private int indexOf(Tile node){
return node.packedPosition();
}
static class NodeRecord extends BinaryHeap.Node {
Tile node;
Tile from;
float costSoFar;
byte category;
int searchId;
public NodeRecord() {
super(0);
}
public float getEstimatedTotalCost() {
return getValue();
}
}
}

View file

@ -0,0 +1,69 @@
package io.anuke.mindustry.maps.generation.pathfinding;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Queue;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.Geometry;
public class FlowPathFinder extends TilePathfinder{
protected float[][] weights;
public FlowPathFinder(Tile[][] tiles){
super(tiles);
this.weights = new float[tiles.length][tiles[0].length];
}
public void search(Tile start, Predicate<Tile> result, Array<Tile> out){
Queue<Tile> queue = new Queue<>();
for(int i = 0; i < weights.length; i++){
for(int j = 0; j < weights[0].length; j++){
if(result.test(tiles[i][j])){
weights[i][j] = Float.MAX_VALUE;
queue.addLast(tiles[i][j]);
}else{
weights[i][j] = 0f;
}
}
}
while(queue.size > 0){
Tile tile = queue.first();
for(GridPoint2 point : Geometry.d4){
int nx = tile.x + point.x, ny = tile.y + point.y;
if(inBounds(nx, ny) && weights[nx][ny] < weights[tile.x][tile.y] && tiles[nx][ny].passable()){
weights[nx][ny] = weights[tile.x][tile.y] - 1;
queue.addLast(tiles[nx][ny]);
}
}
}
out.add(start);
while(true){
Tile tile = out.peek();
Tile max = null;
float maxf = 0f;
for(GridPoint2 point : Geometry.d4){
int nx = tile.x + point.x, ny = tile.y + point.y;
if(inBounds(nx, ny) && (weights[nx][ny] > maxf || max == null)){
max = tiles[nx][ny];
maxf = weights[nx][ny];
if(MathUtils.isEqual(maxf, Float.MAX_VALUE)){
out.add(max);
return;
}
}
}
if(max == null){
break;
}
out.add(max);
}
}
}

View file

@ -0,0 +1,20 @@
package io.anuke.mindustry.maps.generation.pathfinding;
import com.badlogic.gdx.utils.Array;
import io.anuke.mindustry.world.Tile;
import io.anuke.ucore.function.Predicate;
import io.anuke.ucore.util.Mathf;
public abstract class TilePathfinder{
protected Tile[][] tiles;
public TilePathfinder(Tile[][] tiles){
this.tiles = tiles;
}
protected boolean inBounds(int x, int y){
return Mathf.inBounds(x, y, tiles);
}
public abstract void search(Tile start, Predicate<Tile> result, Array<Tile> out);
}