Added path finder & visualization (F5).

This commit is contained in:
Sander Schobers 2019-01-15 21:49:01 +01:00
parent 7634e632fb
commit 6dfc9a5922
3 changed files with 128 additions and 14 deletions

View File

@ -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"
@ -26,6 +27,7 @@ type playLevel struct {
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
View 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
}
}
}

View File

@ -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] }