180 lines
4.1 KiB
Go
180 lines
4.1 KiB
Go
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
|
|
}
|