Added path finder & visualization (F5).
This commit is contained in:
parent
7634e632fb
commit
6dfc9a5922
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
@ -25,7 +26,8 @@ type playLevel struct {
|
|||||||
offset geom.PointF32
|
offset geom.PointF32
|
||||||
scale float32
|
scale float32
|
||||||
|
|
||||||
state playLevelState
|
state playLevelState
|
||||||
|
pathFinding bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type keyPressedState map[allg5.Key]bool
|
type keyPressedState map[allg5.Key]bool
|
||||||
@ -166,6 +168,8 @@ func (l *playLevel) Handle(e allg5.Event) {
|
|||||||
case allg5.KeyEscape:
|
case allg5.KeyEscape:
|
||||||
l.showMenu = true
|
l.showMenu = true
|
||||||
l.menu.Activate(0)
|
l.menu.Activate(0)
|
||||||
|
case allg5.KeyF5:
|
||||||
|
l.pathFinding = !l.pathFinding
|
||||||
}
|
}
|
||||||
l.state.TryPlayerMove(e.KeyCode)
|
l.state.TryPlayerMove(e.KeyCode)
|
||||||
}
|
}
|
||||||
@ -227,6 +231,17 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
font := ctx.Fonts.Get("default")
|
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())
|
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)
|
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) {
|
func (s *State) AddBrick(pos geom.Point) {
|
||||||
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
||||||
s.Bricks.Add(pos, e.ID)
|
s.Bricks.Add(pos, e.ID)
|
||||||
@ -124,11 +116,6 @@ func (s State) Mutate(fn func(s *State)) State {
|
|||||||
return clone
|
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) {
|
func (s *State) SetPlayer(pos geom.Point) {
|
||||||
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
||||||
s.Player = e
|
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] }
|
func IsMagma(s State, p geom.Point) bool { return s.MagmaTiles[p] }
|
||||||
|
Loading…
Reference in New Issue
Block a user