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 }
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
|