X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=advent14%2FMain.hs;fp=advent14%2FMain.hs;h=0ded02b02faa7975a03aadfd38344c0616d72a26;hb=a02f60a5bc25537437380fdcda1097019c75c38a;hp=0000000000000000000000000000000000000000;hpb=0aa43bda9bede3f58f97bc6b2996abd5dbbc97be;p=advent-of-code-22.git diff --git a/advent14/Main.hs b/advent14/Main.hs new file mode 100644 index 0000000..0ded02b --- /dev/null +++ b/advent14/Main.hs @@ -0,0 +1,108 @@ +-- Writeup at https://work.njae.me.uk/2022/12/15/advent-of-code-2022-day-14/ + +import AoC +import Data.Text (Text) +import qualified Data.Text.IO as TIO +import Data.Attoparsec.Text hiding (take, D) +import Control.Applicative +import Data.List +import Data.Ix +import Data.Maybe +import qualified Data.Set as S +import Linear -- hiding (Trace, trace, distance) +import Control.Lens + +type Position = V2 Int -- x, y, y increasing down. + +type Cave = S.Set Position + +data Sand = Falling Position | Blocked Position | Escaped + deriving (Eq, Show) + +-- open floor: sand escapes below given level +-- closed floor: sand blocked by floor with this y +data Floor = Open Int | Closed Int deriving (Eq, Show) + +main :: IO () +main = + do dataFileName <- getDataFileName + text <- TIO.readFile dataFileName + let corners = successfulParse text + let stone = mkCave corners + let floorY = fromJust $ maximumOf (folded . _y) stone + print $ part1 stone floorY + print $ part2 stone floorY + +part1, part2 :: Cave -> Int -> Int +part1 stone floorY = sandQty + where filledCave = dropManySand stone (Open floorY) + sandQty = (S.size filledCave) - (S.size stone) +part2 stone floorY = sandQty + where filledCave = dropManySand stone (Closed (floorY + 2)) + sandQty = (S.size filledCave) - (S.size stone) + +mkCave :: [[Position]] -> Cave +mkCave walls = foldl' addWall S.empty walls + +addWall :: Cave -> [Position] -> Cave +addWall cave wall = foldl' addSegment cave segments + where segments = zip wall $ tail wall + +addSegment :: Cave -> (Position, Position) -> Cave +addSegment cave segment = S.union cave segments + where segments = S.fromList $ range (uncurry min segment, uncurry max segment) + +fallDirections :: [Position] +fallDirections = [V2 0 1, V2 -1 1, V2 1 1] + +sandOrigin :: Position +sandOrigin = V2 500 0 + +fallStep :: Sand -> Cave -> Floor -> Sand +fallStep (Blocked here) _ _ = Blocked here +fallStep Escaped _ _ = Escaped +fallStep (Falling here) cave (Open floorY) + | here ^. _y > floorY = Escaped + | otherwise = maybe (Blocked here) Falling $ find vacant + $ fmap (here ^+^) fallDirections + where vacant there = there `S.notMember` cave +fallStep (Falling here) cave (Closed floorY) = + maybe (Blocked here) Falling $ find vacant $ fmap (here ^+^) fallDirections + where vacant there = (there ^. _y < floorY) && (there `S.notMember` cave) + +fallsTo :: Sand -> Cave -> Floor -> Sand +fallsTo here cave floorY = + case fallStep here cave floorY of + Escaped -> Escaped + Blocked there -> Blocked there + Falling there -> fallsTo (Falling there) cave floorY + +dropManySand :: Cave -> Floor -> Cave +dropManySand cave floorY + | sandOrigin `S.member` cave = cave + | otherwise = case dropOneSand cave floorY of + Nothing -> cave + Just cave' -> dropManySand cave' floorY + +dropOneSand :: Cave -> Floor -> Maybe Cave +dropOneSand cave floorY = + case (fallsTo (Falling sandOrigin) cave floorY) of + Escaped -> Nothing + Blocked there -> Just (S.insert there cave) + Falling _ -> error "sand still falling" + +-- Parse the input file + +wallsP :: Parser [[Position]] +wallP :: Parser [Position] +cornerP :: Parser Position + +wallsP = wallP `sepBy` endOfLine +wallP = cornerP `sepBy` " -> " +cornerP = V2 <$> (decimal <* ",") <*> decimal + +successfulParse :: Text -> [[Position]] +successfulParse input = + case parseOnly wallsP input of + Left _err -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err + Right corners -> corners