#!/usr/bin/env nix-shell
#!nix-shell --pure -i "runghc -- -i../" -p "haskellPackages.ghcWithPackages (pkgs: with pkgs; [ ])"
import Aoc (readAndParseStdin)
import Data.Bifunctor (Bifunctor (first))
import Data.List (sortBy)
import Data.Ord (Down (Down), comparing)
import Text.Parsec (char, digit, endBy, many1, (<|>))
import Text.Parsec.String (Parser)
main = do
(input, _) <- first sortBricks . dropBricks <$> readAndParseStdin parser
print $ part1 input
print $ part2 input
part1 :: [Brick] -> Int
part1 input = length $ findNonLoadBearing input
where
findNonLoadBearing input = filter (flip all input . isNonLoadBearing) input
supportedCount y = length $ filter (`isSupporting` y) input
isNonLoadBearing x y = not (x `isSupporting` y) || supportedCount y > 1
part2 :: [Brick] -> Int
part2 input = sum $ map (snd . dropBricks . dropIndex input) [0 .. length input - 1]
where
dropIndex input i = take i input ++ drop (i + 1) input
dropBricks :: [Brick] -> ([Brick], Int)
dropBricks = foldl processBrick ([], 0)
where
processBrick (acc, n) x =
let (x', n') = dropUntilSupported acc x 0
in (x' : acc, n + min n' 1)
dropUntilSupported acc x n =
if isSupported acc x
then (x, n)
else dropUntilSupported acc (decrementZ x) (n + 1)
isSupported acc x = any (`isSupporting` x) acc || findBaseZ x == 1
decrementZ (Brick a b) = Brick (a {z = z a - 1}) (b {z = z b - 1})
findBaseZ (Brick a b) = min (z a) (z b)
isSupporting :: Brick -> Brick -> Bool
isSupporting a@(Brick a1 a2) b@(Brick b1 b2) =
let (minX1, maxX1) = (min (x a1) (x a2), max (x a1) (x a2))
(minY1, maxY1) = (min (y a1) (y a2), max (y a1) (y a2))
(minZ1, maxZ1) = (min (z a1) (z a2), max (z a1) (z a2))
(minX2, maxX2) = (min (x b1) (x b2), max (x b1) (x b2))
(minY2, maxY2) = (min (y b1) (y b2), max (y b1) (y b2))
(minZ2, maxZ2) = (min (z b1) (z b2), max (z b1) (z b2))
zSupport = minZ2 == (maxZ1 + 1)
xOverlap = not (maxX1 < minX2 || maxX2 < minX1)
yOverlap = not (maxY1 < minY2 || maxY2 < minY1)
in a /= b && zSupport && xOverlap && yOverlap
sortBricks :: [Brick] -> [Brick]
sortBricks = sortBy (\(Brick a1 a2) (Brick b1 b2) -> compare (min (z a1) (z a2)) (min (z b1) (z b2)))
parser :: Parser [Brick]
parser = sortBricks <$> parseBrick `endBy` char '\n'
where
parseBrick = Brick <$> parseCoord <*> (char '~' *> parseCoord)
parseCoord = do
x <- read <$> many1 digit <* char ','
y <- read <$> many1 digit <* char ','
z <- read <$> many1 digit
return $ Coord {x, y, z}
data Coord = Coord {x :: Int, y :: Int, z :: Int} deriving (Show, Eq)
data Brick = Brick Coord Coord deriving (Show, Eq)