Oh, the weather outside is frightful. But 2D grids are delightful. Since CPU temp is high, let it fry, let it fry, let it fry.
The plan is clear - for each day a puzzle, let’s solve them all. By now, I have some experience as well as a good tool set to tackle these problems. I know I don’t have the time at the start of the day, so no fast solves for me. Let’s complete the puzzles and enjoy the ride.
So I did. Minor spoilers ahead, major spoilers in footnotes. If you like programming puzzles, I encourage you to try solving the problems first - they are available in the whole year and free as well!
And while we’re here: Happy new year! May your code be secure, your monitors silent and your users vigilant. Thank You, dear readers, for reading and I hope to improve your days in 2025 at least a bit.
The Story (In The Past?)
The chief historian is missing! He got lost somewhere, or maybe sometime. Equipped with a time machine and an army of (un)helpful historian elves, we go back, back to the past.
As this was a celebration of the past ten years, it makes sense to recall history and jump to the past to remember the fun and the frustration. It was a lot of the fun, even though I didn’t solve all of the years. I was happy to see the Plutonian civilization again (day 11) as well as the onsen (day 19) or the Reindeer-Class Starship (day 21).
A small nitpick is that the core motivation is to find some random person that is formally required for the launch, even though we could just launch. It hits a little too close to home, you know?
Hope You Like 2D Grids
This year I’ve counted 11 puzzles that in some way, shape or form used a 2D map. I was so happy - the refactor I did for the tooling paid off massively. The warm fuzzy feeling of: “how great it is that I don’t have to write yet another flood or ordering of all points with all of the off-by-one errors it causes” really brought joy to me this year. I highly recommend to prepare such a library if you’ll be solving these.
Even though the parsing of these problems was easy, some of them were challenging. Some of them were tricky as ever and I was pleasantly surprised that no two map puzzles felt the same.
Day 14 - Robot Swarm
I think I’ve found my postcard for this year - day 14. In part two, the arrangement of the robots will draw a Christmas tree, so here it is:
I was thinking about drawing this with pixels, but then I’ve realized I’d have to detect the tree somehow. It’s easier for me to just keep it ASCII.
Day 15 - Sokoban
Continuing with the cool visuals - I loved day 15. You receive a set of instructions and a warehouse full of boxes. Then you push them around. It was slightly annoying as the boxes now span two points instead of one, but the visualization is too cool not to include (yes, I drew it myself):
Notice the little F in the first column - this makes it so that if you hold “N” or “Shift+N’ in vim after searching for the “F#” regex you can wind and rewind the full animation from the log file and “easily” view weird parts when debugging.
Day 21 - It’s Robots All The Way Down
The hardest puzzle this year also falls into this category - day 21. You’ve got series of robots that press buttons on two keypads:
Number Keypad Robot Keypad
+---+---+---+ +---+---+
| 7 | 8 | 9 | | ^ | A |
+---+---+---+ +---+---+---+
| 4 | 5 | 6 | | < | v | > |
+---+---+---+ +---+---+---+
| 1 | 2 | 3 |
+---+---+---+
| 0 | A |
+---+---+
You need to input a short code on the number keypad by using the robot - the arrows move the position of the hand between the buttons and the “A” button presses. Unfortunately, there are more robots in the way that are connected using these interfaces. Moreover, if you ever access the missing key, the robot breaks.
So, you’ll need to figure out the minimal number of key presses so that the last robot enters the correct code. Fortunately, as only the number of presses is required, you can use dynamic programming. It’s still a head-scratcher though - the space is huge1.
I threw everything I knew at it - maps, graphs, OOP (lol nope), recursion (no dice), functools
cache... It was very satisfying to see this run under a second. I was exhausted, but proud.
More Goodies - Mostly Dynamic
I’ve enjoyed the day 9, which was about a disk defragmentation. I am old enough to remember this manoeuvre as I did it a couple of times in the past.
A hard puzzle was the search for a “quine” - day 17. I know it is not a pure quine, but I don’t have a better cool-sounding pretentious foreign expression. That was a real fight. Not only you need to code a small emulator in part 1, you also have to reverse-engineer the code you are emulating in part 2. Trust me, I’ve tried to brute-force the part 2 and it wasn’t looking good. Aside from “use dynamic programming” I don’t know how to spoil it without flat-out giving the solution.
Speaking of dynamic programming, that was a weak point for me, but as I’ve encountered a couple of these something clicked. Then I was able to dispatch day 22 with ease that surprised me. Still speaking of dynamic programming, I am not completely sure how to explain what it is and I don’t think Wikipedia did it either. To me, it’s about counting the lengths/counts iteratively in a cache somehow instead of using recursion. That being said, I still do the recursive implementation first and then rewrite it.
I’ll say it - I disliked day 23, even though I managed to code it in 30 minutes or so. I was able to recite the correct spell so the problem folded. I am confident that if I had to code the core myself, it would blow up straight into my face hard2.
Last curveball that hit my face was day 24, part 2. Another one of those - simulate in part 1 and then tackle the system in a new creative way - specifically, it’s a logic gate calculator that has a couple of wrongly wired nodes. It’s a hard but fantastic puzzle, very creative and with an amazing insight in the end3.
Tooling Updates
There were some small updates in the tooling - most too minor to note. I am stupidly happy with this one though - I added a gold star to the successful solve message:
I’ve also found a nice Reddit discussion where they’ve talked about general concepts needed to solve Advent of Code puzzles - I’ve included it in the library as a text file, maybe someone will read it.
Slightly related here - one of the better decisions was to include tqdm in the mix. Having a progress bar that is accurate and reliable is a godsend. It helped me realize I need to refactor plenty of days as well as realizing I only have to brew a coffee on day 14 to get my solution.
Is This.. Pleasant?
If last year was a marathon for me, this year felt more like a jog. Everything was just easier, smoother. I didn’t encounter that many walls and I was happy to rely on my toolset more often than not. Hell, I might even start one of the older years to complete the collection.
Maybe one year I’ll try some sprinting to see if I can grab a leaderboard spot or two. Probably not though, there are many great people that I can’t compete with. Not only that, but this year was the first one when people massively complained about the LLMs solving the issues too fast.
For now, let’s just enjoy the rest of the year. As always, all the code is on Github. Checkout also the tooling article for more details.
There are some properties that are helpful - once you realize you always travel between the pairs of buttons and that it’s always separated by the “A” presses, you can split the full command string into subparts (don’t forget to append those “A”s). This makes the search space bearable.
It’s a graph. Find maximal clique using the greedy algorithm. There, that’s the spell. If you know, it’s trivial, if you don’t… good luck.
After you draw a smaller example (say 6 bits), you can discover a few properties of the system (XOR gates output to output, OR gates shouldn’t output to AND gates, AND gates should output to OR gates…). Fortunately, detecting these discrepancies is enough - no need to actually fix the system.