using UnityEngine; using System.Collections.Generic; using System.Linq; /// /// Handles pathfinding through the maze using A* algorithm /// Takes into account terrain movement costs /// public class MazePathfinder { private MazeData mazeData; public class PathNode { public Vector2Int Position { get; set; } public PathNode Parent { get; set; } public float GCost { get; set; } // Cost from start public float HCost { get; set; } // Heuristic cost to goal public float FCost => GCost + HCost; public PathNode(Vector2Int position, PathNode parent, float gCost, float hCost) { Position = position; Parent = parent; GCost = gCost; HCost = hCost; } } public MazePathfinder(MazeData mazeData) { this.mazeData = mazeData; } /// /// Finds a path from start to goal using A* algorithm /// public List FindPath(Vector2Int start, Vector2Int goal) { var openSet = new List(); var closedSet = new HashSet(); var startNode = new PathNode(start, null, 0, Heuristic(start, goal)); openSet.Add(startNode); while (openSet.Count > 0) { // Find node with lowest F cost int current = 0; for (int i = 1; i < openSet.Count; i++) { if (openSet[i].FCost < openSet[current].FCost) { current = i; } } var currentNode = openSet[current]; if (currentNode.Position == goal) { return ReconstructPath(currentNode); } openSet.RemoveAt(current); closedSet.Add(currentNode.Position); // Check neighbors var neighbors = GetNeighbors(currentNode.Position); foreach (var neighbor in neighbors) { if (closedSet.Contains(neighbor)) continue; float movementCost = GetMovementCost(neighbor); float tentativeGCost = currentNode.GCost + movementCost; var existingNode = openSet.FirstOrDefault(n => n.Position == neighbor); if (existingNode != null) { if (tentativeGCost < existingNode.GCost) { existingNode.Parent = currentNode; existingNode.GCost = tentativeGCost; } } else { var hCost = Heuristic(neighbor, goal); openSet.Add(new PathNode(neighbor, currentNode, tentativeGCost, hCost)); } } } // No path found return new List(); } /// /// Gets the movable neighbors of a position /// private List GetNeighbors(Vector2Int position) { var neighbors = new List(); Vector2Int[] directions = new[] { new Vector2Int(position.x + 1, position.y), new Vector2Int(position.x - 1, position.y), new Vector2Int(position.x, position.y + 1), new Vector2Int(position.x, position.y - 1), // Optionally add diagonals: // new Vector2Int(position.x + 1, position.y + 1), // new Vector2Int(position.x - 1, position.y + 1), // new Vector2Int(position.x + 1, position.y - 1), // new Vector2Int(position.x - 1, position.y - 1), }; foreach (var dir in directions) { if (mazeData.IsWalkable(dir.x, dir.y)) { neighbors.Add(dir); } } return neighbors; } /// /// Gets the movement cost for a tile based on terrain /// private float GetMovementCost(Vector2Int position) { var tile = mazeData.GetTile(position.x, position.y); if (tile == null) return float.MaxValue; return tile.GetMovementCost(); } /// /// Heuristic function (Manhattan distance) /// private float Heuristic(Vector2Int from, Vector2Int to) { return Mathf.Abs(from.x - to.x) + Mathf.Abs(from.y - to.y); } /// /// Reconstructs the path from goal to start /// private List ReconstructPath(PathNode node) { var path = new List(); while (node != null) { path.Add(node.Position); node = node.Parent; } path.Reverse(); return path; } /// /// Finds all reachable tiles from a starting position within a certain distance /// public HashSet FindReachableTiles(Vector2Int start, float maxDistance) { var reachable = new HashSet(); var queue = new Queue<(Vector2Int pos, float distance)>(); queue.Enqueue((start, 0)); reachable.Add(start); while (queue.Count > 0) { var (current, distance) = queue.Dequeue(); var neighbors = GetNeighbors(current); foreach (var neighbor in neighbors) { if (!reachable.Contains(neighbor)) { float cost = GetMovementCost(neighbor); float newDistance = distance + cost; if (newDistance <= maxDistance) { reachable.Add(neighbor); queue.Enqueue((neighbor, newDistance)); } } } } return reachable; } }