mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-01-24 18:27:05 -05:00
18003a8543
* fixed lint errors in d2astar * Update astar.go
216 lines
4.6 KiB
Go
216 lines
4.6 KiB
Go
package d2astar
|
|
|
|
// 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{ // nolint:gochecknoglobals // it's a test
|
|
KindPlain: '.',
|
|
KindRiver: '~',
|
|
KindMountain: 'M',
|
|
KindBlocker: 'X',
|
|
KindFrom: 'F',
|
|
KindTo: 'T',
|
|
KindPath: '●',
|
|
}
|
|
|
|
// RuneKinds map input runes to tile kinds.
|
|
var RuneKinds = map[rune]int{ // nolint:gochecknoglobals // it's a test
|
|
'.': KindPlain,
|
|
'~': KindRiver,
|
|
'M': KindMountain,
|
|
'X': KindBlocker,
|
|
'F': KindFrom,
|
|
'T': KindTo,
|
|
}
|
|
|
|
// KindCosts map tile kinds to movement costs.
|
|
var KindCosts = map[int]float64{ // nolint:gochecknoglobals // it's a test
|
|
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 := make([]Pather, 0)
|
|
|
|
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
|
|
}
|