-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
425 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
|
||
## Day 16: Reindeer Maze | ||
|
||
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. | ||
|
||
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? | ||
|
||
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). | ||
|
||
To figure out the best place to sit, you start by grabbing a map (your puzzle input) from a nearby kiosk. For example: | ||
|
||
```txt | ||
############### | ||
#.......#....E# | ||
#.#.###.#.###.# | ||
#.....#.#...#.# | ||
#.###.#####.#.# | ||
#.#.#.......#.# | ||
#.#.#####.###.# | ||
#...........#.# | ||
###.#.#####.#.# | ||
#...#.....#.#.# | ||
#.#.#.###.#.#.# | ||
#.....#...#.#.# | ||
#.###.#.#.#.#.# | ||
#S..#.....#...# | ||
############### | ||
``` | ||
|
||
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: | ||
|
||
```txt | ||
############### | ||
#.......#....E# | ||
#.#.###.#.###^# | ||
#.....#.#...#^# | ||
#.###.#####.#^# | ||
#.#.#.......#^# | ||
#.#.#####.###^# | ||
#..>>>>>>>>v#^# | ||
###^#.#####v#^# | ||
#>>^#.....#v#^# | ||
#^#.#.###.#v#^# | ||
#^....#...#v#^# | ||
#^###.#.#.#v#^# | ||
#S..#.....#>>^# | ||
############### | ||
``` | ||
|
||
Here's a second example: | ||
|
||
```txt | ||
################# | ||
#...#...#...#..E# | ||
#.#.#.#.#.#.#.#.# | ||
#.#.#.#...#...#.# | ||
#.#.#.#.###.#.#.# | ||
#...#.#.#.....#.# | ||
#.#.#.#.#.#####.# | ||
#.#...#.#.#.....# | ||
#.#.#####.#.###.# | ||
#.#.#.......#...# | ||
#.#.###.#####.### | ||
#.#.#...#.....#.# | ||
#.#.#.#####.###.# | ||
#.#.#.........#.# | ||
#.#.#.#########.# | ||
#S#.............# | ||
################# | ||
``` | ||
|
||
In this maze, the best paths cost `11048` points; following one such path would look like this: | ||
|
||
```txt | ||
################# | ||
#...#...#...#..E# | ||
#.#.#.#.#.#.#.#^# | ||
#.#.#.#...#...#^# | ||
#.#.#.#.###.#.#^# | ||
#>>v#.#.#.....#^# | ||
#^#v#.#.#.#####^# | ||
#^#v..#.#.#>>>>^# | ||
#^#v#####.#^###.# | ||
#^#v#..>>>>^#...# | ||
#^#v###^#####.### | ||
#^#v#>>^#.....#.# | ||
#^#v#^#####.###.# | ||
#^#v#^........#.# | ||
#^#v#^#########.# | ||
#S#>>^..........# | ||
################# | ||
``` | ||
|
||
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. | ||
|
||
Analyze your map carefully. What is the lowest score a Reindeer could possibly get? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package day16 | ||
|
||
import ( | ||
"slices" | ||
"strings" | ||
) | ||
|
||
const EMPTY = '.' | ||
const END = 'E' | ||
const START = 'S' | ||
|
||
type direction uint8 | ||
|
||
const ( | ||
EAST direction = iota | ||
SOUTH | ||
WEST | ||
NORTH | ||
) | ||
|
||
type point struct { | ||
x, y int | ||
} | ||
|
||
func (p point) valid(w, h int) bool { | ||
return p.x >= 0 && p.y >= 0 && p.x < w && p.y < h | ||
} | ||
|
||
type maze struct { | ||
emptySpaces map[point]bool | ||
start point | ||
end point | ||
width int | ||
height int | ||
} | ||
|
||
type thread struct { | ||
p point | ||
dir direction | ||
moves uint | ||
turns uint | ||
} | ||
|
||
func (t thread) cost() uint { | ||
return t.moves + 1000*t.turns | ||
} | ||
|
||
func Solve(input string) uint { | ||
m := parseInput(input) | ||
return solve(m) | ||
} | ||
|
||
func parseInput(input string) maze { | ||
var result maze | ||
result.emptySpaces = make(map[point]bool) | ||
|
||
result.height = len(input) | ||
for j, line := range strings.Split(input, "\n") { | ||
result.width = len(line) | ||
for i, r := range line { | ||
if r == EMPTY || r == START || r == END { | ||
result.emptySpaces[point{x: i, y: j}] = true | ||
if r == START { | ||
result.start = point{x: i, y: j} | ||
} else if r == END { | ||
result.end = point{x: i, y: j} | ||
} | ||
} | ||
} | ||
} | ||
return result | ||
} | ||
|
||
func solve(m maze) uint { | ||
seenSpaces := make(map[point]uint) | ||
seenSpaces[m.start] = 1 | ||
threads := []thread{{p: m.start, dir: EAST}} | ||
var solution uint = 0 | ||
|
||
for { | ||
nextThreads := make([]thread, 0) | ||
for _, t := range threads { | ||
if t.p == m.end { | ||
if solution == 0 || t.cost() < solution { | ||
solution = t.cost() | ||
} | ||
} else { | ||
nextThreads = append(nextThreads, calcNextMoves(m, t, seenSpaces)...) | ||
} | ||
} | ||
|
||
if len(nextThreads) == 0 { | ||
if solution == 0 { | ||
panic("No solution with no more spaces to check") | ||
} | ||
return solution | ||
} | ||
for i, t := range nextThreads { | ||
if solution > 0 && t.cost() >= solution { | ||
nextThreads = slices.Delete(nextThreads, i, i) | ||
} | ||
if seenSpaces[t.p] == 0 || t.cost() < seenSpaces[t.p] { | ||
seenSpaces[t.p] = t.cost() | ||
} | ||
} | ||
threads = nextThreads | ||
} | ||
} | ||
|
||
func calcNextMoves(m maze, curr thread, seenSpaces map[point]uint) []thread { | ||
nextMoves := make([]thread, 0) | ||
possibilities := []thread{{p: point{x: curr.p.x + 1, y: curr.p.y}, dir: EAST, moves: curr.moves, turns: curr.turns}, | ||
{p: point{x: curr.p.x, y: curr.p.y + 1}, dir: SOUTH, moves: curr.moves, turns: curr.turns}, | ||
{p: point{x: curr.p.x - 1, y: curr.p.y}, dir: WEST, moves: curr.moves, turns: curr.turns}, | ||
{p: point{x: curr.p.x, y: curr.p.y - 1}, dir: NORTH, moves: curr.moves, turns: curr.turns}} | ||
for i, t := range possibilities { | ||
if t.p.valid(m.width, m.height) && m.emptySpaces[t.p] { | ||
t.moves++ | ||
if i%2 != int(curr.dir)%2 { | ||
t.turns++ | ||
} else if int(curr.dir) != i { | ||
t.turns += 2 | ||
} | ||
if seenSpaces[t.p] == 0 || t.cost() < seenSpaces[t.p] { | ||
nextMoves = append(nextMoves, t) | ||
} | ||
} | ||
} | ||
return nextMoves | ||
} |
Oops, something went wrong.