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 } // Potential calculates distance to target func (g *graph) Potential(a, b position) int16 { return aoc.ManhattanDistance(a.loc, b.loc) } 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 }