Skip to content

Commit 8d21a04

Browse files
committed
feat: Solving day 16
1 parent e13d780 commit 8d21a04

File tree

3 files changed

+425
-0
lines changed

3 files changed

+425
-0
lines changed

docs/day16.md

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
2+
## Day 16: Reindeer Maze
3+
4+
It's time again for the Reindeer Olympics! This year, the big event is the Reindeer Maze, where the Reindeer compete for the lowest score.
5+
6+
You and The Historians arrive to search for the Chief right as the event is about to start. It wouldn't hurt to watch a little, right?
7+
8+
The Reindeer start on the Start Tile (marked `S`) facing East and need to reach the End Tile (marked `E`). They can move forward one tile at a time (increasing their score by `1` point), but never into a wall (`#`). They can also rotate clockwise or counterclockwise 90 degrees at a time (increasing their score by `1000` points).
9+
10+
To figure out the best place to sit, you start by grabbing a map (your puzzle input) from a nearby kiosk. For example:
11+
12+
```txt
13+
###############
14+
#.......#....E#
15+
#.#.###.#.###.#
16+
#.....#.#...#.#
17+
#.###.#####.#.#
18+
#.#.#.......#.#
19+
#.#.#####.###.#
20+
#...........#.#
21+
###.#.#####.#.#
22+
#...#.....#.#.#
23+
#.#.#.###.#.#.#
24+
#.....#...#.#.#
25+
#.###.#.#.#.#.#
26+
#S..#.....#...#
27+
###############
28+
```
29+
30+
There are many paths through this maze, but taking any of the best paths would incur a score of only `7036`. This can be achieved by taking a total of `36` steps forward and turning 90 degrees a total of `7` times:
31+
32+
```txt
33+
###############
34+
#.......#....E#
35+
#.#.###.#.###^#
36+
#.....#.#...#^#
37+
#.###.#####.#^#
38+
#.#.#.......#^#
39+
#.#.#####.###^#
40+
#..>>>>>>>>v#^#
41+
###^#.#####v#^#
42+
#>>^#.....#v#^#
43+
#^#.#.###.#v#^#
44+
#^....#...#v#^#
45+
#^###.#.#.#v#^#
46+
#S..#.....#>>^#
47+
###############
48+
```
49+
50+
Here's a second example:
51+
52+
```txt
53+
#################
54+
#...#...#...#..E#
55+
#.#.#.#.#.#.#.#.#
56+
#.#.#.#...#...#.#
57+
#.#.#.#.###.#.#.#
58+
#...#.#.#.....#.#
59+
#.#.#.#.#.#####.#
60+
#.#...#.#.#.....#
61+
#.#.#####.#.###.#
62+
#.#.#.......#...#
63+
#.#.###.#####.###
64+
#.#.#...#.....#.#
65+
#.#.#.#####.###.#
66+
#.#.#.........#.#
67+
#.#.#.#########.#
68+
#S#.............#
69+
#################
70+
```
71+
72+
In this maze, the best paths cost `11048` points; following one such path would look like this:
73+
74+
```txt
75+
#################
76+
#...#...#...#..E#
77+
#.#.#.#.#.#.#.#^#
78+
#.#.#.#...#...#^#
79+
#.#.#.#.###.#.#^#
80+
#>>v#.#.#.....#^#
81+
#^#v#.#.#.#####^#
82+
#^#v..#.#.#>>>>^#
83+
#^#v#####.#^###.#
84+
#^#v#..>>>>^#...#
85+
#^#v###^#####.###
86+
#^#v#>>^#.....#.#
87+
#^#v#^#####.###.#
88+
#^#v#^........#.#
89+
#^#v#^#########.#
90+
#S#>>^..........#
91+
#################
92+
```
93+
94+
Note that the path shown above includes one 90 degree turn as the very first move, rotating the Reindeer from facing East to facing North.
95+
96+
Analyze your map carefully. What is the lowest score a Reindeer could possibly get?

src/day16/day16.go

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package day16
2+
3+
import (
4+
"slices"
5+
"strings"
6+
)
7+
8+
const EMPTY = '.'
9+
const END = 'E'
10+
const START = 'S'
11+
12+
type direction uint8
13+
14+
const (
15+
EAST direction = iota
16+
SOUTH
17+
WEST
18+
NORTH
19+
)
20+
21+
type point struct {
22+
x, y int
23+
}
24+
25+
func (p point) valid(w, h int) bool {
26+
return p.x >= 0 && p.y >= 0 && p.x < w && p.y < h
27+
}
28+
29+
type maze struct {
30+
emptySpaces map[point]bool
31+
start point
32+
end point
33+
width int
34+
height int
35+
}
36+
37+
type thread struct {
38+
p point
39+
dir direction
40+
moves uint
41+
turns uint
42+
}
43+
44+
func (t thread) cost() uint {
45+
return t.moves + 1000*t.turns
46+
}
47+
48+
func Solve(input string) uint {
49+
m := parseInput(input)
50+
return solve(m)
51+
}
52+
53+
func parseInput(input string) maze {
54+
var result maze
55+
result.emptySpaces = make(map[point]bool)
56+
57+
result.height = len(input)
58+
for j, line := range strings.Split(input, "\n") {
59+
result.width = len(line)
60+
for i, r := range line {
61+
if r == EMPTY || r == START || r == END {
62+
result.emptySpaces[point{x: i, y: j}] = true
63+
if r == START {
64+
result.start = point{x: i, y: j}
65+
} else if r == END {
66+
result.end = point{x: i, y: j}
67+
}
68+
}
69+
}
70+
}
71+
return result
72+
}
73+
74+
func solve(m maze) uint {
75+
seenSpaces := make(map[point]uint)
76+
seenSpaces[m.start] = 1
77+
threads := []thread{{p: m.start, dir: EAST}}
78+
var solution uint = 0
79+
80+
for {
81+
nextThreads := make([]thread, 0)
82+
for _, t := range threads {
83+
if t.p == m.end {
84+
if solution == 0 || t.cost() < solution {
85+
solution = t.cost()
86+
}
87+
} else {
88+
nextThreads = append(nextThreads, calcNextMoves(m, t, seenSpaces)...)
89+
}
90+
}
91+
92+
if len(nextThreads) == 0 {
93+
if solution == 0 {
94+
panic("No solution with no more spaces to check")
95+
}
96+
return solution
97+
}
98+
for i, t := range nextThreads {
99+
if solution > 0 && t.cost() >= solution {
100+
nextThreads = slices.Delete(nextThreads, i, i)
101+
}
102+
if seenSpaces[t.p] == 0 || t.cost() < seenSpaces[t.p] {
103+
seenSpaces[t.p] = t.cost()
104+
}
105+
}
106+
threads = nextThreads
107+
}
108+
}
109+
110+
func calcNextMoves(m maze, curr thread, seenSpaces map[point]uint) []thread {
111+
nextMoves := make([]thread, 0)
112+
possibilities := []thread{{p: point{x: curr.p.x + 1, y: curr.p.y}, dir: EAST, moves: curr.moves, turns: curr.turns},
113+
{p: point{x: curr.p.x, y: curr.p.y + 1}, dir: SOUTH, moves: curr.moves, turns: curr.turns},
114+
{p: point{x: curr.p.x - 1, y: curr.p.y}, dir: WEST, moves: curr.moves, turns: curr.turns},
115+
{p: point{x: curr.p.x, y: curr.p.y - 1}, dir: NORTH, moves: curr.moves, turns: curr.turns}}
116+
for i, t := range possibilities {
117+
if t.p.valid(m.width, m.height) && m.emptySpaces[t.p] {
118+
t.moves++
119+
if i%2 != int(curr.dir)%2 {
120+
t.turns++
121+
} else if int(curr.dir) != i {
122+
t.turns += 2
123+
}
124+
if seenSpaces[t.p] == 0 || t.cost() < seenSpaces[t.p] {
125+
nextMoves = append(nextMoves, t)
126+
}
127+
}
128+
}
129+
return nextMoves
130+
}

0 commit comments

Comments
 (0)