advent-of-code/day17/main.go

222 lines
4.4 KiB
Go
Raw Normal View History

2023-12-26 12:41:46 -07:00
package main
import (
"bufio"
_ "embed"
"fmt"
aoc "go.sour.is/advent-of-code"
)
2024-01-01 12:44:08 -07:00
var log = aoc.Log
2023-12-26 12:41:46 -07:00
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) {
2024-01-01 09:26:31 -07:00
var m aoc.Map[int16, rune]
2023-12-26 12:41:46 -07:00
for scan.Scan() {
text := scan.Text()
m = append(m, []rune(text))
}
2024-01-01 12:44:08 -07:00
log("start day 17")
2023-12-26 12:41:46 -07:00
result := result{}
result.valuePT1 = search(m, 1, 3, seenFn)
2024-01-01 12:44:08 -07:00
log("result from part 1 = ", result.valuePT1)
result.valuePT2 = search(m, 4, 10, nil)
2024-01-01 12:44:08 -07:00
log("result from part 2 = ", result.valuePT2)
2023-12-26 12:41:46 -07:00
return &result, nil
}
2024-01-01 09:26:31 -07:00
type Point = aoc.Point[int16]
type Map = aoc.Map[int16, rune]
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
// rotate for changing direction
type rotate int8
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
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}
)
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
var directions = []Point{U, R, D, L}
2023-12-29 21:10:59 -07:00
2024-01-01 09:26:31 -07:00
var directionIDX = func() map[Point]direction {
m := make(map[Point]direction, len(directions))
for k, v := range directions {
m[v] = direction(k)
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
return m
}()
// position on the map
type position struct {
loc Point
direction Point
steps int8
}
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
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
2024-01-01 09:26:31 -07:00
}
// Neighbors returns valid steps from given position. if at target returns none.
func (g *graph) Neighbors(current position) []position {
var nbs []position
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
if current.steps == 0 {
return []position{
{R, R, 1},
{D, D, 1},
}
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
if current.loc == g.target {
return nil
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
if left := current.rotateAndStep(CCW); current.steps >= g.min && g.m.Valid(left.loc) {
nbs = append(nbs, left)
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
if right := current.rotateAndStep(CW); current.steps >= g.min && g.m.Valid(right.loc) {
nbs = append(nbs, right)
2023-12-28 18:57:22 -07:00
}
2024-01-01 09:26:31 -07:00
if forward := current.step(); current.steps < g.max && g.m.Valid(forward.loc) {
nbs = append(nbs, forward)
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
return nbs
}
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
// 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')
}
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
// Potential calculates distance to target
func (g *graph) Potential(a position) int16 {
return aoc.ManhattanDistance(a.loc, g.target)
}
2023-12-26 12:41:46 -07:00
// 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 {
2024-01-01 09:57:08 -07:00
return true
}
return false
}
2024-01-01 09:26:31 -07:00
// 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
}
2024-01-01 09:26:31 -07:00
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 {
2024-01-01 09:26:31 -07:00
rows, cols := m.Size()
start := Point{}
target := Point{rows - 1, cols - 1}
2023-12-26 12:41:46 -07:00
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})
2023-12-29 21:10:59 -07:00
log("total map reads = ", g.reads, "cost = ", cost)
printGraph(m, path, closed, g.seenFn)
2023-12-26 12:41:46 -07:00
2024-01-01 09:26:31 -07:00
return int(cost)
}
2023-12-29 21:10:59 -07:00
// printGraph with the path/cost overlay
func printGraph(m Map, path []position, closed map[position]int16, seenFn func(a position) position) {
2024-01-01 09:26:31 -07:00
pts := make(map[Point]position, len(path))
for _, pt := range path {
pts[pt.loc] = pt
}
2023-12-26 12:41:46 -07:00
clpt := make(map[position]position, len(closed))
for pt := range closed {
clpt[position{loc: pt.loc, steps: pt.steps}] = pt
}
2024-01-01 09:26:31 -07:00
for r, row := range m {
if r == 0 {
for c := range row {
if c == 0 {
fmt.Print(" ")
}
fmt.Printf("% 5d", c)
}
fmt.Println("")
}
2024-01-01 09:26:31 -07:00
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])
2024-01-01 09:26:31 -07:00
continue
}
2023-12-26 12:41:46 -07:00
fmt.Print(" ....")
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
fmt.Println("")
2023-12-26 12:41:46 -07:00
}
2024-01-01 09:26:31 -07:00
fmt.Println("")
2023-12-26 12:41:46 -07:00
}