-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay22.hs
295 lines (265 loc) · 10.3 KB
/
Day22.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
{-# LANGUAGE TupleSections #-}
module Day22 (solve) where
import Data.Function ( (&) )
import Data.List
import qualified Data.Char as Char
import qualified Data.List.Split as Split
import qualified Data.Map as Map
import qualified Data.Set as Set
-- It ain't DRY, it's turkey driven code
solve input lines = do
part1 lines
let [grove, [instrTxt]] = lines & Split.splitOn [""]
let start = getStart grove
let instrs = readInstrsWithF instrTxt
let dim = getDim grove
let cube = getCube dim grove start lines
let (surfCoord, s0 : _) = navigate grove dim cube instrs
let Just groveCoord = Map.lookup surfCoord s0
print $ password (groveCoord, face s0)
face surface = do
let Just (x, y) = Map.lookup (0, 0) surface
let Just (x', y') = Map.lookup (0, 1) surface
let delta = (x' - x, y' - y)
case delta of (1, 0) -> 0; (0, 1) -> 1; (-1, 0) -> 2; (0, -1) -> 3
navigate grove dim cube instrs =
foldl tryInstrCube cube instrs
where
tryInstrCube cube (dir, dist) = do
let cube' =
case dir of
-1 -> leftCube dim cube
0 -> cube
1 -> rightCube dim cube
_ -> error "Unexplected"
let cube'' =
forwardN cube' dist
cube''
forwardN cube n = do
let path = iterate forwardOne cube & take (n + 1) & takeWhile isFree
last path
forwardOne ((x, y), ss) = do
let y' = y + 1
if y' < dim then ((x, y'), ss) else ((x, 0), spin dim ss)
isFree cube = value cube == '.'
value (coord@(x, y), s0 : _ : s2 : _) = do
let (Just (gx, gy)) = Map.lookup coord s0
grove !! gy !! gx
getCube dim grove start lines = do
let pathsToSquares = getPathsToSquares dim grove start
let paths = pathsToSquares & sortOn length
let incPaths = incrementalPaths paths
let rpath = reversePath (last paths)
let cube =
foldl (\ (pos, cube) path -> do
let pos' = applyPathToGrove pos path
let cube' = applyPathToCube dim cube path
let cube'' = mapSurfaceToSquare grove dim pos' cube'
(pos', cube''))
(start, emptyCube) incPaths
& snd
& (\ cube -> applyPathToCube dim cube rpath)
cube
reversePath path = do
let rpath = (1,0) : (1,0) : reverse path
let pairs = zip rpath (tail rpath)
pairs
& map (\ ((nxtDir, nxtDist), (prevDir, prevDist)) ->
(flipDir nxtDir, prevDist))
& (++ [(1, 0), (1, 0)])
where
flipDir d = case d of -1 -> 1; 1 -> -1 ; 0 -> 0
emptyCube = ((0, 0), [0 .. 5] & map (const Map.empty))
mapSurfaceToSquare grove dim ((x, y), f) (surfPos, surf : rest) = do
let diff = case f of
0 -> \(x , y) -> (y, x)
1 -> \(x , y) -> (-x, y)
2 -> \(x , y) -> (-y, -x)
3 -> \(x , y) -> (x, -y)
let coords =
[0 .. dim - 1] & concatMap (\ y ->
[0 .. dim - 1] & map (,y))
let groveCoord surfCoord = do
let (dx, dy) = diff surfCoord
(x + dx, y + dy)
let lookup surfCoord = do
let (x, y) = groveCoord surfCoord
grove !! y !! x
let surface =
Map.fromList $ coords
& map (\ coord -> (coord, groveCoord coord))
(surfPos, surface : rest)
incrementalPaths paths =
zip paths (tail paths)
& map (\ (p, p') -> drop (length p) p')
& (head paths : )
applyPathToCube dim cube path = do
foldl applyInstrCube cube path
where
applyInstrCube cube (dir, dist) = do
let cube' = case dir of
-1 -> leftCube dim cube
0 -> cube
1 -> rightCube dim cube
iterate (forwardCube dim) cube' !! dist
getPathsToSquares dim grove pos = do
let paths = unfoldr nextSquare ([], pos)
uniqueSquares paths & dropWhile ((< 6) . length)
& head
& Map.toList
& map (fst . snd)
where
uniqueSquares sqrPaths = do
scanl
(\ mp path@(_, pos) -> do
let sqrId = squareId pos
if not $ Map.member sqrId mp
then Map.insert sqrId path mp
else mp)
Map.empty sqrPaths
walkSquares grove pos = unfoldr nextSquare ([], pos)
nextSquare (legs, pos) = do
let candidates = map (\ leg -> (legs ++ leg, applyPathToGrove pos leg)) candRoutes
let next = candidates & filter (notEmpty . snd) & head
Just ((legs, pos), next)
notEmpty ((x, y), f) =
y >= 0 && y < length grove
&& x >= 0 && x < length (grove !! y)
&& grove !! y !! x /= ' '
candRoutes =
[ readInstrs $ "L1"
, readInstrs $ "F" ++ dimStr
, readInstrs $ "F" ++ dimStrDec ++ "R" ++ dimStr
, readInstrs $ "R" ++ dimStrDec ++ "R1" ]
width = grove & map length & maximum
dimStr = show dim
dimStrDec = show (dim - 1)
height = length grove
sqrsHigh = height `div` dim
sqrsWide = width `div` dim
squareId ((x, y), _) = x `div` dim + sqrsWide * (y `div` dim)
applyPathToGrove pos instrs = do
foldl applyStep pos instrs
where
applyStep (coord, f) (turn, dist) = do
let f' = (f + turn) `mod` 4
let mv = case f' of
0 -> \ (x, y) -> (x + 1, y)
1 -> \ (x, y) -> (x, y + 1)
2 -> \ (x, y) -> (x - 1, y)
3 -> \ (x, y) -> (x, y - 1)
let coord' = iterate mv coord !! dist
(coord', f')
rightCube dim = leftCube dim . leftCube dim . leftCube dim
leftCube dim ((x,y), surfaces) = do
let max = dim - 1
((y, max - x), clockwise dim surfaces)
forwardCube dim ((x,y), surfaces) = do
let y' = y + 1
if y' < dim then ((x, y'), surfaces) else ((x, 0), spin dim surfaces)
clockwise dim [s0, s1, s2, s3, s4, s5] =
[surfaceClockwise dim s0, s4, surfaceAnticlock dim s2,
surfaceOneEighty dim s5, surfaceOneEighty dim s3, s1]
-- clockwise
-- 3 u5
-- 2 a2
-- 1 4
-- 4 0 5 u3 c0 1
spin dim [s0, s1, s2, s3, s4, s5] =
[s1, s2, s3, s0, surfaceClockwise dim s4, surfaceAnticlock dim s5]
-- spin
-- 3 0
-- 2 3
-- 1 2
-- 4 0 5 c4 1 a5
surfaceAnticlock dim = surfaceClockwise dim . surfaceOneEighty dim
surfaceOneEighty dim = surfaceClockwise dim . surfaceClockwise dim
surfaceClockwise dim = Map.fromList . flipVert dim . transposeKvps . Map.toList
transposeKvps kvps = kvps & map (\ ((x, y), v) -> ((y, x), v))
flipVert dim kvps = do
let max = dim - 1
kvps & map (\ ((x, y), v) -> ((x, max - y), v))
getDim grove =
grove & concatMap (filter (/= ' ')) & length & (`div` 6)
& fromIntegral & sqrt & floor
---------------------------------- PART ONE ----------------------------------
part1 lines = do
let (grove, instrs) = readGrove1 lines
let start = getStart lines
let segments = foldl (addSegment grove) [[start]] instrs
let final = segments & head & last
print $ final & password
password ((x, y), f) = 1000 * (y + 1) + 4 * (x + 1) + f
getStart lines = (( head lines & filter (== ' ') & length, 0), 0)
addSegment grove segments instr = do
let pos = segments & head & last
let newSeg = followInstr pos instr
newSeg : segments
where
followInstr pos@(coord, dir) instr@(turn, dist) = do
let dir' = (dir + turn) `mod` 4
advance (coord, dir') dist
advance pos n = unfoldr tryAdvanceOne pos & take (n + 1)
tryAdvanceOne pos =
if isWall pos then Nothing else Just (pos, nextPos pos)
isWall (coord, dir) = Set.member coord (walls grove)
nextPos ((x, y), dir) =
case dir of
0 -> ((x + 1, y), dir) & wrapX
1 -> ((x, y + 1), dir) & wrapY
2 -> ((x - 1, y), dir) & wrapX
3 -> ((x, y - 1), dir) & wrapY
_ -> error (show dir)
where
wrapX ((x, y), dir) = do
let (start, end) = rows grove !! y
let x' = (x - start) `mod` (end - start) & (+ start)
((x', y), dir)
wrapY ((x, y), dir) = do
let (start, end) = cols grove !! x
let y' = (y - start) `mod` (end - start) & (+ start)
((x, y'), dir)
data Grove = Grove
{ rows :: [(Int, Int)]
, cols :: [(Int, Int)]
, walls :: Set.Set (Int, Int)
} deriving (Show)
readGrove1 lines = do
let route = lines !! (gap + 1)
let rows = map tiles groveTxt
let cols = map tiles (transpose groveTxt)
let grove = Grove
{ rows = rows
, cols = cols
, walls = walls }
let instrs = readInstrsWithF (lines !! (gap + 1))
(grove, instrs)
where
Just gap = elemIndex "" lines
groveTxt =
lines & take gap
& map (\ line -> (line ++ repeat ' ') & take width)
height = length groveTxt
width = length $ map length groveTxt
tiles line =
(length $ takeWhile (== ' ') line
, length $ dropWhileEnd (== ' ') line)
walls =
[0 .. height - 1] & concatMap (\ y ->
[0 .. width - 1] & map (,y))
& filter (\ (x, y) -> groveTxt !! y !! x == '#')
& Set.fromList
readInstrsWithF txt = readInstrs ('F' : txt)
readInstrs =
readInstructins
where
readInstructins [] = []
readInstructins routeTxt = do
let (chunk, rest) = readSingle routeTxt
chunk : readInstructins rest
readSingle instrTxt = do
let t = head instrTxt
let ds = takeWhile Char.isNumber (tail instrTxt)
let chunk = t : ds
let turn = case t of 'L' -> -1; 'R' -> 1; 'F' -> 0
((turn, read ds :: Int), drop (length chunk) instrTxt)