krampus19/soko/pathfinder.go

100 lines
2.2 KiB
Go

package soko
import (
"sort"
"opslag.de/schobers/geom"
)
type PathFinder struct {
state State
}
func NewPathFinder(s State) PathFinder {
return PathFinder{s}
}
type betterNeighbourFn func(geom.Point, int)
func (p PathFinder) findBetterNeighbours(distances map[geom.Point]int, curr geom.Point, better betterNeighbourFn) {
currDistance := distances[curr]
newDistance := currDistance + 1
neighbours := Neighbours(curr)
for _, next := range neighbours {
if !p.state.IsWalkable(next) || p.state.Bricks.Has(next) { // filter neighbours
continue
}
if distance, ok := distances[next]; ok && distance <= newDistance { // skip when shorter path exists
continue
}
distances[next] = newDistance
better(next, newDistance)
}
}
func (p PathFinder) Find(target geom.Point) []geom.Point {
source := p.state.Player.Pos
frontier := []geom.Point{source}
distances := map[geom.Point]int{source: 0}
moves := map[geom.Point]geom.Point{source: source}
heuristic := func(i int) int {
pos := frontier[i]
return distances[pos] + pos.DistInt(target)
}
for {
curr := frontier[0]
if curr == target {
break
}
frontier = frontier[1:]
p.findBetterNeighbours(distances, curr, func(next geom.Point, newDistance int) {
moves[next] = curr
frontier = append(frontier, next)
})
if len(frontier) == 0 {
return nil // no path
}
// apply heuristic to frontier (favor points closer to target)
sort.Slice(frontier, func(i, j int) bool { return heuristic(i) < heuristic(j) })
}
// build reverse path
curr := target
path := []geom.Point{curr}
for {
curr = moves[curr]
if curr == source {
break
}
path = append(path, curr)
}
// reverse path
n := len(path)
for i := 0; i < n/2; i++ {
path[i], path[n-i-1] = path[n-i-1], path[i]
}
return path
}
func (p PathFinder) FindDistances() map[geom.Point]int {
source := p.state.Player.Pos
frontier := []geom.Point{source}
distances := map[geom.Point]int{source: 0}
for {
curr := frontier[0]
frontier = frontier[1:]
p.findBetterNeighbours(distances, curr, func(next geom.Point, newDistance int) {
frontier = append(frontier, next)
})
if len(frontier) == 0 {
return distances
}
}
}