222 lines
4.4 KiB
Go
222 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"fmt"
|
|
|
|
aoc "go.sour.is/advent-of-code"
|
|
)
|
|
|
|
var log = aoc.Log
|
|
|
|
func main() { aoc.MustResult(aoc.Runner(run)) }
|
|
|
|
type result struct {
|
|
valuePT1 int
|
|
valuePT2 int
|
|
}
|
|
|
|
func (r result) String() string { return fmt.Sprintf("%#v", r) }
|
|
|
|
func run(scan *bufio.Scanner) (*result, error) {
|
|
var m aoc.Map[int16, rune]
|
|
|
|
for scan.Scan() {
|
|
text := scan.Text()
|
|
m = append(m, []rune(text))
|
|
}
|
|
log("start day 17")
|
|
|
|
result := result{}
|
|
result.valuePT1 = search(m, 1, 3, seenFn)
|
|
log("result from part 1 = ", result.valuePT1)
|
|
|
|
result.valuePT2 = search(m, 4, 10, nil)
|
|
log("result from part 2 = ", result.valuePT2)
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
type Point = aoc.Point[int16]
|
|
type Map = aoc.Map[int16, rune]
|
|
|
|
// rotate for changing direction
|
|
type rotate int8
|
|
|
|
const (
|
|
CW rotate = 1
|
|
CCW rotate = -1
|
|
)
|
|
|
|
// diretion of path steps
|
|
type direction int8
|
|
|
|
var (
|
|
U = Point{-1, 0}
|
|
R = Point{0, 1}
|
|
D = Point{1, 0}
|
|
L = Point{0, -1}
|
|
)
|
|
|
|
var directions = []Point{U, R, D, L}
|
|
|
|
var directionIDX = func() map[Point]direction {
|
|
m := make(map[Point]direction, len(directions))
|
|
for k, v := range directions {
|
|
m[v] = direction(k)
|
|
}
|
|
return m
|
|
}()
|
|
|
|
// position on the map
|
|
type position struct {
|
|
loc Point
|
|
direction Point
|
|
steps int8
|
|
}
|
|
|
|
func (p position) step() position {
|
|
return position{p.loc.Add(p.direction), p.direction, p.steps + 1}
|
|
}
|
|
func (p position) rotateAndStep(towards rotate) position {
|
|
d := directions[(int8(directionIDX[p.direction])+int8(towards)+4)%4]
|
|
return position{p.loc.Add(d), d, 1}
|
|
}
|
|
|
|
// implements FindPath graph interface
|
|
type graph struct {
|
|
min, max int8
|
|
m Map
|
|
target Point
|
|
reads int
|
|
seenFn func(a position) position
|
|
}
|
|
|
|
// Neighbors returns valid steps from given position. if at target returns none.
|
|
func (g *graph) Neighbors(current position) []position {
|
|
var nbs []position
|
|
|
|
if current.steps == 0 {
|
|
return []position{
|
|
{R, R, 1},
|
|
{D, D, 1},
|
|
}
|
|
}
|
|
|
|
if current.loc == g.target {
|
|
return nil
|
|
}
|
|
|
|
if left := current.rotateAndStep(CCW); current.steps >= g.min && g.m.Valid(left.loc) {
|
|
nbs = append(nbs, left)
|
|
}
|
|
|
|
if right := current.rotateAndStep(CW); current.steps >= g.min && g.m.Valid(right.loc) {
|
|
nbs = append(nbs, right)
|
|
}
|
|
|
|
if forward := current.step(); current.steps < g.max && g.m.Valid(forward.loc) {
|
|
nbs = append(nbs, forward)
|
|
}
|
|
|
|
return nbs
|
|
}
|
|
|
|
// Cost calculates heat cost to neighbor from map
|
|
func (g *graph) Cost(a, b position) int16 {
|
|
g.reads++
|
|
_, r, _ := g.m.Get(b.loc)
|
|
return int16(r - '0')
|
|
}
|
|
|
|
// Potential calculates distance to target
|
|
func (g *graph) Potential(a position) int16 {
|
|
return aoc.ManhattanDistance(a.loc, g.target)
|
|
}
|
|
|
|
// Target returns true when target reached. receives node and cost.
|
|
func (g *graph) Target(a position, c int16) bool {
|
|
if a.loc == g.target && a.steps >= g.min && a.steps <= g.max {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Seen attempt at simplifying the seen to use horizontal/vertical and no steps.
|
|
// It returns correct for part1 but not part 2..
|
|
func (g *graph) Seen(a position) position {
|
|
if g.seenFn != nil {
|
|
return g.seenFn(a)
|
|
}
|
|
return a
|
|
}
|
|
|
|
func seenFn(a position) position {
|
|
if a.direction == U {
|
|
a.direction = D
|
|
}
|
|
if a.direction == L {
|
|
a.direction = R
|
|
}
|
|
// a.steps = 0
|
|
return a
|
|
}
|
|
|
|
func search(m Map, minSteps, maxSteps int8, seenFn func(position) position) int {
|
|
rows, cols := m.Size()
|
|
start := Point{}
|
|
target := Point{rows - 1, cols - 1}
|
|
|
|
g := graph{min: minSteps, max: maxSteps, m: m, target: target, seenFn: seenFn}
|
|
cost, path, closed := aoc.FindPath[int16, position](&g, position{loc: start}, position{loc: target})
|
|
|
|
log("total map reads = ", g.reads, "cost = ", cost)
|
|
printGraph(m, path, closed, g.seenFn)
|
|
|
|
return int(cost)
|
|
}
|
|
|
|
// printGraph with the path/cost overlay
|
|
func printGraph(m Map, path []position, closed map[position]int16, seenFn func(a position) position) {
|
|
pts := make(map[Point]position, len(path))
|
|
for _, pt := range path {
|
|
pts[pt.loc] = pt
|
|
}
|
|
|
|
clpt := make(map[position]position, len(closed))
|
|
for pt := range closed {
|
|
clpt[position{loc: pt.loc, steps: pt.steps}] = pt
|
|
}
|
|
|
|
for r, row := range m {
|
|
if r == 0 {
|
|
for c := range row {
|
|
if c == 0 {
|
|
fmt.Print(" ")
|
|
}
|
|
fmt.Printf("% 5d", c)
|
|
}
|
|
fmt.Println("")
|
|
}
|
|
for c := range row {
|
|
if c == 0 {
|
|
fmt.Printf("% 5d", r)
|
|
}
|
|
|
|
if pt, ok := pts[Point{int16(r), int16(c)}]; ok {
|
|
if seenFn != nil {
|
|
pt = seenFn(pt)
|
|
}
|
|
fmt.Printf("% 5d", closed[pt])
|
|
|
|
continue
|
|
}
|
|
|
|
fmt.Print(" ....")
|
|
}
|
|
fmt.Println("")
|
|
}
|
|
fmt.Println("")
|
|
}
|