package game import ( "container/heap" "fmt" "gitea.boner.be/bdnugget/goonscape/types" ) // Node represents a node in the A* pathfinding algorithm type Node struct { Tile types.Tile Parent *Node G, H, F float32 // G = cost from start, H = heuristic to goal, F = G + H } // PriorityQueue implements a min-heap for nodes ordered by F value type PriorityQueue []*Node // Implement the heap.Interface for PriorityQueue func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].F < pq[j].F } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] } func (pq *PriorityQueue) Push(x interface{}) { item := x.(*Node) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] *pq = old[0 : n-1] return item } // Helper to check if tile is in priority queue func isInQueue(queue *PriorityQueue, tile types.Tile) (bool, *Node) { for _, node := range *queue { if node.Tile.X == tile.X && node.Tile.Y == tile.Y { return true, node } } return false, nil } // FindPath implements A* pathfinding algorithm with a priority queue func FindPath(start, end types.Tile) []types.Tile { // Initialize open and closed sets openSet := &PriorityQueue{} heap.Init(openSet) closedSet := make(map[[2]int]bool) // Create start node and add to open set startNode := &Node{ Tile: start, Parent: nil, G: 0, H: heuristic(start, end), } startNode.F = startNode.G + startNode.H heap.Push(openSet, startNode) // Main search loop for openSet.Len() > 0 { // Get node with lowest F score current := heap.Pop(openSet).(*Node) // If we reached the goal, reconstruct and return the path if current.Tile.X == end.X && current.Tile.Y == end.Y { return reconstructPath(current) } // Add current to closed set closedSet[[2]int{current.Tile.X, current.Tile.Y}] = true // Check all neighbors for _, neighbor := range GetNeighbors(current.Tile) { // Skip if in closed set or not walkable if !neighbor.Walkable || closedSet[[2]int{neighbor.X, neighbor.Y}] { continue } // Calculate tentative G score tentativeG := current.G + distance(current.Tile, neighbor) // Check if in open set inOpen, existingNode := isInQueue(openSet, neighbor) // If not in open set or better path found if !inOpen || tentativeG < existingNode.G { // Create or update the node var neighborNode *Node if inOpen { neighborNode = existingNode } else { neighborNode = &Node{ Tile: neighbor, Parent: current, } } // Update scores neighborNode.G = tentativeG neighborNode.H = heuristic(neighbor, end) neighborNode.F = neighborNode.G + neighborNode.H neighborNode.Parent = current // Add to open set if not already there if !inOpen { heap.Push(openSet, neighborNode) } } } } // No path found return nil } // reconstructPath builds the path from goal node to start func reconstructPath(node *Node) []types.Tile { path := []types.Tile{} current := node // Follow parent pointers back to start for current != nil { path = append([]types.Tile{current.Tile}, path...) current = current.Parent } fmt.Printf("Path found: %v\n", path) return path } // heuristic estimates cost from current to goal (Manhattan distance) func heuristic(a, b types.Tile) float32 { return float32(abs(a.X-b.X) + abs(a.Y-b.Y)) } // distance calculates cost between adjacent tiles func distance(a, b types.Tile) float32 { return 1.0 // uniform cost for now } // GetNeighbors returns walkable tiles adjacent to the given tile func GetNeighbors(tile types.Tile) []types.Tile { directions := [][2]int{ {1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}, } neighbors := []types.Tile{} grid := GetMapGrid() for _, dir := range directions { nx, ny := tile.X+dir[0], tile.Y+dir[1] if nx >= 0 && nx < types.MapWidth && ny >= 0 && ny < types.MapHeight { if grid[nx][ny].Walkable { neighbors = append(neighbors, grid[nx][ny]) } } } return neighbors } // abs returns the absolute value of x func abs(x int) int { if x < 0 { return -x } return x }