236 lines
4.4 KiB
Go
236 lines
4.4 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
_ "embed"
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
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]
|
||
|
|
||
|
start := Point{0, 0}
|
||
|
target := Point{0, 0}
|
||
|
var text string
|
||
|
for scan.Scan() {
|
||
|
text = scan.Text()
|
||
|
if start == target {
|
||
|
start[1] = int16(strings.IndexRune(text, '.'))
|
||
|
}
|
||
|
m = append(m, []rune(text))
|
||
|
}
|
||
|
target[0] = int16(len(m) - 1)
|
||
|
target[1] = int16(strings.IndexRune(text, '.'))
|
||
|
|
||
|
result := &result{}
|
||
|
|
||
|
result.valuePT1 = search(&graph{m: m, start: start, target: target, neighbors: part1nbs})
|
||
|
result.valuePT2 = search(&graph{m: m, start: start, target: target, neighbors: part2nbs})
|
||
|
|
||
|
return result, nil
|
||
|
}
|
||
|
|
||
|
type Point = aoc.Point[int16]
|
||
|
type Map = aoc.Map[int16, rune]
|
||
|
|
||
|
// 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 dirIDX = func() map[Point]direction {
|
||
|
m := make(map[Point]direction, len(directions))
|
||
|
for k, v := range directions {
|
||
|
m[v] = direction(k)
|
||
|
}
|
||
|
return m
|
||
|
}()
|
||
|
|
||
|
var arrows = []rune{'^', '>', 'v', '<'}
|
||
|
|
||
|
var arrowIDX = func() map[rune]Point {
|
||
|
m := make(map[rune]Point, len(arrows))
|
||
|
for k, v := range arrows {
|
||
|
m[v] = directions[k]
|
||
|
}
|
||
|
return m
|
||
|
}()
|
||
|
|
||
|
// position on the map
|
||
|
type position struct {
|
||
|
loc Point
|
||
|
direction Point
|
||
|
}
|
||
|
|
||
|
func (p position) step(to Point) position {
|
||
|
return position{p.loc.Add(to), to}
|
||
|
}
|
||
|
|
||
|
// implements FindPath graph interface
|
||
|
type graph struct {
|
||
|
m Map
|
||
|
start Point
|
||
|
target Point
|
||
|
|
||
|
neighbors func(g *graph, current position) []position
|
||
|
}
|
||
|
|
||
|
// Neighbors returns valid steps from given position. if at target returns none.
|
||
|
func (g *graph) Neighbors(current position) []position {
|
||
|
return g.neighbors(g, current)
|
||
|
}
|
||
|
|
||
|
// Cost calculates heat cost to neighbor from map
|
||
|
func (g *graph) Cost(a, b position) int16 {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func (g *graph) Target(a position, c int16) bool {
|
||
|
return a.loc == g.target
|
||
|
}
|
||
|
|
||
|
func (g *graph) Seen(a position) position {
|
||
|
a.direction = Point{}
|
||
|
return a
|
||
|
}
|
||
|
|
||
|
func match[T comparable](match T, lis ...T) bool {
|
||
|
for _, b := range lis {
|
||
|
if b == match {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func search(g *graph) int {
|
||
|
costs, paths := aoc.FindPaths[int16, position](g, position{loc: g.start}, position{loc: g.target})
|
||
|
|
||
|
for i, path := range paths {
|
||
|
log("path length = ", costs[i])
|
||
|
printGraph(g.m, path)
|
||
|
}
|
||
|
|
||
|
return int(aoc.Max(0, costs...))
|
||
|
}
|
||
|
|
||
|
// printGraph with the path overlay
|
||
|
func printGraph(m Map, path []position) {
|
||
|
pts := make(map[Point]position, len(path))
|
||
|
for _, pt := range path {
|
||
|
pts[pt.loc] = pt
|
||
|
}
|
||
|
|
||
|
for r, row := range m {
|
||
|
for c, x := range row {
|
||
|
if _, ok := pts[Point{int16(r), int16(c)}]; ok {
|
||
|
if x == '.' {
|
||
|
fmt.Print("*")
|
||
|
} else {
|
||
|
fmt.Print(string(x))
|
||
|
}
|
||
|
continue
|
||
|
}
|
||
|
fmt.Print(".")
|
||
|
_ = x
|
||
|
// fmt.Print(string(x))
|
||
|
}
|
||
|
fmt.Println("")
|
||
|
}
|
||
|
fmt.Println("")
|
||
|
}
|
||
|
|
||
|
func opposite(d Point) Point {
|
||
|
return directions[(dirIDX[d]+2)%4]
|
||
|
}
|
||
|
|
||
|
|
||
|
func part1nbs(g *graph, current position) []position {
|
||
|
var nbs []position
|
||
|
|
||
|
if current.loc == g.start {
|
||
|
return []position{
|
||
|
{current.loc.Add(D), D},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if current.loc == g.target {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// only one direction on arrow.
|
||
|
_, r, _ := g.m.Get(current.loc)
|
||
|
if match(r, arrows...) {
|
||
|
to := arrowIDX[r]
|
||
|
if next := current.step(to); g.m.Valid(next.loc) {
|
||
|
_, r, _ := g.m.Get(next.loc)
|
||
|
d := arrows[(dirIDX[to]+2)%4] // flow from opposite direction
|
||
|
if !match(r, rune(d), '#') {
|
||
|
nbs = append(nbs, next)
|
||
|
}
|
||
|
}
|
||
|
return nbs
|
||
|
}
|
||
|
|
||
|
for _, to := range directions {
|
||
|
if next := current.step(to); g.m.Valid(next.loc) {
|
||
|
_, r, _ := g.m.Get(next.loc)
|
||
|
d := arrows[(dirIDX[to]+2)%4] // flow from opposite direction
|
||
|
if !match(r, rune(d), '#') {
|
||
|
nbs = append(nbs, next)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nbs
|
||
|
}
|
||
|
|
||
|
func part2nbs(g *graph, current position) []position {
|
||
|
var nbs []position
|
||
|
|
||
|
if current.loc == g.start {
|
||
|
return []position{
|
||
|
{current.loc.Add(D), D},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if current.loc == g.target {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
for _, to := range directions {
|
||
|
if next := current.step(to); g.m.Valid(next.loc) {
|
||
|
if next.direction == opposite(current.direction) {
|
||
|
continue
|
||
|
}
|
||
|
_, r, _ := g.m.Get(next.loc)
|
||
|
if r == '#' {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
nbs = append(nbs, next)
|
||
|
}
|
||
|
}
|
||
|
return nbs
|
||
|
}
|