From a9fba179437ebb86b673ad87ba1cc0f677b8e17b Mon Sep 17 00:00:00 2001
From: kts of kettek <kts@kettek.net>
Date: Sat, 3 Feb 2024 00:03:45 -0800
Subject: [PATCH] Add pathfinding garbagio

---
 cmd/prog/main.go |  28 +++++++++++
 sauce/astar.go   | 127 ++++++++++++++++++++++++++---------------------
 2 files changed, 99 insertions(+), 56 deletions(-)

diff --git a/cmd/prog/main.go b/cmd/prog/main.go
index d58cd6d..33b050c 100644
--- a/cmd/prog/main.go
+++ b/cmd/prog/main.go
@@ -27,6 +27,8 @@ type Game struct {
 	cellX, cellY int
 	fromX, fromY int
 	toX, toY     int
+	path         []sauce.Node
+	pathSuccess  bool
 	whichFromTo  bool
 }
 
@@ -35,6 +37,10 @@ func NewGame() *Game {
 	g.world = sauce.NewWorld(3, 3)
 	g.world.HueristicLine(10, 0, 10, 10, 1)
 	g.world.HueristicLine(10, 13, 10, 30, 1)
+	g.fromX = -1
+	g.fromY = -1
+	g.toX = -1
+	g.toY = -1
 	return g
 }
 
@@ -62,10 +68,13 @@ func (g *Game) Update() error {
 	g.cellX = cursorX / CellSize
 	g.cellY = cursorY / CellSize
 	if cell := g.world.At(g.cellX, g.cellY); cell != nil {
+		var reroll bool
 		if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
 			cell.Hueristic++
+			reroll = true
 		} else if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) {
 			cell.Hueristic--
+			reroll = true
 		} else if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonMiddle) {
 			if !g.whichFromTo {
 				g.fromX, g.fromY = g.cellX, g.cellY
@@ -73,6 +82,13 @@ func (g *Game) Update() error {
 				g.toX, g.toY = g.cellX, g.cellY
 			}
 			g.whichFromTo = !g.whichFromTo
+			reroll = true
+		}
+		if reroll {
+			if g.fromX != -1 && g.fromY != -1 && g.toX != -1 && g.toY != -1 {
+				g.path, g.pathSuccess = sauce.FindPath(g.world, g.fromX, g.fromY, g.toX, g.toY)
+			}
+
 		}
 	}
 	return nil
@@ -117,6 +133,18 @@ func (g *Game) Draw(screen *ebiten.Image) {
 			}
 		}
 	}
+	// Draw path dots overtop.
+	for _, node := range g.path {
+		x := node.X*CellSize + CellSize/2
+		y := node.Y*CellSize + CellSize/2
+
+		c := color.RGBA{0, 255, 0, 255}
+		if !g.pathSuccess {
+			c = color.RGBA{255, 0, 0, 255}
+		}
+
+		vector.DrawFilledCircle(screen, float32(x), float32(y), 5, c, false)
+	}
 	ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f\nFPS: %0.2f", ebiten.ActualTPS(), ebiten.ActualFPS()))
 }
 
diff --git a/sauce/astar.go b/sauce/astar.go
index a197fa9..2654fc8 100644
--- a/sauce/astar.go
+++ b/sauce/astar.go
@@ -1,85 +1,100 @@
 package sauce
 
-type Node struct {
-	parent  *Node
-	x, y    int
-	f, g, h int
+import (
+	"math"
+)
+
+type Coord struct {
+	X, Y int16
 }
 
-func FindPath(w *World, fromX, fromY int, toX, toY int) []Node {
-	openList := make([]*Node, 0)
-	closedList := make([]*Node, 0)
+type Node struct {
+	Coord
+	parent *Node
+	g      int
+	f      int
+	d      int
+}
 
-	startNode := &Node{x: fromX, y: fromY}
-	endNode := &Node{x: toX, y: toY}
+type Nodes map[Coord]*Node
 
-	openList = append(openList, startNode)
+func FindPath(w *World, fromX, fromY int, toX, toY int) ([]Node, bool) {
+	var open []*Node
+	visited := make(Nodes)
 
-	for len(openList) > 0 {
-		var cnode *Node
-		var cindex int
-		for i, node := range openList {
-			if cnode == nil || node.f < cnode.f {
-				cnode = node
-				cindex = i
+	open = append(open, &Node{Coord: Coord{int16(fromX), int16(fromY)}})
+
+	for len(open) > 0 {
+		var current *Node
+		var currentI int
+		// Set current to lowest f in open
+		for i, n := range open {
+			if current == nil || n.f < current.f {
+				current = n
+				currentI = i
 			}
 		}
-		openList = append(openList[:cindex], openList[cindex+1:]...)
-		closedList = append(closedList, cnode)
 
-		if cnode.x == endNode.x && cnode.y == endNode.y {
-			path := make([]Node, 0)
-			for cnode != nil {
-				path = append(path, *cnode)
-				cnode = cnode.parent
+		// Return reconstructed path if current is the destination.
+		if current.X == int16(toX) && current.Y == int16(toY) {
+			var path []Node
+			for current.parent != nil {
+				path = append(path, *current)
+				current = current.parent
 			}
-			return path
+			return path, true
 		}
+		// Remove current from the open set.
+		open = append(open[:currentI], open[currentI+1:]...)
 
-		var neighbors []Node
+		// Get neighbors.
+		var neighbors []*Node
 		for x := -1; x <= 1; x++ {
 			for y := -1; y <= 1; y++ {
 				if x == 0 && y == 0 {
 					continue
 				}
-				if x != 0 && y != 0 {
+				coord := Coord{current.X + int16(x), current.Y + int16(y)}
+				if cell := w.At(int(coord.X), int(coord.Y)); cell == nil || cell.Hueristic == 255 {
 					continue
 				}
-				if cell := w.At(cnode.x+x, cnode.y+y); cell != nil {
-					neighbors = append(neighbors, Node{x: cnode.x + x, y: cnode.y + y, parent: cnode})
-				}
+				neighbors = append(neighbors, &Node{Coord: coord, parent: current, g: math.MaxUint16, f: math.MaxUint16})
 			}
 		}
 
+		// Iterate over neighbors.
 		for _, neighbor := range neighbors {
-			exists := false
-			for _, node := range closedList {
-				if node.x == neighbor.x && node.y == neighbor.y {
-					exists = true
-					break
+			tentativeG := current.g + 1
+			distance := int(math.Abs(float64(neighbor.X)-float64(toX)) + math.Abs(float64(neighbor.Y)-float64(toY)))
+			tentativeF := tentativeG + distance
+
+			var node *Node
+			if node = visited[neighbor.Coord]; node == nil || tentativeF < node.f {
+				if node == nil {
+					node = neighbor
 				}
+				node.g = tentativeG
+				node.f = tentativeF
+				node.d = distance
+				visited[node.Coord] = node
+				open = append(open, node)
 			}
-			if exists {
-				continue
-			}
-
-			exists = false
-			for _, node := range openList {
-				if node.x == neighbor.x && node.y == neighbor.y {
-					exists = true
-					break
-				}
-			}
-			if exists {
-				continue
-			}
-
-			neighbor.g = cnode.g + 1                             // this value potentially should be a float and modified if a diagonal.
-			neighbor.h = (toX - neighbor.x) + (toY - neighbor.y) // Maybe should be calculated differently.
-			neighbor.f = neighbor.g + neighbor.h
-
-			openList = append(openList, &neighbor)
 		}
 	}
-	return nil
+
+	var current *Node
+	// Set current to lowest f in open
+	for _, n := range visited {
+		if current == nil || n.d < current.d {
+			current = n
+		}
+	}
+
+	// Return reconstructed path if current is the destination.
+	var path []Node
+	for current != nil && current.parent != nil {
+		path = append(path, *current)
+		current = current.parent
+	}
+	return path, false
 }