My (attempted) solutions to the 2022 Advent of Code
Log | Files | Refs | README

```commit b0b0af461faa5613705241360c59e33741d28575
parent 09fc897885233cca0455911979cb1e31ef19731f
Date:   Thu, 29 Dec 2022 13:20:41 -0800

Solution to day 24, part 1.

Diffstat:
Asrc/day_24.jl | 194+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
```
```1 file changed, 194 insertions(+), 0 deletions(-)
diff --git a/src/day_24.jl b/src/day_24.jl
@@ -0,0 +1,194 @@
+#!/usr/bin/env julia
+using DataStructures
+
+    #.######
+    #>>.<^<#
+    #.<..<<#
+    #>v.><>#
+    #<^v^^>#
+    ######.#"""))
+
+# Each node in our graph is represented by a three-tuple of time, y, and x
+Node = Tuple{Int,Int,Int}
+
+# The blizzard map is represented by 4 boolean matrices, each indicating the
+# blizzards at each location moving in a specific direction. We also bundle
+# with it the current timestep represented by the map
+mutable struct BlizzardMap
+    blizzards::Array{Bool, 3}
+    timestep::Int
+end
+
+"""
+Read the puzzle input and return the blizzard positions.
+"""
+function BlizzardMap(input::Vector{<:AbstractString})
+    maparray = zeros(Bool,
+        length(input)-2,
+        length(input[1])-2,
+        4
+    )
+
+    # Hardcoding the start and end positions, but checking that things look ok
+    @assert input[1][2] == '.'
+    @assert input[end][end-1] == '.'
+
+    # Reading the rest of the map
+    for (i, line) = enumerate(input[2:end-1])
+        linechars = split(line, "")[2:end-1]
+        maparray[i,:,1] = linechars .== "^"
+        maparray[i,:,2] = linechars .== "v"
+        maparray[i,:,3] = linechars .== "<"
+        maparray[i,:,4] = linechars .== ">"
+    end
+
+    BlizzardMap(maparray, 0)
+end
+
+Base.size(blizzardmap::BlizzardMap, dim::Int) = size(blizzardmap.blizzards, dim)
+
+"""
+Set the state of the blizzard map to a given time.
+"""
+function setmaptime!(blizzardmap::BlizzardMap, newtime::Int)
+    timeoffset = newtime - blizzardmap.timestep
+    blizzardmap.timestep = newtime
+
+    if timeoffset == 0
+        blizzardmap
+    else
+        oldmap = copy(blizzardmap.blizzards)
+        for y = 1:size(blizzardmap, 1), x = 1:size(blizzardmap, 2)
+            blizzardmap.blizzards[y, x, 1] = oldmap[
+                1+mod(y-1+timeoffset, size(blizzardmap, 1)), x, 1
+            ]
+            blizzardmap.blizzards[y, x, 2] = oldmap[
+                1+mod(y-1-timeoffset, size(blizzardmap, 1)), x, 2
+            ]
+            blizzardmap.blizzards[y, x, 3] = oldmap[
+                y, 1+mod(x-1+timeoffset, size(blizzardmap, 2)), 3
+            ]
+            blizzardmap.blizzards[y, x, 4] = oldmap[
+                y, 1+mod(x-1-timeoffset, size(blizzardmap, 2)), 4
+            ]
+        end
+        blizzardmap
+    end
+end
+
+"""
+Return true iff the node is the goal node
+"""
+function isgoal(node::Node, blizzardmap::BlizzardMap)
+    node[2] == size(blizzardmap, 1) + 1 &&
+        node[3] == size(blizzardmap, 2)
+end
+
+"""
+Return the neighbors (if any) of a given node in the blizzard map.
+
+This changes the time of the blizzard map.
+"""
+function get_neighbors(node::Node, blizzardmap::BlizzardMap)::Vector{Node}
+    if node[2] == size(blizzardmap, 1) && node[3] == size(blizzardmap, 2)
+        # One tiny optimization/hack I'll use here is to only return the goal
+        # node if we're next to it already.
+        return([(node[1]+1, node[2]+1, node[3])])
+    elseif isgoal(node, blizzardmap)
+        # This is just defensive programming; if I do everything right before I
+        # call this function, this branch will never be visited. Nevertheless!
+        return([node])
+    end
+
+    setmaptime!(blizzardmap, node[1]+1)
+    # nextmap shows where blizzards are, ie where we can't step
+    # it's still a 3D array, but the third dimension length == 1
+    nextmap = reduce(|, blizzardmap.blizzards, dims = 3)
+    neighbors = Vector{Node}()
+
+    if node[2] == 0 && node[3] == 1
+        # We're at the start, where we always have the option to stay
+        push!(neighbors, node .+ (1, 0, 0))
+        !nextmap[1, 1, 1] && push!(neighbors, node .+ (1, 1, 0))
+    else
+        for (Δy, Δx) = [(-1, 0), (1, 0), (0, -1), (0, 1), (0, 0)]
+            (t, y, x) = node .+ (1, Δy, Δx)
+            if y > 0 && x > 0 &&
+                    y <= size(blizzardmap, 1) &&
+                    x <= size(blizzardmap, 2) &&
+                    !nextmap[y, x, 1]
+                push!(neighbors, (t, y, x))
+            end
+        end
+    end
+    neighbors
+end
+
+"""
+Return the manhattan distance from the node to the goal.
+"""
+function manhattandist(node::Node, blizzardmap::BlizzardMap)
+    isgoal(node, blizzardmap) ? 0 :
+        size(blizzardmap, 1) - node[2] + size(blizzardmap, 2) - node[3] + 1
+end
+
+function reconstruct_path(camefrom::Dict{Node, Node}, current::Node)
+    totalpath = [current]
+    while current ∈ keys(camefrom)
+        current = camefrom[current]
+        pushfirst!(totalpath, current)
+    end
+    totalpath
+end
+
+"""
+Find the shortest path from the beginning to end of the map.
+
+This looks like A* search (I even cribbed from wikipedia's pseudocode, again),
+but I think it's actually just breadth-first search. Might need to fix that
+depending on the non-example input and part 2 requirements.
+"""
+function navigatemap(blizzardmap::BlizzardMap, h::Function)
+    start = (0, 0, 1)
+    camefrom = Dict{Node, Node}()
+
+    # This is usually a map giving the lowest known cost path from start to
+    # each known. Since the first element of each node codes that implicitly,
+    # we can just use a set.
+    gscore = Set([start])
+
+    # The set of discovered nodes
+    openset = PriorityQueue{Node, Int}(start => h(start, blizzardmap))
+
+    while !isempty(openset)
+        current = dequeue!(openset)
+
+        if isgoal(current, blizzardmap)
+            return reconstruct_path(camefrom, current)
+        end
+
+        for neighbor = get_neighbors(current, blizzardmap)
+            if neighbor ∉ gscore
+                # We haven't found a path through this node before
+                openset[neighbor] = neighbor[1] + h(neighbor, blizzardmap)
+                camefrom[neighbor] = current
+            end
+        end
+    end
+    nothing
+end
+
+function part_1(input)
+    length(navigatemap(BlizzardMap(input), manhattandist))-1
+end
+@assert part_1(example) == 18
+@info part_1(input)
+
+function part_2(input)
+    nothing
+end
+@info part_2(input)
```