Day 17 done
[advent-of-code-18.git] / src / advent17 / advent17.hs
diff --git a/src/advent17/advent17.hs b/src/advent17/advent17.hs
new file mode 100644 (file)
index 0000000..97a2b59
--- /dev/null
@@ -0,0 +1,244 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+import Debug.Trace
+
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO
+
+import Data.Void (Void)
+
+import Text.Megaparsec
+import Text.Megaparsec.Char
+import qualified Text.Megaparsec.Char.Lexer as L
+import qualified Control.Applicative as CA
+
+import Data.List
+-- import qualified Data.Set as S
+
+import qualified Data.Map.Strict as M
+import Data.Map.Strict ((!))
+import Data.Tuple (swap)
+
+type SoilSpecLine = ((Text, Integer), (Text, Integer, Integer))
+type Coord = (Integer, Integer) -- x, y
+data Soil = Sand | Clay | Still | Flowing deriving (Eq, Show, Enum, Bounded, Ord)
+type Ground = M.Map Coord Soil
+
+main :: IO ()
+main = do 
+    text <- TIO.readFile "data/advent17.txt"
+    let soilSpec = successfulParse text
+    -- print soilSpec
+    let ground = makeGround soilSpec
+    -- print ground
+    -- putStrLn $ showGround ground
+    -- putStrLn $ showGround $ handleSource ground (500, 0)
+    -- print $ handleSource ground (500, 0)
+    let ground' = filled ground
+    print $ part1 ground'
+    print $ part2 ground'
+    -- print $ part1 tests
+    -- print $ part2 tests program
+
+
+part1 ground = M.size $ M.union still flowing
+    where (_minX, _maxX, minY, maxY) = bounds ground
+          inBoundGround = M.filterWithKey (\(_x, y) _ -> (y >= minY) && (y <= maxY)) ground
+          still = M.filter (== Still) inBoundGround
+          flowing = M.filter (== Flowing) inBoundGround
+
+part2 ground = M.size $ still
+    where (_minX, _maxX, minY, maxY) = bounds ground
+          inBoundGround = M.filterWithKey (\(_x, y) _ -> (y >= minY) && (y <= maxY)) ground
+          still = M.filter (== Still) inBoundGround    
+
+makeGround :: [SoilSpecLine] -> Ground
+makeGround soilSpec = foldl' addSpecLine M.empty soilSpec
+
+addSpecLine :: Ground -> SoilSpecLine -> Ground
+addSpecLine ground ((fixed, fixedVal), (ranged, from, to)) =
+    foldr (\c -> M.insert c Clay) ground addedCells
+    where cells = [(fixedVal, i) | i <- [from..to] ]
+          addedCells = if fixed == "x" then cells else map swap cells
+
+showGround :: Ground -> String
+showGround ground = unlines $ map (showGroundLine minX maxX ground) [minY..maxY]
+    where (minX, maxX, minY, maxY) = bounds ground
+
+showGroundLine :: Integer -> Integer -> Ground -> Integer -> String
+showGroundLine minX maxX ground y = [showGroundCell x | x <- [minX..maxX]]
+    where showGroundCell x = if (x, y) `M.member` ground
+                               then case ground!(x, y) of
+                                        Clay -> '#' -- '\x2591'
+                                        Flowing -> '|'
+                                        Still -> 'o' -- '\x2593'
+                                        Sand -> '.'
+                                else '.'
+
+bounds :: Ground -> (Integer, Integer, Integer, Integer)
+bounds ground = (minX, maxX, minY, maxY)
+    where keys = M.keys ground -- $ M.filter (== Clay) ground
+          minX = minimum $ map fst keys
+          maxX = maximum $ map fst keys
+          minY = minimum $ map snd keys
+          maxY = maximum $ map snd keys
+
+up (x, y) = (x, y-1)
+down (x, y) = (x, y+1)
+left (x, y) = (x-1, y)
+right (x, y) = (x+1, y)
+
+
+filled :: Ground -> Ground
+filled ground = handleSource ground (500, 0)
+
+
+handleSource :: Ground -> Coord -> Ground
+-- handleSource ground here | trace ("source " ++ show here ++ "\n" ++ showGround ground) False = undefined
+handleSource ground here 
+    | (snd here) > maxY = ground
+    | otherwise = flood ground' here
+    where (_minX, _maxX, _minY, maxY) = bounds ground
+          under = M.findWithDefault Sand (down here) ground
+          ground' = if under == Sand 
+                    then handleSource (M.insert here Flowing ground) (down here)
+                    else M.insert here Flowing ground
+
+    -- else if (down here) `M.notMember` ground
+    --      then handleSource ground' (down here)
+    --      else flood ground' here
+    -- where (_minX, _maxX, _minY, maxY) = bounds ground
+    --       ground' = (M.insert here Water ground)
+
+flood :: Ground -> Coord -> Ground
+-- flood ground here | trace ("flood " ++ show here) False = undefined
+flood ground here = foldl' handleSource groundB sources
+    where (groundL, sourcesL, boundL) = floodLeft ground here
+          (groundR, sourcesR, boundR) = floodRight groundL here
+          sources = sourcesL ++ sourcesR
+          groundB = if boundL && boundR 
+                    then stillWater groundR here
+                    else groundR
+
+
+    -- if null sources
+    -- then flood groundLR (up here)
+    -- else foldl' handleSource groundLR sources
+    -- where (groundL, sourcesL) = floodLeft ground here 
+    --       (groundLR,  sourcesR) = floodRight groundL here 
+    --       sources = sourcesL ++ sourcesR
+
+-- if the ground under is sand, create a new source 
+-- otherwise, 
+-- if the groudnd to the left is clay, stop
+-- if the ground to the left isn't clay, flood left 
+
+floodLeft :: Ground -> Coord -> (Ground, [Coord], Bool)
+-- floodLeft ground here | trace ("flood <= " ++ show here) False = undefined
+floodLeft ground here = 
+    if leftWards == Clay
+    then (ground, [], True)
+    else case (under, underLeft) of
+        (Clay, Sand) -> (ground', [left here], False)
+        (Clay, Clay) -> floodLeft ground' (left here)
+        (Still, Still) -> floodLeft ground' (left here)
+        (Still, Clay) -> floodLeft ground' (left here)
+        (Clay, Still) -> floodLeft ground' (left here)
+        _             -> (ground, [], False)
+    where ground' = (M.insert (left here) Flowing ground)
+          under = M.findWithDefault Sand (down here) ground
+          leftWards = M.findWithDefault Sand (left here) ground
+          underLeft =  M.findWithDefault Sand (left (down here)) ground
+
+
+floodRight :: Ground -> Coord -> (Ground, [Coord], Bool)
+-- floodRight ground here | trace ("flood => " ++ show here) False = undefined
+floodRight ground here =
+    if rightWards == Clay
+    then (ground, [], True)
+    else case (under, underRight) of
+        (Clay, Sand) -> (ground', [right here], False)
+        (Clay, Clay) -> floodRight ground' (right here)
+        (Still, Still) -> floodRight ground' (right here)
+        (Still, Clay) -> floodRight ground' (right here)
+        (Clay, Still) -> floodRight ground' (right here)
+        _             -> (ground, [], False)
+    where ground' = (M.insert (right here) Flowing ground)
+          under = M.findWithDefault Sand (down here) ground
+          rightWards = M.findWithDefault Sand (right here) ground
+          underRight =  M.findWithDefault Sand (right (down here)) ground
+    -- if under == Sand
+    -- then (ground', [here])
+    -- else if rightWards == Clay
+    --      then (ground', [])
+    --      else if (under == Water) && (rightWards == Sand) 
+    --           then (ground', [])
+    --           else floodRight ground' (left here)
+    -- where ground' = (M.insert here Water ground)
+    --       under = M.findWithDefault Sand (down here) ground
+    --       rightWards = M.findWithDefault Sand (right here) ground
+-- floodRight ground here = 
+    -- case under of
+    --     Sand -> (ground', [here])
+    --     Water -> (ground', [])
+    --     otherwise -> if (right here) `M.notMember` ground
+    --                  then floodRight ground' (right here)
+    --                  else if ground!(right here) == Water
+    --                       then floodRight ground' (right here)
+    --                       else (ground', [])
+    -- where ground' = (M.insert here Water ground)
+    --       under = M.findWithDefault Sand (down here) ground
+
+stillWater :: Ground -> Coord -> Ground
+-- stillWater ground here | trace ("stilling " ++ show here) False = undefined
+stillWater ground here = stillWaterR groundL here
+    where groundL = stillWaterL ground here
+
+stillWaterL :: Ground -> Coord -> Ground
+-- stillWaterL ground here | trace ("stilling L" ++ show here) False = undefined
+stillWaterL ground here = 
+    if ground!(left here) == Clay
+    then ground'
+    else stillWaterL ground' (left here)
+    where ground' =(M.insert here Still ground)
+
+stillWaterR :: Ground -> Coord -> Ground
+-- stillWaterR ground here | trace ("stilling R" ++ show here) False = undefined
+stillWaterR ground here = 
+    if ground!(right here) == Clay
+    then ground'
+    else stillWaterR ground' (right here)
+    where ground' = (M.insert here Still ground)
+
+
+
+-- Parse the input file
+
+type Parser = Parsec Void Text
+
+sc :: Parser ()
+sc = L.space (skipSome spaceChar) CA.empty CA.empty
+
+lexeme  = L.lexeme sc
+integer = lexeme L.decimal
+symb = L.symbol sc
+
+equalP = symb "="
+commaP = symb ","
+ellipsisP = ".."
+axisP = symb "x" <|> symb "y"
+
+fileP = many rowP
+
+rowP = (,) <$> fixedP <* commaP <*> rangeP
+
+fixedP = (,) <$> axisP <* equalP <*> integer
+rangeP = (,,) <$> axisP <* equalP <*> integer <* ellipsisP <*> integer
+
+
+successfulParse :: Text -> [SoilSpecLine]
+successfulParse input = 
+        case parse fileP "input" input of
+                Left  _error -> [] -- TIO.putStr $ T.pack $ parseErrorPretty err
+                Right soilSpec -> soilSpec
\ No newline at end of file