|
1 |
| -{-# Language QuasiQuotes, TemplateHaskell, ImportQualifiedPost, LambdaCase, BangPatterns #-} |
| 1 | +{-# Language QuasiQuotes, BangPatterns, ConstraintKinds, TemplateHaskell, ImportQualifiedPost, LambdaCase, ImplicitParams, DataKinds #-} |
2 | 2 | {-|
|
3 | 3 | Module : Main
|
4 | 4 | Description : Day 22 solution
|
5 |
| -Copyright : (c) Eric Mertens, 2022 |
| 5 | +Copyright : (c) Eric Mertens, 2024 |
6 | 6 | License : ISC
|
7 | 7 |
|
8 | 8 |
|
9 | 9 | <https://adventofcode.com/2022/day/22>
|
10 | 10 |
|
| 11 | +This solution works by first exploring the input file and assigning a cube |
| 12 | +location to each flattened location. The path is explored in terms of the cube |
| 13 | +coordinates and then is converted back into input file coordinates at the end. |
| 14 | +
|
| 15 | +>>> :{ |
| 16 | +:main + " ...# |
| 17 | + .#.. |
| 18 | + #... |
| 19 | + .... |
| 20 | +...#.......# |
| 21 | +........#... |
| 22 | +..#....#.... |
| 23 | +..........#. |
| 24 | + ...#.... |
| 25 | + .....#.. |
| 26 | + .#...... |
| 27 | + ......#. |
| 28 | +\& |
| 29 | +10R5L5R10L4R5L5 |
| 30 | +" |
| 31 | +:} |
| 32 | +6032 |
| 33 | +5031 |
| 34 | +
|
11 | 35 | -}
|
12 |
| -module Main where |
| 36 | +module Main (main) where |
13 | 37 |
|
| 38 | +import Advent (stageTH, format, countBy) |
| 39 | +import Advent.Coord (Coord(..), coordLines, above, below, left, origin, right, east, turnLeft, turnRight) |
| 40 | +import Advent.Permutation (Permutation, mkPermutation, invert) |
| 41 | +import Advent.Search (dfsOn) |
14 | 42 | import Data.Map (Map)
|
15 | 43 | import Data.Map qualified as Map
|
16 |
| -import Data.List (foldl') |
17 |
| - |
18 |
| -import Advent (format, stageTH) |
19 |
| -import Advent.Coord |
| 44 | +import Data.Set (Set) |
| 45 | +import Data.Set qualified as Set |
20 | 46 |
|
| 47 | +-- | Left and right turns |
21 | 48 | data D = DL | DR
|
22 | 49 |
|
| 50 | +data C = C_HASH | C_DOT deriving (Eq) |
| 51 | + |
23 | 52 | stageTH
|
24 | 53 |
|
| 54 | +-- | Constraint for upper bound of cube coordinates |
| 55 | +type HiVal = ?hiVal :: Int |
| 56 | + |
25 | 57 | -- |
|
26 | 58 | -- >>> :main
|
27 | 59 | -- 162186
|
28 | 60 | -- 55267
|
29 | 61 | main :: IO ()
|
30 | 62 | main =
|
31 |
| - do (rawmap, path) <- [format|2022 22 (( |.|#)*!%n)*%n(%u|@D)*%n|] |
32 |
| - let board = Map.filter (' ' /=) (Map.fromList (coordLines rawmap)) |
33 |
| - let start = minimum (Map.keys board) |
34 |
| - print (score (go1 path start board)) |
35 |
| - print (score (go2 path start board)) |
36 |
| - |
37 |
| -score :: (Coord, Coord) -> Int |
38 |
| -score (C y x, dir) = 1000 * (y+1) + 4 * (x+1) + faceval |
39 |
| - where |
40 |
| - faceval |
41 |
| - | dir == east = 0 |
42 |
| - | dir == south = 1 |
43 |
| - | dir == west = 2 |
44 |
| - | dir == north = 3 |
45 |
| - | otherwise = error "faceval: bad direction" |
46 |
| - |
47 |
| -go1 :: [Either Int D] -> Coord -> Map Coord Char -> (Coord, Coord) |
48 |
| -go1 commands start board = foldl' f (start, east) commands |
49 |
| - where |
50 |
| - f (!here, !dir) = \case |
51 |
| - Left n -> (walk1 n dir here board, dir) |
52 |
| - Right DL -> (here, turnLeft dir) |
53 |
| - Right DR -> (here, turnRight dir) |
54 |
| - |
55 |
| -walk1 :: Int -> Coord -> Coord -> Map Coord Char -> Coord |
56 |
| -walk1 0 _ here _ = here |
57 |
| -walk1 n dir here board |
58 |
| - | board Map.! here' == '#' = here |
59 |
| - | otherwise = walk1 (n-1) dir here' board |
60 |
| - where |
61 |
| - here' |
62 |
| - | Map.member (here+dir) board = here+dir |
63 |
| - | otherwise = last (takeWhile (`Map.member` board) (iterate (subtract dir) here)) |
64 |
| - |
65 |
| -go2 :: [Either Int D] -> Coord -> Map Coord Char -> (Coord, Coord) |
66 |
| -go2 commands start board = foldl' f (start, east) commands |
| 63 | + do (rawMaze, cmds) <- [format|2022 22 (( |@C)*%n)*%n(%u|@D)*%n|] |
| 64 | + let maze = parseMaze rawMaze |
| 65 | + print (part1 maze cmds) |
| 66 | + print (part2 maze cmds) |
| 67 | + |
| 68 | +-- | Build a coordinate map of the maze |
| 69 | +parseMaze :: [[Maybe C]] -> Map Coord C |
| 70 | +parseMaze cs = Map.fromList [(c, x) | (c, Just x) <- coordLines cs] |
| 71 | + |
| 72 | +-- | The password is determined from the ending coordinate and direction. |
| 73 | +password :: Coord -> Int -> Int |
| 74 | +password (C y x) z = 1000 * (y + 1) + 4 * (x + 1) + z |
| 75 | + |
| 76 | +-- | Follow the command sequence while using a simple wrap-around |
| 77 | +-- logic to compute the password. |
| 78 | +part1 :: Map Coord C -> [Either Int D] -> Int |
| 79 | +part1 maze cmds = password end (d `mod` 4) |
67 | 80 | where
|
68 |
| - f (!here, !dir) = \case |
69 |
| - Left n -> walk2 n dir here board |
70 |
| - Right DL -> (here, turnLeft dir) |
71 |
| - Right DR -> (here, turnRight dir) |
72 |
| - |
73 |
| -walk2 :: Int -> Coord -> Coord -> Map Coord Char -> (Coord, Coord) |
74 |
| -walk2 0 dir here _ = (here,dir) |
75 |
| -walk2 n dir here board |
76 |
| - | board Map.! here' == '#' = (here,dir) |
77 |
| - | otherwise = walk2 (n-1) dir' here' board |
| 81 | + (start, _) = Map.findMin maze |
| 82 | + (end, _, d) = foldl (applyCommand1 maze) (start, east, 0) cmds |
| 83 | + |
| 84 | +applyCommand1 :: Map Coord C -> (Coord, Coord, Int) -> Either Int D -> (Coord, Coord, Int) |
| 85 | +applyCommand1 board (!here, !dir, !facing) = \case |
| 86 | + Right DL -> (here, turnLeft dir, facing - 1) |
| 87 | + Right DR -> (here, turnRight dir, facing + 1) |
| 88 | + Left n -> (here', dir, facing) |
| 89 | + where |
| 90 | + here' = last (takeWhile isOpen (take (n + 1) (iterate step here))) |
| 91 | + isOpen x = board Map.! x == C_DOT |
| 92 | + step x |
| 93 | + | let x' = x + dir, Map.member x' board = x' |
| 94 | + | otherwise = last (takeWhile (`Map.member` board) (iterate (subtract dir) x)) |
| 95 | + |
| 96 | +-- | Follow the command sequence while treating the maze as a cube net |
| 97 | +-- to compute the resulting password. |
| 98 | +part2 :: Map Coord C -> [Either Int D] -> Int |
| 99 | +part2 maze cmds = |
| 100 | + do -- figure out the side-length of the cube we're working with |
| 101 | + -- so that we can handle both examples and regular inputs |
| 102 | + let ?hiVal = until (\x -> 6 * x * x >= length maze) (1 +) 1 - 1 |
| 103 | + |
| 104 | + -- associate cube coordinates with all of the input file coordinates |
| 105 | + let cube = buildCube (Map.keysSet (Map.filter (C_DOT ==) maze)) |
| 106 | + |
| 107 | + -- figure out the cube coordinate that our path ends on |
| 108 | + let (end, facing) = cube Map.! foldl (flip (applyCommand2 cube)) locOrigin cmds |
| 109 | + |
| 110 | + -- compute the "password" from the end location |
| 111 | + password end facing |
| 112 | + |
| 113 | +-- | Given the set of flat path coordinates compute the cube-coordinate |
| 114 | +-- to flat coordinate and facing map. |
| 115 | +buildCube :: HiVal => Set Coord -> Map Loc (Coord, Int) |
| 116 | +buildCube input = Map.fromList |
| 117 | + [(li, (c, i)) | (l, c) <- dfsOn snd step (locOrigin, Set.findMin input) |
| 118 | + , (li, i) <- zip (iterate locRotateL l) [0..3]] |
78 | 119 | where
|
79 |
| - (here', dir') = |
80 |
| - let fr = coordRow here `mod` 50 |
81 |
| - fc = coordCol here `mod` 50 |
82 |
| - fr' = 49 - fr in |
83 |
| - case (cubeface here, cubeface (here+dir)) of |
84 |
| - (_,y) | -1 /= y -> (here+dir, dir) |
85 |
| - |
86 |
| - (1,_) | dir == north -> (C (150 + fc ) 0,east) |
87 |
| - (1,_) | dir == west -> (C (100 + fr') 0, east) |
88 |
| - |
89 |
| - (2,_) | dir == north -> (C 199 fc, north) |
90 |
| - (2,_) | dir == east -> (C (100 + fr') 99, west) |
91 |
| - (2,_) | dir == south -> (C ( 50 + fc ) 99, west) |
92 |
| - |
93 |
| - (3,_) | dir == east -> (C 49 (100 + fr), north) |
94 |
| - (3,_) | dir == west -> (C 100 fr , south) |
95 |
| - |
96 |
| - (4,_) | dir == east -> (C fr' 149, west) |
97 |
| - (4,_) | dir == south -> (C (150 + fc) 49, west) |
98 |
| - |
99 |
| - (5,_) | dir == north -> (C (50 + fc) 50, east) |
100 |
| - (5,_) | dir == west -> (C fr' 50, east) |
101 |
| - |
102 |
| - (6,_) | dir == east -> (C 149 ( 50 + fr), north) |
103 |
| - (6,_) | dir == south -> (C 0 (100 + fc), south) |
104 |
| - (6,_) | dir == west -> (C 0 ( 50 + fr), south) |
105 |
| - |
106 |
| - (a,b) -> error (show (a,b, dir)) |
107 |
| - |
108 |
| -cubeface :: Coord -> Int |
109 |
| -cubeface (C y x) = |
110 |
| - case (div y 50, div x 50) of |
111 |
| - (0,1) -> 1 |
112 |
| - (0,2) -> 2 |
113 |
| - (1,1) -> 3 |
114 |
| - (2,0) -> 5 |
115 |
| - (2,1) -> 4 |
116 |
| - (3,0) -> 6 |
117 |
| - _ -> -1 |
| 120 | + step (l, c) = |
| 121 | + [(locRight l, right c) | right c `Set.member` input] ++ |
| 122 | + [(locLeft l, left c) | left c `Set.member` input] ++ |
| 123 | + [(locUp l, above c) | above c `Set.member` input] ++ |
| 124 | + [(locDown l, below c) | below c `Set.member` input] |
| 125 | + |
| 126 | +-- | Apply a command to the state of the walker on the cube. |
| 127 | +-- Each move is either forward a certain number or a turn. |
| 128 | +applyCommand2 :: HiVal => Map Loc a -> Either Int D -> Loc -> Loc |
| 129 | +applyCommand2 maze = \case |
| 130 | + Left n -> last . takeWhile (`Map.member` maze) . take (n + 1) . iterate locRight |
| 131 | + Right DL -> locRotateR |
| 132 | + Right DR -> locRotateL |
| 133 | + |
| 134 | +-- | Symmetric group S4 corresponds to the symmetries of a cube. |
| 135 | +-- |
| 136 | +-- This cube's diagonals are labeled and the face is read off the |
| 137 | +-- top clockwise. Rotations about an axis use left-hand rule. |
| 138 | +-- |
| 139 | +-- @ |
| 140 | +-- 0--1 z |
| 141 | +-- /| /| | |
| 142 | +-- 3--2 | o-x |
| 143 | +-- | 2|-3 / |
| 144 | +-- |/ |/ y |
| 145 | +-- 1--0 |
| 146 | +-- @ |
| 147 | +type S4 = Permutation 4 |
| 148 | + |
| 149 | +rotX, rotY, rotZ :: S4 |
| 150 | +rotX = mkPermutation ([3,2,0,1] !!) |
| 151 | +rotY = mkPermutation ([2,0,3,1] !!) |
| 152 | +rotZ = mkPermutation ([3,0,1,2] !!) |
| 153 | + |
| 154 | +-- | A pair a rotation of a cube face and a position on that face. |
| 155 | +data Loc = Loc S4 Coord |
| 156 | + deriving (Show, Ord, Eq) |
| 157 | + |
| 158 | +-- | Initial location on the top-left of a face. |
| 159 | +locOrigin :: Loc |
| 160 | +locOrigin = Loc mempty origin |
| 161 | + |
| 162 | +locRight, locLeft, locUp, locDown, locRotateL, locRotateR :: HiVal => Loc -> Loc |
| 163 | +locRight (Loc p (C y x)) |
| 164 | + | x < ?hiVal = Loc p (C y (x + 1)) |
| 165 | + | otherwise = Loc (p <> invert rotY) (C y 0) |
| 166 | + |
| 167 | +locLeft (Loc p (C y x)) |
| 168 | + | 0 < x = Loc p (C y (x - 1)) |
| 169 | + | otherwise = Loc (p <> rotY) (C y ?hiVal) |
| 170 | + |
| 171 | +locDown (Loc p (C y x)) |
| 172 | + | y < ?hiVal = Loc p (C (y + 1) x) |
| 173 | + | otherwise = Loc (p <> rotX) (C 0 x) |
| 174 | + |
| 175 | +locUp (Loc p (C y x)) |
| 176 | + | 0 < y = Loc p (C (y - 1) x) |
| 177 | + | otherwise = Loc (p <> invert rotX) (C ?hiVal x) |
| 178 | + |
| 179 | +locRotateR (Loc p (C y x)) = Loc (p <> rotZ) (C x (?hiVal - y)) |
| 180 | + |
| 181 | +locRotateL (Loc p (C y x)) = Loc (p <> invert rotZ) (C (?hiVal - x) y) |
0 commit comments