mourning-ember / src / main / java / dev / llan / model / board / pathfinding / GridHeuristic.java
GridHeuristic.java
Raw
package dev.llan.model.board.pathfinding;

import dev.llan.model.board.Grid;
import dev.llan.model.board.Point;
import dev.llan.model.board.Tile.TileType;

import java.util.*;

public class GridHeuristic implements PathFinder {
  private Grid grid;

  public GridHeuristic(Grid grid) {
    this.grid = grid;
  }

  @Override
  public Optional<Path> findPath(Point from, Point to) {
    PriorityQueue<Node> pq = new PriorityQueue<>();
    Map<Point, Double> costs = new HashMap<>();
    Set<Point> visited = new HashSet<>();

    Node original = new Node(from, Point.getDistance(from, to));
    pq.add(original);
    costs.put(from, 0.0);

    while (!pq.isEmpty()) {
      Node current = pq.poll();
      if (visited.contains(current.getPoint())) {
        continue;
      }
      visited.add(current.getPoint());

      if (current.getPoint().equals(to)) {
        return reconstructPath(current, from);
      }

      Point point = current.getPoint();
      List<Point> neighbors = point.findImmediateNeighbors(grid);
      addEndNode(neighbors, point, to);
      List<Node> nodes = processNeighbors(neighbors, current, to);
      addViableNodes(nodes, costs, pq);
    }

    return Optional.empty();
  }

  private void addViableNodes(List<Node> nodes, Map<Point, Double> costs, PriorityQueue<Node> pq) {
    for (Node node : nodes) {
      Point point = node.getPoint();
      double newCost = node.getCost();
      if (!costs.containsKey(point) || newCost < costs.get(point)) {
        costs.put(point, newCost);
        pq.add(node);
      }
    }
  }

  private List<Node> processNeighbors(List<Point> neighbors, Node parent, Point to) {
    List<Node> nodes = new ArrayList<>();
    for (Point neighbor : neighbors) {
      double cost = parent.getCost() + grid.getMovementCost(neighbor, parent.getPoint());
      Node node = new Node(neighbor, parent, Point.getDistance(neighbor, to), cost);
      nodes.add(node);
    }
    return nodes;
  }

  private void addEndNode(List<Point> neighbors, Point current, Point to) {
    TileType type = grid.tileAt(to).getType();
    if (type == TileType.OCCUPIED || type == TileType.UNMOVABLE) {
      return;
    }
    if (Point.getDistance(current, to) < 1.5) {
      neighbors.add(to);
    }
  }

  private Optional<Path> reconstructPath(Node lastNode, Point start) {
    Path path = new Path();

    Node current = lastNode;
    while (!current.getPoint().equals(start)) {
      path.addFirst(current.getPoint());

      if (current.getParent().isPresent()) {
        current = current.getParent().get();
      }
    }

    path.calculateCosts(start, grid);

    return Optional.of(path);
  }
}