aboutsummaryrefslogtreecommitdiff
path: root/aoc-2022-dotnet/Day24/Program.fs
blob: 0446c068016b1b64029a4b7b1bb3624bb4541a94 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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