forked from beefsack/go-astar
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pather_test.go
198 lines (180 loc) · 4.45 KB
/
pather_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package astar
// pather_test.go implements a basic world and tiles that implement Pather for
// the sake of testing. This functionality forms the back end for
// path_test.go, and serves as an example for how to use A* for a grid.
import (
"fmt"
"strings"
)
// Kind* constants refer to tile kinds for input and output.
const (
// KindPlain (.) is a plain tile with a movement cost of 1.
KindPlain = iota
// KindRiver (~) is a river tile with a movement cost of 2.
KindRiver
// KindMountain (M) is a mountain tile with a movement cost of 3.
KindMountain
// KindBlocker (X) is a tile which blocks movement.
KindBlocker
// KindFrom (F) is a tile which marks where the path should be calculated
// from.
KindFrom
// KindTo (T) is a tile which marks the goal of the path.
KindTo
// KindPath (●) is a tile to represent where the path is in the output.
KindPath
)
// KindRunes map tile kinds to output runes.
var KindRunes = map[int]rune{
KindPlain: '.',
KindRiver: '~',
KindMountain: 'M',
KindBlocker: 'X',
KindFrom: 'F',
KindTo: 'T',
KindPath: '●',
}
// RuneKinds map input runes to tile kinds.
var RuneKinds = map[rune]int{
'.': KindPlain,
'~': KindRiver,
'M': KindMountain,
'X': KindBlocker,
'F': KindFrom,
'T': KindTo,
}
// KindCosts map tile kinds to movement costs.
var KindCosts = map[int]float64{
KindPlain: 1.0,
KindFrom: 1.0,
KindTo: 1.0,
KindRiver: 2.0,
KindMountain: 3.0,
}
// A Tile is a tile in a grid which implements Pather.
type Tile struct {
// Kind is the kind of tile, potentially affecting movement.
Kind int
// X and Y are the coordinates of the tile.
X, Y int
// W is a reference to the World that the tile is a part of.
W World
}
// PathNeighbors returns the neighbors of the tile, excluding blockers and
// tiles off the edge of the board.
func (t *Tile) PathNeighbors() []Pather {
neighbors := []Pather{}
for _, offset := range [][]int{
{-1, 0},
{1, 0},
{0, -1},
{0, 1},
} {
if n := t.W.Tile(t.X+offset[0], t.Y+offset[1]); n != nil &&
n.Kind != KindBlocker {
neighbors = append(neighbors, n)
}
}
return neighbors
}
// PathNeighborCost returns the movement cost of the directly neighboring tile.
func (t *Tile) PathNeighborCost(to Pather) float64 {
toT := to.(*Tile)
return KindCosts[toT.Kind]
}
// PathEstimatedCost uses Manhattan distance to estimate orthogonal distance
// between non-adjacent nodes.
func (t *Tile) PathEstimatedCost(to Pather) float64 {
toT := to.(*Tile)
absX := toT.X - t.X
if absX < 0 {
absX = -absX
}
absY := toT.Y - t.Y
if absY < 0 {
absY = -absY
}
return float64(absX + absY)
}
// World is a two dimensional map of Tiles.
type World map[int]map[int]*Tile
// Tile gets the tile at the given coordinates in the world.
func (w World) Tile(x, y int) *Tile {
if w[x] == nil {
return nil
}
return w[x][y]
}
// SetTile sets a tile at the given coordinates in the world.
func (w World) SetTile(t *Tile, x, y int) {
if w[x] == nil {
w[x] = map[int]*Tile{}
}
w[x][y] = t
t.X = x
t.Y = y
t.W = w
}
// FirstOfKind gets the first tile on the board of a kind, used to get the from
// and to tiles as there should only be one of each.
func (w World) FirstOfKind(kind int) *Tile {
for _, row := range w {
for _, t := range row {
if t.Kind == kind {
return t
}
}
}
return nil
}
// From gets the from tile from the world.
func (w World) From() *Tile {
return w.FirstOfKind(KindFrom)
}
// To gets the to tile from the world.
func (w World) To() *Tile {
return w.FirstOfKind(KindTo)
}
// RenderPath renders a path on top of a world.
func (w World) RenderPath(path []Pather) string {
width := len(w)
if width == 0 {
return ""
}
height := len(w[0])
pathLocs := map[string]bool{}
for _, p := range path {
pT := p.(*Tile)
pathLocs[fmt.Sprintf("%d,%d", pT.X, pT.Y)] = true
}
rows := make([]string, height)
for x := 0; x < width; x++ {
for y := 0; y < height; y++ {
t := w.Tile(x, y)
r := ' '
if pathLocs[fmt.Sprintf("%d,%d", x, y)] {
r = KindRunes[KindPath]
} else if t != nil {
r = KindRunes[t.Kind]
}
rows[y] += string(r)
}
}
return strings.Join(rows, "\n")
}
// ParseWorld parses a textual representation of a world into a world map.
func ParseWorld(input string) World {
w := World{}
for y, row := range strings.Split(strings.TrimSpace(input), "\n") {
for x, raw := range row {
kind, ok := RuneKinds[raw]
if !ok {
kind = KindBlocker
}
w.SetTile(&Tile{
Kind: kind,
}, x, y)
}
}
return w
}