Added solver & optimized state.
Positions are kept by index only.
This commit is contained in:
parent
6dfc9a5922
commit
d820376e8e
@ -16,6 +16,6 @@ type entityLoc struct {
|
|||||||
z float32
|
z float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEntity(e soko.Entity) *entity {
|
func newEntity(e soko.Entity, pos geom.Point) *entity {
|
||||||
return &entity{e.ID, e.Typ, entityLoc{e.Pos.ToF32(), 0}}
|
return &entity{e.ID, e.Typ, entityLoc{pos.ToF32(), 0}}
|
||||||
}
|
}
|
||||||
|
@ -233,12 +233,12 @@ func (l *playLevel) Render(ctx *alui.Context, bounds geom.RectangleF32) {
|
|||||||
font := ctx.Fonts.Get("default")
|
font := ctx.Fonts.Get("default")
|
||||||
|
|
||||||
if l.pathFinding {
|
if l.pathFinding {
|
||||||
dists := soko.NewPathFinder(l.state.state).FindDistances()
|
dists := soko.NewPathFinder(&l.state.state).FindDistances()
|
||||||
for i := range level.Tiles {
|
for i := range level.Tiles {
|
||||||
pos := geom.Pt(i%level.Width, i/level.Width)
|
pos := geom.Pt(i%level.Width, i/level.Width)
|
||||||
scr := entityLoc{pos.ToF32(), 0}
|
scr := entityLoc{pos.ToF32(), 0}
|
||||||
posDist := l.posToScreenF32(scr.pos, -20)
|
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]))
|
ctx.Fonts.DrawAlignFont(font, posDist.X, posDist.Y, posDist.X, ctx.Palette.Text, allg5.AlignCenter, strconv.Itoa(dists[i]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,8 +56,12 @@ func (s *playLevelState) Particles(at geom.Point) []particle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) FindSunkenBrickEntity(pos geom.Point) *entity {
|
func (s *playLevelState) FindSunkenBrickEntity(pos geom.Point) *entity {
|
||||||
id, ok := s.state.SunkenBricks[pos]
|
idx := s.level.PosToIdx(pos)
|
||||||
if ok {
|
if idx == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := s.state.SunkenBricks[idx]
|
||||||
|
if id != -1 {
|
||||||
return s.sunken[id]
|
return s.sunken[id]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -76,11 +80,16 @@ func (s *playLevelState) Init(ctx *Context, pack, level string, onComplete func(
|
|||||||
s.sunken = nil
|
s.sunken = nil
|
||||||
s.splash = map[geom.Point]*splashAnimation{}
|
s.splash = map[geom.Point]*splashAnimation{}
|
||||||
|
|
||||||
|
newEntity := func(e soko.Entity) *entity { return newEntity(e, s.state.IdxToPos[e.Pos]) }
|
||||||
|
|
||||||
s.player = newEntity(s.state.Player)
|
s.player = newEntity(s.state.Player)
|
||||||
s.egg = newEntity(s.state.Target)
|
s.egg = newEntity(s.state.Target)
|
||||||
s.bricks = entityMap{}
|
s.bricks = entityMap{}
|
||||||
for pos, id := range s.state.Bricks {
|
for _, e := range s.state.Entities {
|
||||||
s.bricks[id] = newEntity(soko.Entity{ID: id, Pos: pos, Typ: soko.EntityTypeBrick})
|
if e.Typ != soko.EntityTypeBrick {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s.bricks[e.ID] = newEntity(soko.Entity{ID: e.ID, Pos: e.Pos, Typ: soko.EntityTypeBrick})
|
||||||
}
|
}
|
||||||
s.sunken = entityMap{}
|
s.sunken = entityMap{}
|
||||||
s.keysDown = keyPressedState{}
|
s.keysDown = keyPressedState{}
|
||||||
@ -121,7 +130,8 @@ func (s *playLevelState) TryPlayerMove(key allg5.Key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *playLevelState) tryPlayerMove(dir soko.Direction) {
|
func (s *playLevelState) tryPlayerMove(dir soko.Direction) {
|
||||||
if s.player.scr.pos != s.state.Player.Pos.ToF32() {
|
playerPt := s.state.IdxToPos[s.state.Player.Pos]
|
||||||
|
if s.player.scr.pos != playerPt.ToF32() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,31 +142,33 @@ func (s *playLevelState) tryPlayerMove(dir soko.Direction) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
to := state.Player.Pos
|
to := state.Player.Pos
|
||||||
|
toPt := state.IdxToPos[to]
|
||||||
|
|
||||||
if brickID, ok := s.state.Bricks[to]; ok {
|
if brickID := s.state.Bricks[to]; brickID != -1 {
|
||||||
log.Printf("Brick %d moved", brickID)
|
log.Printf("Brick %d moved", brickID)
|
||||||
brick := s.bricks[brickID]
|
brick := s.bricks[brickID]
|
||||||
brickTo := state.Entities[brickID].Pos
|
brickTo := state.Entities.ByID(brickID).Pos
|
||||||
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(brick, to, brickTo), func() {
|
brickToPt := state.IdxToPos[brickTo]
|
||||||
|
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(brick, toPt, brickToPt), func() {
|
||||||
log.Printf("Brick movement finished")
|
log.Printf("Brick movement finished")
|
||||||
if sunkenBrickID, ok := state.SunkenBricks[brickTo]; ok && sunkenBrickID == brickID {
|
if sunkenBrickID := state.SunkenBricks[brickTo]; sunkenBrickID == brickID {
|
||||||
log.Printf("Sinking brick %d", brickID)
|
log.Printf("Sinking brick %d", brickID)
|
||||||
delete(s.bricks, brickID)
|
delete(s.bricks, brickID)
|
||||||
s.sunken[brickID] = brick
|
s.sunken[brickID] = brick
|
||||||
s.ani.Start(s.ctx.Tick, newSinkAnimation(brick))
|
s.ani.Start(s.ctx.Tick, newSinkAnimation(brick))
|
||||||
|
|
||||||
splash := newSplashAnimation(brickTo)
|
splash := newSplashAnimation(brickToPt)
|
||||||
s.splash[brickTo] = splash
|
s.splash[brickToPt] = splash
|
||||||
s.ani.StartFn(s.ctx.Tick, splash, func() {
|
s.ani.StartFn(s.ctx.Tick, splash, func() {
|
||||||
delete(s.splash, brickTo)
|
delete(s.splash, brickToPt)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
s.steps++
|
s.steps++
|
||||||
log.Printf("Moving player to %s", to)
|
log.Printf("Moving player to %s", toPt)
|
||||||
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(s.player, s.state.Player.Pos, to), func() {
|
s.ani.StartFn(s.ctx.Tick, newMoveAnimation(s.player, playerPt, toPt), func() {
|
||||||
log.Printf("Player movement finished")
|
log.Printf("Player movement finished")
|
||||||
if to == state.Target.Pos {
|
if to == state.Target.Pos {
|
||||||
s.complete = true
|
s.complete = true
|
||||||
|
@ -13,6 +13,20 @@ const (
|
|||||||
|
|
||||||
var Directions = [4]Direction{DirectionUp, DirectionRight, DirectionDown, DirectionLeft}
|
var Directions = [4]Direction{DirectionUp, DirectionRight, DirectionDown, DirectionLeft}
|
||||||
|
|
||||||
|
func (d Direction) Invert() Direction {
|
||||||
|
switch d {
|
||||||
|
case DirectionUp:
|
||||||
|
return DirectionDown
|
||||||
|
case DirectionRight:
|
||||||
|
return DirectionLeft
|
||||||
|
case DirectionDown:
|
||||||
|
return DirectionUp
|
||||||
|
case DirectionLeft:
|
||||||
|
return DirectionRight
|
||||||
|
}
|
||||||
|
panic("invalid direction")
|
||||||
|
}
|
||||||
|
|
||||||
func (d Direction) String() string {
|
func (d Direction) String() string {
|
||||||
switch d {
|
switch d {
|
||||||
case DirectionUp:
|
case DirectionUp:
|
||||||
|
@ -1,19 +1,48 @@
|
|||||||
package soko
|
package soko
|
||||||
|
|
||||||
import "opslag.de/schobers/geom"
|
|
||||||
|
|
||||||
type Entity struct {
|
type Entity struct {
|
||||||
ID int
|
ID int
|
||||||
Pos geom.Point
|
Pos int
|
||||||
Typ EntityType
|
Typ EntityType
|
||||||
}
|
}
|
||||||
|
|
||||||
type Entities map[int]Entity
|
type Entities []Entity
|
||||||
|
|
||||||
|
func (e Entities) ByID(id int) Entity {
|
||||||
|
idx := e.IdxById(id)
|
||||||
|
if idx == -1 {
|
||||||
|
return Entity{ID: -1}
|
||||||
|
}
|
||||||
|
return e[idx]
|
||||||
|
}
|
||||||
|
|
||||||
func (e Entities) Clone() Entities {
|
func (e Entities) Clone() Entities {
|
||||||
clone := Entities{}
|
clone := make(Entities, len(e))
|
||||||
for id, e := range e {
|
copy(clone, e)
|
||||||
clone[id] = e
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e Entities) IdxById(id int) int {
|
||||||
|
for i, e := range e {
|
||||||
|
if e.ID == id {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ints []int
|
||||||
|
|
||||||
|
func NewInts(n int) Ints {
|
||||||
|
ids := make(Ints, n)
|
||||||
|
for i := range ids {
|
||||||
|
ids[i] = -1
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Ints) Clone() Ints {
|
||||||
|
clone := make(Ints, len(e))
|
||||||
|
copy(clone, e)
|
||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
type Level struct {
|
type Level struct {
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
|
|
||||||
Tiles []TileType
|
Tiles []TileType
|
||||||
Entities []EntityType
|
Entities []EntityType
|
||||||
}
|
}
|
||||||
@ -20,28 +21,71 @@ func (l Level) PosToIdx(p geom.Point) int {
|
|||||||
return p.Y*l.Width + p.X
|
return p.Y*l.Width + p.X
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Level) State() State {
|
func (l Level) Moves() []Moves {
|
||||||
s := NewState()
|
moves := make([]Moves, len(l.Tiles))
|
||||||
for i, t := range l.Tiles {
|
w := l.Width
|
||||||
pos := l.IdxToPos(i)
|
for y := 0; y < l.Height; y++ {
|
||||||
switch t {
|
for x := 0; x < w; x++ {
|
||||||
case TileTypeBasic:
|
var m Moves
|
||||||
s.BasicTiles.Add(pos)
|
idx := y*w + x
|
||||||
case TileTypeMagma:
|
if y > 0 {
|
||||||
s.MagmaTiles.Add(pos)
|
m.Valid = append(m.Valid, idx-w)
|
||||||
|
m.All[DirectionUp] = idx - w
|
||||||
|
} else {
|
||||||
|
m.All[DirectionUp] = -1
|
||||||
|
}
|
||||||
|
if y < (l.Height - 1) {
|
||||||
|
m.Valid = append(m.Valid, idx+w)
|
||||||
|
m.All[DirectionDown] = idx + w
|
||||||
|
} else {
|
||||||
|
m.All[DirectionDown] = -1
|
||||||
|
}
|
||||||
|
if x > 0 {
|
||||||
|
m.Valid = append(m.Valid, idx-1)
|
||||||
|
m.All[DirectionLeft] = idx - 1
|
||||||
|
} else {
|
||||||
|
m.All[DirectionLeft] = idx - 1
|
||||||
|
}
|
||||||
|
if x < (w - 1) {
|
||||||
|
m.Valid = append(m.Valid, idx+1)
|
||||||
|
m.All[DirectionRight] = idx + 1
|
||||||
|
} else {
|
||||||
|
m.All[DirectionRight] = idx + 1
|
||||||
|
}
|
||||||
|
moves[idx] = m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return moves
|
||||||
for i, e := range l.Entities {
|
}
|
||||||
pos := l.IdxToPos(i)
|
|
||||||
switch e {
|
func (l Level) MoveIdx(idx int, dir Direction) int {
|
||||||
case EntityTypeBrick:
|
switch dir {
|
||||||
s.AddBrick(pos)
|
case DirectionUp:
|
||||||
case EntityTypePlayer:
|
if idx < l.Width {
|
||||||
s.SetPlayer(pos)
|
return -1
|
||||||
case EntityTypeTarget:
|
}
|
||||||
s.SetTarget(pos)
|
return idx - l.Width
|
||||||
}
|
case DirectionRight:
|
||||||
}
|
if (idx+1)%l.Width == 0 {
|
||||||
return s
|
return -1
|
||||||
|
}
|
||||||
|
return idx + 1
|
||||||
|
case DirectionDown:
|
||||||
|
if idx >= (l.Width*l.Height)-l.Width {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return idx + l.Width
|
||||||
|
case DirectionLeft:
|
||||||
|
if idx%l.Width == 0 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return idx - 1
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Level) Size() int { return l.Width * l.Height }
|
||||||
|
|
||||||
|
func (l Level) State() State {
|
||||||
|
return NewState(l)
|
||||||
}
|
}
|
||||||
|
6
soko/moves.go
Normal file
6
soko/moves.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
type Moves struct {
|
||||||
|
All [4]int
|
||||||
|
Valid []int
|
||||||
|
}
|
@ -2,69 +2,57 @@ package soko
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PathFinder struct {
|
type PathFinder struct {
|
||||||
state State
|
state *State
|
||||||
|
moves []Moves
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPathFinder(s State) PathFinder {
|
func NewPathFinder(s *State) PathFinder {
|
||||||
return PathFinder{s}
|
return PathFinder{s, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
type betterNeighbourFn func(geom.Point, int)
|
func NewPathFinderMoves(s *State, moves []Moves) PathFinder {
|
||||||
|
return PathFinder{s, moves}
|
||||||
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 {
|
func (p PathFinder) Find(target int) []int {
|
||||||
source := p.state.Player.Pos
|
source := p.state.Player.Pos
|
||||||
frontier := []geom.Point{source}
|
level := p.state.Level
|
||||||
distances := map[geom.Point]int{source: 0}
|
size := level.Size()
|
||||||
moves := map[geom.Point]geom.Point{source: source}
|
distances := NewInts(size)
|
||||||
|
distances[source] = 0
|
||||||
|
moves := NewInts(size)
|
||||||
|
moves[source] = source
|
||||||
|
state := p.newPathFinderState(source)
|
||||||
|
|
||||||
heuristic := func(i int) int {
|
heuristic := func(i int) int {
|
||||||
pos := frontier[i]
|
idx := state.frontier[i]
|
||||||
return distances[pos] + pos.DistInt(target)
|
return distances[idx] + p.state.IdxToPos[idx].DistInt(p.state.IdxToPos[target])
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
curr := frontier[0]
|
curr := state.frontier[0]
|
||||||
if curr == target {
|
if curr == target {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
frontier = frontier[1:]
|
state.frontier = state.frontier[1:]
|
||||||
|
|
||||||
p.findBetterNeighbours(distances, curr, func(next geom.Point, newDistance int) {
|
state.findBetterNeighbours(distances, curr, func(nextIdx, newDistance int) {
|
||||||
moves[next] = curr
|
moves[nextIdx] = curr
|
||||||
frontier = append(frontier, next)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if len(frontier) == 0 {
|
if len(state.frontier) == 0 {
|
||||||
return nil // no path
|
return nil // no path
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply heuristic to frontier (favor points closer to target)
|
// apply heuristic to frontier (favor points closer to target)
|
||||||
sort.Slice(frontier, func(i, j int) bool { return heuristic(i) < heuristic(j) })
|
sort.Slice(state.frontier, func(i, j int) bool { return heuristic(i) < heuristic(j) })
|
||||||
}
|
}
|
||||||
|
|
||||||
// build reverse path
|
// build reverse path
|
||||||
curr := target
|
curr := target
|
||||||
path := []geom.Point{curr}
|
path := []int{curr}
|
||||||
for {
|
for {
|
||||||
curr = moves[curr]
|
curr = moves[curr]
|
||||||
if curr == source {
|
if curr == source {
|
||||||
@ -80,20 +68,66 @@ func (p PathFinder) Find(target geom.Point) []geom.Point {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p PathFinder) FindDistances() map[geom.Point]int {
|
func (p PathFinder) FindDistances() Ints {
|
||||||
source := p.state.Player.Pos
|
source := p.state.Player.Pos
|
||||||
frontier := []geom.Point{source}
|
level := p.state.Level
|
||||||
distances := map[geom.Point]int{source: 0}
|
size := level.Size()
|
||||||
|
distances := NewInts(size)
|
||||||
|
distances[source] = 0
|
||||||
|
state := p.newPathFinderState(source)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
curr := frontier[0]
|
curr := state.frontier[0]
|
||||||
frontier = frontier[1:]
|
state.frontier = state.frontier[1:]
|
||||||
|
|
||||||
p.findBetterNeighbours(distances, curr, func(next geom.Point, newDistance int) {
|
state.findBetterNeighbours(distances, curr, nil)
|
||||||
frontier = append(frontier, next)
|
|
||||||
})
|
|
||||||
|
|
||||||
if len(frontier) == 0 {
|
if len(state.frontier) == 0 {
|
||||||
return distances
|
return distances
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p PathFinder) newPathFinderState(source int) *pathFinderState {
|
||||||
|
return &pathFinderState{p.state, append(make([]int, 0, p.state.Level.Size()), source), p.moves}
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathFinderState struct {
|
||||||
|
state *State
|
||||||
|
frontier []int
|
||||||
|
moves []Moves
|
||||||
|
}
|
||||||
|
|
||||||
|
type betterNeighbourFn func(int, int)
|
||||||
|
|
||||||
|
func (s *pathFinderState) findBetterNeighbours(distances []int, curr int, better betterNeighbourFn) {
|
||||||
|
currDistance := distances[curr]
|
||||||
|
newDistance := currDistance + 1
|
||||||
|
|
||||||
|
var moves []int
|
||||||
|
if s.moves == nil {
|
||||||
|
for _, dir := range Directions {
|
||||||
|
nextIdx := s.state.Level.MoveIdx(curr, dir)
|
||||||
|
if nextIdx == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
moves = append(moves, nextIdx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
moves = s.moves[curr].Valid
|
||||||
|
}
|
||||||
|
for _, nextIdx := range moves {
|
||||||
|
if distance := distances[nextIdx]; distance != -1 && distance <= newDistance { // skip when shorter path exists
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !s.state.Walkable[nextIdx] || s.state.Bricks[nextIdx] != -1 { // filter neighbours
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
distances[nextIdx] = newDistance
|
||||||
|
s.frontier = append(s.frontier, nextIdx)
|
||||||
|
if better == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
better(nextIdx, newDistance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
94
soko/solver.go
Normal file
94
soko/solver.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
func Solve(l Level) int {
|
||||||
|
state := l.State()
|
||||||
|
return solve(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func solve(state State) int {
|
||||||
|
level := state.Level
|
||||||
|
target := state.Target.Pos
|
||||||
|
costs := &stateCostQueue{}
|
||||||
|
costs.Put(&stateCost{&state, 0, nil})
|
||||||
|
costsByPlayerIdx := make([][]*stateCost, level.Size())
|
||||||
|
findCost := func(s *State) *stateCost {
|
||||||
|
costs := costsByPlayerIdx[s.Player.Pos]
|
||||||
|
for i := 0; i < len(costs); i++ {
|
||||||
|
if !costs[i].state.Equal(s) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return costs[i]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
addCost := func(next *State, nextCost int) {
|
||||||
|
cost := findCost(next)
|
||||||
|
if cost == nil {
|
||||||
|
newCost := &stateCost{next, nextCost, nil}
|
||||||
|
costs.Put(newCost)
|
||||||
|
player := next.Player.Pos
|
||||||
|
costsByPlayerIdx[player] = append(costsByPlayerIdx[player], newCost)
|
||||||
|
} else if nextCost < cost.cost {
|
||||||
|
cost.cost = nextCost
|
||||||
|
costs.Update(cost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
moves := level.Moves()
|
||||||
|
type direction struct {
|
||||||
|
dir Direction
|
||||||
|
inverse Direction
|
||||||
|
}
|
||||||
|
dirs := make([]direction, 4)
|
||||||
|
for _, dir := range Directions {
|
||||||
|
dirs[dir] = direction{dir, dir.Invert()}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
curr := costs.Get()
|
||||||
|
currPlayerPos := curr.state.Player.Pos
|
||||||
|
if currPlayerPos == target {
|
||||||
|
return curr.cost
|
||||||
|
}
|
||||||
|
|
||||||
|
if curr.distances == nil {
|
||||||
|
curr.distances = NewPathFinderMoves(curr.state, moves).FindDistances()
|
||||||
|
}
|
||||||
|
distances := curr.distances
|
||||||
|
for _, e := range curr.state.Entities {
|
||||||
|
idx := e.Pos
|
||||||
|
if e.Typ != EntityTypeBrick || curr.state.SunkenBricks[idx] == e.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
player := moves[e.Pos].All[dirs[i].inverse]
|
||||||
|
if player == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
walk := distances[player]
|
||||||
|
if walk == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !curr.state.IsOpenForBrick(curr.state.IdxToPos[moves[e.Pos].All[i]]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
curr.state.Player.Pos = player
|
||||||
|
next, ok := curr.state.MovePlayer(dirs[i].dir)
|
||||||
|
if !ok {
|
||||||
|
panic("should be a valid move")
|
||||||
|
}
|
||||||
|
nextCost := curr.cost + walk + 1
|
||||||
|
addCost(&next, nextCost)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
curr.state.Player.Pos = currPlayerPos
|
||||||
|
if walk := distances[target]; walk != -1 {
|
||||||
|
next := curr.state.Clone()
|
||||||
|
next.Player.Pos = target
|
||||||
|
nextCost := curr.cost + walk
|
||||||
|
addCost(&next, nextCost)
|
||||||
|
}
|
||||||
|
|
||||||
|
if costs.Empty() {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
soko/solver_test.go
Normal file
111
soko/solver_test.go
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var levelBenchmark = `level:
|
||||||
|
._._._._._._._._._._._._._._._._._._._._
|
||||||
|
._._._._._#_#_#_._._._._._._._._._._._._
|
||||||
|
._._._._._#_#_#_._._._._._._._._._._._._
|
||||||
|
._._._._._#_#_#_._._._._._._._._._._._._
|
||||||
|
._._._#_#_#_#_#_#_._._._._._._._._._._._
|
||||||
|
._._._#_._#_._._#_._._._._#X#_#_~_#_#_._
|
||||||
|
._#_#_#_._#_._._#_._._._._._._._~_#_#_._
|
||||||
|
._#_#B#_#_#B#_#_#_#_#_#_#_#_#_#_#_#_#_._
|
||||||
|
._._._._._#_._._._#_._#@._._._._#_#_._._
|
||||||
|
._._._._._#_#_#_#_#_._._._._._._._._._._
|
||||||
|
._._._._._._._._._._._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
var levelEasy = `level:
|
||||||
|
._._._._._._._._._._
|
||||||
|
._#@#_#_#B#_~_#_#X._
|
||||||
|
._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
var level1 = `level:
|
||||||
|
._._._._._._._._._._
|
||||||
|
._#_#_#_#_~_~_~_#_._
|
||||||
|
._#_#_._#B~_~_#_#_._
|
||||||
|
._#_#_#_#_~_~_~_#_._
|
||||||
|
._#_#_._#B~_~_#_#_._
|
||||||
|
._#@#_#_#_~_~_#_#X._
|
||||||
|
._#_#_~_~_~_#_#_#_._
|
||||||
|
._#_#_~_~_~_#_#_#_._
|
||||||
|
._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
var level2 = `level:
|
||||||
|
._._._._._._._._._._._._._._._._._._._._
|
||||||
|
._._._._._#_#_#_._._._._._._._._._._._._
|
||||||
|
._._._._._#B#_#_._._._._._._._._._._._._
|
||||||
|
._._._._._#_#_#B._._._._._._._._._._._._
|
||||||
|
._._._#_#_#B#_#B#_._._._._._._._._._._._
|
||||||
|
._._._#_._#_._._#_._._._._#X#_~_~_~_#_._
|
||||||
|
._#_#_#_._#_._._#_._._._._._._._~_~_#_._
|
||||||
|
._#_#B#_#_#B#_#_#_#_#_#_#_#_#_#_~_#_#_._
|
||||||
|
._._._._._#_._._._#_._#@._._._._#_#_._._
|
||||||
|
._._._._._#_#_#_#_#_._._._._._._._._._._
|
||||||
|
._._._._._._._._._._._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
var level3 = `level:
|
||||||
|
._._._._._._._._._._
|
||||||
|
._._._._._._._._._._
|
||||||
|
._~_#_#_#_#B#_._._._
|
||||||
|
._~_#B#_#_._#_._._._
|
||||||
|
._~_._#_~_#_#_._._._
|
||||||
|
._#X._#_~_#_._._._._
|
||||||
|
._._#_#B#_#_._._._._
|
||||||
|
._._#@#_._._._._._._
|
||||||
|
._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
var level4 = `level:
|
||||||
|
._._._._._._._._._._._._
|
||||||
|
._._#_#_._._._._._._._._
|
||||||
|
._._#_#B#_#B#@#_#_._._._
|
||||||
|
._._#_#_#_#_#B#_#_._._._
|
||||||
|
._._._._._._#_._._._._._
|
||||||
|
._._._._._._#_._._._._._
|
||||||
|
._._._._._._#B._._._._._
|
||||||
|
._._._#_#_._#_._#_#_._._
|
||||||
|
._#_#_#_#_~_#_#_#_#_#_._
|
||||||
|
._#_._._#_._#_._#_._#_._
|
||||||
|
._#_._#X~_._~_._~_._#_._
|
||||||
|
._#_._._#_._#_._#_._#_._
|
||||||
|
._#_#_#_#_#_~_#_#_#_#_._
|
||||||
|
._._._._._._._._._._._._
|
||||||
|
:level`
|
||||||
|
|
||||||
|
func mustParseLevel(s string) Level {
|
||||||
|
l, err := ParseLevel(bytes.NewBufferString(s))
|
||||||
|
if err != nil {
|
||||||
|
panic("couldn't parse level")
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolver(t *testing.T) {
|
||||||
|
solve := func(s string) int {
|
||||||
|
l := mustParseLevel(s)
|
||||||
|
steps := Solve(l)
|
||||||
|
return steps
|
||||||
|
}
|
||||||
|
assert.Equal(t, 7, solve(levelEasy))
|
||||||
|
assert.Equal(t, 35, solve(level1))
|
||||||
|
// assert.Equal(t, -1, solve(level2))
|
||||||
|
assert.Equal(t, 49, solve(level3))
|
||||||
|
// assert.Equal(t, -1, solve(level4))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSolver(b *testing.B) {
|
||||||
|
l := mustParseLevel(levelBenchmark)
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
Solve(l)
|
||||||
|
}
|
||||||
|
}
|
212
soko/state.go
212
soko/state.go
@ -6,25 +6,48 @@ type State struct {
|
|||||||
Player Entity
|
Player Entity
|
||||||
Target Entity
|
Target Entity
|
||||||
|
|
||||||
BasicTiles Locations
|
|
||||||
MagmaTiles Locations
|
|
||||||
|
|
||||||
Bricks EntityLocations
|
|
||||||
SunkenBricks EntityLocations
|
|
||||||
|
|
||||||
Entities Entities
|
Entities Entities
|
||||||
|
|
||||||
|
Level Level
|
||||||
|
IdxToPos []geom.Point
|
||||||
|
Bricks Ints
|
||||||
|
SunkenBricks Ints
|
||||||
|
Walkable []bool
|
||||||
|
|
||||||
nextEntityID int
|
nextEntityID int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewState() State {
|
func NewState(l Level) State {
|
||||||
return State{
|
size := l.Size()
|
||||||
BasicTiles: Locations{},
|
s := State{
|
||||||
MagmaTiles: Locations{},
|
|
||||||
Bricks: EntityLocations{},
|
|
||||||
SunkenBricks: EntityLocations{},
|
|
||||||
Entities: Entities{},
|
Entities: Entities{},
|
||||||
|
Level: l,
|
||||||
|
IdxToPos: make([]geom.Point, size),
|
||||||
|
Bricks: NewInts(size),
|
||||||
|
SunkenBricks: NewInts(size),
|
||||||
|
Walkable: make([]bool, size),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for idx, t := range l.Tiles {
|
||||||
|
switch t {
|
||||||
|
case TileTypeBasic:
|
||||||
|
s.Walkable[idx] = true
|
||||||
|
}
|
||||||
|
s.IdxToPos[idx] = s.Level.IdxToPos(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, e := range l.Entities {
|
||||||
|
pos := s.IdxToPos[idx]
|
||||||
|
switch e {
|
||||||
|
case EntityTypeBrick:
|
||||||
|
s.addBrick(pos)
|
||||||
|
case EntityTypePlayer:
|
||||||
|
s.initPlayer(pos)
|
||||||
|
case EntityTypeTarget:
|
||||||
|
s.initTarget(pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone creates and returns a clone of the state.
|
// Clone creates and returns a clone of the state.
|
||||||
@ -32,21 +55,16 @@ func (s State) Clone() State {
|
|||||||
return State{
|
return State{
|
||||||
Player: s.Player,
|
Player: s.Player,
|
||||||
Target: s.Target,
|
Target: s.Target,
|
||||||
BasicTiles: s.BasicTiles.Clone(),
|
Entities: s.Entities.Clone(),
|
||||||
MagmaTiles: s.MagmaTiles.Clone(),
|
Level: s.Level,
|
||||||
|
IdxToPos: s.IdxToPos,
|
||||||
Bricks: s.Bricks.Clone(),
|
Bricks: s.Bricks.Clone(),
|
||||||
SunkenBricks: s.SunkenBricks.Clone(),
|
SunkenBricks: s.SunkenBricks.Clone(),
|
||||||
Entities: s.Entities.Clone(),
|
Walkable: append(s.Walkable[:0:0], s.Walkable...),
|
||||||
nextEntityID: s.nextEntityID,
|
nextEntityID: s.nextEntityID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) AddBrick(pos geom.Point) {
|
|
||||||
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
|
||||||
s.Bricks.Add(pos, e.ID)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s State) All(pred func(State, geom.Point) bool, p ...geom.Point) bool {
|
func (s State) All(pred func(State, geom.Point) bool, p ...geom.Point) bool {
|
||||||
for _, p := range p {
|
for _, p := range p {
|
||||||
if !pred(s, p) {
|
if !pred(s, p) {
|
||||||
@ -65,50 +83,98 @@ func (s State) Any(pred func(State, geom.Point) bool, p ...geom.Point) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s State) IsOpenForBrick(p geom.Point) bool {
|
func (s *State) Equal(other *State) bool {
|
||||||
if s.IsWalkable(p) {
|
for i := 0; i < len(s.Entities); i++ {
|
||||||
return !s.Bricks.Has(p)
|
if other.Entities[i].Pos != s.Entities[i].Pos {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return s.MagmaTiles[p]
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWalkable indicates that a player could walk over this tile regardless of any bricks that might be there.
|
func (s State) IsOpenForBrick(p geom.Point) bool {
|
||||||
|
idx := s.Level.PosToIdx(p)
|
||||||
|
if idx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.isOpenForBrick(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWalkable indicates that a player could walk over this tile regardless of any Bricks that might be there.
|
||||||
func (s State) IsWalkable(p geom.Point) bool {
|
func (s State) IsWalkable(p geom.Point) bool {
|
||||||
return s.BasicTiles[p] || (s.MagmaTiles[p] && s.SunkenBricks.Has(p))
|
idx := s.Level.PosToIdx(p)
|
||||||
|
if idx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Walkable[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsWalkableAndFree indicates that a player can walk over this tile (no brick is at the tile).
|
||||||
|
func (s State) IsWalkableAndFree(p geom.Point) bool {
|
||||||
|
idx := s.Level.PosToIdx(p)
|
||||||
|
if idx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Walkable[idx] && s.Bricks[idx] == -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// MovePlayer tries to move the player in the specified direction. Returns the new state and true if the move is valid. Returns the current state and false is the move was invalid.
|
// MovePlayer tries to move the player in the specified direction. Returns the new state and true if the move is valid. Returns the current state and false is the move was invalid.
|
||||||
func (s State) MovePlayer(dir Direction) (State, bool) {
|
func (s State) MovePlayer(dir Direction) (State, bool) {
|
||||||
to := s.Player.Pos.Add(dir.ToPoint())
|
to := s.Level.MoveIdx(s.Player.Pos, dir)
|
||||||
|
if to == -1 {
|
||||||
if !s.IsWalkable(to) {
|
|
||||||
return s, false
|
return s, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.Bricks.Has(to) {
|
if !s.Walkable[to] {
|
||||||
return s.Mutate(func(s *State) {
|
|
||||||
s.movePlayer(to)
|
|
||||||
}), true
|
|
||||||
}
|
|
||||||
|
|
||||||
brickTo := to.Add(dir.ToPoint())
|
|
||||||
if !s.IsOpenForBrick(brickTo) {
|
|
||||||
return s, false
|
return s, false
|
||||||
}
|
}
|
||||||
|
|
||||||
brickID := s.Bricks[to]
|
brickID := s.Bricks[to]
|
||||||
|
if brickID == -1 {
|
||||||
|
return s.Mutate(func(s *State) {
|
||||||
|
s.movePlayer(to)
|
||||||
|
}), true
|
||||||
|
}
|
||||||
|
|
||||||
|
brickTo := s.Level.MoveIdx(to, dir)
|
||||||
|
if brickTo == -1 {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
if !s.isOpenForBrick(brickTo) {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
return s.Mutate(func(s *State) {
|
return s.Mutate(func(s *State) {
|
||||||
s.movePlayer(to)
|
s.movePlayer(to)
|
||||||
|
|
||||||
if s.MagmaTiles[brickTo] && !s.SunkenBricks.Has(brickTo) {
|
if s.Level.Tiles[brickTo] == TileTypeMagma && s.SunkenBricks[brickTo] == -1 {
|
||||||
s.Bricks.Remove(to)
|
s.SunkenBricks[brickTo] = brickID
|
||||||
s.SunkenBricks.Add(brickTo, brickID)
|
s.Walkable[brickTo] = true
|
||||||
|
s.Bricks[to] = -1
|
||||||
} else {
|
} else {
|
||||||
s.Bricks.Move(to, brickTo)
|
s.Bricks[brickTo] = brickID
|
||||||
|
s.Bricks[to] = -1
|
||||||
}
|
}
|
||||||
s.Entities[brickID] = Entity{brickID, brickTo, EntityTypeBrick}
|
s.updateEntity(brickID, Entity{brickID, brickTo, EntityTypeBrick})
|
||||||
}), true
|
}), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s State) SetPlayer(to geom.Point) (State, bool) {
|
||||||
|
toIdx := s.Level.PosToIdx(to)
|
||||||
|
if toIdx == -1 {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
if !s.Walkable[toIdx] {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
if s.Bricks[toIdx] != -1 {
|
||||||
|
return s, false
|
||||||
|
}
|
||||||
|
return s.Mutate(func(s *State) {
|
||||||
|
s.movePlayer(toIdx)
|
||||||
|
}), true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Mutate clones the state, applies the mutation on the clone and returns the clone.
|
// Mutate clones the state, applies the mutation on the clone and returns the clone.
|
||||||
func (s State) Mutate(fn func(s *State)) State {
|
func (s State) Mutate(fn func(s *State)) State {
|
||||||
clone := s.Clone()
|
clone := s.Clone()
|
||||||
@ -116,29 +182,59 @@ func (s State) Mutate(fn func(s *State)) State {
|
|||||||
return clone
|
return clone
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) SetPlayer(pos geom.Point) {
|
func (s *State) addBrick(pos geom.Point) {
|
||||||
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
s.addEntity(pos, EntityTypeBrick, func(e Entity) {
|
||||||
s.Player = e
|
idx := s.Level.PosToIdx(pos)
|
||||||
})
|
if idx != -1 {
|
||||||
}
|
s.Bricks[idx] = e.ID
|
||||||
|
}
|
||||||
func (s *State) SetTarget(pos geom.Point) {
|
|
||||||
s.addEntity(pos, EntityTypeTarget, func(e Entity) {
|
|
||||||
s.Target = e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
func (s *State) addEntity(pos geom.Point, typ EntityType, add func(Entity)) {
|
||||||
id := s.nextEntityID
|
id := s.nextEntityID
|
||||||
e := Entity{id, pos, typ}
|
e := Entity{id, s.Level.PosToIdx(pos), typ}
|
||||||
add(e)
|
add(e)
|
||||||
s.Entities[id] = e
|
s.Entities = append(s.Entities, e)
|
||||||
s.nextEntityID++
|
s.nextEntityID++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) movePlayer(to geom.Point) {
|
func (s *State) initPlayer(pos geom.Point) {
|
||||||
s.Player.Pos = to
|
s.addEntity(pos, EntityTypePlayer, func(e Entity) {
|
||||||
s.Entities[s.Player.ID] = Entity{s.Player.ID, to, EntityTypePlayer}
|
s.Player = e
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsMagma(s State, p geom.Point) bool { return s.MagmaTiles[p] }
|
func (s *State) initTarget(pos geom.Point) {
|
||||||
|
s.addEntity(pos, EntityTypeTarget, func(e Entity) {
|
||||||
|
s.Target = e
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s State) isOpenForBrick(idx int) bool {
|
||||||
|
if s.Walkable[idx] {
|
||||||
|
return s.Bricks[idx] == -1
|
||||||
|
}
|
||||||
|
return s.Level.Tiles[idx] == TileTypeMagma
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) updateEntity(id int, e Entity) {
|
||||||
|
idx := s.Entities.IdxById(id)
|
||||||
|
if idx == -1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.Entities[idx] = e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) movePlayer(to int) {
|
||||||
|
s.Player.Pos = to
|
||||||
|
s.updateEntity(s.Player.ID, Entity{s.Player.ID, to, EntityTypePlayer})
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsMagma(s State, p geom.Point) bool {
|
||||||
|
idx := s.Level.PosToIdx(p)
|
||||||
|
if idx == -1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return s.Level.Tiles[idx] == TileTypeMagma
|
||||||
|
}
|
||||||
|
71
soko/statecost.go
Normal file
71
soko/statecost.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package soko
|
||||||
|
|
||||||
|
type stateCost struct {
|
||||||
|
state *State
|
||||||
|
cost int
|
||||||
|
distances Ints
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateCostQueue struct {
|
||||||
|
first *stateCostQueueItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateCostQueueItem struct {
|
||||||
|
Value *stateCost
|
||||||
|
Next *stateCostQueueItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *stateCostQueue) Empty() bool {
|
||||||
|
return q.first == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *stateCostQueue) Get() *stateCost {
|
||||||
|
if q.Empty() {
|
||||||
|
panic("nothing in queue")
|
||||||
|
}
|
||||||
|
first := q.first
|
||||||
|
q.first = first.Next
|
||||||
|
return first.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *stateCostQueue) Put(value *stateCost) {
|
||||||
|
priority := value.cost
|
||||||
|
item := &stateCostQueueItem{value, nil}
|
||||||
|
if q.Empty() {
|
||||||
|
q.first = item
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if priority < q.first.Value.cost {
|
||||||
|
item.Next = q.first
|
||||||
|
q.first = item
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curr := q.first
|
||||||
|
for curr.Next != nil {
|
||||||
|
if priority < curr.Next.Value.cost {
|
||||||
|
item.Next = curr.Next
|
||||||
|
curr.Next = item
|
||||||
|
return
|
||||||
|
}
|
||||||
|
curr = curr.Next
|
||||||
|
}
|
||||||
|
curr.Next = item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *stateCostQueue) Update(value *stateCost) {
|
||||||
|
var prev *stateCostQueueItem
|
||||||
|
curr := q.first
|
||||||
|
for curr.Value != value {
|
||||||
|
prev = curr
|
||||||
|
curr = curr.Next
|
||||||
|
if curr == nil {
|
||||||
|
panic("not in queue")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if prev == nil {
|
||||||
|
q.first = curr.Next
|
||||||
|
} else {
|
||||||
|
prev.Next = curr.Next
|
||||||
|
}
|
||||||
|
q.Put(value)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user