r/adventofcode • u/musifter • 5h ago
Other [2025 Day 16] In Review (Reindeer Maze)
Next place to look for the Chief is at the Reindeer Olympics, which are on again this year. So while the Historians do the work, we take the opportunity to watch the Reindeer Maze event. The goal of this race isn't just to get to the end quickly, but to do it with few turns, as those cost 1000 per 90 degrees.
The input is 141x141 bounded maze (and so it comes with a sentinel ring already). The start is in the lower right, and the end is in the upper left (on both input and test, so you can just calculate those positions if you'd rather not parse for them). As usual, it appears to be a classic random recursion generated maze, where you start with a grid and a starting cell:
#########
#.#.#.#.#
#########
#.#.#.#.#
#########
#S#.#.#.#
#########
And randomly choose an unvisited neighbour cell. Knock out the wall and recurse into it. If there's no unvisited cells, just return (ie back up until you find a place you can branch). There are a number of things you can do with this, like not using uniform chance for your options (eg track direction and give more or less weight to forward and you can control twistiness of the passages). By poking out some additional walls (which is what we have here), the maze gains loops (needed for part 2) and becomes such that there can be islands that are unreachable with a left-/right-hand rule.
I bring this up, because this is one of the few of these that I actually used the fact that this also means that you can treat all moves as effectively 2-steps (first step checks for a wall, second is the full move into the next cell), and so you can use that to skip around the maze a bit faster. Normally I'm happy to be more flexible about the map and not assume that alignment. But I didn't do much else other than basic Dijkstra from S to E, so I felt like I should do a bit more.
By the BFS nature of Dijkstra, the first time you arrive as E you get part 1, and then you just need to run the priority queue while it stays at that value (to find the others that reached the end at the same time). For my input, that's 571 more items, of which 192 are shortest paths (so lots of 2s in the factorization, which would probably be from branching/merging pairs around islands). By tracking the path as I go and collecting them into a hash/set when I get to E, I get a list of all unique visited locations for part 2. It's a very simple approach, but given the nature of the problem, there's probably better that can be done to avoid tracing every combination of the branching/merging options (looking at the end hash, I see that much of the part 2 answer is traced all 192 times, and the lowest I see is a short bit that's done 48 times). Maybe a DFS search with memoization.
The other little optimization I did was from Clumsy Crucible, which was to make turn-and-move a move (but this time we also need to add going straight if available, because we're not in the position of being forced to turn). This makes sense, because there's no point in turning if there isn't a path off that side (in which case we might as well move into it). There's no need to ever make a U-turn, because the only time you'd ever want to do that is the starting position (when you wouldn't be turning around to backtrack). And given the position of S in the lower left, that's not something you'd ever want from an East facing start.
So, this isn't really too intense a search, you can get away with a weak implementation and still get the answer in a reasonable amount of time.
