Sometimes in Advent of Code there's a pathfinding thing. So I'll keep some pathfinding code here.
It's a bit much, so I'm putting it here instead of where the other AoC code for copypasting is. I think it's probably Dijkstra's algorithm, or at least something along those lines.
Typically we're dealing with 2D positions and directions. Vectors:
And typically the nodes in the graph are not 2D positions, but more than that. If turning is an action with a cost, the nodes probably consist of positions and directions, and the neighbours of a node will include "turned" notes with the same position. Last year it was something like position and direction and momentum, along with some peculiar rules concerning the momentum stuff. It was a little confusing. Either way, it is good to figure out what should go in a node, and not just go "oh, 2D-map, so the nodes are x,y-positions."
Our node will be a thing, with positions and directions:
HERE'S SOME LAZINESS:
We'll discover neighbouring nodes through a neighbours function and add them to the set of open nodes. And use a priority queue kind of thing for the set of open nodes. I don't think this is a very smart implementation of the queue, but it seems fine so far. It keeps track of the lowest and highest costs in the queue and for each cost present it keeps a linked list (in lists) with all the nodes that cost that much. Something like that.
It's not a general purpose queue and I tend bake in some more pathfinding stuff. Here I'm putting the seen/visited nodes in there as well, so I can add stuff to the queue and then the queue just won't bother with it if I shouldn't have added it. Stuff like that. Also information about how we got to each of those nodes (the from stuff below). Can add and remove and modify stuff to suit the puzzle. For the example it's fun to have something we can reconstruct a path with.
I might be doing something a bit muddy and weird here. But I'm not sure if it is and if it is I'm not sure how weird. Anyway the distinction between a node we've seen since we've added it as a neighbour and a visited/closed node that we know we've got the lowest possible cost for might not be that clear. The if seen then bit takes care of stuff if we find the same node with a lower cost later, and we only know that we've got the shortest part to a node when we get it from the queue, not when it's added to seen. I think that's fine? But I dunno.
(Things depend on the map/graph and in this example I don't think the "lower cost for previously seen node" case comes up, so I could get away with renaming seen to visited or closed remove a bunch if stuff from the if seen then part. But I won't.)
Pretty objecty. Ok.
Map-reading and map-writing. S and E for start and end positions:
The neighbours function gets neighbours of a node along with the cost for going to them:
One step picks a minimally expensive thing from the queue and then adds its neighbours to the queue. Returns the thing and its cost. Caller can decide if we're done or wanna keep stepping.
And then a function that does the stuff. Reads a map, does the steps until we get to the end positions, reconstructs a path and prints stuff.
(This should find all paths from start to end. But we're reconstructing only one of them. Just, needed all paths for this year's puzzle, so it's there.)