Blog

Advent of Code day 16: Curses!

16 Dec, 2019
Xebia Background Header Wave

I knew it had to come someday: those puzzles where you run around on a character-based grid. Day 15 was one of those. I’m definitely not one of those people who just pull maze traversal algorithms out of thin air, and to learn them I read an overview of the algorithm and then try to implement is. And then start with the headache of why things aren’t working, or your robot just walks up and down along 2 coordinates, etc. etc.

So again I needed visualization to help me grok things. First try: keep it simple. Just use a whole bunch of print() statements, and combined with some manual path programming I thought I was doing well. Haha Mr. Wastl, I defy your fiendish puzzle! I’ll just walk manually! I thought I was Mr. Smartypants for keeping things so simple, but I just had to keep walking… and walking… and… well.

def manual_route():
    top_cap = '131143133131413131231231231231323242323132324232313'
    to_bottom = '23234234234234234234232424122323232323232324124223242424'
    right_cave = '143143143114142424242142424242424141413123123123231314131313232232323131131'
    finish_bottom = '313232323224231313131313413141441413413413141414242424242441414141141424141131313133131444'
    finish_top_left = '131331313323223232324432332322323112222424'
    backtrack = '3311441111441144222242444'
    explore_right = '41421421421421421421424141341341413131323233131331311414141314141414414141313313143143143143141'
    from_chimney = '31323131431431413131231231231232313131414141441411'
    t_split_left = '3131313131231231231323243234232424112224232424242424242223233232324244242342342424111122223323231'
    appendix = '232424331131331314431313131323233131331314131323131413131231232313131414141441413141424142342423232324242421141414141133'
    backtrack_to_t = '442233333311441133'
    going_up_from_t = '1113134331313413411'
    yet_another_t = '4141221133313123123123123132323242424242232342331312323131443331313232323423423423423423242'
    going_down_left = '414111222242423242412412421413344224242423232323323244323323232322232312312323131413133'
    route = top_cap + to_bottom + right_cave + finish_bottom + finish_top_left + backtrack + explore_right + from_chimney + t_split_left + appendix + backtrack_to_t + going_up_from_t + yet_another_t + going_down_left
    return route

Yes, I really was that dumb to go “3”, Ctrl-R, “41”, Ctrl-R, <Backspace>2, Ctrl-R… All the above was my manual exploration of the maze. I was sort of Zen, I admit 🙂

Soo… it was time for the real thing. A wall hugging algorithm and curses. I prefer to use as few external libraries as I can, so I turned to the standard curses module. This was slightly more complicated than my use of Pillow because Unix and history and terminals and other weirdness. Like every coordinate being in y, x order. Because reasons.

curses output of Advent of Code puzzle day 16 part 2. Maze filled with oxygen.
We’re safe! The maze is filled with oxygen! And long live \u2588, the Full Block Unicode character!

There’s actually quite a lot of nifty things you can do with curses, like windows and flashing characters, but I chose to keep it tightly coupled to puzzle solving. This means that you should just be able to pass in the puzzle’s coordinate system and projecting that onto the screen is done for you.

Another part is handling the initialization and teardown, which is a bit fiddly, but clearly explained in the docs. Because of this I chose to implement it as a ContextManager by implementing the __enter__() and __exit__() methods. I really like how convenient the with... syntax is.

curses has a nice abstraction to deal with large areas that fall outside the projectable area, a pad. This means we don’t have to worry about the screen size, we can call a refresh() with parameters that allow us to specify which part of the pad to display.

I’m pretty happy with how the convenience class turned out. Here’s a simple way to plot a bunch of points in a list.

bounds = CursesVisualizer.boundaries(list_of_points, padding=1)
with CursesVisualizer(bounds) as cv:
    for point in list_of_points:
        cv.plot(point, '#')
    cv.refresh()

Have a look at the convenience class here: have fun cursing!

Questions?

Get in touch with us to learn more about the subject and related solutions

Explore related posts