aboutsummaryrefslogtreecommitdiff
path: root/aoc-2022-dotnet/Day24/Program.fs
diff options
context:
space:
mode:
Diffstat (limited to 'aoc-2022-dotnet/Day24/Program.fs')
-rw-r--r--aoc-2022-dotnet/Day24/Program.fs107
1 files changed, 107 insertions, 0 deletions
diff --git a/aoc-2022-dotnet/Day24/Program.fs b/aoc-2022-dotnet/Day24/Program.fs
new file mode 100644
index 0000000..0446c06
--- /dev/null
+++ b/aoc-2022-dotnet/Day24/Program.fs
@@ -0,0 +1,107 @@
+module Day24
+
+open System.IO
+open FSharpPlus
+open Common
+
+type Valley =
+ { MaxX: int
+ MaxY: int
+ ExpeditionSuperposition: Set<Vec2>
+ Walls: Set<Vec2>
+ Blizzards: Map<Vec2, Set<Vec2>> }
+
+ static member private empty =
+ { MaxX = 0
+ MaxY = 0
+ ExpeditionSuperposition = Set.empty
+ Walls = Set.empty
+ Blizzards =
+ Vec2.directions4
+ |> Set.map (fun d -> d, Set.empty)
+ |> Map.ofSeq }
+
+ static member private withExpeditionAt target valley =
+ { valley with ExpeditionSuperposition = Set.singleton <| target valley }
+
+ static member private hasReached target valley =
+ Set.contains (target valley) valley.ExpeditionSuperposition
+
+ static member startOf({ MaxY = maxY }) = Vec2(1, maxY)
+ static member endOf({ MaxX = maxX }) = Vec2(maxX - 1, 0)
+
+ static member parse input =
+ input
+ |> Seq.rev
+ |> array2D
+ |> Array2D.mapi (fun r c char -> Vec2(c, r), char)
+ |> Seq.cast
+ |> Seq.fold
+ (fun valley (pos, char) ->
+ match char with
+ | '.' -> valley
+ | '#' ->
+ { valley with
+ Walls = valley.Walls.Add(pos)
+ MaxX = max valley.MaxX pos.x
+ MaxY = max valley.MaxY pos.y }
+ | '^'
+ | '>'
+ | 'v'
+ | '<' ->
+ { valley with Blizzards = valley.Blizzards.Change(Vec2.dirFromChar char, Option.map <| Set.add pos) }
+ | c -> failwithf "Invalid valley character: %c" c)
+ Valley.empty
+ |> Valley.withExpeditionAt Valley.startOf
+
+ member private valley.moveBlizzard offset pos =
+ pos + offset
+ |> Vec2.mapX (Util.wrapInRangeInc 1 (valley.MaxX - 1))
+ |> Vec2.mapY (Util.wrapInRangeInc 1 (valley.MaxY - 1))
+
+ static member private step valley =
+ let valley =
+ { valley with Blizzards = Map.map (fun dir -> Set.map <| valley.moveBlizzard dir) valley.Blizzards }
+
+ { valley with
+ ExpeditionSuperposition =
+ valley.ExpeditionSuperposition
+ |> Seq.collect Vec2.neighbours5
+ |> Seq.filter (fun pos ->
+ pos.y >= 0
+ && pos.y <= valley.MaxY
+ && Util.notIn valley.Walls pos
+ && Seq.forall (not << Set.contains pos) valley.Blizzards.Values)
+ |> Set }
+
+ static member moveTo target valley =
+ Some(valley, 0)
+ |> Seq.unfold (
+ Option.map
+ <| fun (v, m) ->
+ (v, m),
+ match Valley.hasReached target v with
+ | true -> None
+ | false -> Some(Valley.step v, m + 1)
+ )
+ |> Seq.last
+ |> mapItem1 (Valley.withExpeditionAt target)
+
+let solution1 = Valley.parse >> Valley.moveTo Valley.endOf >> snd
+
+let solution2 input =
+ let valley = Valley.parse input
+
+ let (valley, minutes1) = Valley.moveTo Valley.endOf valley
+ let (valley, minutes2) = Valley.moveTo Valley.startOf valley
+ let (_, minutes3) = Valley.moveTo Valley.endOf valley
+
+ minutes1 + minutes2 + minutes3
+
+let test = File.ReadLines("test.txt")
+assert (solution1 test = 18)
+assert (solution2 test = 54)
+
+let input = File.ReadLines("input.txt")
+printfn "%d" <| solution1 input
+printfn "%d" <| solution2 input