Files
adventofcode/2024/go/day20/day20.go
2025-02-14 15:14:46 +00:00

221 lines
5.0 KiB
Go

package day20
import (
"adventofcode2024/utils/dijkstra"
"adventofcode2024/utils/grid2d"
"adventofcode2024/utils/inputs"
"fmt"
)
type (
Vec struct{ x, y int }
Dir struct{ x, y int }
Cell rune
Move struct {
p Vec
d Dir
}
Maze struct {
height, width int
start Vec
end Vec
grid *grid2d.Grid[Cell]
}
)
const (
WALL Cell = '#'
CORRIDOR Cell = '.'
START Cell = 'S'
END Cell = 'E'
PATH Cell = '*'
)
var (
NORTH = Dir{0, -1}
EAST = Dir{1, 0}
SOUTH = Dir{0, 1}
WEST = Dir{-1, 0}
DIRECTIONS = []Dir{NORTH, EAST, SOUTH, WEST}
)
func Part1(input string) int {
maze := Maze{}.Parse(input)
fmt.Println(maze)
data := inputGraph(*maze)
graph := dijkstra.CreateGraph(data)
path, dist := dijkstra.GetShortestPath(data.From, data.To, graph)
for _, p := range path {
maze.grid.Set(p.X, p.Y, PATH)
}
fmt.Println(maze)
return dist
}
func Part2(input string) int {
const saves int = 100
count := 0
maze := Maze{}.Parse(input)
// fmt.Println(maze)
data := inputGraph(*maze)
graph := dijkstra.CreateGraph(data)
path, dist := dijkstra.GetShortestPath(data.From, data.To, graph)
cost := make(map[Vec]int)
cheats := []Cheat{}
for i, p := range path {
cost[Vec{p.X, p.Y}] = i
}
cheat_data := cheatGraph(*maze)
for _, s := range path[:len(path)-saves] {
fmt.Println("Checking start:", s)
for _, e := range path[saves:] {
fmt.Println("\tChecking end", e)
cheat_data_1 := cheatGraphAdd(maze, cheat_data, s)
cheat_data_2 := cheatGraphAdd(maze, cheat_data_1, e)
cheat_graph := dijkstra.CreateGraph(cheat_data_2)
_, cheat_dist := dijkstra.GetShortestPath(dijkstra.Point{X: s.X, Y: s.Y}, dijkstra.Point{X: e.X, Y: e.Y}, cheat_graph)
fmt.Println("\t\tcheat distance:", cheat_dist)
if cheat_dist <= 20 {
cheat := Cheat{start: Vec{s.X, s.Y}, end: Vec{e.X, e.Y}, cost: cost[Vec{s.X, s.Y}] + cheat_dist + dist - cost[Vec{e.X, e.Y}]}
fmt.Println("\t\tSaving cheat:", cheat)
cheats = append(cheats, cheat)
}
}
}
for _, cheat := range cheats {
save := dist - cheat.cost
// fmt.Println("[", i, "]", "save", save)
if save >= saves {
count++
}
}
return count
}
func abs(in int) int {
if in < 0 {
return in * -1
}
return in
}
func inputGraph(maze Maze) dijkstra.InputGraph {
data := dijkstra.InputGraph{}
data.From = dijkstra.Point{X: maze.start.x, Y: maze.start.y}
data.To = dijkstra.Point{X: maze.end.x, Y: maze.end.y}
for y := 0; y < maze.height; y++ {
for x := 0; x < maze.width; x++ {
if maze.grid.Get(x, y) == WALL {
continue
}
for _, dir := range DIRECTIONS {
nx, ny := x+dir.x, y+dir.y
if nx < 0 || ny < 0 || nx >= maze.width || ny >= maze.height {
continue
}
if maze.grid.Get(nx, ny) == WALL {
continue
}
data.Graph = append(data.Graph, dijkstra.InputData{
Source: dijkstra.Point{X: x, Y: y},
Destination: dijkstra.Point{X: nx, Y: ny},
Weight: 1,
})
}
}
}
return data
}
func cheatGraph(maze Maze) dijkstra.InputGraph {
data := dijkstra.InputGraph{}
for y := 0; y < maze.height; y++ {
for x := 0; x < maze.width; x++ {
if maze.grid.Get(x, y) == CORRIDOR {
continue
}
for _, dir := range DIRECTIONS {
nx, ny := x+dir.x, y+dir.y
if nx < 0 || ny < 0 || nx >= maze.width || ny >= maze.height {
continue
}
if maze.grid.Get(nx, ny) == CORRIDOR {
continue
}
data.Graph = append(data.Graph, dijkstra.InputData{
Source: dijkstra.Point{X: x, Y: y},
Destination: dijkstra.Point{X: nx, Y: ny},
Weight: 1,
})
}
}
}
return data
}
func cheatGraphAdd(maze *Maze, in dijkstra.InputGraph, s dijkstra.Point) dijkstra.InputGraph {
x := s.X
y := s.Y
out := dijkstra.InputGraph{}
out.From = in.From
out.To = in.To
out.Graph = append([]dijkstra.InputData{}, in.Graph...)
for _, dir := range DIRECTIONS {
nx, ny := x+dir.x, y+dir.y
if nx < 0 || ny < 0 || nx >= maze.width || ny >= maze.height {
continue
}
if maze.grid.Get(nx, ny) == CORRIDOR {
continue
}
out.Graph = append(out.Graph, dijkstra.InputData{
Source: dijkstra.Point{X: x, Y: y},
Destination: dijkstra.Point{X: nx, Y: ny},
Weight: 1,
})
}
return out
}
func (m Maze) Parse(input string) *Maze {
m.grid = inputs.ToGrid2D(input, "\n", "", '?', func(c string) Cell { return Cell(c[0]) })
m.height, m.width = m.grid.SizeY(), m.grid.SizeX()
for y := 0; y < m.height; y++ {
for x := 0; x < m.width; x++ {
c := m.grid.Get(x, y)
switch c {
case WALL, CORRIDOR:
m.grid.Set(x, y, c)
case START:
m.grid.Set(x, y, CORRIDOR)
m.start = Vec{x, y}
case END:
m.grid.Set(x, y, CORRIDOR)
m.end = Vec{x, y}
}
}
}
return &m
}
func (m *Maze) String() string {
s := ""
for y := 0; y < m.height; y++ {
for x := 0; x < m.width; x++ {
if y == m.start.y && x == m.start.x {
s += string(START)
} else if y == m.end.y && x == m.end.x {
s += string(END)
} else {
s += string(m.grid.Get(x, y))
}
}
s += "\n"
}
return s
}