Added path finder & visualization (F5).
This commit is contained in:
parent
7634e632fb
commit
6dfc9a5922
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
@ -26,6 +27,7 @@ type playLevel struct {
|
||||
scale float32
|
||||
|
||||
state playLevelState
|
||||
pathFinding bool
|
||||
}
|
||||
|
||||
type keyPressedState map[allg5.Key]bool
|
||||
@ -166,6 +168,8 @@ func (l *playLevel) Handle(e allg5.Event) {
|
||||
case allg5.KeyEscape:
|
||||
l.showMenu = true
|
||||
l.menu.Activate(0)
|
||||
case allg5.KeyF5:
|
||||
l.pathFinding = !l.pathFinding
|
||||
}
|
||||
l.state.TryPlayerMove(e.KeyCode)
|
||||
}
|
||||
@ -227,6 +231,17 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
||||
}
|
||||
|
||||
font := ctx.Fonts.Get("default")
|
||||
|
||||
if l.pathFinding {
|
||||
dists := soko.NewPathFinder(l.state.state).FindDistances()
|
||||
for i := range level.Tiles {
|
||||
pos := geom.Pt(i%level.Width, i/level.Width)
|
||||
scr := entityLoc{pos.ToF32(), 0}
|
||||
posDist := l.posToScreenF32(scr.pos, -20)
|
||||
ctx.Fonts.DrawAlignFont(font, posDist.X, posDist.Y, posDist.X, ctx.Palette.Text, allg5.AlignCenter, strconv.Itoa(dists[pos]))
|
||||
}
|
||||
}
|
||||
|
||||
steps := fmt.Sprintf("STEPS: %d", l.state.Steps())
|
||||
ctx.Fonts.DrawAlignFont(font, bounds.Min.X, 24, bounds.Max.X, ctx.Palette.Text, allg5.AlignCenter, steps)
|
||||
|
||||
|
99
soko/pathfinder.go
Normal file
99
soko/pathfinder.go
Normal file
@ -0,0 +1,99 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -41,14 +41,6 @@ func (s State) Clone() State {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
||||
id := s.nextEntityID
|
||||
e := Entity{id, pos, typ}
|
||||
add(e)
|
||||
s.Entities[id] = e
|
||||
s.nextEntityID++
|
||||
}
|
||||
|
||||
func (s *State) AddBrick(pos geom.Point) {
|
||||
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
||||
s.Bricks.Add(pos, e.ID)
|
||||
@ -124,11 +116,6 @@ func (s State) Mutate(fn func(s *State)) State {
|
||||
return clone
|
||||
}
|
||||
|
||||
func (s *State) movePlayer(to geom.Point) {
|
||||
s.Player.Pos = to
|
||||
s.Entities[s.Player.ID] = Entity{s.Player.ID, to, EntityTypePlayer}
|
||||
}
|
||||
|
||||
func (s *State) SetPlayer(pos geom.Point) {
|
||||
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
||||
s.Player = e
|
||||
@ -141,4 +128,17 @@ func (s *State) SetTarget(pos geom.Point) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
||||
id := s.nextEntityID
|
||||
e := Entity{id, pos, typ}
|
||||
add(e)
|
||||
s.Entities[id] = e
|
||||
s.nextEntityID++
|
||||
}
|
||||
|
||||
func (s *State) movePlayer(to geom.Point) {
|
||||
s.Player.Pos = to
|
||||
s.Entities[s.Player.ID] = Entity{s.Player.ID, to, EntityTypePlayer}
|
||||
}
|
||||
|
||||
func IsMagma(s State, p geom.Point) bool { return s.MagmaTiles[p] }
|
||||
|
Loading…
Reference in New Issue
Block a user