Basic pathfinding implementations
This commit is contained in:
@@ -9,6 +9,7 @@ import io.anuke.mindustry.content.blocks.StorageBlocks;
|
|||||||
import io.anuke.mindustry.game.Team;
|
import io.anuke.mindustry.game.Team;
|
||||||
import io.anuke.mindustry.type.AmmoType;
|
import io.anuke.mindustry.type.AmmoType;
|
||||||
import io.anuke.mindustry.type.Item;
|
import io.anuke.mindustry.type.Item;
|
||||||
|
import io.anuke.mindustry.type.Recipe;
|
||||||
import io.anuke.mindustry.world.Block;
|
import io.anuke.mindustry.world.Block;
|
||||||
import io.anuke.mindustry.world.Edges;
|
import io.anuke.mindustry.world.Edges;
|
||||||
import io.anuke.mindustry.world.Tile;
|
import io.anuke.mindustry.world.Tile;
|
||||||
@@ -44,7 +45,7 @@ public class FortressGenerator{
|
|||||||
void gen(){
|
void gen(){
|
||||||
gen.setBlock(enemyX, enemyY, StorageBlocks.core, team);
|
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> 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));
|
Array<Block> drills = find(b -> b instanceof Drill && !b.consumes.has(ConsumePower.class));
|
||||||
@@ -62,19 +63,24 @@ public class FortressGenerator{
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item item = gen.drillItem(x, y, drill);
|
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);
|
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
|
//place down turrets
|
||||||
for(int x = 0; x < gen.width; x++){
|
for(int x = 0; x < gen.width; x++){
|
||||||
for(int y = 0; y < gen.height; y++){
|
for(int y = 0; y < gen.height; y++){
|
||||||
if(Vector2.dst(x, y, enemyX, enemyY) > coreDst + 4 || !gen.canPlace(x, y, turret)){
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +104,8 @@ public class FortressGenerator{
|
|||||||
|
|
||||||
if(found){
|
if(found){
|
||||||
gen.setBlock(x, y, turret, team);
|
gen.setBlock(x, y, turret, team);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +133,6 @@ public class FortressGenerator{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean accepts(AmmoType[] types, Item item){
|
boolean accepts(AmmoType[] types, Item item){
|
||||||
@@ -140,7 +147,7 @@ public class FortressGenerator{
|
|||||||
Array<Block> find(Predicate<Block> pred){
|
Array<Block> find(Predicate<Block> pred){
|
||||||
Array<Block> out = new Array<>();
|
Array<Block> out = new Array<>();
|
||||||
for(Block block : Block.all()){
|
for(Block block : Block.all()){
|
||||||
if(pred.evaluate(block)){
|
if(pred.evaluate(block) && Recipe.getByResult(block) != null){
|
||||||
out.add(block);
|
out.add(block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user