-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrun.hs
200 lines (177 loc) · 6.37 KB
/
run.hs
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TupleSections #-}
import AoC
import AoC.Parse (numP)
import Control.Monad (guard)
import Data.Hashable (Hashable)
import Data.Void (Void)
import GHC.Generics (Generic)
import Text.Megaparsec
import Text.Megaparsec.Char
type Parser = Parsec Void String
type N = Int
data Blueprint = Blueprint { bpId :: Int
, oreBot :: N
, clayBot :: N
, obsidianBot :: (N, N)
, geodeBot :: (N, N)
}
deriving Show
blueprintP :: Parser Blueprint
blueprintP =
Blueprint <$> (string "Blueprint " *> numP <* string ": ")
<*> (string "Each ore robot costs " *> numP <* string " ore. ")
<*> (string "Each clay robot costs " *> numP <* string " ore. ")
<*> ((,) <$> (string "Each obsidian robot costs " *> numP) <*> (string " ore and " *> numP <* string " clay. "))
<*> ((,) <$> (string "Each geode robot costs " *> numP) <*> (string " ore and " *> numP <* string " obsidian."))
parseAll :: String -> [Blueprint]
parseAll = map (\case (Right x) -> x) . map (parse blueprintP "") . lines
data S = S { time :: !Int
, ore :: !Int
, clay :: !Int
, obsidian :: !Int
, geode :: !Int
, oreBots :: !Int
, clayBots :: !Int
, obsidianBots :: !Int
, geodeBots :: !Int
}
deriving (Eq, Ord, Generic, Show)
instance Hashable S
starting :: S
starting = S 24 0 0 0 0 1 0 0 0
-- TODO: Extract BFS trying to find the maximum with heurestic for
-- early exit, to AoC.Search.
optimize :: Blueprint -> N -> N
optimize bp t0 =
let neighbors !s = do
next <- moves s
let !t = timeTo bp next s
case next of
End -> pure $ tick t s
_ -> do
guard $ t < time s
let !s' = tick t s
pure $ constructTick bp s' next
go m [] = m
go !m (!s:fs)
| time s < 0 = error $ "FAIL: " ++ show s
| time s == 0 = go (max (geode s) m) fs
| time s == 1 = let s' = tick 1 s
in go (max (geode s') m) fs
| theoreticalMax s < m = go m fs
| otherwise = go m (neighbors s ++ fs)
in
go 0 [starting { time = t0 }]
-- TODO: Improve
theoreticalMax :: S -> Int
theoreticalMax S{..} =
geode -- current geode
+ geodeBots * time -- gained by current geode bots
+ time * (time - 1) `div` 2 -- gained by new geode bots if we add
-- one per tick. It's an upper bound,
-- since we might not have enough bots
-- of the other kinds to produce one
-- geode bot per tick. A better bound
-- would take this into account.
timeTo :: Blueprint -> Move -> S -> N
timeTo Blueprint {..} target S {..} =
let (geodeOre, geodeObsidian) = geodeBot
(obsidianOre, obsidianClay) = obsidianBot
in
case target of
Geode -> max 0 $ max (timeTo' obsidian geodeObsidian obsidianBots)
(timeTo' ore geodeOre oreBots)
Obsidian -> max 0 $ max (timeTo' clay obsidianClay clayBots)
(timeTo' ore obsidianOre oreBots)
Clay -> max 0 $ timeTo' ore clayBot oreBots
Ore -> max 0 $ timeTo' ore oreBot oreBots
End -> time
timeTo' :: N -> N -> N -> N
timeTo' current target bots =
case (target - current) `divMod` bots of
(x, 0) -> x
(x, _) -> x + 1
{-# INLINE timeTo' #-}
constructTick :: Blueprint -> S -> Move -> S
constructTick Blueprint {..} s@S {..} =
let (geodeOre, geodeObsidian) = geodeBot
(obsidianOre, obsidianClay) = obsidianBot
in
\case Geode -> s { time = time - 1
, geodeBots = geodeBots + 1
, geode = geode + geodeBots
, obsidian = obsidian + obsidianBots - geodeObsidian
, clay = clay + clayBots
, ore = ore + oreBots - geodeOre }
Obsidian -> s { time = time - 1
, obsidianBots = obsidianBots + 1
, geode = geode + geodeBots
, obsidian = obsidian + obsidianBots
, clay = clay + clayBots - obsidianClay
, ore = ore + oreBots - obsidianOre
}
Clay -> s { time = time - 1
, clayBots = clayBots + 1
, geode = geode + geodeBots
, obsidian = obsidian + obsidianBots
, clay = clay + clayBots
, ore = ore + oreBots - clayBot
}
Ore -> s { time = time - 1
, oreBots = oreBots + 1
, geode = geode + geodeBots
, obsidian = obsidian + obsidianBots
, clay = clay + clayBots
, ore = ore + oreBots - oreBot
}
End -> s
tick :: Int -> S -> S
tick n s@S {..} =
s { time = time - n
, ore = ore + n * oreBots
, clay = clay + n * clayBots
, obsidian = obsidian + n * obsidianBots
, geode = geode + n * geodeBots
}
moves :: S -> [Move]
moves S {..} =
let m = concat $
[ if obsidianBots >= 1 && time >= 2 then [Geode] else []
, if clayBots >= 1 && time >= 2 then [Obsidian] else []
, if time >= 3 then [Clay] else []
, if time >= 4 then [Ore] else []
]
in
if null m && geodeBots >= 1
then [End]
else m
data Move = Geode
| Obsidian
| Clay
| Ore
| End
deriving (Show, Eq)
part1 :: [Blueprint] -> Int
part1 =
sum
. map (\bp -> bpId bp * optimize bp 24)
part2 :: [Blueprint] -> Int
part2 =
product
. map (`optimize` 32)
. take 3
main :: IO ()
main = main' "input.txt"
exampleMain :: IO ()
exampleMain = main' "example.txt"
main' :: FilePath -> IO ()
main' file = do
input <- parseAll <$> readFile file
print (part1 input)
print (part2 input)