Skip to content

Commit d12dabe

Browse files
committed
[2024] Solution for Day 16
1 parent 99a8dfa commit d12dabe

File tree

3 files changed

+287
-1
lines changed

3 files changed

+287
-1
lines changed

2024/day16/main.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"math"
7+
"os"
8+
"slices"
9+
"strings"
10+
11+
"github.com/kfarnung/advent-of-code/2024/lib"
12+
)
13+
14+
type point struct {
15+
x, y int
16+
}
17+
18+
type seenKey struct {
19+
position point
20+
direction int
21+
}
22+
23+
type reindeer struct {
24+
position point
25+
direction int
26+
score int64
27+
path []point
28+
}
29+
30+
func part1(input string) int64 {
31+
maze, start, _ := parseInput(input)
32+
queue := []reindeer{{position: start, direction: 90, score: 0}}
33+
seen := make(map[seenKey]int64)
34+
35+
for len(queue) > 0 {
36+
current := queue[0]
37+
queue = queue[1:]
38+
39+
key := seenKey{current.position, current.direction}
40+
if score, ok := seen[key]; ok && current.score >= score {
41+
continue
42+
}
43+
44+
seen[key] = current.score
45+
46+
if maze[current.position.x][current.position.y] == 'E' {
47+
return current.score
48+
}
49+
50+
straight := getMove(current.position, current.direction)
51+
if isValid(straight, maze) {
52+
queue = append(queue, reindeer{
53+
position: straight,
54+
direction: current.direction,
55+
score: current.score + 1})
56+
}
57+
58+
left := getMove(current.position, lib.Mod(current.direction-90, 360))
59+
if isValid(left, maze) {
60+
queue = append(queue, reindeer{
61+
position: left,
62+
direction: lib.Mod(current.direction-90, 360),
63+
score: current.score + 1001})
64+
}
65+
66+
right := getMove(current.position, lib.Mod(current.direction+90, 360))
67+
if isValid(right, maze) {
68+
queue = append(queue, reindeer{
69+
position: right,
70+
direction: lib.Mod(current.direction+90, 360),
71+
score: current.score + 1001})
72+
}
73+
74+
slices.SortFunc(queue, func(i, j reindeer) int {
75+
return int(i.score - j.score)
76+
})
77+
}
78+
79+
log.Fatal("No path found")
80+
return int64(0)
81+
}
82+
83+
func part2(input string) int64 {
84+
maze, start, _ := parseInput(input)
85+
queue := []reindeer{{position: start, direction: 90, score: 0, path: []point{start}}}
86+
seen := make(map[seenKey]int64)
87+
var successPaths [][]point
88+
var successScore int64 = math.MaxInt64
89+
90+
for len(queue) > 0 {
91+
current := queue[0]
92+
queue = queue[1:]
93+
94+
if current.score > successScore {
95+
// We can't get a better score than we already have
96+
break
97+
}
98+
99+
key := seenKey{current.position, current.direction}
100+
score, ok := seen[key]
101+
if ok && current.score > score {
102+
continue
103+
}
104+
105+
seen[key] = current.score
106+
107+
if maze[current.position.x][current.position.y] == 'E' {
108+
successScore = current.score
109+
successPaths = append(successPaths, current.path)
110+
}
111+
112+
straight := getMove(current.position, current.direction)
113+
if isValid(straight, maze) {
114+
queue = append(queue, reindeer{
115+
position: straight,
116+
direction: current.direction,
117+
score: current.score + 1,
118+
path: duplicatePath(current.path, straight)})
119+
}
120+
121+
left := getMove(current.position, lib.Mod(current.direction-90, 360))
122+
if isValid(left, maze) {
123+
queue = append(queue, reindeer{
124+
position: left,
125+
direction: lib.Mod(current.direction-90, 360),
126+
score: current.score + 1001,
127+
path: duplicatePath(current.path, left)})
128+
}
129+
130+
right := getMove(current.position, lib.Mod(current.direction+90, 360))
131+
if isValid(right, maze) {
132+
queue = append(queue, reindeer{
133+
position: right,
134+
direction: lib.Mod(current.direction+90, 360),
135+
score: current.score + 1001,
136+
path: duplicatePath(current.path, right)})
137+
}
138+
139+
slices.SortFunc(queue, func(i, j reindeer) int {
140+
return int(i.score - j.score)
141+
})
142+
}
143+
144+
visited := make(map[point]bool)
145+
for _, path := range successPaths {
146+
for _, position := range path {
147+
visited[position] = true
148+
}
149+
}
150+
151+
return int64(len(visited))
152+
}
153+
154+
func duplicatePath(path []point, current point) []point {
155+
duplicate := append([]point(nil), path...)
156+
duplicate = append(duplicate, current)
157+
return duplicate
158+
}
159+
160+
func getMove(position point, direction int) point {
161+
switch direction {
162+
case 0:
163+
return point{position.x - 1, position.y}
164+
case 90:
165+
return point{position.x, position.y + 1}
166+
case 180:
167+
return point{position.x + 1, position.y}
168+
case 270:
169+
return point{position.x, position.y - 1}
170+
}
171+
172+
log.Fatalf("Invalid direction: %d", direction)
173+
return point{}
174+
}
175+
176+
func isValid(position point, maze [][]rune) bool {
177+
return position.x >= 0 && position.x < len(maze) &&
178+
position.y >= 0 && position.y < len(maze[position.x]) &&
179+
maze[position.x][position.y] != '#'
180+
}
181+
182+
func parseInput(input string) ([][]rune, point, point) {
183+
var data [][]rune
184+
var start, end point
185+
for i, line := range lib.SplitLines(input) {
186+
if len(line) == 0 {
187+
continue
188+
}
189+
190+
if startIndex := strings.Index(line, "S"); startIndex != -1 {
191+
start = point{i, startIndex}
192+
}
193+
194+
if endIndex := strings.Index(line, "E"); endIndex != -1 {
195+
end = point{i, endIndex}
196+
}
197+
198+
data = append(data, []rune(line))
199+
}
200+
201+
return data, start, end
202+
}
203+
204+
func main() {
205+
name := os.Args[1]
206+
content, err := lib.LoadFileContent(name)
207+
if err != nil {
208+
log.Fatal(err)
209+
}
210+
211+
fmt.Printf("Part 1: %d\n", part1(content))
212+
fmt.Printf("Part 2: %d\n", part2(content))
213+
}

2024/day16/main_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/kfarnung/advent-of-code/2024/lib"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
var input1 = strings.Join([]string{
12+
"###############",
13+
"#.......#....E#",
14+
"#.#.###.#.###.#",
15+
"#.....#.#...#.#",
16+
"#.###.#####.#.#",
17+
"#.#.#.......#.#",
18+
"#.#.#####.###.#",
19+
"#...........#.#",
20+
"###.#.#####.#.#",
21+
"#...#.....#.#.#",
22+
"#.#.#.###.#.#.#",
23+
"#.....#...#.#.#",
24+
"#.###.#.#.#.#.#",
25+
"#S..#.....#...#",
26+
"###############",
27+
"",
28+
}, "\n")
29+
30+
var input2 = strings.Join([]string{
31+
"#################",
32+
"#...#...#...#..E#",
33+
"#.#.#.#.#.#.#.#.#",
34+
"#.#.#.#...#...#.#",
35+
"#.#.#.#.###.#.#.#",
36+
"#...#.#.#.....#.#",
37+
"#.#.#.#.#.#####.#",
38+
"#.#...#.#.#.....#",
39+
"#.#.#####.#.###.#",
40+
"#.#.#.......#...#",
41+
"#.#.###.#####.###",
42+
"#.#.#...#.....#.#",
43+
"#.#.#.#####.###.#",
44+
"#.#.#.........#.#",
45+
"#.#.#.#########.#",
46+
"#S#.............#",
47+
"#################",
48+
"",
49+
}, "\n")
50+
51+
func TestPart1(t *testing.T) {
52+
assert.Equal(t, int64(7036), part1(input1))
53+
assert.Equal(t, int64(11048), part1(input2))
54+
55+
inputContent, err := lib.GetInputContent()
56+
if err != nil {
57+
t.Fatal(err)
58+
}
59+
60+
assert.Equal(t, int64(102504), part1(inputContent))
61+
}
62+
63+
func TestPart2(t *testing.T) {
64+
assert.Equal(t, int64(45), part2(input1))
65+
assert.Equal(t, int64(64), part2(input2))
66+
67+
inputContent, err := lib.GetInputContent()
68+
if err != nil {
69+
t.Fatal(err)
70+
}
71+
72+
assert.Equal(t, int64(535), part2(inputContent))
73+
}

private

0 commit comments

Comments
 (0)