fixed lint errors in d2astar (#533)

* fixed lint errors in d2astar

* Update astar.go
This commit is contained in:
dk 2020-07-03 15:33:14 -07:00 committed by GitHub
parent 62b8a610c0
commit 18003a8543
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 64 additions and 26 deletions

View File

@ -76,15 +76,16 @@ func (nm nodeMap) get(p Pather) *node {
n.pather = p n.pather = p
nm[p] = n nm[p] = n
} }
return n return n
} }
// Path calculates a short path and the distance between the two Pather nodes. // Path calculates a short path and the distance between the two Pather nodes.
//
// If no path is found, found will be false and path will be the closest node to the target with a valid path. // If no path is found, found will be false and path will be the closest node to the target with a valid path.
func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, found bool) { func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, found bool) {
nm := nodeMapPool.Get().(nodeMap) nm := nodeMapPool.Get().(nodeMap)
nq := priorityQueuePool.Get().(priorityQueue) nq := priorityQueuePool.Get().(priorityQueue)
defer func() { defer func() {
for k, v := range nm { for k, v := range nm {
v.reset() v.reset()
@ -93,7 +94,9 @@ func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, fo
} }
nq = nq[0:0] nq = nq[0:0]
nodeMapPool.Put(nm) nodeMapPool.Put(nm)
priorityQueuePool.Put(nq) priorityQueuePool.Put(nq)
}() }()
@ -106,7 +109,9 @@ func Path(from, to Pather, maxCost float64) (path []Pather, distance float64, fo
// There's no path, fallback to closest. // There's no path, fallback to closest.
break break
} }
current := heap.Pop(&nq).(*node) current := heap.Pop(&nq).(*node)
current.open = false current.open = false
current.closed = true current.closed = true

2
d2common/d2astar/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package d2astar provides an a* pathing algorithm
package d2astar

View File

@ -15,18 +15,22 @@ package d2astar
// The key differences between this example and the Tile world: // The key differences between this example and the Tile world:
// 1) There is no grid. Trucks have arbitrary coordinates. // 1) There is no grid. Trucks have arbitrary coordinates.
// 2) Edges are not implied by the grid positions. Instead edges are explicitly // 2) Edges are not implied by the grid positions. Instead edges are explicitly
// modelled as Tubes. // modeled as Tubes.
// //
// The key similarities between this example and the Tile world: // The key similarities between this example and the Tile world:
// 1) They both use Manhattan distance as their heuristic // 1) They both use Manhattan distance as their heuristic
// 2) Both implement Pather // 2) Both implement Pather
//Goreland represents a world of trucks and tubes. // Goreland represents a world of trucks and tubes.
type Goreland struct { type Goreland struct {
// trucks map[int]*Truck // not needed really // trucks map[int]*Truck // not needed really
} }
//Tube is an edge. They connect Trucks, and have a cost. const (
_tenFuckingMillion = 10000000
)
// Tube is an edge. They connect Trucks, and have a cost.
type Tube struct { type Tube struct {
from *Truck from *Truck
to *Truck to *Truck
@ -47,39 +51,41 @@ type Truck struct {
// PathNeighbors returns the neighbors of the Truck // PathNeighbors returns the neighbors of the Truck
func (t *Truck) PathNeighbors() []Pather { func (t *Truck) PathNeighbors() []Pather {
neighbors := []Pather{} neighbors := []Pather{}
for _, tubeElement := range t.outTo { for _, tubeElement := range t.outTo {
neighbors = append(neighbors, Pather(tubeElement.to)) neighbors = append(neighbors, Pather(tubeElement.to))
} }
return neighbors return neighbors
} }
// PathNeighborCost returns the cost of the tube leading to Truck. // PathNeighborCost returns the cost of the tube leading to Truck.
func (t *Truck) PathNeighborCost(to Pather) float64 { func (t *Truck) PathNeighborCost(to Pather) float64 {
for _, tubeElement := range (t).outTo { for _, tubeElement := range (t).outTo {
if Pather((tubeElement.to)) == to { if Pather((tubeElement.to)) == to {
return tubeElement.Cost return tubeElement.Cost
} }
} }
return 10000000
return _tenFuckingMillion
} }
// PathEstimatedCost uses Manhattan distance to estimate orthogonal distance // PathEstimatedCost uses Manhattan distance to estimate orthogonal distance
// between non-adjacent nodes. // between non-adjacent nodes.
func (t *Truck) PathEstimatedCost(to Pather) float64 { func (t *Truck) PathEstimatedCost(to Pather) float64 {
toT := to.(*Truck) toT := to.(*Truck)
absX := toT.X - t.X absX := toT.X - t.X
if absX < 0 { if absX < 0 {
absX = -absX absX = -absX
} }
absY := toT.Y - t.Y absY := toT.Y - t.Y
if absY < 0 { if absY < 0 {
absY = -absY absY = -absY
} }
r := float64(absX + absY) r := float64(absX + absY)
return r return r
@ -87,11 +93,12 @@ func (t *Truck) PathEstimatedCost(to Pather) float64 {
// RenderPath renders a path on top of a Goreland world. // RenderPath renders a path on top of a Goreland world.
func (w Goreland) RenderPath(path []Pather) string { func (w Goreland) RenderPath(path []Pather) string {
s := "" s := ""
for _, p := range path { for _, p := range path {
pT := p.(*Truck) pT := p.(*Truck)
s = pT.label + " " + s s = pT.label + " " + s
} }
return s return s
} }

View File

@ -5,15 +5,16 @@ import (
"testing" "testing"
) )
func AddTruck(x int, y int, label string) *Truck { func addTruck(x, y int, label string) *Truck {
t1 := new(Truck) t1 := new(Truck)
t1.X = x t1.X = x
t1.Y = y t1.Y = y
t1.label = label t1.label = label
return t1 return t1
} }
func AddTube(t1, t2 *Truck, cost float64) *Tube { func addTube(t1, t2 *Truck, cost float64) *Tube {
tube1 := new(Tube) tube1 := new(Tube)
tube1.Cost = cost tube1.Cost = cost
tube1.from = t1 tube1.from = t1
@ -49,17 +50,16 @@ func AddTube(t1, t2 *Truck, cost float64) *Tube {
// Solver should avoid the plugged tube. // Solver should avoid the plugged tube.
// Expect solution Start,Middle,End Total cost: 2.0 // Expect solution Start,Middle,End Total cost: 2.0
func createGorelandGraphPathDiagonal(t *testing.T, diagonalCost float64, expectedDist float64) { func createGorelandGraphPathDiagonal(t *testing.T, diagonalCost, expectedDist float64) {
world := new(Goreland) world := new(Goreland)
trStart := AddTruck(0, 0, "Start") trStart := addTruck(0, 0, "Start")
trMid := AddTruck(0, 1, "Middle") trMid := addTruck(0, 1, "Middle")
trEnd := AddTruck(1, 1, "End") trEnd := addTruck(1, 1, "End")
AddTube(trStart, trEnd, diagonalCost) addTube(trStart, trEnd, diagonalCost)
AddTube(trStart, trMid, 1) addTube(trStart, trMid, 1)
AddTube(trMid, trEnd, 1) addTube(trMid, trEnd, 1)
t.Logf("Goreland. Diagonal cost: %v\n\n", diagonalCost) t.Logf("Goreland. Diagonal cost: %v\n\n", diagonalCost)
@ -70,9 +70,11 @@ func createGorelandGraphPathDiagonal(t *testing.T, diagonalCost float64, expecte
} else { } else {
t.Logf("Resulting path\n%s", world.RenderPath(p)) t.Logf("Resulting path\n%s", world.RenderPath(p))
} }
if !found && expectedDist >= 0 { if !found && expectedDist >= 0 {
t.Fatal("Could not find a path") t.Fatal("Could not find a path")
} }
if found && dist != expectedDist { if found && dist != expectedDist {
t.Fatalf("Expected dist to be %v but got %v", expectedDist, dist) t.Fatalf("Expected dist to be %v but got %v", expectedDist, dist)
} }

View File

@ -16,14 +16,17 @@ func testPath(worldInput string, t *testing.T, expectedDist float64) {
world := ParseWorld(worldInput) world := ParseWorld(worldInput)
t.Logf("Input world\n%s", world.RenderPath([]Pather{})) t.Logf("Input world\n%s", world.RenderPath([]Pather{}))
p, dist, found := Path(world.From(), world.To(), math.MaxFloat64) p, dist, found := Path(world.From(), world.To(), math.MaxFloat64)
if !found { if !found {
t.Log("Could not find a path") t.Log("Could not find a path")
} else { } else {
t.Logf("Resulting path\n%s", world.RenderPath(p)) t.Logf("Resulting path\n%s", world.RenderPath(p))
} }
if !found && expectedDist >= 0 { if !found && expectedDist >= 0 {
t.Fatal("Could not find a path") t.Fatal("Could not find a path")
} }
if found && dist != expectedDist { if found && dist != expectedDist {
t.Fatalf("Expected dist to be %v but got %v", expectedDist, dist) t.Fatalf("Expected dist to be %v but got %v", expectedDist, dist)
} }
@ -126,6 +129,7 @@ F............................~.................................................
..............................................X....~.......MM......X.X.X.X.X.X. ..............................................X....~.......MM......X.X.X.X.X.X.
...............................................X...~.......M.........X...X...XT ...............................................X...~.......M.........X...X...XT
`) `)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Path(world.From(), world.To(), math.MaxFloat64) Path(world.From(), world.To(), math.MaxFloat64)
} }

View File

@ -29,7 +29,7 @@ const (
) )
// KindRunes map tile kinds to output runes. // KindRunes map tile kinds to output runes.
var KindRunes = map[int]rune{ var KindRunes = map[int]rune{ // nolint:gochecknoglobals // it's a test
KindPlain: '.', KindPlain: '.',
KindRiver: '~', KindRiver: '~',
KindMountain: 'M', KindMountain: 'M',
@ -40,7 +40,7 @@ var KindRunes = map[int]rune{
} }
// RuneKinds map input runes to tile kinds. // RuneKinds map input runes to tile kinds.
var RuneKinds = map[rune]int{ var RuneKinds = map[rune]int{ // nolint:gochecknoglobals // it's a test
'.': KindPlain, '.': KindPlain,
'~': KindRiver, '~': KindRiver,
'M': KindMountain, 'M': KindMountain,
@ -50,7 +50,7 @@ var RuneKinds = map[rune]int{
} }
// KindCosts map tile kinds to movement costs. // KindCosts map tile kinds to movement costs.
var KindCosts = map[int]float64{ var KindCosts = map[int]float64{ // nolint:gochecknoglobals // it's a test
KindPlain: 1.0, KindPlain: 1.0,
KindFrom: 1.0, KindFrom: 1.0,
KindTo: 1.0, KindTo: 1.0,
@ -71,7 +71,8 @@ type Tile struct {
// PathNeighbors returns the neighbors of the tile, excluding blockers and // PathNeighbors returns the neighbors of the tile, excluding blockers and
// tiles off the edge of the board. // tiles off the edge of the board.
func (t *Tile) PathNeighbors() []Pather { func (t *Tile) PathNeighbors() []Pather {
neighbors := []Pather{} neighbors := make([]Pather, 0)
for _, offset := range [][]int{ for _, offset := range [][]int{
{-1, 0}, {-1, 0},
{1, 0}, {1, 0},
@ -83,6 +84,7 @@ func (t *Tile) PathNeighbors() []Pather {
neighbors = append(neighbors, n) neighbors = append(neighbors, n)
} }
} }
return neighbors return neighbors
} }
@ -97,13 +99,16 @@ func (t *Tile) PathNeighborCost(to Pather) float64 {
func (t *Tile) PathEstimatedCost(to Pather) float64 { func (t *Tile) PathEstimatedCost(to Pather) float64 {
toT := to.(*Tile) toT := to.(*Tile)
absX := toT.X - t.X absX := toT.X - t.X
if absX < 0 { if absX < 0 {
absX = -absX absX = -absX
} }
absY := toT.Y - t.Y absY := toT.Y - t.Y
if absY < 0 { if absY < 0 {
absY = -absY absY = -absY
} }
return float64(absX + absY) return float64(absX + absY)
} }
@ -115,6 +120,7 @@ func (w World) Tile(x, y int) *Tile {
if w[x] == nil { if w[x] == nil {
return nil return nil
} }
return w[x][y] return w[x][y]
} }
@ -123,6 +129,7 @@ func (w World) SetTile(t *Tile, x, y int) {
if w[x] == nil { if w[x] == nil {
w[x] = map[int]*Tile{} w[x] = map[int]*Tile{}
} }
w[x][y] = t w[x][y] = t
t.X = x t.X = x
t.Y = y t.Y = y
@ -139,6 +146,7 @@ func (w World) FirstOfKind(kind int) *Tile {
} }
} }
} }
return nil return nil
} }
@ -158,41 +166,50 @@ func (w World) RenderPath(path []Pather) string {
if width == 0 { if width == 0 {
return "" return ""
} }
height := len(w[0]) height := len(w[0])
pathLocs := map[string]bool{} pathLocs := map[string]bool{}
for _, p := range path { for _, p := range path {
pT := p.(*Tile) pT := p.(*Tile)
pathLocs[fmt.Sprintf("%d,%d", pT.X, pT.Y)] = true pathLocs[fmt.Sprintf("%d,%d", pT.X, pT.Y)] = true
} }
rows := make([]string, height) rows := make([]string, height)
for x := 0; x < width; x++ { for x := 0; x < width; x++ {
for y := 0; y < height; y++ { for y := 0; y < height; y++ {
t := w.Tile(x, y) t := w.Tile(x, y)
r := ' ' r := ' '
if pathLocs[fmt.Sprintf("%d,%d", x, y)] { if pathLocs[fmt.Sprintf("%d,%d", x, y)] {
r = KindRunes[KindPath] r = KindRunes[KindPath]
} else if t != nil { } else if t != nil {
r = KindRunes[t.Kind] r = KindRunes[t.Kind]
} }
rows[y] += string(r) rows[y] += string(r)
} }
} }
return strings.Join(rows, "\n") return strings.Join(rows, "\n")
} }
// ParseWorld parses a textual representation of a world into a world map. // ParseWorld parses a textual representation of a world into a world map.
func ParseWorld(input string) World { func ParseWorld(input string) World {
w := World{} w := World{}
for y, row := range strings.Split(strings.TrimSpace(input), "\n") { for y, row := range strings.Split(strings.TrimSpace(input), "\n") {
for x, raw := range row { for x, raw := range row {
kind, ok := RuneKinds[raw] kind, ok := RuneKinds[raw]
if !ok { if !ok {
kind = KindBlocker kind = KindBlocker
} }
w.SetTile(&Tile{
Kind: kind, w.SetTile(&Tile{Kind: kind}, x, y)
}, x, y)
} }
} }
return w return w
} }

View File

@ -31,5 +31,6 @@ func (pq *priorityQueue) Pop() interface{} {
no := old[n-1] no := old[n-1]
no.index = -1 no.index = -1
*pq = old[0 : n-1] *pq = old[0 : n-1]
return no return no
} }