198 lines
4.2 KiB
Go
198 lines
4.2 KiB
Go
package tins2021
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
|
|
"opslag.de/schobers/geom"
|
|
)
|
|
|
|
var AllDirections = []Direction{DirectionDownRight, DirectionDownLeft, DirectionUpLeft, DirectionUpRight}
|
|
|
|
func RandomDirection() Direction {
|
|
return AllDirections[rand.Intn(len(AllDirections))]
|
|
}
|
|
|
|
func AdjacentPosition(pos geom.Point, dir Direction) geom.Point {
|
|
if pos.Y%2 == 0 {
|
|
switch dir {
|
|
case DirectionDownRight:
|
|
return geom.Pt(pos.X+1, pos.Y+1)
|
|
case DirectionDownLeft:
|
|
return geom.Pt(pos.X, pos.Y+1)
|
|
case DirectionUpLeft:
|
|
return geom.Pt(pos.X, pos.Y-1)
|
|
case DirectionUpRight:
|
|
return geom.Pt(pos.X+1, pos.Y-1)
|
|
default:
|
|
panic("invalid direction")
|
|
}
|
|
}
|
|
switch dir {
|
|
case DirectionDownRight:
|
|
return geom.Pt(pos.X, pos.Y+1)
|
|
case DirectionDownLeft:
|
|
return geom.Pt(pos.X-1, pos.Y+1)
|
|
case DirectionUpLeft:
|
|
return geom.Pt(pos.X-1, pos.Y-1)
|
|
case DirectionUpRight:
|
|
return geom.Pt(pos.X, pos.Y-1)
|
|
default:
|
|
panic("invalid direction")
|
|
}
|
|
}
|
|
|
|
type Direction int
|
|
|
|
const (
|
|
DirectionDownRight Direction = iota
|
|
DirectionDownLeft
|
|
DirectionUpLeft
|
|
DirectionUpRight
|
|
)
|
|
|
|
func (d Direction) Reverse() Direction {
|
|
switch d {
|
|
case DirectionDownRight:
|
|
return DirectionUpLeft
|
|
case DirectionDownLeft:
|
|
return DirectionUpRight
|
|
case DirectionUpLeft:
|
|
return DirectionDownRight
|
|
case DirectionUpRight:
|
|
return DirectionDownLeft
|
|
default:
|
|
panic("direction not supported")
|
|
}
|
|
}
|
|
|
|
type Tile struct {
|
|
Inversed bool
|
|
Star bool
|
|
Heart bool
|
|
}
|
|
|
|
func (t *Tile) Occupied() bool {
|
|
return t.Star || t.Heart
|
|
}
|
|
|
|
func (t *Tile) Invert() { t.Inversed = !t.Inversed }
|
|
|
|
type Tiles map[geom.Point]*Tile
|
|
|
|
func (t Tiles) AllReachable(from geom.Point) bool {
|
|
visited := map[geom.Point]bool{}
|
|
visit := []geom.Point{from}
|
|
for len(visit) > 0 {
|
|
next := visit[0]
|
|
visit = visit[1:]
|
|
for _, dir := range AllDirections {
|
|
neighbour, ok := t.CanMove(next, dir)
|
|
if !ok || visited[neighbour] {
|
|
continue
|
|
}
|
|
visited[neighbour] = true
|
|
visit = append(visit, neighbour)
|
|
}
|
|
}
|
|
return len(visited) == len(t)
|
|
}
|
|
|
|
func (t Tiles) CanMove(p geom.Point, dir Direction) (geom.Point, bool) {
|
|
towards := AdjacentPosition(p, dir)
|
|
from := t[p]
|
|
to := t[towards]
|
|
if to == nil {
|
|
return geom.Point{}, false
|
|
}
|
|
if dir == DirectionDownRight || dir == DirectionDownLeft {
|
|
if !from.Inversed && to.Inversed {
|
|
return geom.Point{}, false
|
|
}
|
|
} else {
|
|
if from.Inversed && !to.Inversed {
|
|
return geom.Point{}, false
|
|
}
|
|
}
|
|
return towards, true
|
|
}
|
|
|
|
func (t Tiles) CanMoveTile(p geom.Point, dir Direction) (geom.Point, *Tile, bool) {
|
|
to, ok := t.CanMove(p, dir)
|
|
if !ok {
|
|
return geom.Point{}, nil, false
|
|
}
|
|
return to, t[to], true
|
|
}
|
|
|
|
func (t Tiles) Distances(from geom.Point) map[geom.Point]int {
|
|
distance := map[geom.Point]int{
|
|
from: 0,
|
|
}
|
|
visit := []geom.Point{from}
|
|
for len(visit) > 0 {
|
|
next := visit[0]
|
|
visit = visit[1:]
|
|
for _, dir := range AllDirections {
|
|
neighbour, ok := t.CanMove(next, dir)
|
|
if !ok {
|
|
continue
|
|
}
|
|
d := distance[next] + 1
|
|
if neighbourD, ok := distance[neighbour]; ok && neighbourD <= d {
|
|
continue
|
|
}
|
|
distance[neighbour] = d
|
|
visit = append(visit, neighbour)
|
|
}
|
|
}
|
|
return distance
|
|
}
|
|
|
|
func (t Tiles) ShortestPath(from, to geom.Point, canMoveTo func(geom.Point, *Tile) bool) []geom.Point {
|
|
distances := map[geom.Point]int{
|
|
from: 0,
|
|
}
|
|
origins := map[geom.Point]geom.Point{
|
|
from: from,
|
|
}
|
|
estimated := map[geom.Point]int{
|
|
from: from.DistInt(to),
|
|
}
|
|
visit := map[geom.Point]bool{from: true}
|
|
for len(visit) > 0 {
|
|
var next geom.Point
|
|
best := math.MaxInt32
|
|
for candidate := range visit {
|
|
e := estimated[candidate]
|
|
if e < best {
|
|
next = candidate
|
|
best = e
|
|
}
|
|
}
|
|
if next == to {
|
|
path := []geom.Point{to}
|
|
for path[0] != from {
|
|
path = append([]geom.Point{origins[path[0]]}, path...)
|
|
}
|
|
return path
|
|
}
|
|
delete(visit, next)
|
|
for _, dir := range AllDirections {
|
|
neighbour, ok := t.CanMove(next, dir)
|
|
if !ok || (canMoveTo != nil && !canMoveTo(neighbour, t[neighbour])) {
|
|
continue
|
|
}
|
|
d := distances[next] + 1
|
|
if neighbourD, ok := distances[neighbour]; ok && neighbourD <= d {
|
|
continue
|
|
}
|
|
distances[neighbour] = d
|
|
origins[neighbour] = next
|
|
estimated[neighbour] = d + neighbour.DistInt(to)
|
|
visit[neighbour] = true
|
|
}
|
|
}
|
|
return []geom.Point{}
|
|
}
|