aboutsummaryrefslogtreecommitdiff
path: root/aoc-2022-dotnet/Day17/Program.fs
blob: 70374f43ebb35cbc268b4f8143338fe3a7baa864 (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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
module Day17

open System.IO
open FSharpPlus
open Common

type Dir =
    | Left
    | Right

    static member movesFromInput(input: string) =
        input
        |> String.trimWhiteSpaces
        |> Seq.map Dir.fromChar
        |> List.ofSeq

    static member private fromChar =
        function
        | '<' -> Left
        | '>' -> Right
        | c -> failwithf "Invalid character: %c" c

    static member shiftOp =
        function
        | Left -> (<<<)
        | Right -> (>>>)

module Row =
    let full = 0b1111111uy
    let empty = 0b0000000uy

    let private countBlocks row =
        let rec helper =
            function
            | 0uy -> 0
            | n -> int (n &&& 1uy) + helper (n >>> 1)

        helper (row &&& full)

    let shift dir shape settled =
        let shape' = (Dir.shiftOp dir) shape 1

        let overflow = countBlocks shape' <> countBlocks shape
        let collision = shape' &&& settled <> empty

        match overflow || collision with
        | true -> None
        | false -> Some(shape')

module Chamber =
    let private shapes =
        [ [ 0b0011110uy ] // horizontal line

          [ 0b0001000uy
            0b0011100uy
            0b0001000uy ] // cross

          [ 0b0000100uy
            0b0000100uy
            0b0011100uy ] // L-shape

          [ 0b0010000uy
            0b0010000uy
            0b0010000uy
            0b0010000uy ] // vertical line

          [ 0b0011000uy; 0b0011000uy ] ] // square

    let private makeSpaceFor shape settled =
        (List.replicate (List.length shape + 3) Row.empty)
        @ settled

    let private shift dir (shape, settled) =
        let shiftedRows = List.map2Shortest (Row.shift dir) shape settled

        let shape =
            shiftedRows
            |> Util.liftList
            |> Option.defaultValue shape

        shape, settled

    let private fall (shape, settled) =
        let (shape', settled') =
            match settled with
            | [ _ ] -> [], shape
            | h :: t when h = Row.empty -> shape, t
            | _ -> Row.empty :: shape, settled

        let collision =
            List.map2Shortest (&&&) shape' settled'
            |> List.exists ((<>) Row.empty)

        if collision then
            [],
            (List.map2Shortest (|||) shape settled)
            @ List.skip (List.length shape) settled
        else
            shape', settled'

    let private stateFingerprint moves shapes tower =
        hash (moves, List.head shapes, List.truncate 128 tower)

    let towerHeight n moves =
        let rec helper (moves, shapes, tower, cache, n) =
            let cacheKey = stateFingerprint moves shapes tower
            let towerHeight = int64 <| List.length tower

            if Map.containsKey cacheKey cache then
                let (oldCount, oldHeight) = cache[cacheKey]
                let countDiff = oldCount - n
                let heightDiff = towerHeight - oldHeight
                let skippedCycles = n / countDiff
                let skippedHeight = skippedCycles * heightDiff
                let leftoverCount = n - skippedCycles * countDiff + 1L

                skippedHeight
                + helper (moves, shapes, tower, Map.empty, leftoverCount)
            else
                let cache = cache |> Map.add cacheKey (n, towerHeight)

                let (shape, shapes) = Util.cycle shapes
                let tower = tower |> makeSpaceFor shape

                let rec step moves shape tower =
                    let (move, moves) = Util.cycle moves
                    let (shape, tower) = shift move (shape, tower)
                    let (shape, tower) = fall (shape, tower)

                    if List.isEmpty shape then
                        (moves, tower)
                    else
                        step moves shape tower

                let (moves, tower) = step moves shape tower

                let n = n - 1L

                if n = 0L then
                    towerHeight
                else
                    helper (moves, shapes, tower, cache, n)

        helper (moves, shapes, [], Map.empty, n)

let solution n =
    Dir.movesFromInput >> Chamber.towerHeight n

let test = File.ReadAllText("test.txt")
assert (solution 2022 test = 3068)
assert (solution 1_000_000_000_000L test = 1514285714288L)

let input = File.ReadAllText("input.txt")
printfn "%d" <| solution 2022 input
printfn "%d" <| solution 1_000_000_000_000L input