func (pr *PathRange) jump(p, dir, to gruid.Point, cost int) (gruid.Point, int) {
	switch {
	case dir.X == 0 || dir.Y == 0:
		return pr.jumpStraight(p, dir, to)
	default:
		return pr.jumpDiagonal(p, dir, to, cost)
	}
}
jump makes a jump from a position in a given direction in order to find an
appropriate successor, skipping nodes that do not require being added to the
open list.
package paths
import (
	"github.com/anaseto/gruid"
)
func (pr *PathRange) JPSPath(path []gruid.Point, from, to gruid.Point, passable func(gruid.Point) bool, diags bool) []gruid.Point {
	if !from.In(pr.Rg) || !to.In(pr.Rg) {
		return nil
	}
	if from == to {
		return append(path, from)
	}
	pr.passable = passable
	pr.diags = diags
	path = path[:0]
	pr.initAstar()
	nm := pr.AstarNodes
	nm.Idx++
	defer checkNodesIdx(nm)
	pr.AstarQueue = pr.AstarQueue[:0]
	pqInit(&pr.AstarQueue)
	fromNode := nm.get(pr, from)
	fromNode.Closed = true
	fromNode.Open = false
	neighbors := make([]gruid.Point, 0, 8)
	pr.expandOrigin(from, to)
	for {
		if (&pr.AstarQueue).Len() == 0 {
			return nil
		}
		n := pqPop(&pr.AstarQueue)
		n.Open = false
		n.Closed = true
		if n.P == to {
			path = pr.path(path, from, n)
			return path
		}
		neighbors = pr.neighbors(neighbors[:0], n, to)
		for _, p := range neighbors {
			dir := p.Sub(n.P)
			var q gruid.Point
			var i int
			if pr.diags {
				q, i = pr.jump(p, dir, to, n.Cost)
			} else {
				q, i = pr.jumpNoDiags(p, dir, to, n.Cost)
			}
			if i > 0 {
				pr.addSuccessor(q, n.P, to, n.Cost+i)
			}
		}
	}
}
func (pr *PathRange) expandOrigin(from, to gruid.Point) {
	for y := -1; y <= 1; y++ {
		for x := -1; x <= 1; x++ {
			if x == 0 && y == 0 {
				continue
			}
			dir := gruid.Point{x, y}
			q := from.Add(dir)
			if !pr.diags {
				if dir.X != 0 && dir.Y != 0 {
					if pr.pass(from.Add(gruid.Point{dir.X, 0})) || pr.pass(from.Add(gruid.Point{0, dir.Y})) {
						pr.addSuccessor(q, from, to, 2)
					}
					continue
				}
				pr.addSuccessor(q, from, to, 1)
				continue
			}
			pr.addSuccessor(q, from, to, 1)
		}
	}
}
func (pr *PathRange) pass(p gruid.Point) bool {
	return p.In(pr.Rg) && pr.passable(p)
}
func (pr *PathRange) obstacle(p gruid.Point) bool {
	return p.In(pr.Rg) && !pr.passable(p)
}
func right(p gruid.Point, dir gruid.Point) gruid.Point {
	return gruid.Point{p.X - dir.Y, p.Y + dir.X}
}
func left(p gruid.Point, dir gruid.Point) gruid.Point {
	return gruid.Point{p.X + dir.Y, p.Y - dir.X}
}
type forcedSucc int
const (
	fsNone forcedSucc = iota
	fsLeft
	fsRight
	fsBoth
)
func (pr *PathRange) straightMax(p, dir gruid.Point) (int, forcedSucc) {
	fs := fsBoth
	max := 0
	switch {
	case dir.X > 0:
		max = pr.Rg.Max.X - p.X
		if p.Y == 0 {
			fs -= fsLeft
		}
		if p.Y == pr.Rg.Max.Y-1 {
			fs -= fsRight
		}
	case dir.X < 0:
		max = -pr.Rg.Min.X + p.X + 1
		if p.Y == 0 {
			fs -= fsRight
		}
		if p.Y == pr.Rg.Max.Y-1 {
			fs -= fsLeft
		}
	case dir.Y > 0:
		max = pr.Rg.Max.Y - p.Y
		if p.X == 0 {
			fs -= fsRight
		}
		if p.X == pr.Rg.Max.X-1 {
			fs -= fsLeft
		}
	case dir.Y < 0:
		max = -pr.Rg.Min.Y + p.Y + 1
		if p.X == 0 {
			fs -= fsLeft
		}
		if p.X == pr.Rg.Max.X-1 {
			fs -= fsRight
		}
	}
	return max, fs
}
func (pr *PathRange) jumpStraight(p, dir, to gruid.Point) (gruid.Point, int) {
	max, fs := pr.straightMax(p, dir)
	switch fs {
	case fsNone:
		return pr.jumpStraightNone(p, dir, to, max)
	case fsRight:
		return pr.jumpStraightRight(p, dir, to, max)
	case fsLeft:
		return pr.jumpStraightLeft(p, dir, to, max)
	default:
		for i := 1; i < max+1; i++ {
			if !pr.passable(p) {
				return p, 0
			}
			if p == to {
				return p, i
			}
			np := p.Add(dir)
			if q := left(p, dir); !pr.passable(q) && pr.pass(q.Add(dir)) {
				return p, i
			}
			if q := right(p, dir); !pr.passable(q) && pr.pass(q.Add(dir)) {
				return p, i
			}
			p = np
		}
		return p, 0
	}
}
func (pr *PathRange) jumpStraightNone(p, dir, to gruid.Point, max int) (gruid.Point, int) {
	for i := 1; i < max+1; i++ {
		if !pr.passable(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		p = p.Add(dir)
	}
	return p, 0
}
func (pr *PathRange) jumpStraightLeft(p, dir, to gruid.Point, max int) (gruid.Point, int) {
	for i := 1; i < max+1; i++ {
		if !pr.passable(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		np := p.Add(dir)
		if q := left(p, dir); !pr.passable(q) && pr.pass(q.Add(dir)) {
			return p, i
		}
		p = np
	}
	return p, 0
}
func (pr *PathRange) jumpStraightRight(p, dir, to gruid.Point, max int) (gruid.Point, int) {
	for i := 1; i < max+1; i++ {
		if !pr.passable(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		np := p.Add(dir)
		if q := right(p, dir); !pr.passable(q) && pr.pass(q.Add(dir)) {
			return p, i
		}
		p = np
	}
	return p, 0
}
func (pr *PathRange) jumpStraightNoDiags(p, dir, to gruid.Point) (gruid.Point, int) {
	max, fs := pr.straightMax(p, dir)
	switch fs {
	case fsNone:
		return pr.jumpStraightNone(p, dir, to, max)
	case fsRight:
		return pr.jumpStraightRightNoDiags(p, dir, to, max)
	case fsLeft:
		return pr.jumpStraightLeftNoDiags(p, dir, to, max)
	default:
		for i := 1; i < max+1; i++ {
			if !pr.passable(p) {
				return p, 0
			}
			if p == to {
				return p, i
			}
			np := p.Add(dir)
			if q := left(p, dir); !pr.passable(q) {
				if pr.pass(q.Add(dir)) && pr.pass(np) {
					return p, i
				}
			}
			if q := right(p, dir); !pr.passable(q) {
				if pr.pass(q.Add(dir)) && pr.pass(np) {
					return p, i
				}
			}
			p = np
		}
		return p, 0
	}
}
func (pr *PathRange) jumpStraightLeftNoDiags(p, dir, to gruid.Point, max int) (gruid.Point, int) {
	for i := 1; i < max+1; i++ {
		if !pr.passable(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		np := p.Add(dir)
		if q := left(p, dir); !pr.passable(q) {
			if pr.pass(q.Add(dir)) && pr.pass(np) {
				return p, i
			}
		}
		p = np
	}
	return p, 0
}
func (pr *PathRange) jumpStraightRightNoDiags(p, dir, to gruid.Point, max int) (gruid.Point, int) {
	for i := 1; i < max+1; i++ {
		if !pr.passable(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		np := p.Add(dir)
		if q := right(p, dir); !pr.passable(q) {
			if pr.pass(q.Add(dir)) && pr.pass(np) {
				return p, i
			}
		}
		p = np
	}
	return p, 0
}
func (pr *PathRange) jumpDiagonal(p, dir, to gruid.Point, cost int) (gruid.Point, int) {
	i := 1
	from := p.Sub(dir)
	for {
		if !pr.pass(p) {
			return p, 0
		}
		if p == to {
			return p, i
		}
		if q := p.Shift(-dir.X, 0); pr.obstacle(q) {
			if pr.pass(p.Add(gruid.Point{-dir.X, dir.Y})) {
				return p, i
			}
		}
		if q := p.Shift(0, -dir.Y); pr.obstacle(q) {
			if pr.pass(p.Add(gruid.Point{dir.X, -dir.Y})) {
				return p, i
			}
		}
		q, j := pr.jumpStraight(p.Shift(dir.X, 0), gruid.Point{dir.X, 0}, to)
		if j > 0 {
			pr.addSuccessor(q, from, to, cost+i+j)
		}
		q, j = pr.jumpStraight(p.Shift(0, dir.Y), gruid.Point{0, dir.Y}, to)
		if j > 0 {
			pr.addSuccessor(q, from, to, cost+i+j)
		}
		p = p.Add(dir)
		i++
	}
}
func (pr *PathRange) jumpDiagonalNoDiags(p, dir, to gruid.Point, cost int) (gruid.Point, int) {
	i := 2  
	from := p.Sub(dir)
	for {
		if !pr.pass(p) {
			return p, 0
		}
		px := p.Shift(-dir.X, 0)
		py := p.Shift(0, -dir.Y)
		pxpass := pr.pass(px)
		pypass := pr.pass(py)
		if !pxpass && !pypass {
			return p, 0
		}
		if p == to {
			return p, i
		}
		if !pxpass {
			if pr.pass(p.Add(gruid.Point{-dir.X, dir.Y})) && pr.pass(p.Add(gruid.Point{0, dir.Y})) {
				return p, i
			}
		}
		if !pypass {
			if pr.pass(p.Add(gruid.Point{dir.X, -dir.Y})) && pr.pass(p.Add(gruid.Point{dir.X, 0})) {
				return p, i
			}
		}
		q, j := pr.jumpStraightNoDiags(p.Shift(dir.X, 0), gruid.Point{dir.X, 0}, to)
		if j > 0 {
			pr.addSuccessor(q, from, to, cost+i+j)
		}
		q, j = pr.jumpStraightNoDiags(p.Shift(0, dir.Y), gruid.Point{0, dir.Y}, to)
		if j > 0 {
			pr.addSuccessor(q, from, to, cost+i+j)
		}
		p = p.Add(dir)
		i += 2
	}
}