mirror of
https://github.com/Anuken/Mindustry.git
synced 2026-04-02 04:10:43 -07:00
Basic pathfinding implementations
This commit is contained in:
parent
6570b411d1
commit
1ce3b40b4e
4 changed files with 382 additions and 22 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue